Trac #74: Don't Required Class Registration at Runtime
diff --git a/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/CoverageTransformerTest.java b/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/CoverageTransformerTest.java
index 20ff79e..2e14021 100644
--- a/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/CoverageTransformerTest.java
+++ b/org.jacoco.agent.rt.test/src/org/jacoco/agent/rt/CoverageTransformerTest.java
@@ -128,7 +128,9 @@
 	}

 

 	private static class StubRuntime extends AbstractRuntime {

-		public int generateDataAccessor(long classid, MethodVisitor mv) {

+

+		public int generateDataAccessor(long classid, String classname,

+				int probecount, MethodVisitor mv) {

 			return 0;

 		}

 

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 a86dcc5..369545a 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
@@ -68,8 +68,39 @@
 				nameOutput);

 	}

 

-	@Test(expected = IllegalArgumentException.class)

-	public void testIdClash() {

+	@Test

+	public void testGetWithoutCreate() {

+		boolean[] data = new boolean[] { false, false, true };

+		store.put(1000, "Sample", data);

+		assertSame(data, store.getData(Long.valueOf(1000), "Sample", 3));

+	}

+

+	@Test

+	public void testGetWithCreate() {

+		boolean[] data = store.getData(Long.valueOf(1000), "Sample", 3);

+		assertEquals(3, data.length, 0.0);

+		assertFalse(data[0]);

+		assertFalse(data[1]);

+		assertFalse(data[2]);

+		assertSame(data, store.getData(Long.valueOf(1000), "Sample", 3));

+	}

+

+	@Test(expected = IllegalStateException.class)

+	public void testGetNegative1() {

+		boolean[] data = new boolean[] { false, false, true };

+		store.put(1000, "Sample", data);

+		assertSame(data, store.getData(Long.valueOf(1000), "Other", 3));

+	}

+

+	@Test(expected = IllegalStateException.class)

+	public void testGetNegative2() {

+		boolean[] data = new boolean[] { false, false, true };

+		store.put(1000, "Sample", data);

+		assertSame(data, store.getData(Long.valueOf(1000), "Sample", 4));

+	}

+

+	@Test(expected = IllegalStateException.class)

+	public void testPutNegative() {

 		boolean[] data = new boolean[0];

 		store.put(1000, "Sample1", data);

 		store.put(1000, "Sample2", data);

@@ -90,7 +121,7 @@
 	}

 

 	@Test(expected = IllegalStateException.class)

-	public void testNegative1() {

+	public void testMergeNegative() {

 		boolean[] data1 = new boolean[] { false, false };

 		store.visitClassExecution(1000, "Sample", data1);

 		boolean[] data2 = new boolean[] { false, false, false };

diff --git a/org.jacoco.core.test/src/org/jacoco/core/instr/MethodInstrumenterTest.java b/org.jacoco.core.test/src/org/jacoco/core/instr/MethodInstrumenterTest.java
index 9df317b..11de689 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/instr/MethodInstrumenterTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/instr/MethodInstrumenterTest.java
@@ -14,10 +14,10 @@
 

 import static org.junit.Assert.assertEquals;

 

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

 import org.jacoco.core.test.MethodRecorder;

 import org.junit.Before;

 import org.junit.Test;

+import org.objectweb.asm.ClassVisitor;

 import org.objectweb.asm.MethodVisitor;

 import org.objectweb.asm.Opcodes;

 

@@ -29,7 +29,7 @@
  */

 public class MethodInstrumenterTest {

 

-	private IExecutionDataAccessorGenerator accessorGenerator;

+	private IProbeArrayStrategy probeArrayStrategy;

 

 	private MethodInstrumenter instrumenter;

 

@@ -41,16 +41,20 @@
 	public void setup() {

 		actual = new MethodRecorder();

 		expected = new MethodRecorder();

-		accessorGenerator = new IExecutionDataAccessorGenerator() {

+		probeArrayStrategy = new IProbeArrayStrategy() {

 

-			public int generateDataAccessor(long classid, MethodVisitor mv) {

+			public int pushInstance(MethodVisitor mv) {

 				mv.visitMethodInsn(Opcodes.INVOKESTATIC, "Target",

 						"$jacocoInit", "()[Z");

 				return 1;

 			}

+

+			public void addMembers(ClassVisitor delegate) {

+			}

+

 		};

 		instrumenter = new MethodInstrumenter(actual, 0, "test", "()V",

-				accessorGenerator, 0);

+				probeArrayStrategy);

 	}

 

 	void sampleReturn() {

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

+ * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors

+ * 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.concurrent.Callable;

+

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

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

+import org.junit.Before;

+import org.junit.Test;

+import org.objectweb.asm.ClassWriter;

+import org.objectweb.asm.MethodVisitor;

+import org.objectweb.asm.Opcodes;

+import org.objectweb.asm.Type;

+

+/**

+ * Unit tests for {@link ExecutionDataAccess}.

+ * 

+ * @author Marc R. Hoffmann

+ * @version $Revision: $

+ */

+public class ExecutionDataAccessTest {

+

+	private ExecutionDataStore store;

+

+	private Object access;

+

+	@Before

+	public void setup() {

+		store = new ExecutionDataStore();

+		access = new ExecutionDataAccess(store);

+	}

+

+	@Test

+	public void testGetExecutionData1() {

+		Object[] args = new Object[] { Long.valueOf(123), "Foo",

+				Integer.valueOf(3) };

+		access.equals(args);

+		boolean[] data = (boolean[]) args[0];

+		assertEquals(3, data.length, 0.0);

+		assertFalse(data[0]);

+		assertFalse(data[1]);

+		assertFalse(data[2]);

+		assertSame(store.getData(Long.valueOf(123)), data);

+		assertEquals("Foo", store.getName(Long.valueOf(123)));

+	}

+

+	@Test

+	public void testGenerateArgumentArray() throws Exception {

+		final ClassWriter writer = new ClassWriter(0);

+		writer.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, "Sample", null,

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

+						.getInternalName(Callable.class) });

+

+		// Constructor

+		MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "<init>",

+				"()V", null, new String[0]);

+		mv.visitCode();

+		mv.visitVarInsn(Opcodes.ALOAD, 0);

+		mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>",

+				"()V");

+		mv.visitInsn(Opcodes.RETURN);

+		mv.visitMaxs(1, 1);

+		mv.visitEnd();

+

+		// call()

+		mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "call",

+				"()Ljava/lang/Object;", null, new String[0]);

+		mv.visitCode();

+		ExecutionDataAccess.generateArgumentArray(1000, "Sample", 15, mv);

+		mv.visitInsn(Opcodes.ARETURN);

+		mv.visitMaxs(5, 1);

+		mv.visitEnd();

+

+		writer.visitEnd();

+		final TargetLoader loader = new TargetLoader("Sample", writer

+				.toByteArray());

+		Callable<?> callable = (Callable<?>) loader.newTargetInstance();

+		final Object[] args = (Object[]) callable.call();

+		assertEquals(3, args.length, 0.0);

+		assertEquals(Long.valueOf(1000), args[0]);

+		assertEquals("Sample", args[1]);

+		assertEquals(Integer.valueOf(15), args[2]);

+	}

+

+	@Test

+	public void testGenerateAccessCall() throws Exception {

+		final boolean[] data = store.getData(Long.valueOf(1234), "Sample", 5);

+

+		final ClassWriter writer = new ClassWriter(0);

+		writer.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, "Sample", null,

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

+						.getInternalName(Callable.class) });

+

+		// Constructor

+		MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "<init>",

+				"(Ljava/lang/Object;)V", null, new String[0]);

+		mv.visitCode();

