Introduce new logging based runtime.
diff --git a/org.jacoco.agent/src/org/jacoco/agent/JacocoAgent.java b/org.jacoco.agent/src/org/jacoco/agent/JacocoAgent.java
index 009b710..252276e 100644
--- a/org.jacoco.agent/src/org/jacoco/agent/JacocoAgent.java
+++ b/org.jacoco.agent/src/org/jacoco/agent/JacocoAgent.java
@@ -21,7 +21,7 @@
 import org.jacoco.core.data.ExecutionDataWriter;

 import org.jacoco.core.runtime.AgentOptions;

 import org.jacoco.core.runtime.IRuntime;

-import org.jacoco.core.runtime.SystemPropertiesRuntime;

+import org.jacoco.core.runtime.LoggerRuntime;

 

 /**

  * The agent which is referred as the <code>Premain-Class</code>.

@@ -35,7 +35,7 @@
 			final Instrumentation inst) {

 

 		final AgentOptions options = new AgentOptions(agentArgs);

-		final IRuntime runtime = new SystemPropertiesRuntime();

+		final IRuntime runtime = new LoggerRuntime();

 		runtime.startup();

 

 		inst.addTransformer(new CoverageTransformer(runtime, options));

diff --git a/org.jacoco.core.test/src/org/jacoco/core/instr/ClassInstrumenterTest.java b/org.jacoco.core.test/src/org/jacoco/core/instr/ClassInstrumenterTest.java
index 0ead0ad..341dda7 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/instr/ClassInstrumenterTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/instr/ClassInstrumenterTest.java
@@ -13,7 +13,7 @@
 package org.jacoco.core.instr;

 

 import org.jacoco.core.runtime.IRuntime;

-import org.jacoco.core.runtime.SystemPropertiesRuntime;

+import org.jacoco.core.runtime.LoggerRuntime;

 import org.jacoco.core.runtime.SystemPropertiesRuntimeTest.IWriteAccess;

 import org.junit.Before;

 import org.junit.Test;

@@ -38,7 +38,7 @@
 

 	@Before

 	public void setup() {

-		runtime = new SystemPropertiesRuntime(0);

+		runtime = new LoggerRuntime();

 		instrumenter = new ClassInstrumenter(123, runtime, new EmptyVisitor());

 	}

 

diff --git a/org.jacoco.core.test/src/org/jacoco/core/runtime/LoggerRuntimeTest.java b/org.jacoco.core.test/src/org/jacoco/core/runtime/LoggerRuntimeTest.java
new file mode 100644
index 0000000..7956950
--- /dev/null
+++ b/org.jacoco.core.test/src/org/jacoco/core/runtime/LoggerRuntimeTest.java
@@ -0,0 +1,195 @@
+/*******************************************************************************

+ * Copyright (c) 2009 Mountainminds GmbH & Co. KG and others

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *

+ * Contributors:

+ *    Marc R. Hoffmann - initial API and implementation

+ *    

+ * $Id: $

+ *******************************************************************************/

+package org.jacoco.core.runtime;

+

+import static org.junit.Assert.assertEquals;

+import static org.junit.Assert.assertFalse;

+import static org.junit.Assert.assertSame;

+

+import java.util.HashMap;

+import java.util.Map;

+

+import org.jacoco.core.data.IExecutionDataVisitor;

+import org.jacoco.core.test.TargetLoader;

+import org.junit.After;

+import org.junit.Before;

+import org.junit.Test;

+import org.objectweb.asm.ClassWriter;

+import org.objectweb.asm.Opcodes;

+import org.objectweb.asm.Type;

+import org.objectweb.asm.commons.GeneratorAdapter;

+import org.objectweb.asm.commons.Method;

+

+/**

+ * Unit tests for {@link LoggerRuntime}.

+ * 

+ * @author Marc R. Hoffmann

+ * @version $Revision: $

+ */

