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 *******************************************************************************/
   12package org.jacoco.core.runtime;
   13
   14import java.util.logging.Handler;
   15import java.util.logging.Level;
   16import java.util.logging.LogRecord;
   17import java.util.logging.Logger;
   18
   19import org.jacoco.core.instr.InstrSupport;
   20import org.objectweb.asm.MethodVisitor;
   21import org.objectweb.asm.Opcodes;
   22
   23/**
   24 * This {@link IRuntime} implementation uses the Java logging API to report
   25 * coverage data.
   26 * <p>
   27 * 
   28 * The implementation uses a dedicated log channel. Instrumented classes call
   29 * {@link Logger#log(Level, String, Object[])} with the class identifier in the
   30 * first slot of the parameter array. The runtime implements a {@link Handler}
   31 * for this channel that puts the probe data structure into the first slot of
   32 * the parameter array.
   33 * 
   34 * @author Marc R. Hoffmann
   35 * @version 0.4.1.20101007204400
   36 */
   37public class LoggerRuntime extends AbstractRuntime {
   38
   39    private static final String CHANNEL = "jacoco-runtime";
   40
   41    private final String key;
   42
   43    private final Logger logger;
   44
   45    private final Handler handler;
   46
   47    /**
   48     * Creates a new runtime.
   49     */
   50    public LoggerRuntime() {
   51        this.key = Integer.toHexString(hashCode());
   52        this.logger = configureLogger();
   53        this.handler = new RuntimeHandler();
   54    }
   55
   56    private Logger configureLogger() {
   57        final Logger l = Logger.getLogger(CHANNEL);
   58        l.setUseParentHandlers(false);
   59        l.setLevel(Level.ALL);
   60        return l;
   61    }
   62
   63    public int generateDataAccessor(final long classid, final String classname,
   64            final int probecount, final MethodVisitor mv) {
   65
   66        // The data accessor performs the following steps:
   67        //
   68        // final Object[] args = new Object[3];
   69        // args[0] = Long.valueOf(classid);
   70        // args[1] = classname;
   71        // args[2] = Integer.valueOf(probecount);
   72        // Logger.getLogger(CHANNEL).log(Level.INFO, key, args);
   73        // final byte[] probedata = (byte[]) args[0];
   74        //
   75        // Note that local variable 'args' is used at two places. As were not
   76        // allowed to allocate local variables we have to keep this value with
   77        // DUP and SWAP operations on the operand stack.
   78
   79        // 1. Create parameter array:
   80
   81        ExecutionDataAccess.generateArgumentArray(classid, classname,
   82                probecount, mv);
   83
   84        // Stack[0]: [Ljava/lang/Object;
   85
   86        mv.visitInsn(Opcodes.DUP);
   87
   88        // Stack[1]: [Ljava/lang/Object;
   89        // Stack[0]: [Ljava/lang/Object;
   90
   91        // 2. Call Logger:
   92
   93        mv.visitLdcInsn(CHANNEL);
   94        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger",
   95                "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;");
   96
   97        // Stack[2]: Ljava/util/logging/Logger;
   98        // Stack[1]: [Ljava/lang/Object;
   99        // Stack[0]: [Ljava/lang/Object;
  100
  101        mv.visitInsn(Opcodes.SWAP);
  102
  103        // Stack[2]: [Ljava/lang/Object;
  104        // Stack[1]: Ljava/util/logging/Logger;
  105        // Stack[0]: [Ljava/lang/Object;
  106
  107        mv.visitFieldInsn(Opcodes.GETSTATIC, "java/util/logging/Level", "INFO",
  108                "Ljava/util/logging/Level;");
  109
  110        // Stack[3]: Ljava/util/logging/Level;
  111        // Stack[2]: [Ljava/lang/Object;
  112        // Stack[1]: Ljava/util/logging/Logger;
  113        // Stack[0]: [Ljava/lang/Object;
  114
  115        mv.visitInsn(Opcodes.SWAP);
  116
  117        // Stack[3]: [Ljava/lang/Object;
  118        // Stack[2]: Ljava/util/logging/Level;
  119        // Stack[1]: Ljava/util/logging/Logger;
  120        // Stack[0]: [Ljava/lang/Object;
  121
  122        mv.visitLdcInsn(key);
  123
  124        // Stack[4]: Ljava/lang/String;
  125        // Stack[3]: [Ljava/lang/Object;
  126        // Stack[2]: Ljava/util/logging/Level;
  127        // Stack[1]: Ljava/util/logging/Logger;
  128        // Stack[0]: [Ljava/lang/Object;
  129
  130        mv.visitInsn(Opcodes.SWAP);
  131
  132        // Stack[4]: [Ljava/lang/Object;
  133        // Stack[3]: Ljava/lang/String;
  134        // Stack[2]: Ljava/util/logging/Level;
  135        // Stack[1]: Ljava/util/logging/Logger;
  136        // Stack[0]: [Ljava/lang/Object;
  137
  138        mv
  139                .visitMethodInsn(Opcodes.INVOKEVIRTUAL,
  140                        "java/util/logging/Logger", "log",
  141                        "(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V");
  142
  143        // Stack[0]: [Ljava/lang/Object;
  144
  145        // 3. Load data structure from parameter array:
  146
  147        mv.visitInsn(Opcodes.ICONST_0);
  148        mv.visitInsn(Opcodes.AALOAD);
  149        mv.visitTypeInsn(Opcodes.CHECKCAST, InstrSupport.DATAFIELD_DESC);
  150
  151        // Stack[0]: [Z
  152
  153        return 5; // Maximum local stack size is 5
  154    }
  155
  156    public void startup() {
  157        setStartTimeStamp();
  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}