#23: Share execution data porperly if the same class definition is loaded multiple times.
diff --git a/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataReaderWriterTest.java b/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataReaderWriterTest.java
index bdf3a4e..523d55f 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataReaderWriterTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataReaderWriterTest.java
@@ -65,7 +65,7 @@
 		boolean[][] blocks = createBlockdata(0, 0);

 		writer.visitClassExecution(Long.MIN_VALUE, blocks);

 		readIntoStore();

-		assertArrayEquals(blocks, store.getBlockdata(Long.MIN_VALUE));

+		assertArrayEquals(blocks, store.get(Long.MIN_VALUE));

 	}

 

 	@Test

@@ -73,7 +73,7 @@
 		boolean[][] blocks = createBlockdata(0, 0);

 		writer.visitClassExecution(Long.MAX_VALUE, blocks);

 		readIntoStore();

-		assertArrayEquals(blocks, store.getBlockdata(Long.MAX_VALUE));

+		assertArrayEquals(blocks, store.get(Long.MAX_VALUE));

 	}

 

 	@Test

@@ -81,7 +81,7 @@
 		boolean[][] blocks = createBlockdata(0, 0);

 		writer.visitClassExecution(3, blocks);

 		readIntoStore();

-		assertArrayEquals(blocks, store.getBlockdata(3));

+		assertArrayEquals(blocks, store.get(3));

 	}

 

 	@Test

@@ -89,7 +89,7 @@
 		boolean[][] blocks = createBlockdata(5, 0);

 		writer.visitClassExecution(3, blocks);

 		readIntoStore();

-		assertArrayEquals(blocks, store.getBlockdata(3));

+		assertArrayEquals(blocks, store.get(3));

 	}

 

 	@Test

@@ -97,7 +97,7 @@
 		boolean[][] blocks = createBlockdata(5, 10);

 		writer.visitClassExecution(3, blocks);

 		readIntoStore();

-		assertArrayEquals(blocks, store.getBlockdata(3));

+		assertArrayEquals(blocks, store.get(3));

 	}

 

 	@Test

@@ -107,8 +107,8 @@
 		writer.visitClassExecution(333, blocks1);

 		writer.visitClassExecution(-45, blocks2);

 		readIntoStore();

-		assertArrayEquals(blocks1, store.getBlockdata(333));

-		assertArrayEquals(blocks2, store.getBlockdata(-45));

+		assertArrayEquals(blocks1, store.get(333));

+		assertArrayEquals(blocks2, store.get(-45));

 	}

 

 	@Test

@@ -116,7 +116,7 @@
 		boolean[][] blocks = createBlockdata(43, 40);

 		writer.visitClassExecution(123, blocks);

 		readIntoStore();

-		assertArrayEquals(blocks, store.getBlockdata(123));

+		assertArrayEquals(blocks, store.get(123));

 	}

 

 	private boolean[][] createBlockdata(int methodCount, int maxBlockCount) {

diff --git a/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataStoreTest.java b/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataStoreTest.java
index 71b8fbb..dc259e6 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataStoreTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/data/ExecutionDataStoreTest.java
@@ -14,6 +14,7 @@
 

 import static org.junit.Assert.assertEquals;

 import static org.junit.Assert.assertFalse;

+import static org.junit.Assert.assertNotNull;

 import static org.junit.Assert.assertNull;

 import static org.junit.Assert.assertSame;

 import static org.junit.Assert.assertTrue;

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

 	@Test

 	public void testEmpty() {

-		assertNull(store.getBlockdata(123));

+		assertNull(store.get(123));

 		store.accept(this);

 		assertEquals(Collections.emptyMap(), output);

 	}

@@ -53,8 +54,8 @@
 	public void testPut() {

 		boolean[][] data = new boolean[][] { new boolean[] { false },

 				new boolean[] { false, true } };

-		store.visitClassExecution(1000, data);

-		assertSame(data, store.getBlockdata(1000));

+		store.put(1000, data);

+		assertSame(data, store.get(1000));

 		store.accept(this);

 		assertEquals(Collections.singletonMap(Long.valueOf(1000), data), output);

 	}

@@ -68,7 +69,7 @@
 				new boolean[] { true, false } };

 		store.visitClassExecution(1000, data2);

 

-		final boolean[][] result = store.getBlockdata(1000);