+public class LoggerRuntimeTest {

+

+	private IRuntime runtime;

+

+	private TestStorage storage;

+

+	@Before

+	public void setup() {

+		runtime = new LoggerRuntime();

+		runtime.startup();

+		storage = new TestStorage();

+	}

+

+	@After

+	public void shutdown() {

+		runtime.shutdown();

+	}

+

+	@Test

+	public void testCollectEmpty() {

+		runtime.collect(storage, false);

+		storage.assertSize(0);

+	}

+

+	@Test

+	public void testCollect1() throws InstantiationException,

+			IllegalAccessException {

+		final boolean[][] data1 = new boolean[3][];

+		generateAndInstantiateClass(1001, data1);

+		runtime.collect(storage, false);

+		storage.assertSize(1);

+		storage.assertData(1001, data1);

+	}

+

+	@Test

+	public void testCollect2() throws InstantiationException,

+			IllegalAccessException {

+		final boolean[][] data1 = new boolean[3][];

+		final boolean[][] data2 = new boolean[5][];

+		generateAndInstantiateClass(1001, data1);

+		generateAndInstantiateClass(1002, data2);

+		runtime.collect(storage, false);

+		storage.assertSize(2);

+		storage.assertData(1001, data1);

+		storage.assertData(1002, data2);

+	}

+

+	@Test

+	public void testReset() throws InstantiationException,

+			IllegalAccessException {

+		final boolean[][] data1 = new boolean[1][];

+		data1[0] = new boolean[] { true, true, true };

+		generateAndInstantiateClass(1001, data1);

+		runtime.reset();

+		assertFalse(data1[0][0]);

+		assertFalse(data1[0][1]);

+		assertFalse(data1[0][2]);

+	}

+

+	@Test

+	public void testCollectAndReset() throws InstantiationException,

+			IllegalAccessException {

+		final boolean[][] data1 = new boolean[1][];

+		data1[0] = new boolean[] { true, true, true };

+		generateAndInstantiateClass(1001, data1);

+		runtime.collect(storage, true);

+		storage.assertSize(1);

+		storage.assertData(1001, data1);

+		assertFalse(data1[0][0]);

+		assertFalse(data1[0][1]);

+		assertFalse(data1[0][2]);

+	}

+

+	/**

+	 * Creates a new class with the given id, loads this class and injects the

+	 * given data instance into the class. Used to check whether the data is

+	 * properly collected in the runtime.

+	 * 

+	 * @param classId

+	 * @param data

+	 * @throws InstantiationException

+	 * @throws IllegalAccessException

+	 */

+	private void generateAndInstantiateClass(int classId, boolean[][] data)

+			throws InstantiationException, IllegalAccessException {

+

+		final String className = "org/jacoco/test/targets/LoggerRuntimeTestTarget";

+		final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);

+		writer.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, className, null,

+				"java/lang/Object", new String[] { Type

+						.getInternalName(IWriteAccess.class) });

+

+		// Constructor

+		GeneratorAdapter gen = new GeneratorAdapter(writer.visitMethod(

+				Opcodes.ACC_PUBLIC, "<init>", "()V", null, new String[0]),

+				Opcodes.ACC_PUBLIC, "<init>", "()V");

+		gen.visitCode();

+		gen.loadThis();

+		gen.invokeConstructor(Type.getType(Object.class), new Method("<init>",

+				"()V"));

+		gen.returnValue();

+		gen.visitMaxs(0, 0);

+		gen.visitEnd();

+		writer.visitEnd();

+

+		// set()

+		gen = new GeneratorAdapter(writer.visitMethod(Opcodes.ACC_PUBLIC,

+				"set", "([[Z)V", null, new String[0]), Opcodes.ACC_PUBLIC,

+				"set", "([[Z)V");

+		gen.visitCode();

+		gen.loadArg(0);

+		runtime.generateRegistration(classId, gen);

+		gen.returnValue();

+		gen.visitMaxs(0, 0);

+		gen.visitEnd();

+		writer.visitEnd();

+

+		final TargetLoader loader = new TargetLoader(className

+				.replace('/', '.'), writer.toByteArray());

+		((IWriteAccess) loader.newTargetInstance()).set(data);

+	}

+

+	/**

+	 * With this interface we inject sample coverage data into the generated

+	 * classes.

+	 */

+	public interface IWriteAccess {

+

+		void set(boolean[][] data);

+

+	}

+

+	private static class TestStorage implements IExecutionDataVisitor {

+

+		private final Map<Long, boolean[][]> data = new HashMap<Long, boolean[][]>();

+

+		public void assertSize(int size) {

+			assertEquals(size, data.size(), 0.0);

+		}

+

+		public boolean[][] getData(long classId) {

+			return data.get(Long.valueOf(classId));

+		}

+

+		public void assertData(long classId, boolean[][] expected) {

+			assertSame(expected, getData(classId));

+		}

+

+		// === ICoverageDataVisitor ===

+

+		public void visitClassExecution(long id, boolean[][] blockdata) {

+			data.put(Long.valueOf(id), blockdata);

+		}

+

+	}

+

+}

diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/InstrumentationScenariosTest.java b/org.jacoco.core.test/src/org/jacoco/core/test/InstrumentationScenariosTest.java
index aefd9f2..b13894a 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/test/InstrumentationScenariosTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/InstrumentationScenariosTest.java
@@ -17,7 +17,7 @@
 import org.jacoco.core.instr.Analyzer;

 import org.jacoco.core.instr.Instrumenter;

 import org.jacoco.core.runtime.IRuntime;

