CoverageTransformer.java revision ae1034c608eeca9765a43bec34bcb8e5bf23eaff
1/*******************************************************************************
2 * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 *    Marc R. Hoffmann - initial API and implementation
10 *
11 *******************************************************************************/
12package org.jacoco.agent.rt.internal;
13
14import static java.lang.String.format;
15
16import java.lang.instrument.ClassFileTransformer;
17import java.lang.instrument.IllegalClassFormatException;
18import java.security.ProtectionDomain;
19
20import org.jacoco.core.instr.Instrumenter;
21import org.jacoco.core.runtime.AgentOptions;
22import org.jacoco.core.runtime.IRuntime;
23import org.jacoco.core.runtime.WildcardMatcher;
24
25/**
26 * Class file transformer to instrument classes for code coverage analysis.
27 */
28public class CoverageTransformer implements ClassFileTransformer {
29
30	private static final String AGENT_PREFIX;
31
32	static {
33		final String name = CoverageTransformer.class.getName();
34		AGENT_PREFIX = toVMName(name.substring(0, name.lastIndexOf('.')));
35	}
36
37	private final IRuntime runtime;
38
39	private final Instrumenter instrumenter;
40
41	private final IExceptionLogger logger;
42
43	private final WildcardMatcher includes;
44
45	private final WildcardMatcher excludes;
46
47	private final WildcardMatcher exclClassloader;
48
49	private final ClassFileDumper classFileDumper;
50
51	/**
52	 * New transformer with the given delegates.
53	 *
54	 * @param runtime
55	 *            coverage runtime
56	 * @param options
57	 *            configuration options for the generator
58	 * @param logger
59	 *            logger for exceptions during instrumentation
60	 */
61	public CoverageTransformer(final IRuntime runtime,
62			final AgentOptions options, final IExceptionLogger logger) {
63		this.runtime = runtime;
64		this.instrumenter = new Instrumenter(runtime);
65		this.logger = logger;
66		// Class names will be reported in VM notation:
67		includes = new WildcardMatcher(toVMName(options.getIncludes()));
68		excludes = new WildcardMatcher(toVMName(options.getExcludes()));
69		exclClassloader = new WildcardMatcher(options.getExclClassloader());
70		classFileDumper = new ClassFileDumper(options.getClassDumpDir());
71	}
72
73	public byte[] transform(final ClassLoader loader, final String classname,
74			final Class<?> classBeingRedefined,
75			final ProtectionDomain protectionDomain,
76			final byte[] classfileBuffer) throws IllegalClassFormatException {
77
78		if (!filter(loader, classname)) {
79			return null;
80		}
81
82		try {
83			classFileDumper.dump(classname, classfileBuffer);
84			if (classBeingRedefined != null) {
85				// For redefined classes we must clear the execution data
86				// reference as probes might have changed.
87				runtime.disconnect(classBeingRedefined);
88			}
89			return instrumenter.instrument(classfileBuffer);
90		} catch (final Exception ex) {
91			final IllegalClassFormatException wrapper = new IllegalClassFormatException(
92					format("Error while instrumenting class %s.", classname));
93			wrapper.initCause(ex);
94			// Report this, as the exception is ignored by the JVM:
95			logger.logExeption(wrapper);
96			throw wrapper;
97		}
98	}
99
100	/**
101	 * Checks whether this class should be instrumented.
102	 *
103	 * @param loader
104	 *            loader for the class
105	 * @param classname
106	 *            VM name of the class to check
107	 * @return <code>true</code> if the class should be instrumented
108	 */
109	protected boolean filter(final ClassLoader loader, final String classname) {
110		// Don't instrument classes of the bootstrap loader:
111		return loader != null &&
112
113		!classname.startsWith(AGENT_PREFIX) &&
114
115		!exclClassloader.matches(loader.getClass().getName()) &&
116
117		includes.matches(classname) &&
118
119		!excludes.matches(classname);
120	}
121
122	private static String toVMName(final String srcName) {
123		return srcName.replace('.', '/');
124	}
125
126}
127