+		final boolean[][] result = store.get(1000);

 		assertFalse(result[0][0]);

 		assertTrue(result[0][1]);

 		assertTrue(result[1][0]);

@@ -94,6 +95,20 @@
 		store.visitClassExecution(1000, data2);

 	}

 

+	@Test

+	public void testReset() throws InstantiationException,

+			IllegalAccessException {

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

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

+		store.put(1000, data1);

+		store.reset();

+		boolean[][] data2 = store.get(1000);

+		assertNotNull(data2);

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

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

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

+	}

+

 	// === IExecutionDataOutput ===

 

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

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 341dda7..deea5f1 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
@@ -14,7 +14,6 @@
 

 import org.jacoco.core.runtime.IRuntime;

 import org.jacoco.core.runtime.LoggerRuntime;

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

 import org.junit.Before;

 import org.junit.Test;

 import org.objectweb.asm.ClassVisitor;

@@ -51,8 +50,7 @@
 

 		final String className = "org/jacoco/test/targets/ClassInstrumenterTestTarget";

 		visitor.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, className, null,

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

-						.getInternalName(IWriteAccess.class) });

+				"java/lang/Object", new String[] {});

 

 		// Constructor

 		GeneratorAdapter gen = new GeneratorAdapter(visitor.visitMethod(

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
index 7956950..8ee9491 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/runtime/LoggerRuntimeTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/runtime/LoggerRuntimeTest.java
@@ -12,23 +12,6 @@
  *******************************************************************************/

 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}.

@@ -36,160 +19,11 @@
  * @author Marc R. Hoffmann

  * @version $Revision: $

  */

-public class LoggerRuntimeTest {

+public class LoggerRuntimeTest extends RuntimeTestBase {

 

-	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);

-		}

-

+	@Override

+	IRuntime createRuntime() {

+		return new LoggerRuntime();

 	}

 

 }

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

+ * 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 static org.junit.Assert.assertTrue;

+

+import java.util.HashMap;

+import java.util.Map;

+

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

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

+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;

+

+/**

+ * Abstract test base for {@link IRuntime} implementations.

+ * 

+ * @author Marc R. Hoffmann

+ * @version $Revision: $

+ */

