LoggerRuntime.java
1/*******************************************************************************
2 * Copyright (c) 2009, 2010 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.MethodVisitor;
22import org.objectweb.asm.Opcodes;
23
24/**
25 * This {@link IRuntime} implementation uses the Java logging API to report
26 * coverage data. The advantage is, that the instrumented classes do not get
27 * dependencies to other classes than the JRE library itself.
28 * <p>
29 *
30 * The implementation uses a dedicated log channel. Instrumented classes call
31 * {@link Logger#log(Level, String, Object[])} with the class identifier in the
32 * first slot of the parameter array. The runtime implements a {@link Handler}
33 * for this channel that puts the probe data structure into the first slot of
34 * the parameter array.
35 *
36 * @author Marc R. Hoffmann
37 * @version $Revision: $
38 */
39public class LoggerRuntime extends AbstractRuntime {
40
41 private static final String CHANNEL = "jacoco-runtime";
42
43 private final String key;
44
45 private final Logger logger;
46
47 private final Handler handler;
48
49 /**
50 * Creates a new runtime.
51 */
52 public LoggerRuntime() {
53 this.key = Integer.toHexString(hashCode());
54 this.logger = configureLogger();
55 this.handler = new RuntimeHandler();
56 }
57
58 private Logger configureLogger() {
59 final Logger l = Logger.getLogger(CHANNEL);
60 l.setUseParentHandlers(false);
61 l.setLevel(Level.ALL);
62 return l;
63 }
64
65 public int generateDataAccessor(final long classid, final MethodVisitor mv) {
66
67 // 1. Create parameter array:
68
69 mv.visitInsn(Opcodes.ICONST_1);
70 mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
71
72 // Stack[0]: [Ljava/lang/Object;
73
74 mv.visitInsn(Opcodes.DUP);
75
76 // Stack[1]: [Ljava/lang/Object;
77 // Stack[0]: [Ljava/lang/Object;
78
79 mv.visitInsn(Opcodes.ICONST_0);
80 mv.visitLdcInsn(Long.valueOf(classid));
81
82 // Stack[4]: J
83 // Stack[3]: .
84 // Stack[2]: I
85 // Stack[1]: [Ljava/lang/Object;
86 // Stack[0]: [Ljava/lang/Object;
87
88 mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",
89 "(J)Ljava/lang/Long;");
90
91 // Stack[3]: Ljava/lang/Long;
92 // Stack[2]: I
93 // Stack[1]: [Ljava/lang/Object;
94 // Stack[0]: [Ljava/lang/Object;
95
96 mv.visitInsn(Opcodes.AASTORE);
97
98 // Stack[0]: [Ljava/lang/Object;
99
100 mv.visitVarInsn(Opcodes.ASTORE, 0);
101
102 // 2. Call Logger:
103
104 mv.visitLdcInsn(CHANNEL);
105 mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger",
106 "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;");
107
108 // Stack[0]: Ljava/util/logging/Logger;
109
110 mv.visitFieldInsn(Opcodes.GETSTATIC, "java/util/logging/Level",
111 "INFO", "Ljava/util/logging/Level;");
112
113 // Stack[1]: Ljava/util/logging/Level;
114 // Stack[0]: Ljava/util/logging/Logger;
115
116 mv.visitLdcInsn(key);
117
118 // Stack[2]: Ljava/lang/String;
119 // Stack[1]: Ljava/util/logging/Level;
120 // Stack[0]: Ljava/util/logging/Logger;
121
122 mv.visitVarInsn(Opcodes.ALOAD, 0);
123
124 // Stack[3]: [Ljava/lang/Object;
125 // Stack[2]: Ljava/lang/String;
126 // Stack[1]: Ljava/util/logging/Level;
127 // Stack[0]: Ljava/util/logging/Logger;
128
129 mv
130 .visitMethodInsn(Opcodes.INVOKEVIRTUAL,
131 "java/util/logging/Logger", "log",
132 "(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V");
133
134 // 3. Load data structure from parameter array:
135
136 mv.visitVarInsn(Opcodes.ALOAD, 0);
137 mv.visitInsn(Opcodes.ICONST_0);
138
139 // Stack[1]: I
140 // Stack[0]: [Ljava/lang/Object;
141
142 mv.visitInsn(Opcodes.AALOAD);
143 mv.visitTypeInsn(Opcodes.CHECKCAST, GeneratorConstants.PROBEDATA_TYPE
144 .getInternalName());
145
146 // Stack[0]: [Z
147
148 return 5; // Maximum local stack size is 5
149 }
150
151 public void startup() {
152 this.logger.addHandler(handler);
153 }
154
155 public void shutdown() {
156 this.logger.removeHandler(handler);
157 }
158
159 private class RuntimeHandler extends Handler {
160
161 @Override
162 public void publish(final LogRecord record) {
163 if (key.equals(record.getMessage())) {
164 final Object[] params = record.getParameters();
165 final Long id = (Long) params[0];
166 synchronized (store) {
167 final boolean[] data = store.getData(id);
168 if (data == null) {
169 throw new IllegalStateException(String.format(
170 "Unknown class id %x.", id));
171 }
172 params[0] = data;
173 }
174 }
175 }
176
177 @Override
178 public void flush() {
179 }
180
181 @Override
182 public void close() throws SecurityException {
183 // The Java logging framework removes and closes all handlers on JVM
184 // shutdown. As soon as our handler has been removed, all classes
185 // that might get instrumented during shutdown (e.g. loaded by other
186 // shutdown hooks) will fail to initialize. Therefore we add ourself
187 // again here.
188 // This is a nasty hack that might fail in some Java
189 // implementations.
190 logger.addHandler(handler);
191 }
192 }
193
194}