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