CoverageTransformer.java

    1/*******************************************************************************
    2 * Copyright (c) 2009, 2010 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;
   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.CRC64;
   21import org.jacoco.core.instr.Instrumenter;
   22import org.jacoco.core.runtime.AgentOptions;
   23import org.jacoco.core.runtime.IRuntime;
   24import org.jacoco.core.runtime.WildcardMatcher;
   25
   26/**
   27 * Class file transformer to instrument classes for code coverage analysis.
   28 * 
   29 * @author Marc R. Hoffmann
   30 * @version 0.4.1.20101007204400
   31 */
   32public class CoverageTransformer implements ClassFileTransformer {
   33
   34    private static final String AGENT_PREFIX;
   35
   36    static {
   37        final String name = CoverageTransformer.class.getName();
   38        AGENT_PREFIX = toVMName(name.substring(0, name.lastIndexOf('.')));
   39    }
   40
   41    private final IExceptionLogger logger;
   42
   43    private final Instrumenter instrumenter;
   44
   45    private final WildcardMatcher includes;
   46
   47    private final WildcardMatcher excludes;
   48
   49    private final WildcardMatcher exclClassloader;
   50
   51    public CoverageTransformer(IRuntime runtime, AgentOptions options,
   52            final IExceptionLogger logger) {
   53        this.instrumenter = new Instrumenter(runtime);
   54        this.logger = logger;
   55        // Class names will be reported in VM notation:
   56        includes = new WildcardMatcher(toVMName(options.getIncludes()));
   57        excludes = new WildcardMatcher(toVMName(options.getExcludes()));
   58        exclClassloader = new WildcardMatcher(options.getExclClassloader());
   59    }
   60
   61    public byte[] transform(ClassLoader loader, String classname,
   62            Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
   63            byte[] classfileBuffer) throws IllegalClassFormatException {
   64
   65        if (!filter(loader, classname)) {
   66            return null;
   67        }
   68
   69        try {
   70            return instrumenter.instrument(classfileBuffer);
   71        } catch (Throwable t) {
   72            final Long id = Long.valueOf(CRC64.checksum(classfileBuffer));
   73            final String msg = "Error while instrumenting class %s (id=%016x).";
   74            final IllegalClassFormatException ex = new IllegalClassFormatException(
   75                    format(msg, classname, id));
   76            ex.initCause(t);
   77            // Report this, as the exception is ignored by the JVM:
   78            logger.logExeption(ex);
   79            throw ex;
   80        }
   81    }
   82
   83    /**
   84     * Checks whether this class should be instrumented.
   85     * 
   86     * @param loader
   87     *            loader for the class
   88     * @return <code>true</code> if the class should be instrumented
   89     */
   90    protected boolean filter(ClassLoader loader, String classname) {
   91        // Don't instrument classes of the bootstrap loader:
   92        return loader != null &&
   93
   94        !classname.startsWith(AGENT_PREFIX) &&
   95
   96        !exclClassloader.matches(loader.getClass().getName()) &&
   97
   98        includes.matches(classname) &&
   99
  100        !excludes.matches(classname);
  101    }
  102
  103    private static String toVMName(String srcName) {
  104        return srcName.replace('.', '/');
  105    }
  106
  107}