+public abstract class RuntimeTestBase {

+

+	private IRuntime runtime;

+

+	private TestStorage storage;

+

+	abstract IRuntime createRuntime();

+

+	@Before

+	public void setup() {

+		runtime = createRuntime();

+		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 testReset() throws InstantiationException,

+			IllegalAccessException {

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

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

+		runtime.registerClass(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 };

+		runtime.registerClass(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]);

+	}

+

+	@Test(expected = IllegalStateException.class)

+	public void testNoClassRegistration() throws InstantiationException,

+			IllegalAccessException {

+		generateAndInstantiateClass(1001);

+	}

+

+	@Test

+	public void testDataAccessor() throws InstantiationException,

+			IllegalAccessException {

+		final boolean[][] data = newStructure();

+		runtime.registerClass(1001, data);

+		ITarget t = generateAndInstantiateClass(1001);

+		assertSame(data, t.get());

+	}

+

+	@Test

+	public void testExecutionRecording() throws InstantiationException,

+			IllegalAccessException {

+		boolean[][] data1 = newStructure();

+		runtime.registerClass(1001, data1);

+		generateAndInstantiateClass(1001).a();

+		runtime.collect(storage, false);

+		storage.assertSize(1);

+		storage.assertData(1001, data1);

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

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

+	}

+

+	@Test

+	public void testLoadSameClassTwice() throws InstantiationException,

+			IllegalAccessException {

+		boolean[][] data1 = newStructure();

+		runtime.registerClass(1001, data1);

+		generateAndInstantiateClass(1001).a();

+		generateAndInstantiateClass(1001).b();

+		runtime.collect(storage, false);

+		storage.assertSize(1);

+		storage.assertData(1001, data1);

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

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

+	}

+

+	/**

+	 * 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 ITarget generateAndInstantiateClass(int classid)

+			throws InstantiationException, IllegalAccessException {

+

+		final String className = "org/jacoco/test/targets/RuntimeTestTarget_"

+				+ classid;

+		Type classType = Type.getObjectType(className);

+

+		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(ITarget.class) });

+

+		writer.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, "data",

+				"[[Z", null, null);

+

+		// 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.loadThis();

+		runtime.generateDataAccessor(classid, gen);

+		gen.putField(classType, "data", GeneratorConstants.DATAFIELD_TYPE);

+		gen.returnValue();

+		gen.visitMaxs(0, 0);

+		gen.visitEnd();

+

+		// get()

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

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

+				"get", "()[[Z");

+		gen.visitCode();

+		gen.loadThis();

+		gen.getField(classType, "data", GeneratorConstants.DATAFIELD_TYPE);

+		gen.returnValue();

+		gen.visitMaxs(0, 0);

+		gen.visitEnd();

+

+		// a()

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

+				"()V", null, new String[0]), Opcodes.ACC_PUBLIC, "a", "()V");

+		gen.visitCode();

+		gen.loadThis();

+		gen.getField(classType, "data", GeneratorConstants.DATAFIELD_TYPE);

+		gen.push(0);

+		gen.arrayLoad(Type.getObjectType("[Z"));

+		gen.push(0);

+		gen.push(1);

+		gen.arrayStore(Type.BOOLEAN_TYPE);

+		gen.returnValue();

+		gen.visitMaxs(0, 0);

+		gen.visitEnd();

+

+		// a()

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

+				"()V", null, new String[0]), Opcodes.ACC_PUBLIC, "b", "()V");

+		gen.visitCode();

+		gen.loadThis();

+		gen.getField(classType, "data", GeneratorConstants.DATAFIELD_TYPE);

+		gen.push(1);

+		gen.arrayLoad(Type.getObjectType("[Z"));

+		gen.push(0);

+		gen.push(1);

+		gen.arrayStore(Type.BOOLEAN_TYPE);

+		gen.returnValue();

+		gen.visitMaxs(0, 0);

+		gen.visitEnd();

+

+		writer.visitEnd();

+

+		final TargetLoader loader = new TargetLoader(className

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

+		return (ITarget) loader.newTargetInstance();

+	}

+

+	/**

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

+	 * classes.

+	 */

+	public interface ITarget {

+

+		boolean[][] get();

+

+		// implementations just mark method 0 as executed

+		void a();

+

+		// implementations just mark method 1 as executed

+		void b();

+

+	}

+

+	private boolean[][] newStructure() {

+		return new boolean[][] { new boolean[] { false },

+				new boolean[] { false } };

+	}

+

+	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/runtime/SystemPropertiesRuntimeTest.java b/org.jacoco.core.test/src/org/jacoco/core/runtime/SystemPropertiesRuntimeTest.java
index c0c8f08..6c955f5 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/runtime/SystemPropertiesRuntimeTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/runtime/SystemPropertiesRuntimeTest.java
@@ -12,190 +12,17 @@
  *******************************************************************************/

 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 SystemPropertiesRuntime}.

  * 

  * @author Marc R. Hoffmann

  * @version $Revision: $

  */

-public class SystemPropertiesRuntimeTest {

+public class SystemPropertiesRuntimeTest extends RuntimeTestBase {

 

-	private IRuntime runtime;

-

-	private TestStorage storage;

-

-	@Before

-	public void setup() {

-		runtime = new SystemPropertiesRuntime();

-		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]);

-	}

-

-	@Test(expected = IllegalStateException.class)

-	public void testShutdown() {

-		runtime.shutdown();

-		runtime.collect(storage, false);

-	}

-

-	/**

-	 * 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/SystemPropertiesRuntimeTestTarget";

-		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);

-		}

-

+	@Override

+	IRuntime createRuntime() {

+		return new SystemPropertiesRuntime();

 	}

 

 }

diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java b/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java
index e3c33f4..0436854 100644
--- a/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java
+++ b/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java
@@ -87,7 +87,7 @@
 

 	public IClassStructureVisitor visitClassStructure(final long id,

 			final String name) {

-		final boolean[][] covered = executionData.getBlockdata(id);

+		final boolean[][] covered = executionData.get(id);

 		final Collection<MethodCoverage> methods = new ArrayList<MethodCoverage>();

 		final String[] sourcename = new String[1];

 		return new IClassStructureVisitor() {

diff --git a/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java b/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java
index 24515f6..a4aa72f 100644
--- a/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java
+++ b/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java
@@ -12,6 +12,7 @@
  *******************************************************************************/

 package org.jacoco.core.data;

 

