1/*******************************************************************************
2 * Copyright (c) 2009, 2015 Mountainminds GmbH & Co. KG and Contributors
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 *******************************************************************************/
12package org.jacoco.core.runtime;
13
14import java.util.logging.Handler;
15import java.util.logging.Level;
16import java.util.logging.LogRecord;
17import java.util.logging.Logger;
18
19import org.jacoco.core.internal.instr.InstrSupport;
20import org.objectweb.asm.MethodVisitor;
21import org.objectweb.asm.Opcodes;
22
23/**
24 * This {@link IRuntime} implementation uses the Java logging API to report
25 * coverage data.
26 * <p>
27 *
28 * The implementation uses a dedicated log channel. Instrumented classes call
29 * {@link Logger#log(Level, String, Object[])} with the class identifier in the
30 * first slot of the parameter array. The runtime implements a {@link Handler}
31 * for this channel that puts the probe data structure into the first slot of
32 * the parameter array.
33 */
34public class LoggerRuntime extends AbstractRuntime {
35
36	private static final String CHANNEL = "jacoco-runtime";
37
38	private final String key;
39
40	private final Logger logger;
41
42	private final Handler handler;
43
44	/**
45	 * Creates a new runtime.
46	 */
47	public LoggerRuntime() {
48		super();
49		this.key = Integer.toHexString(hashCode());
50		this.logger = configureLogger();
51		this.handler = new RuntimeHandler();
52	}
53
54	private Logger configureLogger() {
55		final Logger l = Logger.getLogger(CHANNEL);
56		l.setUseParentHandlers(false);
57		l.setLevel(Level.ALL);
58		return l;
59	}
60
61	public int generateDataAccessor(final long classid, final String classname,
62			final int probecount, final MethodVisitor mv) {
63
64		// The data accessor performs the following steps:
65		//
66		// final Object[] args = new Object[3];
67		// args[0] = Long.valueOf(classid);
68		// args[1] = classname;
69		// args[2] = Integer.valueOf(probecount);
70		// Logger.getLogger(CHANNEL).log(Level.INFO, key, args);
71		// final byte[] probedata = (byte[]) args[0];
72		//
73		// Note that local variable 'args' is used at two places. As were not
74		// allowed to allocate local variables we have to keep this value with
75		// DUP and SWAP operations on the operand stack.
76
77		// 1. Create parameter array:
78
79		RuntimeData.generateArgumentArray(classid, classname, probecount, mv);
80
81		// Stack[0]: [Ljava/lang/Object;
82
83		mv.visitInsn(Opcodes.DUP);
84
85		// Stack[1]: [Ljava/lang/Object;
86		// Stack[0]: [Ljava/lang/Object;
87
88		// 2. Call Logger:
89
90		mv.visitLdcInsn(CHANNEL);
91		mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger",
92				"getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;",
93				false);
94
95		// Stack[2]: Ljava/util/logging/Logger;
96		// Stack[1]: [Ljava/lang/Object;
97		// Stack[0]: [Ljava/lang/Object;
98
99		mv.visitInsn(Opcodes.SWAP);
100
101		// Stack[2]: [Ljava/lang/Object;
102		// Stack[1]: Ljava/util/logging/Logger;
103		// Stack[0]: [Ljava/lang/Object;
104
105		mv.visitFieldInsn(Opcodes.GETSTATIC, "java/util/logging/Level", "INFO",
106				"Ljava/util/logging/Level;");
107
108		// Stack[3]: Ljava/util/logging/Level;
109		// Stack[2]: [Ljava/lang/Object;
110		// Stack[1]: Ljava/util/logging/Logger;
111		// Stack[0]: [Ljava/lang/Object;
112
113		mv.visitInsn(Opcodes.SWAP);
114
115		// Stack[3]: [Ljava/lang/Object;
116		// Stack[2]: Ljava/util/logging/Level;
117		// Stack[1]: Ljava/util/logging/Logger;
118		// Stack[0]: [Ljava/lang/Object;
119
120		mv.visitLdcInsn(key);
121
122		// Stack[4]: Ljava/lang/String;
123		// Stack[3]: [Ljava/lang/Object;
124		// Stack[2]: Ljava/util/logging/Level;
125		// Stack[1]: Ljava/util/logging/Logger;
126		// Stack[0]: [Ljava/lang/Object;
127
128		mv.visitInsn(Opcodes.SWAP);
129
130		// Stack[4]: [Ljava/lang/Object;
131		// Stack[3]: Ljava/lang/String;
132		// Stack[2]: Ljava/util/logging/Level;
133		// Stack[1]: Ljava/util/logging/Logger;
134		// Stack[0]: [Ljava/lang/Object;
135
136		mv.visitMethodInsn(
137				Opcodes.INVOKEVIRTUAL,
138				"java/util/logging/Logger",
139				"log",
140				"(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V",
141				false);
142
143		// Stack[0]: [Ljava/lang/Object;
144
145		// 3. Load data structure from parameter array:
146
147		mv.visitInsn(Opcodes.ICONST_0);
148		mv.visitInsn(Opcodes.AALOAD);
149		mv.visitTypeInsn(Opcodes.CHECKCAST, InstrSupport.DATAFIELD_DESC);
150
151		// Stack[0]: [Z
152
153		return 5; // Maximum local stack size is 5
154	}
155
156	@Override
157	public void startup(final RuntimeData data) throws Exception {
158		super.startup(data);
159		this.logger.addHandler(handler);
160	}
161
162	public void shutdown() {
163		this.logger.removeHandler(handler);
164	}
165
166	private class RuntimeHandler extends Handler {
167
168		@Override
169		public void publish(final LogRecord record) {
170			if (key.equals(record.getMessage())) {
171				data.getProbes(record.getParameters());
172			}
173		}
174
175		@Override
176		public void flush() {
177			// nothing to do
178		}
179
180		@Override
181		public void close() throws SecurityException {
182			// The Java logging framework removes and closes all handlers on JVM
183			// shutdown. As soon as our handler has been removed, all classes
184			// that might get instrumented during shutdown (e.g. loaded by other
185			// shutdown hooks) will fail to initialize. Therefore we add ourself
186			// again here. This is a nasty hack that might fail in some Java
187			// implementations.
188			logger.addHandler(handler);
189		}
190	}
191
192}
193