LoggerRuntime.java
1/*******************************************************************************
2 * Copyright (c) 2009, 2010 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.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 *
34 * @author Marc R. Hoffmann
35 * @version 0.4.1.20101007204400
36 */
37public class LoggerRuntime extends AbstractRuntime {
38
39 private static final String CHANNEL = "jacoco-runtime";
40
41 private final String key;
42
43 private final Logger logger;
44
45 private final Handler handler;
46
47 /**
48 * Creates a new runtime.
49 */
50 public LoggerRuntime() {
51 this.key = Integer.toHexString(hashCode());
52 this.logger = configureLogger();
53 this.handler = new RuntimeHandler();
54 }
55
56 private Logger configureLogger() {
57 final Logger l = Logger.getLogger(CHANNEL);
58 l.setUseParentHandlers(false);
59 l.setLevel(Level.ALL);
60 return l;
61 }
62
63 public int generateDataAccessor(final long classid, final String classname,
64 final int probecount, final MethodVisitor mv) {
65
66 // The data accessor performs the following steps:
67 //
68 // final Object[] args = new Object[3];
69 // args[0] = Long.valueOf(classid);
70 // args[1] = classname;
71 // args[2] = Integer.valueOf(probecount);
72 // Logger.getLogger(CHANNEL).log(Level.INFO, key, args);
73 // final byte[] probedata = (byte[]) args[0];
74 //
75 // Note that local variable 'args' is used at two places. As were not
76 // allowed to allocate local variables we have to keep this value with
77 // DUP and SWAP operations on the operand stack.
78
79 // 1. Create parameter array:
80
81 ExecutionDataAccess.generateArgumentArray(classid, classname,
82 probecount, mv);
83
84 // Stack[0]: [Ljava/lang/Object;
85
86 mv.visitInsn(Opcodes.DUP);
87
88 // Stack[1]: [Ljava/lang/Object;
89 // Stack[0]: [Ljava/lang/Object;
90
91 // 2. Call Logger:
92
93 mv.visitLdcInsn(CHANNEL);
94 mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger",
95 "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;");
96
97 // Stack[2]: Ljava/util/logging/Logger;
98 // Stack[1]: [Ljava/lang/Object;
99 // Stack[0]: [Ljava/lang/Object;
100
101 mv.visitInsn(Opcodes.SWAP);
102
103 // Stack[2]: [Ljava/lang/Object;
104 // Stack[1]: Ljava/util/logging/Logger;
105 // Stack[0]: [Ljava/lang/Object;
106
107 mv.visitFieldInsn(Opcodes.GETSTATIC, "java/util/logging/Level", "INFO",
108 "Ljava/util/logging/Level;");
109
110 // Stack[3]: Ljava/util/logging/Level;
111 // Stack[2]: [Ljava/lang/Object;
112 // Stack[1]: Ljava/util/logging/Logger;
113 // Stack[0]: [Ljava/lang/Object;
114
115 mv.visitInsn(Opcodes.SWAP);
116
117 // Stack[3]: [Ljava/lang/Object;
118 // Stack[2]: Ljava/util/logging/Level;
119 // Stack[1]: Ljava/util/logging/Logger;
120 // Stack[0]: [Ljava/lang/Object;
121
122 mv.visitLdcInsn(key);
123
124 // Stack[4]: Ljava/lang/String;
125 // Stack[3]: [Ljava/lang/Object;
126 // Stack[2]: Ljava/util/logging/Level;
127 // Stack[1]: Ljava/util/logging/Logger;
128 // Stack[0]: [Ljava/lang/Object;
129
130 mv.visitInsn(Opcodes.SWAP);
131
132 // Stack[4]: [Ljava/lang/Object;
133 // Stack[3]: Ljava/lang/String;
134 // Stack[2]: Ljava/util/logging/Level;
135 // Stack[1]: Ljava/util/logging/Logger;
136 // Stack[0]: [Ljava/lang/Object;
137
138 mv
139 .visitMethodInsn(Opcodes.INVOKEVIRTUAL,
140 "java/util/logging/Logger", "log",
141 "(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V");
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 public void startup() {
157 setStartTimeStamp();
158 this.logger.addHandler(handler);
159 }
160
161 public void shutdown() {
162 this.logger.removeHandler(handler);
163 }
164
165 private class RuntimeHandler extends Handler {
166
167 @Override
168 public void publish(final LogRecord record) {
169 if (key.equals(record.getMessage())) {
170 access.getExecutionData(record.getParameters());
171 }
172 }
173
174 @Override
175 public void flush() {
176 }
177
178 @Override
179 public void close() throws SecurityException {
180 // The Java logging framework removes and closes all handlers on JVM
181 // shutdown. As soon as our handler has been removed, all classes
182 // that might get instrumented during shutdown (e.g. loaded by other
183 // shutdown hooks) will fail to initialize. Therefore we add ourself
184 // again here. This is a nasty hack that might fail in some Java
185 // implementations.
186 logger.addHandler(handler);
187 }
188 }
189
190}