LoggerRuntime.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 * $Id: $
   12 *******************************************************************************/
   13package org.jacoco.core.runtime;
   14
   15import java.util.logging.Handler;
   16import java.util.logging.Level;
   17import java.util.logging.LogRecord;
   18import java.util.logging.Logger;
   19
   20import org.jacoco.core.instr.GeneratorConstants;
   21import org.objectweb.asm.MethodVisitor;
   22import org.objectweb.asm.Opcodes;
   23
   24/**
   25 * This {@link IRuntime} implementation uses the Java logging API to report
   26 * coverage data. The advantage is, that the instrumented classes do not get
   27 * dependencies to other classes than the JRE library itself.
   28 * <p>
   29 * 
   30 * The implementation uses a dedicated log channel. Instrumented classes call
   31 * {@link Logger#log(Level, String, Object[])} with the class identifier in the
   32 * first slot of the parameter array. The runtime implements a {@link Handler}
   33 * for this channel that puts the probe data structure into the first slot of
   34 * the parameter array.
   35 * 
   36 * @author Marc R. Hoffmann
   37 * @version $Revision: $
   38 */
   39public class LoggerRuntime extends AbstractRuntime {
   40
   41    private static final String CHANNEL = "jacoco-runtime";
   42
   43    private final String key;
   44
   45    private final Logger logger;
   46
   47    private final Handler handler;
   48
   49    /**
   50     * Creates a new runtime.
   51     */
   52    public LoggerRuntime() {
   53        this.key = Integer.toHexString(hashCode());
   54        this.logger = configureLogger();
   55        this.handler = new RuntimeHandler();
   56    }
   57
   58    private Logger configureLogger() {
   59        final Logger l = Logger.getLogger(CHANNEL);
   60        l.setUseParentHandlers(false);
   61        l.setLevel(Level.ALL);
   62        return l;
   63    }
   64
   65    public int generateDataAccessor(final long classid, final String classname,
   66            final int probecount, final MethodVisitor mv) {
   67
   68        // The data accessor performs the following steps:
   69        //
   70        // final Object[] args = new Object[3];
   71        // args[0] = Long.valueOf(classid);
   72        // args[1] = classname;
   73        // args[2] = Integer.valueOf(probecount);
   74        // Logger.getLogger(CHANNEL).log(Level.INFO, key, args);
   75        // final byte[] probedata = (byte[]) args[0];
   76        //
   77        // Note that local variable 'args' is used at two places. As were not
   78        // allowed to allocate local variables we have to keep this value with
   79        // DUP and SWAP operations on the operand stack.
   80
   81        // 1. Create parameter array:
   82
   83        ExecutionDataAccess.generateArgumentArray(classid, classname, probecount, mv);
   84
   85        // Stack[0]: [Ljava/lang/Object;
   86
   87        mv.visitInsn(Opcodes.DUP);
   88
   89        // Stack[1]: [Ljava/lang/Object;
   90        // Stack[0]: [Ljava/lang/Object;
   91
   92        // 2. Call Logger:
   93
   94        mv.visitLdcInsn(CHANNEL);
   95        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger",
   96                "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;");
   97
   98        // Stack[2]: Ljava/util/logging/Logger;
   99        // Stack[1]: [Ljava/lang/Object;
  100        // Stack[0]: [Ljava/lang/Object;
  101
  102        mv.visitInsn(Opcodes.SWAP);
  103
  104        // Stack[2]: [Ljava/lang/Object;
  105        // Stack[1]: Ljava/util/logging/Logger;
  106        // Stack[0]: [Ljava/lang/Object;
  107
  108        mv.visitFieldInsn(Opcodes.GETSTATIC, "java/util/logging/Level", "INFO",
  109                "Ljava/util/logging/Level;");
  110
  111        // Stack[3]: Ljava/util/logging/Level;
  112        // Stack[2]: [Ljava/lang/Object;
  113        // Stack[1]: Ljava/util/logging/Logger;
  114        // Stack[0]: [Ljava/lang/Object;
  115
  116        mv.visitInsn(Opcodes.SWAP);
  117
  118        // Stack[3]: [Ljava/lang/Object;
  119        // Stack[2]: Ljava/util/logging/Level;
  120        // Stack[1]: Ljava/util/logging/Logger;
  121        // Stack[0]: [Ljava/lang/Object;
  122
  123        mv.visitLdcInsn(key);
  124
  125        // Stack[4]: Ljava/lang/String;
  126        // Stack[3]: [Ljava/lang/Object;
  127        // Stack[2]: Ljava/util/logging/Level;
  128        // Stack[1]: Ljava/util/logging/Logger;
  129        // Stack[0]: [Ljava/lang/Object;
  130
  131        mv.visitInsn(Opcodes.SWAP);
  132
  133        // Stack[4]: [Ljava/lang/Object;
  134        // Stack[3]: Ljava/lang/String;
  135        // Stack[2]: Ljava/util/logging/Level;
  136        // Stack[1]: Ljava/util/logging/Logger;
  137        // Stack[0]: [Ljava/lang/Object;
  138
  139        mv
  140                .visitMethodInsn(Opcodes.INVOKEVIRTUAL,
  141                        "java/util/logging/Logger", "log",
  142                        "(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V");
  143
  144        // Stack[0]: [Ljava/lang/Object;
  145
  146        // 3. Load data structure from parameter array:
  147
  148        mv.visitInsn(Opcodes.ICONST_0);
  149
  150        // Stack[1]: I
  151        // Stack[0]: [Ljava/lang/Object;
  152
  153        mv.visitInsn(Opcodes.AALOAD);
  154        mv.visitTypeInsn(Opcodes.CHECKCAST, GeneratorConstants.PROBEDATA_TYPE
  155                .getInternalName());
  156
  157        // Stack[0]: [Z
  158
  159        return 5; // Maximum local stack size is 5
  160    }
  161
  162    public void startup() {
  163        this.logger.addHandler(handler);
  164    }
  165
  166    public void shutdown() {
  167        this.logger.removeHandler(handler);
  168    }
  169
  170    private class RuntimeHandler extends Handler {
  171
  172        @Override
  173        public void publish(final LogRecord record) {
  174            if (key.equals(record.getMessage())) {
  175                access.getExecutionData(record.getParameters());
  176            }
  177        }
  178
  179        @Override
  180        public void flush() {
  181        }
  182
  183        @Override
  184        public void close() throws SecurityException {
  185            // The Java logging framework removes and closes all handlers on JVM
  186            // shutdown. As soon as our handler has been removed, all classes
  187            // that might get instrumented during shutdown (e.g. loaded by other
  188            // shutdown hooks) will fail to initialize. Therefore we add ourself
  189            // again here.
  190            // This is a nasty hack that might fail in some Java
  191            // implementations.
  192            logger.addHandler(handler);
  193        }
  194    }
  195
  196}