# LoggerRuntime.java

    /*******************************************************************************
     * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
     * All rights reserved. This program and the accompanying materials
     * are made available under the terms of the Eclipse Public License v1.0
     * which accompanies this distribution, and is available at
     * http://www.eclipse.org/legal/epl-v10.html
     *
     * Contributors:
     *    Marc R. Hoffmann - initial API and implementation
     *
     *******************************************************************************/
    package org.jacoco.core.runtime;

    import java.util.logging.Handler;
    import java.util.logging.Level;
    import java.util.logging.LogRecord;
    import java.util.logging.Logger;

    import org.jacoco.core.internal.instr.InstrSupport;
    import org.objectweb.asm.MethodVisitor;
    import org.objectweb.asm.Opcodes;

    /**
     * This {@link IRuntime} implementation uses the Java logging API to report
     * coverage data.
     * <p>
     *
     * The implementation uses a dedicated log channel. Instrumented classes call
     * {@link Logger#log(Level, String, Object[])} with the class identifier in the
     * first slot of the parameter array. The runtime implements a {@link Handler}
     * for this channel that puts the probe data structure into the first slot of
     * the parameter array.
     */
    public class LoggerRuntime extends AbstractRuntime {

    	private static final String CHANNEL = "jacoco-runtime";

    	private final String key;

    	private final Logger logger;

    	private final Handler handler;

    	/**
    	 * Creates a new runtime.
    	 */
    	public LoggerRuntime() {
    		super();
    		this.key = Integer.toHexString(hashCode());
    		this.logger = configureLogger();
    		this.handler = new RuntimeHandler();
    	}

    	private Logger configureLogger() {
    		final Logger l = Logger.getLogger(CHANNEL);
    		l.setUseParentHandlers(false);
    		l.setLevel(Level.ALL);
    		return l;
    	}

    	public int generateDataAccessor(final long classid, final String classname,
    			final int probecount, final MethodVisitor mv) {

    		// The data accessor performs the following steps:
    		//
    		// final Object[] args = new Object[3];
    		// args[0] = Long.valueOf(classid);
    		// args[1] = classname;
    		// args[2] = Integer.valueOf(probecount);
    		// Logger.getLogger(CHANNEL).log(Level.INFO, key, args);
    		// final byte[] probedata = (byte[]) args[0];
    		//
    		// Note that local variable 'args' is used at two places. As were not
    		// allowed to allocate local variables we have to keep this value with
    		// DUP and SWAP operations on the operand stack.

    		// 1. Create parameter array:

    		RuntimeData.generateArgumentArray(classid, classname, probecount, mv);

    		// Stack[0]: [Ljava/lang/Object;

    		mv.visitInsn(Opcodes.DUP);

    		// Stack[1]: [Ljava/lang/Object;
    		// Stack[0]: [Ljava/lang/Object;

    		// 2. Call Logger:

    		mv.visitLdcInsn(CHANNEL);
    		mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger",
    				"getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;");

    		// Stack[2]: Ljava/util/logging/Logger;
    		// Stack[1]: [Ljava/lang/Object;
    		// Stack[0]: [Ljava/lang/Object;

    		mv.visitInsn(Opcodes.SWAP);

    		// Stack[2]: [Ljava/lang/Object;
    		// Stack[1]: Ljava/util/logging/Logger;
    		// Stack[0]: [Ljava/lang/Object;

    		mv.visitFieldInsn(Opcodes.GETSTATIC, "java/util/logging/Level", "INFO",
    				"Ljava/util/logging/Level;");

    		// Stack[3]: Ljava/util/logging/Level;
    		// Stack[2]: [Ljava/lang/Object;
    		// Stack[1]: Ljava/util/logging/Logger;
    		// Stack[0]: [Ljava/lang/Object;

    		mv.visitInsn(Opcodes.SWAP);

    		// Stack[3]: [Ljava/lang/Object;
    		// Stack[2]: Ljava/util/logging/Level;
    		// Stack[1]: Ljava/util/logging/Logger;
    		// Stack[0]: [Ljava/lang/Object;

    		mv.visitLdcInsn(key);

    		// Stack[4]: Ljava/lang/String;
    		// Stack[3]: [Ljava/lang/Object;
    		// Stack[2]: Ljava/util/logging/Level;
    		// Stack[1]: Ljava/util/logging/Logger;
    		// Stack[0]: [Ljava/lang/Object;

    		mv.visitInsn(Opcodes.SWAP);

    		// Stack[4]: [Ljava/lang/Object;
    		// Stack[3]: Ljava/lang/String;
    		// Stack[2]: Ljava/util/logging/Level;
    		// Stack[1]: Ljava/util/logging/Logger;
    		// Stack[0]: [Ljava/lang/Object;

    		mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/util/logging/Logger",
    				"log",
    				"(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V");

    		// Stack[0]: [Ljava/lang/Object;

    		// 3. Load data structure from parameter array:

    		mv.visitInsn(Opcodes.ICONST_0);
    		mv.visitInsn(Opcodes.AALOAD);
    		mv.visitTypeInsn(Opcodes.CHECKCAST, InstrSupport.DATAFIELD_DESC);

    		// Stack[0]: [Z

    		return 5; // Maximum local stack size is 5
    	}

    	@Override
    	public void startup(final RuntimeData data) throws Exception {
    		super.startup(data);
    		this.logger.addHandler(handler);
    	}

    	public void shutdown() {
    		this.logger.removeHandler(handler);
    	}

    	private class RuntimeHandler extends Handler {

    		@Override
    		public void publish(final LogRecord record) {
    			if (key.equals(record.getMessage())) {
    				data.getProbes(record.getParameters());
    			}
    		}

    		@Override
    		public void flush() {
    			// nothing to do
    		}

    		@Override
    		public void close() throws SecurityException {
    			// The Java logging framework removes and closes all handlers on JVM
    			// shutdown. As soon as our handler has been removed, all classes
    			// that might get instrumented during shutdown (e.g. loaded by other
    			// shutdown hooks) will fail to initialize. Therefore we add ourself
    			// again here. This is a nasty hack that might fail in some Java
    			// implementations.
    			logger.addHandler(handler);
    		}
    	}

    }