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.InstrSupport;
   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.
   27 * <p>
   28 * 
   29 * The implementation uses a dedicated log channel. Instrumented classes call
   30 * {@link Logger#log(Level, String, Object[])} with the class identifier in the
   31 * first slot of the parameter array. The runtime implements a {@link Handler}
   32 * for this channel that puts the probe data structure into the first slot of
   33 * the parameter array.
   34 * 
   35 * @author Marc R. Hoffmann
   36 * @version $Revision: $
   37 */
   38public class LoggerRuntime extends AbstractRuntime {
   39
   40    private static final String CHANNEL = "jacoco-runtime";
   41
   42    private final String key;
   43
   44    private final Logger logger;
   45
   46    private final Handler handler;
   47
   48    /**
   49     * Creates a new runtime.
   50     */
   51    public LoggerRuntime() {
   52        this.key = Integer.toHexString(hashCode());
   53        this.logger = configureLogger();
   54        this.handler = new RuntimeHandler();
   55    }
   56
   57    private Logger configureLogger() {
   58        final Logger l = Logger.getLogger(CHANNEL);
   59        l.setUseParentHandlers(false);
   60        l.setLevel(Level.ALL);
   61        return l;
   62    }
   63
   64    public int generateDataAccessor(final long classid, final String classname,
   65            final int probecount, final MethodVisitor mv) {
   66
   67        // The data accessor performs the following steps:
   68        //
   69        // final Object[] args = new Object[3];
   70        // args[0] = Long.valueOf(classid);
   71        // args[1] = classname;
   72        // args[2] = Integer.valueOf(probecount);
   73        // Logger.getLogger(CHANNEL).log(Level.INFO, key, args);
   74        // final byte[] probedata = (byte[]) args[0];
   75        //
   76        // Note that local variable 'args' is used at two places. As were not
   77        // allowed to allocate local variables we have to keep this value with
   78        // DUP and SWAP operations on the operand stack.
   79
   80        // 1. Create parameter array:
   81
   82        ExecutionDataAccess.generateArgumentArray(classid, classname,
   83                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        mv.visitInsn(Opcodes.AALOAD);
  150        mv.visitTypeInsn(Opcodes.CHECKCAST, InstrSupport.DATAFIELD_DESC);
  151
  152        // Stack[0]: [Z
  153
  154        return 5; // Maximum local stack size is 5
  155    }
  156
  157    public void startup() {
  158        this.logger.addHandler(handler);
  159    }
  160
  161    public void shutdown() {
  162        this.logger.removeHandler(handler);
  163    }
  164
  165    private class RuntimeHandler extends Handler {
  166
  167        @Override
  168        public void publish(final LogRecord record) {
  169            if (key.equals(record.getMessage())) {
  170                access.getExecutionData(record.getParameters());
  171            }
  172        }
  173
  174        @Override
  175        public void flush() {
  176        }
  177
  178        @Override
  179        public void close() throws SecurityException {
  180            // The Java logging framework removes and closes all handlers on JVM
  181            // shutdown. As soon as our handler has been removed, all classes
  182            // that might get instrumented during shutdown (e.g. loaded by other
  183            // shutdown hooks) will fail to initialize. Therefore we add ourself
  184            // again here. This is a nasty hack that might fail in some Java
  185            // implementations.
  186            logger.addHandler(handler);
  187        }
  188    }
  189
  190}