LoggerRuntime.java

    1/*******************************************************************************
    2 * Copyright (c) 2009 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.Opcodes;
   22import org.objectweb.asm.Type;
   23import org.objectweb.asm.commons.GeneratorAdapter;
   24
   25/**
   26 * This {@link IRuntime} implementation uses the Java logging API to report
   27 * coverage data. The advantage is, that the instrumented classes do not get
   28 * dependencies to other classes than the JRE library itself.
   29 * <p>
   30 * 
   31 * The implementation uses a dedicated log channel. Instrumented classes call
   32 * {@link Logger#log(Level, String, Object[])} with the class identifier in the
   33 * first slot of the parameter array. The runtime implements a {@link Handler}
   34 * for this channel that puts the block data structure into the first slot of
   35 * the parameter array.
   36 * 
   37 * @author Marc R. Hoffmann
   38 * @version $Revision: $
   39 */
   40public class LoggerRuntime extends AbstractRuntime {
   41
   42    private static final String CHANNEL = "jacoco-runtime";
   43
   44    private final String key;
   45
   46    private final Logger logger;
   47
   48    private final Handler handler;
   49
   50    /**
   51     * Creates a new runtime.
   52     */
   53    public LoggerRuntime() {
   54        this.key = Integer.toHexString(hashCode());
   55        this.logger = configureLogger();
   56        this.handler = new RuntimeHandler();
   57    }
   58
   59    private Logger configureLogger() {
   60        final Logger l = Logger.getLogger(CHANNEL);
   61        l.setUseParentHandlers(false);
   62        l.setLevel(Level.ALL);
   63        return l;
   64    }
   65
   66    public int generateDataAccessor(final long classid,
   67            final GeneratorAdapter gen) {
   68
   69        // 1. Create parameter array:
   70
   71        gen.push(1);
   72        gen.newArray(Type.getObjectType("java/lang/Object"));
   73
   74        // Stack[0]: [Ljava/lang/Object;
   75
   76        gen.dup();
   77
   78        // Stack[1]: [Ljava/lang/Object;
   79        // Stack[0]: [Ljava/lang/Object;
   80
   81        gen.push(0);
   82        gen.push(classid);
   83
   84        // Stack[4]: J
   85        // Stack[3]: .
   86        // Stack[2]: I
   87        // Stack[1]: [Ljava/lang/Object;
   88        // Stack[0]: [Ljava/lang/Object;
   89
   90        gen.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",
   91                "(J)Ljava/lang/Long;");
   92
   93        // Stack[3]: Ljava/lang/Long;
   94        // Stack[2]: I
   95        // Stack[1]: [Ljava/lang/Object;
   96        // Stack[0]: [Ljava/lang/Object;
   97
   98        gen.arrayStore(Type.getObjectType("java/lang/Object"));
   99
  100        // Stack[0]: [Ljava/lang/Object;
  101
  102        final int param = gen.newLocal(Type.getObjectType("java/lang/Object"));
  103        gen.storeLocal(param);
  104
  105        // 2. Call Logger:
  106
  107        gen.push(CHANNEL);
  108        gen.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger",
  109                "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;");
  110
  111        // Stack[0]: Ljava/util/logging/Logger;
  112
  113        gen.getStatic(Type.getObjectType("java/util/logging/Level"), "INFO",
  114                Type.getObjectType("java/util/logging/Level"));
  115
  116        // Stack[1]: Ljava/util/logging/Level;
  117        // Stack[0]: Ljava/util/logging/Logger;
  118
  119        gen.push(key);
  120
  121        // Stack[2]: Ljava/lang/String;
  122        // Stack[1]: Ljava/util/logging/Level;
  123        // Stack[0]: Ljava/util/logging/Logger;
  124
  125        gen.loadLocal(param);
  126
  127        // Stack[3]: [Ljava/lang/Object;
  128        // Stack[2]: Ljava/lang/String;
  129        // Stack[1]: Ljava/util/logging/Level;
  130        // Stack[0]: Ljava/util/logging/Logger;
  131
  132        gen
  133                .visitMethodInsn(Opcodes.INVOKEVIRTUAL,
  134                        "java/util/logging/Logger", "log",
  135                        "(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V");
  136
  137        // 3. Load data structure from parameter array:
  138
  139        gen.loadLocal(param);
  140        gen.push(0);
  141
  142        // Stack[1]: I
  143        // Stack[0]: [Ljava/lang/Object;
  144
  145        gen.arrayLoad(GeneratorConstants.DATAFIELD_TYPE);
  146        gen.checkCast(GeneratorConstants.DATAFIELD_TYPE);
  147
  148        // Stack[0]: [[Z
  149
  150        return 5; // Maximum local stack size is 5
  151    }
  152
  153    public void startup() {
  154        this.logger.addHandler(handler);
  155    }
  156
  157    public void shutdown() {
  158        this.logger.removeHandler(handler);
  159    }
  160
  161    private class RuntimeHandler extends Handler {
  162
  163        @Override
  164        public void publish(final LogRecord record) {
  165            if (key.equals(record.getMessage())) {
  166                final Object[] params = record.getParameters();
  167                final Long id = (Long) params[0];
  168                synchronized (store) {
  169                    final boolean[][] blockdata = store.getData(id);
  170                    if (blockdata == null) {
  171                        throw new IllegalStateException(String.format(
  172                                "Unknown class id %x.", id));
  173                    }
  174                    params[0] = blockdata;
  175                }
  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}