CoverageTransformer.java

/*******************************************************************************
 * Copyright (c) 2009, 2012 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.agent.rt;

import static java.lang.String.format;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

import org.jacoco.core.instr.Instrumenter;
import org.jacoco.core.runtime.AgentOptions;
import org.jacoco.core.runtime.IRuntime;
import org.jacoco.core.runtime.WildcardMatcher;

/**
 * Class file transformer to instrument classes for code coverage analysis.
 */
public class CoverageTransformer implements ClassFileTransformer {

	private static final String AGENT_PREFIX;

	static {
		final String name = CoverageTransformer.class.getName();
		AGENT_PREFIX = toVMName(name.substring(0, name.lastIndexOf('.')));
	}

	private final IExceptionLogger logger;

	private final Instrumenter instrumenter;

	private final WildcardMatcher includes;

	private final WildcardMatcher excludes;

	private final WildcardMatcher exclClassloader;

	public CoverageTransformer(IRuntime runtime, AgentOptions options,
			final IExceptionLogger logger) {
		this.instrumenter = new Instrumenter(runtime);
		this.logger = logger;
		// Class names will be reported in VM notation:
		includes = new WildcardMatcher(toWildcard(toVMName(options.getIncludes())));
		excludes = new WildcardMatcher(toWildcard(toVMName(options.getExcludes())));
		exclClassloader = new WildcardMatcher(toWildcard(options.getExclClassloader()));
	}

	public byte[] transform(ClassLoader loader, String classname,
			Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
			byte[] classfileBuffer) throws IllegalClassFormatException {

		if (!filter(loader, classname)) {
			return null;
		}

		try {
			return instrumenter.instrument(classfileBuffer);
		} catch (Throwable t) {
			final String msg = "Error while instrumenting class %s.";
			final IllegalClassFormatException ex = new IllegalClassFormatException(
					format(msg, classname));
			// Report this, as the exception is ignored by the JVM:
			logger.logExeption(ex);
			throw (IllegalClassFormatException) ex.initCause(t);
		}
	}

	/**
	 * Checks whether this class should be instrumented.
	 * 
	 * @param loader
	 *            loader for the class
	 * @return <code>true</code> if the class should be instrumented
	 */
	protected boolean filter(ClassLoader loader, String classname) {
		// Don't instrument classes of the bootstrap loader:
		return loader != null &&

		!classname.startsWith(AGENT_PREFIX) &&

		!exclClassloader.matches(loader.getClass().getName()) &&

		includes.matches(classname) &&

		!excludes.matches(classname);
	}

	private String toWildcard(String src) {
		if (src.indexOf('|') != -1) {
			final IllegalArgumentException ex = new IllegalArgumentException("Usage of '|' as a list separator for JaCoCo agent options is deprecated and will not work in future versions - use ':' instead.");
			logger.logExeption(ex);
			return src.replace('|', ':');
		}
		return src;
	}

	private static String toVMName(String srcName) {
		return srcName.replace('.', '/');
	}

}