aboutsummaryrefslogtreecommitdiffstats
path: root/org.jacoco.build/src/org/jacoco/build/tools/ant/RenamedClassFileSet.java
blob: 83765361b79ee368d67e814777c5040dc2a260e6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/*******************************************************************************
 * Copyright (c) 2009, 2011 Mountainminds GmbH & Co. KG and Contributors
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Marc R. Hoffmann - initial API and implementation
 *    
 *******************************************************************************/
package org.jacoco.build.tools.ant;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.ResourceCollection;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.commons.Remapper;
import org.objectweb.asm.commons.RemappingClassAdapter;

/**
 * Custom Ant type that renames a collection of class files. Not only the file
 * names are renamed, also the class definitions are adjusted.
 * 
 * @author Marc R. Hoffmann
 * @version $qualified.bundle.version$
 */
public class RenamedClassFileSet implements ResourceCollection {

	/**
	 * Type for the nested mapping element.
	 */
	public static class Mapping {

		private Pattern from;

		private String to;

		/**
		 * @param from
		 *            the pattern that may apply
		 */
		public void setFrom(final String from) {
			this.from = Pattern.compile(from);
		}

		/**
		 * @param to
		 *            replacement text
		 */
		public void setTo(final String to) {
			this.to = to;
		}

		String apply(final String name) {
			return from.matcher(name).replaceAll(to);
		}

	}

	private static final String CLASSEXTENSION = ".class";

	private final List<ResourceCollection> delegates = new ArrayList<ResourceCollection>();

	private Collection<Resource> renamedResources;

	private final List<Mapping> mappings = new ArrayList<Mapping>();

	// === ResourceCollection ===

	public boolean isFilesystemOnly() {
		return false;
	}

	public Iterator<?> iterator() {
		return getRenamedResources().iterator();
	}

	public int size() {
		return getRenamedResources().size();
	}

	// === Configuration API ===

	/**
	 * Adds the given resource collection to the collection of classes where
	 * dependent class files are extracted from.
	 * 
	 * @param collection
	 *            collection to add
	 */
	public void add(final ResourceCollection collection) {
		delegates.add(collection);
	}

	/**
	 * Adds a mapping element.
	 * 
	 * @param mapping
	 *            element to add
	 */
	public void addMapping(final Mapping mapping) {
		mappings.add(mapping);
	}

	// === Internal renaming implementation ===

	private Collection<Resource> getRenamedResources() {
		if (renamedResources != null) {
			return renamedResources;
		}
		renamedResources = new HashSet<Resource>();
		for (final ResourceCollection c : delegates) {
			final Iterator<?> i = c.iterator();
			while (i.hasNext()) {
				final Resource resource = (Resource) i.next();
				if (resource.isExists()) {
					renamedResources.add(rename(resource));
				}
			}
		}
		return renamedResources;
	}

	private String rename(String className) {
		for (final Mapping m : mappings) {
			className = m.apply(className);
		}
		return className;
	}

	private Resource rename(final Resource res) {
		// On Windows we get back slashes:
		final String name = rename(res.getName().replace('\\', '/'));
		return new Resource(name, res.isExists(), res.getLastModified(), res
				.isDirectory(), res.getSize()) {
			@Override
			public InputStream getInputStream() throws IOException {
				final InputStream stream = res.getInputStream();
				if (getName().endsWith(CLASSEXTENSION)) {
					return rename(stream);
				}
				return stream;
			}
		};
	}

	private InputStream rename(final InputStream stream) throws IOException {
		final Remapper remapper = new Remapper() {
			@Override
			public String map(final String typeName) {
				return rename(typeName);
			}
		};
		final ClassReader reader = new ClassReader(stream);
		stream.close();
		// Don't re-use constant pool as we're renaming all classes:
		final ClassWriter writer = new ClassWriter(0);
		reader.accept(new RemappingClassAdapter(writer, remapper),
				ClassReader.EXPAND_FRAMES);
		return new ByteArrayInputStream(writer.toByteArray());
	}

}