-import org.jacoco.core.runtime.SystemPropertiesRuntime;

+import org.jacoco.core.runtime.LoggerRuntime;

 import org.jacoco.core.test.ClassDataRecorder.BlockData;

 import org.jacoco.core.test.targets.Target_cinit_01;

 import org.jacoco.core.test.targets.Target_cinit_02;

@@ -45,7 +45,7 @@
 

 	@Before

 	public void setup() {

-		runtime = new SystemPropertiesRuntime();

+		runtime = new LoggerRuntime();

 		runtime.startup();

 	}

 

diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/RuntimePerformancetest.java b/org.jacoco.core.test/src/org/jacoco/core/test/RuntimePerformancetest.java
index 492c62c..515935f 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/test/RuntimePerformancetest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/test/RuntimePerformancetest.java
@@ -19,7 +19,7 @@
 

 import org.jacoco.core.instr.Instrumenter;

 import org.jacoco.core.runtime.IRuntime;

-import org.jacoco.core.runtime.SystemPropertiesRuntime;

+import org.jacoco.core.runtime.LoggerRuntime;

 import org.jacoco.core.test.targets.Target_performance_01;

 import org.jacoco.core.test.targets.Target_performance_02;

 import org.junit.Before;

@@ -41,7 +41,7 @@
 

 	@Before

 	public void setup() {

-		runtime = new SystemPropertiesRuntime();

+		runtime = new LoggerRuntime();

 		runtime.startup();

 	}

 

diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java
new file mode 100644
index 0000000..40e08b5
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java
@@ -0,0 +1,161 @@
+/*******************************************************************************

+ * Copyright (c) 2009 Mountainminds GmbH & Co. KG and others

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *

+ * Contributors:

+ *    Marc R. Hoffmann - initial API and implementation

+ *    

+ * $Id: $

+ *******************************************************************************/

+package org.jacoco.core.runtime;

+

+import java.util.Arrays;

+import java.util.Collections;

+import java.util.HashMap;

+import java.util.Map;

+import java.util.logging.Handler;

+import java.util.logging.Level;

+import java.util.logging.LogRecord;

+import java.util.logging.Logger;

+

+import org.jacoco.core.data.IExecutionDataVisitor;

+import org.jacoco.core.instr.GeneratorConstants;

+import org.objectweb.asm.Opcodes;

+import org.objectweb.asm.Type;

+import org.objectweb.asm.commons.GeneratorAdapter;

+

+/**

+ * This {@link IRuntime} implementation uses the Java logging API to report

+ * coverage data. The advantage is, that the instrumented classes do not get

+ * dependencies to other classes than the JRE library itself.

+ * 

+ * @author Marc R. Hoffmann

+ * @version $Revision: $

+ */

+public class LoggerRuntime implements IRuntime {

+

+	private static final String CHANNEL = "jacoco-runtime";

+

+	private final String key;

+

+	private final Logger logger;

+

+	private final Handler handler;

+

+	final Map<Long, boolean[][]> dataMap;

+

+	/**

+	 * Creates a new runtime.

+	 */

+	public LoggerRuntime() {

+		this.key = Integer.toHexString(hashCode());

+		this.logger = configureLogger();

+		this.handler = new RuntimeHandler();

+		dataMap = Collections.synchronizedMap(new HashMap<Long, boolean[][]>());

+	}

+

+	private Logger configureLogger() {

+		final Logger l = Logger.getLogger(CHANNEL);

+		l.setUseParentHandlers(false);

+		l.setLevel(Level.ALL);

+		return l;

+	}

+

+	public void generateRegistration(final long classId,

+			final GeneratorAdapter gen) {

+

+		// boolean[][] data = pop()

+		final int data = gen.newLocal(GeneratorConstants.DATAFIELD_TYPE);

+		gen.storeLocal(data);

+

+		// stack := Logger.getLogger(CHANNEL)

+		gen.push(CHANNEL);

+		gen.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger",

+				"getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;");

+

+		// stack = Level.INFO;

+		gen.getStatic(Type.getObjectType("java/util/logging/Level"), "INFO",

+				Type.getObjectType("java/util/logging/Level"));

+

+		// stack := key

+		gen.push(key);

+

+		// stack := new Object[2]

+		gen.push(2);

+		gen.newArray(Type.getObjectType("java/lang/Object"));

+

+		// stack[0] = Long.valueOf(classId)

+		gen.dup();

+		gen.push(0);

+		gen.push(classId);

+		gen.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",

+				"(J)Ljava/lang/Long;");

