LoggerRuntime.java

    1/*******************************************************************************
    2 * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and others
    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 MethodVisitor mv) {
   66
   67        // 1. Create parameter array:
   68
   69        mv.visitInsn(Opcodes.ICONST_1);
   70        mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
   71
   72        // Stack[0]: [Ljava/lang/Object;
   73
   74        mv.visitInsn(Opcodes.DUP);
   75
   76        // Stack[1]: [Ljava/lang/Object;
   77        // Stack[0]: [Ljava/lang/Object;
   78
   79        mv.visitInsn(Opcodes.ICONST_0);
   80        mv.visitLdcInsn(Long.valueOf(classid));
   81
   82        // Stack[4]: J
   83        // Stack[3]: .
   84        // Stack[2]: I
   85        // Stack[1]: [Ljava/lang/Object;
   86        // Stack[0]: [Ljava/lang/Object;
   87
   88        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",
   89                "(J)Ljava/lang/Long;");
   90
   91        // Stack[3]: Ljava/lang/Long;
   92        // Stack[2]: I
   93        // Stack[1]: [Ljava/lang/Object;
   94        // Stack[0]: [Ljava/lang/Object;
   95
   96        mv.visitInsn(Opcodes.AASTORE);
   97
   98        // Stack[0]: [Ljava/lang/Object;
   99
  100        mv.visitVarInsn(Opcodes.ASTORE, 0);
  101
  102        // 2. Call Logger:
  103
  104        mv.visitLdcInsn(CHANNEL);
  105        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger",
  106                "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;");
  107
  108        // Stack[0]: Ljava/util/logging/Logger;
  109
  110        mv.visitFieldInsn(Opcodes.GETSTATIC, "java/util/logging/Level",
  111                "INFO", "Ljava/util/logging/Level;");
  112
  113        // Stack[1]: Ljava/util/logging/Level;
  114        // Stack[0]: Ljava/util/logging/Logger;
  115
  116        mv.visitLdcInsn(key);
  117
  118        // Stack[2]: Ljava/lang/String;
  119        // Stack[1]: Ljava/util/logging/Level;
  120        // Stack[0]: Ljava/util/logging/Logger;
  121
  122        mv.visitVarInsn(Opcodes.ALOAD, 0);
  123
  124        // Stack[3]: [Ljava/lang/Object;
  125        // Stack[2]: Ljava/lang/String;
  126        // Stack[1]: Ljava/util/logging/Level;
  127        // Stack[0]: Ljava/util/logging/Logger;
  128
  129        mv
  130                .visitMethodInsn(Opcodes.INVOKEVIRTUAL,
  131                        "java/util/logging/Logger", "log",
  132                        "(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V");
  133
  134        // 3. Load data structure from parameter array:
  135
  136        mv.visitVarInsn(Opcodes.ALOAD, 0);
  137        mv.visitInsn(Opcodes.ICONST_0);
  138
  139        // Stack[1]: I
  140        // Stack[0]: [Ljava/lang/Object;
  141
  142        mv.visitInsn(Opcodes.AALOAD);
  143        mv.visitTypeInsn(Opcodes.CHECKCAST, GeneratorConstants.PROBEDATA_TYPE
  144                .getInternalName());
  145
  146        // Stack[0]: [Z
  147
  148        return 5; // Maximum local stack size is 5
  149    }
  150
  151    public void startup() {
  152        this.logger.addHandler(handler);
  153    }
  154
  155    public void shutdown() {
  156        this.logger.removeHandler(handler);
  157    }
  158
  159    private class RuntimeHandler extends Handler {
  160
  161        @Override
  162        public void publish(final LogRecord record) {
  163            if (key.equals(record.getMessage())) {
  164                final Object[] params = record.getParameters();
  165                final Long id = (Long) params[0];
  166                synchronized (store) {
  167                    final boolean[] data = store.getData(id);
  168                    if (data == null) {
  169                        throw new IllegalStateException(String.format(
  170                                "Unknown class id %x.", id));
  171                    }
  172                    params[0] = data;
  173                }
  174            }
  175        }
  176
  177        @Override
  178        public void flush() {
  179        }
  180
  181        @Override
  182        public void close() throws SecurityException {
  183            // The Java logging framework removes and closes all handlers on JVM
  184            // shutdown. As soon as our handler has been removed, all classes
  185            // that might get instrumented during shutdown (e.g. loaded by other
  186            // shutdown hooks) will fail to initialize. Therefore we add ourself
  187            // again here.
  188            // This is a nasty hack that might fail in some Java
  189            // implementations.
  190            logger.addHandler(handler);
  191        }
  192    }
  193
  194}