LoggerRuntime.java revision 177434c487364833a95015fbfd38b6a58ca56729
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 void generateDataAccessor(final long classid,
67			final GeneratorAdapter gen) {
68
69		// 1. Create parameter array:
70
71		final int param = gen.newLocal(Type.getObjectType("java/lang/Object"));
72
73		// stack := new Object[1]
74		gen.push(1);
75		gen.newArray(Type.getObjectType("java/lang/Object"));
76
77		// stack[0] = Long.valueOf(classId)
78		gen.dup();
79		gen.push(0);
80		gen.push(classid);
81		gen.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",
82				"(J)Ljava/lang/Long;");
83		gen.arrayStore(Type.getObjectType("java/lang/Object"));
84
85		gen.storeLocal(param);
86
87		// 2. Call Logger:
88
89		// stack := Logger.getLogger(CHANNEL)
90		gen.push(CHANNEL);
91		gen.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger",
92				"getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;");
93
94		// stack = Level.INFO;
95		gen.getStatic(Type.getObjectType("java/util/logging/Level"), "INFO",
96				Type.getObjectType("java/util/logging/Level"));
97
98		// stack := key
99		gen.push(key);
100
101		// stack := param
102		gen.loadLocal(param);
103
104		// stack.log(stack, stack, stack)
105		gen
106				.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
107						"java/util/logging/Logger", "log",
108						"(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V");
109
110		// 3. Load data structure from parameter array:
111
112		gen.loadLocal(param);
113		gen.push(0);
114		gen.arrayLoad(GeneratorConstants.DATAFIELD_TYPE);
115		gen.checkCast(GeneratorConstants.DATAFIELD_TYPE);
116	}
117
118	public void startup() {
119		this.logger.addHandler(handler);
120	}
121
122	public void shutdown() {
123		this.logger.removeHandler(handler);
124	}
125
126	private class RuntimeHandler extends Handler {
127
128		@Override
129		public void publish(final LogRecord record) {
130			if (key.equals(record.getMessage())) {
131				final Object[] params = record.getParameters();
132				final Long id = (Long) params[0];
133				synchronized (store) {
134					final boolean[][] blockdata = store.getData(id);
135					if (blockdata == null) {
136						throw new IllegalStateException(String.format(
137								"Unknown class id %x.", id));
138					}
139					params[0] = blockdata;
140				}
141			}
142		}
143
144		@Override
145		public void flush() {
146		}
147
148		@Override
149		public void close() throws SecurityException {
150			// The Java logging framework removes and closes all handlers on JVM
151			// shutdown. As soon as our handler has been removed, all classes
152			// that might get instrumented during shutdown (e.g. loaded by other
153			// shutdown hooks) will fail to initialize. Therefore we add ourself
154			// again here.
155			// This is a nasty hack that might fail in some Java
156			// implementations.
157			logger.addHandler(handler);
158		}
159	}
160
161}
162