+import java.util.Arrays;

 import java.util.HashMap;

 import java.util.Map;

 

@@ -29,15 +30,39 @@
 

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

 

-	public void visitClassExecution(final long classid, boolean[][] blockdata) {

-		final Long id = Long.valueOf(classid);

-		final boolean[][] current = data.get(id);

+	/**

+	 * Adds the given block data structure into the store. If there is already a

+	 * data structure for this class ID, this structure is merged with the given

+	 * one. In this case a {@link IllegalStateException} is thrown, if both

+	 * executions data structure do have different bloc sizes.

+	 * 

+	 * @param classid

+	 *            unique class identifier

+	 * @param blockdata

+	 *            execution data

+	 */

+	public void put(final Long classid, boolean[][] blockdata) {

+		final boolean[][] current = data.get(classid);

 		if (current != null) {

 			merge(current, blockdata);

 			blockdata = current;

 		}

-		data.put(id, blockdata);

+		data.put(classid, blockdata);

+	}

 

+	/**

+	 * Adds the given block data structure into the store. If there is already a

+	 * data structure for this class ID, this structure is merged with the given

+	 * one. In this case a {@link IllegalStateException} is thrown, if both

+	 * executions data structure do have different bloc sizes.

+	 * 

+	 * @param classid

+	 *            unique class identifier

+	 * @param blockdata

+	 *            execution data

+	 */

+	public void put(final long classid, final boolean[][] blockdata) {

+		put(Long.valueOf(classid), blockdata);

 	}

 

 	private static void merge(final boolean[][] target, final boolean[][] data) {

@@ -68,8 +93,32 @@
 	 *            class identifier

 	 * @return coverage data or <code>null</code>

 	 */

-	public boolean[][] getBlockdata(final long classid) {

-		return data.get(Long.valueOf(classid));

+	public boolean[][] get(final long classid) {

+		return get(Long.valueOf(classid));

+	}

+

+	/**

+	 * Returns the coverage data for the class with the given identifier if

+	 * available.

+	 * 

+	 * @param classid

+	 *            class identifier

+	 * @return coverage data or <code>null</code>

+	 */

+	public boolean[][] get(final Long classid) {

+		return data.get(classid);

+	}

+

+	/**

+	 * Resets all execution data structures, i.e. marks them as not executed.

+	 * The data structures itself are not deleted.

+	 */

+	public void reset() {

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

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

+				Arrays.fill(arr, false);

+			}

+		}

 	}

 

 	/**

@@ -85,4 +134,11 @@
 		}

 	}

 

+	// === IExecutionDataVisitor ===

+

+	public void visitClassExecution(final long classid,

+			final boolean[][] blockdata) {

+		put(classid, blockdata);

+	}

+

 }

diff --git a/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java b/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java
index 187bd7d..4db6958 100644
--- a/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java
+++ b/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java
@@ -121,6 +121,7 @@
 	public void visitEnd() {

 		createDataField();

 		createInitMethod();

+		registerClass();

 		super.visitEnd();

 	}

 

@@ -173,42 +174,12 @@
 	 *            generator to emit code to

 	 */

 	private void genInitializeDataField(final GeneratorAdapter gen) {

-		genInstantiateDataArray(gen); // ................ Stack: [[Z

+		runtime.generateDataAccessor(id, gen);// ........ Stack: [[Z

 		gen.dup(); // ................................... Stack: [[Z [[Z

 		gen.putStatic(type, GeneratorConstants.DATAFIELD_NAME,

 				GeneratorConstants.DATAFIELD_TYPE);

 

 		// .............................................. Stack: [[Z

-

-		gen.dup(); // ................................... Stack: [[Z [[Z

-		runtime.generateRegistration(id, gen);

-

-		// .............................................. Stack: [[Z

-	}

-

-	/**

-	 * Generates the byte code to instantiate the 2-dimensional block data

-	 * array. Each boolean[] entry is created in a length equals to the number

-	 * of blocks in the corresponding method.

-	 * 

-	 * The code will push the [[Z data array on the operand stack.

-	 * 

-	 * TODO: Let the coverage runtime generate this structure

-	 * 

-	 * @param gen

-	 *            generator to emit code to

-	 */

-	private void genInstantiateDataArray(final GeneratorAdapter gen) {

-		gen.push(blockCounters.size()); // .............. Stack: I

-		gen.newArray(GeneratorConstants.BLOCK_ARR); // .. Stack: [[Z

-		for (int blockIdx = 0; blockIdx < blockCounters.size(); blockIdx++) {

-			gen.dup(); // ............................... Stack: [[Z, [[Z

-			gen.push(blockIdx); // ...................... Stack: [[Z, [[Z, I

-			gen.push(blockCounters.get(blockIdx).getBlockCount());

-			// .......................................... Stack: [[Z, [[Z, I, I

-			gen.newArray(Type.BOOLEAN_TYPE);// .......... Stack: [[Z, [[Z, I, [Z

-			gen.arrayStore(GeneratorConstants.BLOCK_ARR); // Stack: [[Z

-		}

 	}

 

 	/**

@@ -232,4 +203,17 @@
 		}

 	}

 

+	/**

+	 * Create a execution data structure according to the structure of this

+	 * class and registers it with the runtime.

+	 */

+	private void registerClass() {

+		final boolean[][] data = new boolean[blockCounters.size()][];

+		for (int blockIdx = 0; blockIdx < blockCounters.size(); blockIdx++) {

+			data[blockIdx] = new boolean[blockCounters.get(blockIdx)

+					.getBlockCount()];

+		}

+		runtime.registerClass(id, data);

+	}

+

 }

diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java
new file mode 100644
index 0000000..708b038
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java
@@ -0,0 +1,57 @@
+/*******************************************************************************

+ * 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 org.jacoco.core.data.ExecutionDataStore;

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

+

+/**

+ * Base {@link IRuntime} implementation.

+ * 

+ * @author Marc R. Hoffmann

+ * @version $Revision: $

+ */

+public abstract class AbstractRuntime implements IRuntime {

+

+	/** store for execution data */

+	protected final ExecutionDataStore store;

+

+	/**

+	 * Creates a new runtime.

+	 */

+	protected AbstractRuntime() {

+		store = new ExecutionDataStore();

+	}

+

+	public final void collect(final IExecutionDataVisitor visitor,

+			final boolean reset) {

+		synchronized (store) {

+			store.accept(visitor);

+			if (reset) {

+				store.reset();

+			}

+		}

+	}

+

+	public final void registerClass(final long classid,

+			final boolean[][] blockdata) {

+		store.put(classid, blockdata);

+	}

+

+	public final void reset() {

+		synchronized (store) {

+			store.reset();

+		}

+	}

+

+}

diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java
index 0be60d4..167e7a7 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java
@@ -25,23 +25,22 @@
 public interface IRuntime {

 

 	/**

-	 * This method generates the byte code required to register the coverage

-	 * data structure of the class with the given id. Typically the

-	 * instrumentation process will embed this code into a method that is called

-	 * on class initialization. This method can be called at any time even

-	 * outside the target VM.

+	 * This method generates the byte code required to obtain the coverage data

+	 * structure for the class with the given id. Typically the instrumentation

+	 * process will embed this code into a method that is called on class

+	 * initialization. This method can be called at any time even outside the

+	 * target VM.

 	 * 

-	 * The generated code must pop a <code>byte[][]</code> instance from the

-	 * operand stack. Except this object on the stack the generated code must

-	 * not make any assumptions about the structure of the embedding method or

-	 * class.

+	 * The generated code must push a <code>byte[][]</code> instance to the

+	 * operand stack. Except this result object the generated code must not make

+	 * any assumptions about the structure of the embedding method or class.

 	 * 

-	 * @param classId

+	 * @param classid

 	 *            identifier of the class

 	 * @param gen

 	 *            code output

 	 */

-	public void generateRegistration(long classId, GeneratorAdapter gen);

+	public void generateDataAccessor(long classid, GeneratorAdapter gen);

 

 	/**

 	 * Starts the coverage runtime. This method MUST be called before any class

@@ -56,6 +55,18 @@
 	public void shutdown();

 

 	/**

+	 * Before a particular class gets loaded, its execution data structure must

+	 * be registered with the runtime through this method. This method must only

+	 * be called between {@link #startup()} and {@link #shutdown()}.

+	 * 

+	 * @param classid

+	 *            identifier of the class

+	 * @param blockdata

+	 *            execution data structure for this method

+	 */

+	public void registerClass(long classid, boolean[][] blockdata);

+

+	/**

 	 * Collects the current execution data and writes it to the given

 	 * {@link IExecutionDataVisitor} object. This method must only be called

 	 * between {@link #startup()} and {@link #shutdown()}.

diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java
index 40e08b5..924fcb2 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java
@@ -12,16 +12,11 @@
  *******************************************************************************/

 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;

@@ -31,11 +26,18 @@
  * 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.

+ * <p>

+ * 

+ * The implementation uses a dedicated log channel. Instrumented classes call

+ * {@link Logger#log(Level, String, Object[])} with the class identifier in the

+ * first slot of the parameter array. The runtime implements a {@link Handler}

+ * for this channel that puts the block data structure into the first slot of

+ * the parameter array.

  * 

  * @author Marc R. Hoffmann

  * @version $Revision: $

  */

-public class LoggerRuntime implements IRuntime {

+public class LoggerRuntime extends AbstractRuntime {

 

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

 

@@ -45,8 +47,6 @@
 

 	private final Handler handler;

 

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

-

 	/**

 	 * Creates a new runtime.

 	 */

@@ -54,7 +54,6 @@
 		this.key = Integer.toHexString(hashCode());

 		this.logger = configureLogger();

 		this.handler = new RuntimeHandler();

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

 	}

 

 	private Logger configureLogger() {

@@ -64,12 +63,28 @@
 		return l;

 	}

 

-	public void generateRegistration(final long classId,

+	public void generateDataAccessor(final long classid,

 			final GeneratorAdapter gen) {

 

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

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

-		gen.storeLocal(data);

+		// 1. Create parameter array:

+

+		final int param = gen.newLocal(Type.getObjectType("java/lang/Object"));

+

+		// stack := new Object[1]

+		gen.push(1);

+		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"));

+

+		gen.storeLocal(param);

+

+		// 2. Call Logger:

 

 		// stack := Logger.getLogger(CHANNEL)

 		gen.push(CHANNEL);

@@ -83,58 +98,27 @@
 		// 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 := param

+		gen.loadLocal(param);

 

 		// 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");

+

+		// 3. Load data structure from parameter array:

+

+		gen.loadLocal(param);

+		gen.push(0);

+		gen.arrayLoad(GeneratorConstants.DATAFIELD_TYPE);

+		gen.checkCast(GeneratorConstants.DATAFIELD_TYPE);

 	}

 

 	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);

 	}

@@ -145,7 +129,15 @@
 		public void publish(final LogRecord record) {

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

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

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

+				final Long id = (Long) params[0];

+				synchronized (store) {

+					final boolean[][] blockdata = store.get(id);

+					if (blockdata == null) {

+						throw new IllegalStateException("Unknown class ID: "

+								+ id);

+					}

+					params[0] = blockdata;

+				}

 			}

 		}

 

@@ -155,6 +147,14 @@
 

 		@Override

 		public void close() throws SecurityException {

+			// The Java logging framework removes and closes all handlers on JVM

+			// shutdown. As soon as our handler has been removed, all classes

+			// that might get instrumented during shutdown (e.g. loaded by other

+			// shutdown hooks) will fail to initialize. Therefore we add ourself

+			// again here.

+			// This is a nasty hack that might fail in some Java

+			// implementations.

+			logger.addHandler(handler);

 		}

 	}

 

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 9f93f60..d333fe2 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java
@@ -12,18 +12,17 @@
  *******************************************************************************/

 package org.jacoco.core.runtime;

 