+		mv.visitVarInsn(Opcodes.ALOAD, 0);

+		mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>",

+				"()V");

+		mv.visitVarInsn(Opcodes.ALOAD, 0);

+		mv.visitVarInsn(Opcodes.ALOAD, 1);

+		mv.visitFieldInsn(Opcodes.PUTFIELD, "Sample", "access",

+				"Ljava/lang/Object;");

+		mv.visitInsn(Opcodes.RETURN);

+		mv.visitMaxs(2, 2);

+		mv.visitEnd();

+

+		// call()

+		mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "call",

+				"()Ljava/lang/Object;", null, new String[0]);

+		mv.visitCode();

+		mv.visitVarInsn(Opcodes.ALOAD, 0);

+		mv.visitFieldInsn(Opcodes.GETFIELD, "Sample", "access",

+				"Ljava/lang/Object;");

+		ExecutionDataAccess.generateAccessCall(1234, "Sample", 5, mv);

+		mv.visitInsn(Opcodes.ARETURN);

+		mv.visitMaxs(6, 1);

+		mv.visitEnd();

+

+		writer.visitField(Opcodes.ACC_PRIVATE, "access", "Ljava/lang/Object;",

+				null, null);

+

+		writer.visitEnd();

+		final TargetLoader loader = new TargetLoader("Sample", writer

+				.toByteArray());

+		Callable<?> callable = (Callable<?>) loader.getTargetClass()

+				.getConstructor(Object.class).newInstance(access);

+		assertSame(data, callable.call());

+	}

+

+}

diff --git a/org.jacoco.core.test/src/org/jacoco/core/runtime/MapAdapterTest.java b/org.jacoco.core.test/src/org/jacoco/core/runtime/MapAdapterTest.java
deleted file mode 100644
index 3e7ae37..0000000
--- a/org.jacoco.core.test/src/org/jacoco/core/runtime/MapAdapterTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*******************************************************************************

- * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors

- * 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.assertSame;

-

-import java.util.Collections;

-import java.util.Map;

-

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

-import org.junit.Before;

-import org.junit.Test;

-

-/**

- * Unit tests for {@link MapAdapter}.

- * 

- * @author Marc R. Hoffmann

- * @version $Revision: $

- */

-public class MapAdapterTest {

-

-	private ExecutionDataStore store;

-

-	private Map<Long, boolean[]> map;

-

-	@Before

-	public void setup() {

-		store = new ExecutionDataStore();

-		map = new MapAdapter(store);

-	}

-

-	@Test

-	public void testGet() {

-		boolean[] arr = new boolean[] { false, true, false };

-		store.put(123, "Foo", arr);

-		assertSame(arr, map.get(Long.valueOf(123)));

-	}

-

-	@Test(expected = IllegalStateException.class)

-	public void testGetNegative() {

-		map.get(Long.valueOf(123));

-	}

-

-	@Test(expected = UnsupportedOperationException.class)

-	public void testClear() {

-		map.clear();

-	}

-

-	@Test(expected = UnsupportedOperationException.class)

-	public void testContainsKey() {

-		map.containsKey(Long.valueOf(1));

-	}

-

-	@Test(expected = UnsupportedOperationException.class)

-	public void testContainsValue() {

-		map.containsValue(Long.valueOf(1));

-	}

-

-	@Test(expected = UnsupportedOperationException.class)

-	public void testEntrySet() {

-		map.entrySet();

-	}

-

-	@Test(expected = UnsupportedOperationException.class)

-	public void testIsEmpty() {

-		map.isEmpty();

-	}

-

-	@Test(expected = UnsupportedOperationException.class)

-	public void testKeySet() {

-		map.keySet();

-	}

-

-	@Test(expected = UnsupportedOperationException.class)

-	public void testPut() {

-		map.put(Long.valueOf(0), new boolean[0]);

-	}

-

-	@Test(expected = UnsupportedOperationException.class)

-	public void testPutAll() {

-		final Map<Long, boolean[]> other = Collections.emptyMap();

-		map.putAll(other);

-	}

-

-	@Test(expected = UnsupportedOperationException.class)

-	public void testRemove() {

-		map.remove(Long.valueOf(0));

-	}

-

-	@Test(expected = UnsupportedOperationException.class)

-	public void testValues() {

-		map.values();

-	}

-

-	@Test(expected = UnsupportedOperationException.class)

-	public void testSize() {

-		map.size();

-	}

-

-}

diff --git a/org.jacoco.core.test/src/org/jacoco/core/runtime/ModifiedSystemClassRuntimeTest.java b/org.jacoco.core.test/src/org/jacoco/core/runtime/ModifiedSystemClassRuntimeTest.java
index a87ac30..54cc51b 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/runtime/ModifiedSystemClassRuntimeTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/runtime/ModifiedSystemClassRuntimeTest.java
@@ -15,7 +15,6 @@
 import static org.junit.Assert.assertEquals;

 import static org.junit.Assert.assertFalse;

 import static org.junit.Assert.assertNull;

-import static org.junit.Assert.assertSame;

 import static org.junit.Assert.assertTrue;

 import static org.junit.Assert.fail;

 

@@ -23,9 +22,7 @@
 import java.lang.instrument.ClassFileTransformer;

 import java.lang.instrument.Instrumentation;

 import java.lang.reflect.Field;

-import java.lang.reflect.Method;

 import java.lang.reflect.Modifier;

-import java.util.Map;

 import java.util.jar.JarFile;

 

 import org.jacoco.core.test.TargetLoader;

@@ -42,8 +39,7 @@
 	@Override

 	IRuntime createRuntime() {

 		return new ModifiedSystemClassRuntime(

-				ModifiedSystemClassRuntimeTest.class, "accessMethod",

-				"dataField");

+				ModifiedSystemClassRuntimeTest.class, "accessField");

 	}

 

 	@Test

@@ -60,13 +56,8 @@
 		ModifiedSystemClassRuntime.createFor(inst, TARGET_CLASS_NAME);

 	}

 

-	// these static members emulate the instrumented system class

-

-	public static Map<Long, boolean[]> dataField;

-

-	public static boolean[] accessMethod(long id) {

-		return dataField.get(Long.valueOf(id));

-	}

+	/** This static member emulate the instrumented system class. */

+	public static Object accessField;

 

 	private static final String TARGET_CLASS_NAME = "org/jacoco/core/runtime/ModifiedSystemClassRuntimeTest";

 

@@ -183,22 +174,8 @@
 				.getTargetClass();

 

 		// Check added field:

-		final Field f = targetClass.getField("$jacocoData");

+		final Field f = targetClass.getField("$jacocoAccess");

 		assertEquals(Modifier.PUBLIC | Modifier.STATIC, f.getModifiers(), 0.0);

-		assertEquals(Map.class, f.getType());

-

-		// Check added method:

-		final Method m = targetClass.getMethod("$jacocoGet", Long.TYPE);

-		assertEquals(Modifier.PUBLIC | Modifier.STATIC, m.getModifiers(), 0.0);

-		assertEquals(boolean[].class, m.getReturnType());

-

-		// See whether the added code works with the runtime:

-		final ModifiedSystemClassRuntime r = new ModifiedSystemClassRuntime(

-				targetClass, "$jacocoGet", "$jacocoData");

-		r.startup();

-		boolean[] data = new boolean[] { true, false, false };

-		r.registerClass(5555, "Foo", data);

-		assertSame(data, m.invoke(null, Long.valueOf(5555)));

+		assertEquals(Object.class, f.getType());

 	}

-

 }

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
index 8edb0dc..e2c7d55 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/runtime/RuntimeTestBase.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/runtime/RuntimeTestBase.java
@@ -67,50 +67,47 @@
 	}

 

 	@Test