+		gen.arrayStore(Type.getObjectType("java/lang/Object"));

+

+		// stack[1] = data

+		gen.dup();

+		gen.push(1);

+		gen.loadLocal(data);

+		gen.arrayStore(Type.getObjectType("java/lang/Object"));

+

+		// stack.log(stack, stack, stack)

+		gen

+				.visitMethodInsn(Opcodes.INVOKEVIRTUAL,

+						"java/util/logging/Logger", "log",

+						"(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V");

+	}

+

+	public void startup() {

+		this.logger.addHandler(handler);

+	}

+

+	public void collect(final IExecutionDataVisitor visitor, final boolean reset) {

+		synchronized (dataMap) {

+			for (final Map.Entry<Long, boolean[][]> entry : dataMap.entrySet()) {

+				final long classId = entry.getKey().longValue();

+				final boolean[][] blockData = entry.getValue();

+				visitor.visitClassExecution(classId, blockData);

+			}

+			if (reset) {

+				reset();

+			}

+		}

+	}

+

+	public void reset() {

+		synchronized (dataMap) {

+			for (final boolean[][] data : dataMap.values()) {

+				for (final boolean[] arr : data) {

+					Arrays.fill(arr, false);

+				}

+			}

+		}

+	}

+

+	public void shutdown() {

+		this.logger.removeHandler(handler);

+	}

+

+	private class RuntimeHandler extends Handler {

+

+		@Override

+		public void publish(final LogRecord record) {

+			if (key.equals(record.getMessage())) {

+				final Object[] params = record.getParameters();

+				dataMap.put((Long) params[0], (boolean[][]) params[1]);

+			}

+		}

+

+		@Override

+		public void flush() {

+		}

+

+		@Override

+		public void close() throws SecurityException {

+		}

+	}

+

+}

diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java
index 8917b84..9f93f60 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java
@@ -43,19 +43,7 @@
 	private final String key;

 

 	/**

-	 * Creates a new runtime with the given id. The id helps to separate

-	 * different runtime instances. The instrumentation and the target VM must

-	 * be based on a runtime with the same id.

-	 * 

-	 * @param id

-	 *            Identifier for the runtime

-	 */

-	public SystemPropertiesRuntime(final int id) {

-		this.key = KEYPREFIX + Integer.toHexString(id);

-	}

-

-	/**

-	 * Creates a new runtime with a random identifier.

+	 * Creates a new runtime.

 	 */

 	public SystemPropertiesRuntime() {

 		this.key = KEYPREFIX + hashCode();

@@ -69,13 +57,11 @@
 		final int data = gen.newLocal(GeneratorConstants.DATAFIELD_TYPE);

 		gen.storeLocal(data);

 

-		// Properties stack := System.getProperties()

+		// stack := System.getProperties()

 		gen.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",

 				"getProperties", "()Ljava/util/Properties;");

 

-		// gen.swap();

-

-		// Map stack := stack.get(key)

+		// stack := stack.get(key)

 		gen.push(key);

 		gen.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/util/Properties",

 				"get", "(Ljava/lang/Object;)Ljava/lang/Object;");

@@ -85,7 +71,7 @@
 		gen.push(classId);

 		gen.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",

 				"(J)Ljava/lang/Long;");

-		// gen.swap();

+

 		gen.loadLocal(data);

 		gen.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Map", "put",

 				"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

diff --git a/org.jacoco.doc/docroot/doc/implementation.html b/org.jacoco.doc/docroot/doc/implementation.html
index 56ddf1c..a835f91 100644
--- a/org.jacoco.doc/docroot/doc/implementation.html
+++ b/org.jacoco.doc/docroot/doc/implementation.html
@@ -222,9 +222,21 @@
   Making a runtime library available to all instrumented classes can be a

   painful or impossible task in frameworks that use there own class loading

   mechanisms. Therefore JaCoCo decouples the instrumented classes and the

-  coverage runtime through official JRE API types. 

+  coverage runtime through official JRE API types. Currently two approaches has

+  been implemented:

 </p>

 

+<ul>

+  <li>Use a shared <code>java.util.logging.Logger</code> instance to report

+    coverage data to. The coverage runtime registers a custom

+    <code>Handler</code> to receive the data. (Default)</li> 

+  <li>Another approach was to store a <code>java.util.Map</code> instance

+    under a system property. This solution breaks the contract that system

+    properties must only contain <code>java.lang.String</code> values and has

+    therefore caused trouble in certain environments.</li> 

+</ul>

+

+

 <h2>Memory Usage</h2>

 

 <p class="intro">