-import java.util.Arrays;

-import java.util.Collections;

-import java.util.HashMap;

+import java.util.Collection;

 import java.util.Map;

+import java.util.Set;

 

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

 import org.jacoco.core.instr.GeneratorConstants;

 import org.objectweb.asm.Opcodes;

 import org.objectweb.asm.commons.GeneratorAdapter;

 

 /**

- * This {@link IRuntime} implementation places the coverage data in the

+ * This {@link IRuntime} implementation makes the execution data available

+ * through a special entry of the type {@link Map} in the

  * {@link System#getProperties()} hash table. The advantage is, that the

  * instrumented classes do not get dependencies to other classes than the JRE

  * library itself.

@@ -36,12 +35,70 @@
  * @author Marc R. Hoffmann

  * @version $Revision: $

  */

-public class SystemPropertiesRuntime implements IRuntime {

+public class SystemPropertiesRuntime extends AbstractRuntime {

 

 	private static final String KEYPREFIX = "jacoco-";

 

 	private final String key;

 

+	private final Map<Long, boolean[][]> dataAccess = new Map<Long, boolean[][]>() {

+

+		public boolean[][] get(final Object key) {

+			final Long id = (Long) key;

+			synchronized (store) {

+				final boolean[][] blockdata = store.get(id);

+				if (blockdata == null) {

+					throw new IllegalStateException("Unknown class ID: " + id);

+				}

+				return blockdata;

+			}

+		}

+

+		public void clear() {

+			throw new UnsupportedOperationException();

+		}

+

+		public boolean containsKey(final Object key) {

+			throw new UnsupportedOperationException();

+		}

+

+		public boolean containsValue(final Object value) {

+			throw new UnsupportedOperationException();

+		}

+

+		public Set<Entry<Long, boolean[][]>> entrySet() {

+			throw new UnsupportedOperationException();

+		}

+

+		public boolean isEmpty() {

+			throw new UnsupportedOperationException();

+		}

+

+		public Set<Long> keySet() {

+			throw new UnsupportedOperationException();

+		}

+

+		public boolean[][] put(final Long key, final boolean[][] value) {

+			throw new UnsupportedOperationException();

+		}

+

+		public void putAll(final Map<? extends Long, ? extends boolean[][]> t) {

+			throw new UnsupportedOperationException();

+		}

+

+		public boolean[][] remove(final Object key) {

+			throw new UnsupportedOperationException();

+		}

+

+		public Collection<boolean[][]> values() {

+			throw new UnsupportedOperationException();

+		}

+

+		public int size() {

+			throw new UnsupportedOperationException();

+		}

+	};

+

 	/**

 	 * Creates a new runtime.

 	 */

@@ -49,14 +106,9 @@
 		this.key = KEYPREFIX + hashCode();

 	}

 

-	// TODO: lokale Variable vermeiden (swap!)

-	public void generateRegistration(final long classId,

+	public void generateDataAccessor(final long classid,

 			final GeneratorAdapter gen) {

 

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

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

-		gen.storeLocal(data);

-

 		// stack := System.getProperties()

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

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

@@ -67,55 +119,18 @@
 				"get", "(Ljava/lang/Object;)Ljava/lang/Object;");

 		gen.visitTypeInsn(Opcodes.CHECKCAST, "java/util/Map");

 

-		// stack.put(classId, data)

-		gen.push(classId);

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

+		gen.push(classid);

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

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

 

-		gen.loadLocal(data);

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

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

-		gen.pop();

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

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

+		gen.checkCast(GeneratorConstants.DATAFIELD_TYPE);

 	}

 

 	public void startup() {

-		final Map<Long, boolean[][]> dataMap = Collections

-				.synchronizedMap(new HashMap<Long, boolean[][]>());

-		System.getProperties().put(key, dataMap);

-	}

-

-	@SuppressWarnings("unchecked")

-	private Map<Long, boolean[][]> getDataMap() {

-		final Object object = System.getProperties().get(key);

-		if (object == null) {

-			throw new IllegalStateException("Runtime not started.");

-		}

-		return (Map<Long, boolean[][]>) object;

-	}

-

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

-		final Map<Long, boolean[][]> dataMap = getDataMap();

-		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() {

-		final Map<Long, boolean[][]> dataMap = getDataMap();

-		synchronized (dataMap) {

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

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

-					Arrays.fill(arr, false);

-				}

-			}

-		}

+		System.getProperties().put(key, dataAccess);

 	}

 

 	public void shutdown() {

diff --git a/org.jacoco.doc/docroot/doc/implementation.html b/org.jacoco.doc/docroot/doc/implementation.html
index a835f91..39d3865 100644
--- a/org.jacoco.doc/docroot/doc/implementation.html
+++ b/org.jacoco.doc/docroot/doc/implementation.html
@@ -222,14 +222,16 @@
   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. Currently two approaches has

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

   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>By default we 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. The problem with this approach is

+    that the logging framework removes all handlers during shutdown. This may

+    break classes that get initialized during JVM shutdown.</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