LoggerRuntime.java revision 804d83f360c024826dff8c37eb392237cc94240c
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 probe 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.PROBEDATA_TYPE);
146		gen.checkCast(GeneratorConstants.PROBEDATA_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[] data = store.getData(id);
170					if (data == null) {
171						throw new IllegalStateException(String.format(
172								"Unknown class id %x.", id));
173					}
174					params[0] = data;
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}
197