+	public void testDataAccessor() throws InstantiationException,

+			IllegalAccessException {

+		ITarget t = generateAndInstantiateClass(1234);

+		runtime.collect(storage, false);

+		storage.assertData(1234, t.get());

+	}

+

+	@Test

 	public void testReset() throws InstantiationException,

 			IllegalAccessException {

-		final boolean[] data1 = new boolean[] { true, true, false };

-		runtime.registerClass(1001, "Target", data1);

+		final ITarget target = generateAndInstantiateClass(1000);

+		target.a();

+		target.b();

+

 		runtime.reset();

-		assertFalse(data1[0]);

-		assertFalse(data1[1]);

-		assertFalse(data1[2]);

+

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

+		assertFalse(data[0]);

+		assertFalse(data[1]);

 	}

 

 	@Test

 	public void testCollectAndReset() throws InstantiationException,

 			IllegalAccessException {

-		final boolean[] data1 = new boolean[] { true, true, true };

-		runtime.registerClass(1001, "Target", data1);

+		final ITarget target = generateAndInstantiateClass(1001);

+		target.a();

+		target.b();

+

 		runtime.collect(storage, true);

+

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

 		storage.assertSize(1);

-		storage.assertData(1001, data1);

-		assertFalse(data1[0]);

-		assertFalse(data1[1]);

-		assertFalse(data1[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, "Target", data);

-		ITarget t = generateAndInstantiateClass(1001);

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

+		storage.assertData(1001, data);

+		assertFalse(data[0]);

+		assertFalse(data[1]);

 	}

 

 	@Test

 	public void testNoLocalVariablesInDataAccessor()

 			throws InstantiationException, IllegalAccessException {

-		final boolean[] data = newStructure();

-		runtime.registerClass(1001, "Target", data);

-		runtime.generateDataAccessor(1001, new EmptyVisitor() {

+		runtime.generateDataAccessor(1001, "Target", 5, new EmptyVisitor() {

 			@Override

 			public void visitVarInsn(int opcode, int var) {

 				fail("No usage of local variables allowed.");

@@ -121,28 +118,24 @@
 	@Test

 	public void testExecutionRecording() throws InstantiationException,

 			IllegalAccessException {

-		boolean[] data1 = newStructure();

-		runtime.registerClass(1001, "Target", data1);

 		generateAndInstantiateClass(1001).a();

 		runtime.collect(storage, false);

 		storage.assertSize(1);

-		storage.assertData(1001, data1);

-		assertTrue(data1[0]);

-		assertFalse(data1[1]);

+		final boolean[] data = storage.getData(1001);

+		assertTrue(data[0]);

+		assertFalse(data[1]);

 	}

 

 	@Test

 	public void testLoadSameClassTwice() throws InstantiationException,

 			IllegalAccessException {

-		boolean[] data1 = newStructure();

-		runtime.registerClass(1001, "Target", data1);

 		generateAndInstantiateClass(1001).a();

 		generateAndInstantiateClass(1001).b();

 		runtime.collect(storage, false);

 		storage.assertSize(1);

-		storage.assertData(1001, data1);

-		assertTrue(data1[0]);

-		assertTrue(data1[0]);

+		final boolean[] data = storage.getData(1001);

+		assertTrue(data[0]);

+		assertTrue(data[0]);

 	}

 

 	/**

@@ -179,7 +172,8 @@
 		gen.invokeConstructor(Type.getType(Object.class), new Method("<init>",

 				"()V"));

 		gen.loadThis();

-		final int size = runtime.generateDataAccessor(classid, gen);

+		final int size = runtime.generateDataAccessor(classid, className, 2,

+				gen);

 		gen.putField(classType, "data", GeneratorConstants.PROBEDATA_TYPE);

 		gen.returnValue();

 		gen.visitMaxs(size + 1, 0);

@@ -237,18 +231,14 @@
 

 		boolean[] get();

 

-		// implementations just mark method 0 as executed

+		// implementations just mark probe 0 as executed

 		void a();

 

-		// implementations just mark method 1 as executed

+		// implementations just mark probe 1 as executed

 		void b();

 

 	}

 

-	private boolean[] newStructure() {

-		return new boolean[] { false, false };

-	}

-

 	private static class TestStorage implements IExecutionDataVisitor {

 

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

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 8cc6a3f..88e4602 100644
--- a/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java
+++ b/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java
@@ -100,6 +100,30 @@
 	}

 

 	/**

+	 * Returns the coverage date for the class with the given identifier. If

+	 * there is not data available under the given id a new entry is created.

+	 * 

+	 * @param classid

+	 *            class identifier

+	 * @param name

+	 *            VM name of the class

+	 * @param probecount

+	 *            probe array length

+	 * @return execution data

+	 */

+	public boolean[] getData(final Long classid, final String name,

+			final int probecount) {

+		Entry entry = entries.get(classid);

+		if (entry == null) {

+			entry = new Entry(name, new boolean[probecount]);

+			entries.put(classid, entry);

+		} else {

+			entry.checkCompatibility(classid, name, probecount);

+		}

+		return entry.data;

+	}

+

+	/**

 	 * Returns the vm name of the class with the given id.

 	 * 

 	 * @param classid

@@ -163,18 +187,23 @@
 			this.data = data;

 		}

 

-		void merge(final Long classid, final String newName,

-				final boolean[] newData) {

-			if (!newName.equals(name)) {

-				throw new IllegalArgumentException(format(

+		void checkCompatibility(final Long classid, final String otherName,

+				final int otherLength) {

+			if (!otherName.equals(name)) {

+				throw new IllegalStateException(format(

 						"Duplicate id %x for classes %s and %s.", classid,

-						name, newName));

+						name, otherName));

 			}

-			if (data.length != newData.length) {

+			if (data.length != otherLength) {

 				throw new IllegalStateException(format(

 						"Incompatible execution data for class %s (id %s).",

 						name, classid));

 			}

+		}

+

+		void merge(final Long classid, final String newName,

+				final boolean[] newData) {

+			checkCompatibility(classid, newName, newData.length);

 			for (int i = 0; i < data.length; i++) {

 				if (!data[i]) {

 					data[i] = newData[i];

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 9461c1a..d1b315d 100644
--- a/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java
+++ b/org.jacoco.core/src/org/jacoco/core/instr/ClassInstrumenter.java
@@ -15,7 +15,6 @@
 import static java.lang.String.format;

 

 import org.jacoco.core.runtime.IExecutionDataAccessorGenerator;

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

 import org.objectweb.asm.AnnotationVisitor;

 import org.objectweb.asm.Attribute;

 import org.objectweb.asm.ClassVisitor;

@@ -30,44 +29,46 @@
  * @author Marc R. Hoffmann

  * @version $Revision: $

  */

-public class ClassInstrumenter extends BlockClassAdapter implements

-		IExecutionDataAccessorGenerator {

+public class ClassInstrumenter extends BlockClassAdapter {

 

 	private final ClassVisitor delegate;

 

 	private final long id;

 

-	private final IRuntime runtime;

+	private final IExecutionDataAccessorGenerator accessorGenerator;

+

+	private IProbeArrayStrategy probeArrayStrategy;

 

 	private String className;

 

-	// Inlining the coverage data init method is required for interfaces because

-	// we can't add additional methods here.

-	private boolean inlineInit;

-

 	/**

 	 * Emits a instrumented version of this class to the given class visitor.

 	 * 

 	 * @param id

 	 *            unique identifier given to this class

-	 * @param runtime

-	 *            this runtime will be used for instrumentation

+	 * @param accessorGenerator

+	 *            this generator will be used for instrumentation

 	 * @param cv

 	 *            next delegate in the visitor chain will receive the

 	 *            instrumented class

 	 */

-	public ClassInstrumenter(final long id, final IRuntime runtime,

+	public ClassInstrumenter(final long id,

+			final IExecutionDataAccessorGenerator accessorGenerator,

 			final ClassVisitor cv) {

 		this.delegate = cv;

 		this.id = id;

-		this.runtime = runtime;

+		this.accessorGenerator = accessorGenerator;

 	}

 

 	public void visit(final int version, final int access, final String name,

 			final String signature, final String superName,

 			final String[] interfaces) {

 		this.className = name;

-		this.inlineInit = (access & Opcodes.ACC_INTERFACE) != 0;

+		if ((access & Opcodes.ACC_INTERFACE) == 0) {

+			this.probeArrayStrategy = new ClassTypeStrategy();

+		} else {

+			this.probeArrayStrategy = new InterfaceTypeStrategy();

+		}

 		delegate.visit(version, access, name, signature, superName, interfaces);

 	}

 

@@ -90,8 +91,8 @@
 		if (mv == null) {

 			return null;

 		}

-		final IExecutionDataAccessorGenerator gen = inlineInit ? runtime : this;

-		return new MethodInstrumenter(mv, access, name, desc, gen, id);

+		return new MethodInstrumenter(mv, access, name, desc,

+				probeArrayStrategy);

 	}

 

 	@Override

@@ -102,82 +103,10 @@
 	}

 

 	public void visitEnd() {

-		if (!inlineInit) {

-			createDataField();

-			createInitMethod();

-		}

-		registerClass();

+		probeArrayStrategy.addMembers(delegate);

 		delegate.visitEnd();

 	}

 

-	private void createDataField() {

-		delegate.visitField(GeneratorConstants.DATAFIELD_ACC,

-				GeneratorConstants.DATAFIELD_NAME,

-				GeneratorConstants.PROBEDATA_TYPE.getDescriptor(), null, null);

-	}

-

-	private void createInitMethod() {

-		final int access = GeneratorConstants.INITMETHOD_ACC;

-		final MethodVisitor mv = delegate.visitMethod(access,

-				GeneratorConstants.INITMETHOD_NAME,

-				GeneratorConstants.INITMETHOD_DESC, null, null);

-

-		// Load the value of the static data field:

-		mv.visitFieldInsn(Opcodes.GETSTATIC, className,

-				GeneratorConstants.DATAFIELD_NAME,

-				GeneratorConstants.PROBEDATA_TYPE.getDescriptor());

-		mv.visitInsn(Opcodes.DUP);

-

-		// Stack[1]: [Z

-		// Stack[0]: [Z

-

-		// Skip initialization when we already have a data array:

-		final Label alreadyInitialized = new Label();

-		mv.visitJumpInsn(Opcodes.IFNONNULL, alreadyInitialized);

-

-		// Stack[0]: [Z

-

-		mv.visitInsn(Opcodes.POP);

-		final int size = genInitializeDataField(mv);

-

-		// Stack[0]: [Z

-

-		// Return the method's block array:

-		mv.visitLabel(alreadyInitialized);

-		mv.visitInsn(Opcodes.ARETURN);

-

-		mv.visitMaxs(Math.max(size, 2), 1); // Maximum local stack size is 2

-		mv.visitEnd();

-	}

-

-	/**

-	 * Generates the byte code to initialize the static coverage data field

-	 * within this class.

-	 * 

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

-	 * 

-	 * @param mv

-	 *            generator to emit code to

-	 */

-	private int genInitializeDataField(final MethodVisitor mv) {

-		final int size = runtime.generateDataAccessor(id, mv);

-

-		// Stack[0]: [Z

-

-		mv.visitInsn(Opcodes.DUP);

-

-		// Stack[1]: [Z

-		// Stack[0]: [Z

-

-		mv.visitFieldInsn(Opcodes.PUTSTATIC, className,

-				GeneratorConstants.DATAFIELD_NAME,

-				GeneratorConstants.PROBEDATA_TYPE.getDescriptor());

-

-		// Stack[0]: [Z

-

-		return Math.max(size, 2); // Maximum local stack size is 2

-	}

-

 	/**

 	 * Ensures that the given member does not correspond to a internal member

 	 * created by the instrumentation process. This would mean that the class

@@ -199,15 +128,6 @@
 		}

 	}

 

-	/**

-	 * 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[getProbeCount()];

-		runtime.registerClass(id, className, data);

-	}

-

 	// Methods we simply delegate:

 

 	public AnnotationVisitor visitAnnotation(final String desc,

@@ -233,13 +153,108 @@
 		delegate.visitSource(source, debug);

 	}

 

-	// === IExecutionDataAccessorGenerator ===

+	// === probe array strategies ===

 

-	public int generateDataAccessor(final long classid, final MethodVisitor mv) {

-		mv.visitMethodInsn(Opcodes.INVOKESTATIC, className,

-				GeneratorConstants.INITMETHOD_NAME,

-				GeneratorConstants.INITMETHOD_DESC);

-		return 1;

+	private class ClassTypeStrategy implements IProbeArrayStrategy {

+

+		public int pushInstance(final MethodVisitor mv) {

+			mv.visitMethodInsn(Opcodes.INVOKESTATIC, className,

+					GeneratorConstants.INITMETHOD_NAME,

+					GeneratorConstants.INITMETHOD_DESC);

+			return 1;

+		}

+

+		public void addMembers(final ClassVisitor delegate) {

+			createDataField();

+			createInitMethod();

+		}

+

+		private void createDataField() {

+			delegate.visitField(GeneratorConstants.DATAFIELD_ACC,

+					GeneratorConstants.DATAFIELD_NAME,

+					GeneratorConstants.PROBEDATA_TYPE.getDescriptor(), null,

+					null);

+		}

+

+		private void createInitMethod() {

+			final MethodVisitor mv = delegate.visitMethod(

+					GeneratorConstants.INITMETHOD_ACC,

+					GeneratorConstants.INITMETHOD_NAME,

+					GeneratorConstants.INITMETHOD_DESC, null, null);

+

+			// Load the value of the static data field:

+			mv.visitFieldInsn(Opcodes.GETSTATIC, className,

+					GeneratorConstants.DATAFIELD_NAME,

+					GeneratorConstants.PROBEDATA_TYPE.getDescriptor());

+			mv.visitInsn(Opcodes.DUP);

+

+			// Stack[1]: [Z

+			// Stack[0]: [Z

+

+			// Skip initialization when we already have a data array:

+			final Label alreadyInitialized = new Label();

+			mv.visitJumpInsn(Opcodes.IFNONNULL, alreadyInitialized);

+

+			// Stack[0]: [Z

+

+			mv.visitInsn(Opcodes.POP);

+			final int size = genInitializeDataField(mv);

+

+			// Stack[0]: [Z

+

+			// Return the method's block array:

+			mv.visitLabel(alreadyInitialized);

+			mv.visitInsn(Opcodes.ARETURN);

+

+			mv.visitMaxs(Math.max(size, 2), 1); // Maximum local stack size is 2

+			mv.visitEnd();

+		}

+

+		/**

+		 * Generates the byte code to initialize the static coverage data field

+		 * within this class.

+		 * 

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

+		 * 

+		 * @param mv

+		 *            generator to emit code to

+		 */

+		private int genInitializeDataField(final MethodVisitor mv) {

+			final int size = accessorGenerator.generateDataAccessor(id,

+					className, getProbeCount(), mv);

+

+			// Stack[0]: [Z

+

+			mv.visitInsn(Opcodes.DUP);

+

+			// Stack[1]: [Z

+			// Stack[0]: [Z

+

+			mv.visitFieldInsn(Opcodes.PUTSTATIC, className,

+					GeneratorConstants.DATAFIELD_NAME,

+					GeneratorConstants.PROBEDATA_TYPE.getDescriptor());

+

+			// Stack[0]: [Z

+

+			return Math.max(size, 2); // Maximum local stack size is 2

+		}

+	}

+

+	private class InterfaceTypeStrategy implements IProbeArrayStrategy {

+

+		public int pushInstance(final MethodVisitor mv) {

+			// TODO As we don't know the actual probe count at this point in

+			// time use a hard coded value of 64. This is typically be way too

+			// big and will break static initializers in interfaces with more

+			// than 64 blocks. So this has to be replaced with the actual probe

+			// count.

+			final int size = accessorGenerator.generateDataAccessor(id,

+					className, 64, mv);

+			return size;

+		}

+

+		public void addMembers(final ClassVisitor delegate) {

+		}

 	}

 

 }

diff --git a/org.jacoco.core/src/org/jacoco/core/instr/IProbeArrayStrategy.java b/org.jacoco.core/src/org/jacoco/core/instr/IProbeArrayStrategy.java
new file mode 100644
index 0000000..34f386f
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/instr/IProbeArrayStrategy.java
@@ -0,0 +1,45 @@
+/*******************************************************************************

+ * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors

+ * 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.instr;

+

+import org.objectweb.asm.ClassVisitor;

+import org.objectweb.asm.MethodVisitor;

+

+/**

+ * Strategies to retrieve the probe array instance for each method within a

+ * type. This abstraction is required as we need to follow a different strategy

+ * depending on whether the instrumented type is a class or interface.

+ * 

+ * @author Marc R. Hoffmann

+ * @version $Revision: $

+ */

+interface IProbeArrayStrategy {

+

+	/**

+	 * Creates code that pushes the probe array instance on the operand stack.

+	 * 

+	 * @param mv

+	 *            visitor to create code

+	 * @return maximum stack size required by the generated code

+	 */

+	int pushInstance(MethodVisitor mv);

+

+	/**

+	 * Adds additional class members required by this strategy.

+	 * 

+	 * @param delegate

+	 *            visitor to create fields and classes

+	 */

+	void addMembers(ClassVisitor delegate);

+

+}

diff --git a/org.jacoco.core/src/org/jacoco/core/instr/MethodInstrumenter.java b/org.jacoco.core/src/org/jacoco/core/instr/MethodInstrumenter.java
index d60a6b6..34ac71d 100644
--- a/org.jacoco.core/src/org/jacoco/core/instr/MethodInstrumenter.java
+++ b/org.jacoco.core/src/org/jacoco/core/instr/MethodInstrumenter.java
@@ -12,7 +12,6 @@
  *******************************************************************************/

 package org.jacoco.core.instr;

 

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

 import org.objectweb.asm.MethodVisitor;

 import org.objectweb.asm.Opcodes;

 import org.objectweb.asm.commons.GeneratorAdapter;

@@ -24,12 +23,10 @@
  * @author Marc R. Hoffmann

  * @version $Revision: $

  */

-public class MethodInstrumenter extends GeneratorAdapter implements

+class MethodInstrumenter extends GeneratorAdapter implements

 		IBlockMethodVisitor {

 

-	private final IExecutionDataAccessorGenerator accessorGenerator;

-

-	private final long classid;

+	private final IProbeArrayStrategy probeArrayStrategy;

 

 	private int accessorStackSize;

 

@@ -46,18 +43,14 @@
 	 *            name of the method

 	 * @param desc

 	 *            description of the method

-	 * @param accessorGenerator

-	 *            the current coverage runtime

-	 * @param classid

-	 *            the id of the enclosing type

+	 * @param probeArrayStrategy

+	 *            strategy to get access to the probe array

 	 */

 	public MethodInstrumenter(final MethodVisitor mv, final int access,

 			final String name, final String desc,

-			final IExecutionDataAccessorGenerator accessorGenerator,

-			final long classid) {

+			final IProbeArrayStrategy probeArrayStrategy) {

 		super(mv, access, name, desc);

-		this.accessorGenerator = accessorGenerator;

-		this.classid = classid;

+		this.probeArrayStrategy = probeArrayStrategy;

 	}

 

 	@Override

@@ -65,7 +58,7 @@
 		super.visitCode();

 		// At the very beginning of the method we load the boolean[] array into

 		// a local variable that stores the probes for this class.

-		accessorStackSize = accessorGenerator.generateDataAccessor(classid, mv);

+		accessorStackSize = probeArrayStrategy.pushInstance(mv);

 

 		// Stack[0]: [Z

 

diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java
index 5d34dd7..65a3800 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/AbstractRuntime.java
@@ -26,11 +26,15 @@
 	/** store for execution data */

 	protected final ExecutionDataStore store;

 

+	/** access for this runtime instance */

+	protected final ExecutionDataAccess access;

+

 	/**

 	 * Creates a new runtime.

 	 */

 	protected AbstractRuntime() {

 		store = new ExecutionDataStore();

+		access = new ExecutionDataAccess(store);

 	}

 

 	public final void collect(final IExecutionDataVisitor visitor,

@@ -43,11 +47,6 @@
 		}

 	}

 

-	public final void registerClass(final long classid, final String name,

-			final boolean[] data) {

-		store.put(classid, name, data);

-	}

-

 	public final void reset() {

 		synchronized (store) {

 			store.reset();

diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/ExecutionDataAccess.java b/org.jacoco.core/src/org/jacoco/core/runtime/ExecutionDataAccess.java
new file mode 100644
index 0000000..95f30a6
--- /dev/null
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/ExecutionDataAccess.java
@@ -0,0 +1,166 @@
+/*******************************************************************************

+ * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors

+ * 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.ListIterator;

+

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

+import org.objectweb.asm.MethodVisitor;

+import org.objectweb.asm.Opcodes;

+

+/**

+ * This class implements the access from instrumented classes to execution data

+ * storage in the runtime. Instead of directly referencing JaCoCo implementation

+ * classes the access is decoupled through a JRE API. This avoids dependencies

+ * from instrumented classes on JaCoCo APIs.

+ * 

+ * The JRE interface method used is {@link Object#equals(Object)} where the

+ * passed argument is an {@link Object} array containing the class id (

+ * {@link Long}), the class vm name ({@link String}) and the probe count (

+ * {@link Integer}). After the method call the probe array instance is stored in

+ * the first slot of the {@link Object} array.

+ * 

+ * @author Marc R. Hoffmann

+ * @version $Revision: $

+ */

+class ExecutionDataAccess {

+

+	private final ExecutionDataStore store;

+

+	ExecutionDataAccess(final ExecutionDataStore store) {

+		this.store = store;

+	}

+

+	/**

+	 * Retrieves the execution probe array for a given class. The passed

+	 * {@link Object} array instance is used for parameters and the return value

+	 * as follows. Call parameters:

+	 * 

+	 * <ul>

+	 * <li>args[0]: class id ({@link Long})

+	 * <li>args[1]: vm class name ({@link String})

+	 * <li>args[2]: probe count ({@link Integer})

+	 * </ul>

+	 * 

+	 * Return value:

+	 * 

+	 * <ul>

+	 * <li>args[0]: probe array (<code>boolean[]</code>)

+	 * </ul>

+	 * 

+	 * @param args

+	 *            parameter array of length 3

+	 */

+	public void getExecutionData(final Object[] args) {

+		final Long classid = (Long) args[0];

+		final String name = (String) args[1];

+		final int probecount = ((Integer) args[2]).intValue();

+		synchronized (store) {

+			args[0] = store.getData(classid, name, probecount);

+		}

+	}

+

+	/**

+	 * Generates code that creates the argument array for the

+	 * <code>getExecutionData()</code> method. The array instance is left on the

+	 * operand stack. The generated code requires a stack size of 5.

+	 * 

+	 * @param classid

+	 *            class identifier

+	 * @param classname

+	 *            VM class name

+	 * @param probecount

+	 *            probe count for this class

+	 * @param mv

+	 *            visitor to emit generated code

+	 */

+	public static void generateArgumentArray(final long classid,

+			final String classname, final int probecount, final MethodVisitor mv) {

+		mv.visitInsn(Opcodes.ICONST_3);

+		mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");

+

+		// Class Id:

+		mv.visitInsn(Opcodes.DUP);

+		mv.visitInsn(Opcodes.ICONST_0);

+		mv.visitLdcInsn(Long.valueOf(classid));

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

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

+		mv.visitInsn(Opcodes.AASTORE);

+

+		// Class Name:

+		mv.visitInsn(Opcodes.DUP);

+		mv.visitInsn(Opcodes.ICONST_1);

+		mv.visitLdcInsn(classname);

+		mv.visitInsn(Opcodes.AASTORE);

+

+		// Probe Count:

+		mv.visitInsn(Opcodes.DUP);

+		mv.visitInsn(Opcodes.ICONST_2);

+		mv.visitLdcInsn(Integer.valueOf(probecount));

+		mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Integer",

+				"valueOf", "(I)Ljava/lang/Integer;");

+		mv.visitInsn(Opcodes.AASTORE);

+	}

+

+	/**

+	 * Generates the code that calls the runtime data access through the JRE API

+	 * method {@link ListIterator#add(Object)}. The code pops a

+	 * {@link ListIterator} instance from the stack and pushes the probe array

+	 * of type <code>boolean[]</code> on the operand stack. The generated code

+	 * requires a stack size of 6.

+	 * 

+	 * @param classid

+	 * @param classname

+	 * @param probecount

+	 * @param mv

+	 */

+	public static void generateAccessCall(final long classid,

+			final String classname, final int probecount, final MethodVisitor mv) {

+		// stack[0]: Ljava/util/ListIterator;

+

+		generateArgumentArray(classid, classname, probecount, mv);

+

+		// stack[1]: [Ljava/lang/Object;

+		// stack[0]: Ljava/util/ListIterator;

+

+		mv.visitInsn(Opcodes.DUP_X1);

+

+		// stack[2]: [Ljava/lang/Object;

+		// stack[1]: Ljava/util/ListIterator;

+		// stack[0]: [Ljava/lang/Object;

+

+		mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "equals",

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

+		mv.visitInsn(Opcodes.POP);

+

+		// stack[0]: [Ljava/lang/Object;

+

+		mv.visitInsn(Opcodes.ICONST_0);

+		mv.visitInsn(Opcodes.AALOAD);

+

+		// stack[0]: [Z

+

+		mv.visitTypeInsn(Opcodes.CHECKCAST, "[Z");

+	}

+

+	/**

+	 * In violation of the regular semantic of {@link Object#equals(Object)}

+	 * this implementation is used as the interface to the execution data store.

+	 */

+	@Override

+	public boolean equals(final Object o) {

+		getExecutionData((Object[]) o);

+		return false;

+	}

+

+}

diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/IExecutionDataAccessorGenerator.java b/org.jacoco.core/src/org/jacoco/core/runtime/IExecutionDataAccessorGenerator.java
index 4358d90..ae8f3e9 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/IExecutionDataAccessorGenerator.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/IExecutionDataAccessorGenerator.java
@@ -18,7 +18,7 @@
  * The instrumented classes need a piece of code that obtains a
  * <code>boolean[]</code> instance from the runtime. The mechanism is runtime
  * specific and therefore abstracted by this interface. Implementations are
- * therefore provided by {@link IRuntime} implementations and are used by the
+ * provided by {@link IRuntime} implementations and are used by the
  * instrumentation process.
  * 
  * @author Marc R. Hoffmann
@@ -40,11 +40,16 @@
 	 * 
 	 * @param classid
 	 *            identifier of the class
+	 * @param classname
+	 *            VM class name
+	 * @param probecount
+	 *            probe count for this class
 	 * @param mv
 	 *            code output
 	 * @return additional stack size required by the implementation, including
 	 *         the instance pushed to the stack
 	 */
-	public int generateDataAccessor(long classid, MethodVisitor mv);
+	public int generateDataAccessor(final long classid, final String classname,
+			final int probecount, MethodVisitor mv);
 
 }
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 1db5298..aa10f0b 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/IRuntime.java
@@ -39,20 +39,6 @@
 	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 name

-	 *            VM name of the class

-	 * @param data

-	 *            execution data structure for this class

-	 */

-	public void registerClass(long classid, final String name, boolean[] data);

-

-	/**

 	 * 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 ada12ea..68cf0e9 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/LoggerRuntime.java
@@ -62,12 +62,15 @@
 		return l;

 	}

 

-	public int generateDataAccessor(final long classid, final MethodVisitor mv) {

+	public int generateDataAccessor(final long classid, final String classname,

+			final int probecount, final MethodVisitor mv) {

 

 		// The data accessor performs the following steps:

 		//

-		// final Object[] args = new Object[1];

+		// final Object[] args = new Object[3];

 		// args[0] = Long.valueOf(classid);

+		// args[1] = classname;

+		// args[2] = Integer.valueOf(probecount);

 		// Logger.getLogger(CHANNEL).log(Level.INFO, key, args);

 		// final byte[] probedata = (byte[]) args[0];

 		//

@@ -77,34 +80,7 @@
 

 		// 1. Create parameter array:

 

-		mv.visitInsn(Opcodes.ICONST_1);

-		mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");

-

-		// Stack[0]: [Ljava/lang/Object;

-

-		mv.visitInsn(Opcodes.DUP);

-

-		// Stack[1]: [Ljava/lang/Object;

-		// Stack[0]: [Ljava/lang/Object;

-

-		mv.visitInsn(Opcodes.ICONST_0);

-		mv.visitLdcInsn(Long.valueOf(classid));

-

-		// Stack[4]: J

-		// Stack[3]: .

-		// Stack[2]: I

-		// Stack[1]: [Ljava/lang/Object;

-		// Stack[0]: [Ljava/lang/Object;

-

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

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

-

-		// Stack[3]: Ljava/lang/Long;

-		// Stack[2]: I

-		// Stack[1]: [Ljava/lang/Object;

-		// Stack[0]: [Ljava/lang/Object;

-

-		mv.visitInsn(Opcodes.AASTORE);

+		ExecutionDataAccess.generateArgumentArray(classid, classname, probecount, mv);

 

 		// Stack[0]: [Ljava/lang/Object;

 

@@ -196,16 +172,7 @@
 		@Override

 		public void publish(final LogRecord record) {

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

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

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

-				synchronized (store) {

-					final boolean[] data = store.getData(id);

-					if (data == null) {

-						throw new IllegalStateException(String.format(

-								"Unknown class id %x.", id));

-					}

-					params[0] = data;

-				}

+				access.getExecutionData(record.getParameters());

 			}

 		}

 

diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/MapAdapter.java b/org.jacoco.core/src/org/jacoco/core/runtime/MapAdapter.java
deleted file mode 100644
index 378a55a..0000000
--- a/org.jacoco.core/src/org/jacoco/core/runtime/MapAdapter.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*******************************************************************************

- * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors

- * 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.Collection;

-import java.util.Map;

-import java.util.Set;

-

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

-

-/**

- * Utility class that wraps read-access to an {@link ExecutionDataStore} in a

- * {@link Map} interface. The map interface can then safely be referenced by

- * instrumented code.

- * 

- * @author Marc R. Hoffmann

- * @version $Revision: $

- */

-class MapAdapter implements Map<Long, boolean[]> {

-

-	private final ExecutionDataStore store;

-

-	MapAdapter(final ExecutionDataStore store) {

-		this.store = store;

-	}

-

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

-		final Long id = (Long) key;

-		synchronized (store) {

-			final boolean[] data = store.getData(id);

-			if (data == null) {

-				throw new IllegalStateException(String.format(

-						"Unknown class id %x.", id));

-			}

-			return data;

-		}

-	}

-

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

-	}

-

-}

diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/ModifiedSystemClassRuntime.java b/org.jacoco.core/src/org/jacoco/core/runtime/ModifiedSystemClassRuntime.java
index 2ff3550..a8ac39a 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/ModifiedSystemClassRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/ModifiedSystemClassRuntime.java
@@ -38,55 +38,48 @@
  */

 public class ModifiedSystemClassRuntime extends AbstractRuntime {

 

+	private static final String ACCESS_FIELD_TYPE = "Ljava/lang/Object;";

+

 	private final Class<?> systemClass;

 

 	private final String systemClassName;

 

-	private final String accessMethod;

-

-	private final String dataField;

+	private final String accessFieldName;

 

 	/**

 	 * Creates a new runtime based on the given class and members.

 	 * 

 	 * @param systemClass

 	 *            system class that contains the execution data

-	 * @param accessMethod

-	 *            name of the public static access method

-	 * @param dataField

-	 *            name of the public static data field

+	 * @param accessFieldName

+	 *            name of the public static runtime access field

 	 * 

 	 */

 	public ModifiedSystemClassRuntime(final Class<?> systemClass,

-			final String accessMethod, final String dataField) {

+			final String accessFieldName) {

 		this.systemClass = systemClass;

 		this.systemClassName = systemClass.getName().replace('.', '/');

-		this.accessMethod = accessMethod;

-		this.dataField = dataField;

+		this.accessFieldName = accessFieldName;

 	}

 

 	public void startup() throws Exception {

-		final Field field = systemClass.getField(dataField);

-		field.set(null, new MapAdapter(store));

+		final Field field = systemClass.getField(accessFieldName);

+		field.set(null, new ExecutionDataAccess(store));

 	}

 

 	public void shutdown() {

 		// nothing to do

 	}

 

-	public int generateDataAccessor(final long classid, final MethodVisitor mv) {

+	public int generateDataAccessor(final long classid, final String classname,

+			final int probecount, final MethodVisitor mv) {

 

-		mv.visitLdcInsn(Long.valueOf(classid));

+		mv.visitFieldInsn(Opcodes.GETSTATIC, systemClassName, accessFieldName,

+				ACCESS_FIELD_TYPE);

 

-		// Stack[1]: J

-		// Stack[0]: .

+		ExecutionDataAccess.generateAccessCall(classid, classname, probecount, mv);

 

-		mv.visitMethodInsn(Opcodes.INVOKESTATIC, systemClassName, accessMethod,

-				"(J)[Z");

-

-		// Stack[0]: [Z

-

-		return 2;

+		return 6;

 	}

 

 	/**

@@ -105,7 +98,7 @@
 	 */

 	public static IRuntime createFor(final Instrumentation inst,

 			final String className) throws ClassNotFoundException {

-		return createFor(inst, className, "$jacocoGet", "$jacocoData");

+		return createFor(inst, className, "$jacocoAccess");

 	}

 

 	/**

@@ -117,18 +110,16 @@
 	 *            instrumentation interface

 	 * @param className

 	 *            VM name of the class to use

-	 * @param accessMethod

-	 *            name of the added access method

-	 * @param dataField

-	 *            name of the added data field

+	 * @param accessFieldName

+	 *            name of the added runtime access field

 	 * @return new runtime instance

 	 * 

 	 * @throws ClassNotFoundException

 	 *             id the given class can not be found

 	 */

 	public static IRuntime createFor(final Instrumentation inst,

-			final String className, final String accessMethod,

-			final String dataField) throws ClassNotFoundException {

+			final String className, final String accessFieldName)

+			throws ClassNotFoundException {

 		final boolean[] instrumented = new boolean[] { false };

 		final ClassFileTransformer transformer = new ClassFileTransformer() {

 			public byte[] transform(final ClassLoader loader,

@@ -137,7 +128,7 @@
 					throws IllegalClassFormatException {

 				if (name.equals(className)) {

 					instrumented[0] = true;

-					return instrument(source, accessMethod, dataField);

+					return instrument(source, accessFieldName);

 				}

 				return null;

 			}

@@ -149,7 +140,7 @@
 			final String msg = format("Class %s was not loaded.", className);

 			throw new RuntimeException(msg);

 		}

-		return new ModifiedSystemClassRuntime(clazz, accessMethod, dataField);

+		return new ModifiedSystemClassRuntime(clazz, accessFieldName);

 	}

 

 	/**

@@ -158,33 +149,19 @@
 	 * 

 	 * @param source

 	 *            class definition source

-	 * @param accessMethod

-	 *            name of the access method

-	 * @param dataField

-	 *            name of the data field

+	 * @param accessFieldName

+	 *            name of the runtime access field

 	 * @return instrumented version with added members

 	 */

 	public static byte[] instrument(final byte[] source,

-			final String accessMethod, final String dataField) {

+			final String accessFieldName) {

 		final ClassReader reader = new ClassReader(source);

 		final ClassWriter writer = new ClassWriter(reader, 0);

 		reader.accept(new ClassAdapter(writer) {

 

-			private String className;

-

-			@Override

-			public void visit(final int version, final int access,

-					final String name, final String signature,

-					final String superName, final String[] interfaces) {

-				className = name;

-				super.visit(version, access, name, signature, superName,

-						interfaces);

-			}

-

 			@Override

 			public void visitEnd() {

-				createDataField(cv, dataField);

-				createAccessMethod(cv, className, accessMethod, dataField);

+				createDataField(cv, accessFieldName);

 				super.visitEnd();

 			}

 

@@ -195,46 +172,7 @@
 	private static void createDataField(final ClassVisitor visitor,

 			final String dataField) {

 		visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, dataField,

-				"Ljava/util/Map;", "Ljava/util/Map<Ljava/lang/Long;[Z>;", null);

-	}

-

-	private static void createAccessMethod(final ClassVisitor visitor,

-			final String className, final String accessMethod,

-			final String dataField) {

-		final int access = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;

-		final String desc = "(J)[Z";

-		final MethodVisitor mv = visitor.visitMethod(access, accessMethod,

-				desc, null, null);

-

-		mv.visitFieldInsn(Opcodes.GETSTATIC, className, dataField,

-				"Ljava/util/Map;");

-

-		// Stack[0]: Ljava/util/Map;

-

-		mv.visitVarInsn(Opcodes.LLOAD, 0);

-

-		// Stack[2]: J

-		// Stack[1]: .

-		// Stack[0]: Ljava/util/Map;

-

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

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

-

-		// Stack[1]: Ljava/lang/Long;

-		// Stack[0]: Ljava/util/Map;

-

-		mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Map", "get",

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

-

-		// Stack[0]: Ljava/lang/Object;

-

-		mv.visitTypeInsn(Opcodes.CHECKCAST, "[Z");

-

-		// Stack[0]: [Z

-

-		mv.visitInsn(Opcodes.ARETURN);

-		mv.visitMaxs(3, 2);

-		mv.visitEnd();

+				ACCESS_FIELD_TYPE, null, null);

 	}

 

 }

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 338df48..6f467af 100644
--- a/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java
+++ b/org.jacoco.core/src/org/jacoco/core/runtime/SystemPropertiesRuntime.java
@@ -14,7 +14,6 @@
 

 import java.util.Map;

 

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

 import org.objectweb.asm.MethodVisitor;

 import org.objectweb.asm.Opcodes;

 

@@ -38,8 +37,6 @@
 

 	private final String key;

 

-	private final Map<Long, boolean[]> dataAccess = new MapAdapter(store);

-

 	/**

 	 * Creates a new runtime.

 	 */

@@ -47,8 +44,8 @@
 		this.key = KEYPREFIX + hashCode();

 	}

 

-	public int generateDataAccessor(final long classid, final MethodVisitor mv) {

-

+	public int generateDataAccessor(final long classid, final String classname,

+			final int probecount, final MethodVisitor mv) {

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

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

 

@@ -64,37 +61,15 @@
 

 		// Stack[0]: Ljava/lang/Object;

 

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

-

-		// Stack[0]: Ljava/util/Map;

-

-		mv.visitLdcInsn(Long.valueOf(classid));

-

-		// Stack[2]: J

-		// Stack[1]: .

-		// Stack[0]: Ljava/util/Map;

-

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

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

-

-		// Stack[1]: Ljava/lang/Long;

-		// Stack[0]: Ljava/util/Map;

-

-		mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Map", "get",

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

-

-		// Stack[0]: Ljava/lang/Object;

-

-		mv.visitTypeInsn(Opcodes.CHECKCAST, GeneratorConstants.PROBEDATA_TYPE

-				.getInternalName());

+		ExecutionDataAccess.generateAccessCall(classid, classname, probecount, mv);

 

 		// Stack[0]: [Z

 

-		return 3; // Maximum local stack size is 3

+		return 6; // Maximum local stack size is 3

 	}

 

 	public void startup() {

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

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

 	}

 

 	public void shutdown() {

diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html
index 7a3e14b..dbc1f91 100644
--- a/org.jacoco.doc/docroot/doc/changes.html
+++ b/org.jacoco.doc/docroot/doc/changes.html
@@ -28,7 +28,10 @@
 

 <h3>API Changes</h3>

 <ul>

-  <li>Consistent usage of the term "Missed" instead of "NotCovered" in all APIs (Trac #72).</li>

+  <li>Consistent usage of the term "Missed" instead of "NotCovered" in all APIs

+      (Trac #72).</li>

+  <li>To support "off-line" instrumentation scenarios it is not required any

+      more to register instrumented classes with the runtime (Trac #74).</li>

 </ul>

 

 <h2>Release 0.3.1 (2010/02/09)</h2>

diff --git a/org.jacoco.doc/docroot/doc/implementation.html b/org.jacoco.doc/docroot/doc/implementation.html
index b9b6b7a..ec1be8c 100644
--- a/org.jacoco.doc/docroot/doc/implementation.html
+++ b/org.jacoco.doc/docroot/doc/implementation.html
@@ -143,10 +143,11 @@
 <p>

   The JaCoCo build renames all classes contained in the

   <code>jacocoagent.jar</code> into classes with a 

-  <code>org.jacoco.&lt;randomid&gt;</code> prefix, including the required ASM

-  library classes. The identifier is created from a random number. As the agent

-  does not provide any API, no one should be affected by this renaming. This

-  trick also allows that JaCoCo tests can be verified with JaCoCo.

+  <code>org.jacoco.agent.rt_&lt;randomid&gt;</code> prefix, including the

+  required ASM library classes. The identifier is created from a random number.

+  As the agent does not provide any API, no one should be affected by this

+  renaming. This trick also allows that JaCoCo tests can be verified with

+  JaCoCo.

 </p>

 

 

@@ -217,41 +218,63 @@
 <p class="intro">

   Instrumented code typically gets a dependency to a coverage runtime which is

   responsible for collecting and storing execution data. JaCoCo uses JRE types

-  and interfaces only in generated instrumentation code. 

+  only in generated instrumentation code. 

 </p>

 

 <p>

-   

-  

   Making a runtime library available to all instrumented classes can be a

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

   mechanisms. Since Java 1.6 <code>java.lang.instrument.Instrumentation</code>

   has an API to extends the bootsstrap loader. As our minimum target is Java 1.5 

   JaCoCo decouples the instrumented classes and the coverage runtime through

-  official JRE API types only. Different approaches have been implemented and

-  tested so far:

+  official JRE API types only. The instrumented classes communicate through the

+  <code>Object.equals(Object)</code> method with the runtime. A instrumented

+  class can retrieve its probe array instance with the following code. Note

+  that only JRE APIs are used:   

+</p>

+

+

+<pre class="source">

+<span class="nr">    1</span>Object access = ...                          // Retrieve instance

+<span class="nr">    2</span>

+<span class="nr">    3</span>Object[] args = new Object[3];

+<span class="nr">    4</span>args[0] = Long.valueOf(8060044182221863588); // class id 

+<span class="nr">    5</span>args[1] = "com/example/MyClass";             // class name

+<span class="nr">    6</span>args[2] = Integer.valueOf(24);               // probe count

+<span class="nr">    7</span>

+<span class="nr">    8</span>access.equals(args);

+<span class="nr">    9</span>

+<span class="nr">   10</span>boolean[] probes = (boolean[]) args[0];

+</pre>

+

+<p>

+  The most tricky part takes place in line 1 and is not shown in the snippet

+  above. The object instance providing access to the coverage runtime through

+  its <code>equals()</code> method has to be obtained. Different approaches have

+  been implemented and tested so far:

 </p>

 

 <ul>

-  <li><b><code>SystemPropertiesRuntime</code></b>: This approach stores a

-    <code>java.util.Map</code> instance under a system property containing the

-    execution data. This solution breaks the contract that system properties

-    must only contain <code>java.lang.String</code> values and therefore causes

-    trouble in applications that rely on this definition (e.g. Ant).</li>

+  <li><b><code>SystemPropertiesRuntime</code></b>: This approach stores the

+    object instance under a system property. This solution breaks the contract

+    that system properties must only contain <code>java.lang.String</code>

+    values and therefore causes trouble in applications that rely on this

+    definition (e.g. Ant).</li>

   <li><b><code>LoggerRuntime</code></b>: Here 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. This approach might break environments that install their own log

+    <code>java.util.logging.Logger</code> and communicate through the logging

+    parameter array instead of a <code>equals()</code> method. The coverage

+    runtime registers a custom <code>Handler</code> to receive the parameter

+    array. This approach might break environments that install their own log

     managers (e.g. Glassfish).</li> 

-  <li><b><code>ModifiedSystemClassRuntime</code></b>: This approach adds

-    additional APIs to existing JRE classes through instrumentation. Unlike the

-    other methods above this is only possible for environments where a Java

+  <li><b><code>ModifiedSystemClassRuntime</code></b>: This approach adds a

+    public static field to an existing JRE class through instrumentation. Unlike

+    the other methods above this is only possible for environments where a Java

     agent is active.</li> 

 </ul>

 

 <p>

   The current JaCoCo Java agent implementation uses the 

-  <code>ModifiedSystemClassRuntime</code> adding APIs to the class

+  <code>ModifiedSystemClassRuntime</code> adding a field to the class

   <code>java.sql.Types</code>.

 </p>