Bug 512741 - Indexer is performing too many writes during indexing

Implement a new FieldList class and use it to replace several
FieldOneToMany fields.

Change-Id: I44e4d415453d77d034be2da7cff2c2318e0a878f
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/FieldListTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/FieldListTest.java
new file mode 100644
index 0000000..ad718ce
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/FieldListTest.java
@@ -0,0 +1,320 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Google, Inc 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:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.core.tests.nd;
+
+import java.util.List;
+
+import org.eclipse.jdt.core.tests.nd.util.BaseTestCase;
+import org.eclipse.jdt.internal.core.nd.IDestructable;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.NdNodeTypeRegistry;
+import org.eclipse.jdt.internal.core.nd.NdStruct;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.FieldList;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+import junit.framework.Test;
+
+public class FieldListTest extends BaseTestCase {
+	static int nodeDeletions;
+	static int structDeletions;
+
+	public static class ElementNode extends NdNode {
+		public static final FieldString NAME;
+		public static final FieldOneToMany<TestStruct> RELATED_STRUCTS;
+		public static final FieldManyToOne<TestStruct> PARENT_NODE;
+		public static final FieldList<TestStruct> LIST_CONTENTS;
+
+		@SuppressWarnings("hiding")
+		public static final StructDef<ElementNode> type;
+
+		static {
+			type = StructDef.create(ElementNode.class, NdNode.type);
+
+			NAME = type.addString();
+			RELATED_STRUCTS = FieldOneToMany.create(type, TestStruct.RELATED_NODE);
+			PARENT_NODE = FieldManyToOne.createOwner(type, TestStruct.CHILD_NODES);
+			LIST_CONTENTS = FieldList.create(type, TestStruct.type, 3);
+			type.done();
+		}
+
+		public ElementNode(Nd nd, long record) {
+			super(nd, record);
+		}
+
+		public ElementNode(Nd nd, String name, TestStruct parent) {
+			super(nd);
+
+			NAME.put(nd, this.address, name);
+			PARENT_NODE.put(nd, this.address, parent);
+		}
+
+		public TestStruct createChild(String name) {
+			TestStruct result = LIST_CONTENTS.append(this.nd, this.address);
+			result.setName(name);
+			return result;
+		}
+
+		public List<TestStruct> getChildren() {
+			return LIST_CONTENTS.asList(getNd(), getAddress());
+		}
+
+		@Override
+		public void destruct() {
+			super.destruct();
+
+			FieldListTest.nodeDeletions++;
+		}
+
+		public IString getName() {
+			return NAME.get(getNd(), getAddress());
+		}
+
+		public String toString() {
+			StringBuilder builder = new StringBuilder();
+			printStringTo(builder);
+			return builder.toString();
+		}
+
+		public void printStringTo(StringBuilder builder) {
+			builder.append(getName().getString());
+			if (!RELATED_STRUCTS.isEmpty(getNd(), getAddress())) {
+				builder.append("->[");
+				boolean isFirst = true;
+				for (TestStruct struct : getRelatedStructs()) {
+					if (!isFirst) {
+						builder.append(", ");
+					}
+					isFirst = false;
+					builder.append(struct.getName());
+				}
+				builder.append("]");
+			}
+
+			List<TestStruct> children = getChildren();
+			if (!children.isEmpty()) {
+				builder.append("(");
+				boolean isFirst = true;
+				for (TestStruct struct : children) {
+					if (!isFirst) {
+						builder.append(", ");
+					}
+					isFirst = false;
+					struct.printStringTo(builder);
+				}
+				builder.append(")");
+			}
+		}
+
+		public List<TestStruct> getRelatedStructs() {
+			return RELATED_STRUCTS.asList(getNd(), getAddress());
+		}
+	}
+
+	public static class TestStruct extends NdStruct implements IDestructable {
+		public static final FieldString NAME;
+		public static final FieldOneToMany<ElementNode> CHILD_NODES;
+		public static final FieldManyToOne<ElementNode> RELATED_NODE;
+		public static final FieldByte EXTRA_BYTE;
+
+		@SuppressWarnings("hiding")
+		public static final StructDef<TestStruct> type;
+
+		static {
+			type = StructDef.create(TestStruct.class, NdStruct.type);
+
+			NAME = type.addString();
+			CHILD_NODES = FieldOneToMany.create(type, ElementNode.PARENT_NODE);
+			RELATED_NODE = FieldManyToOne.create(type, ElementNode.RELATED_STRUCTS);
+			EXTRA_BYTE = type.addByte();
+			type.done();
+		}
+
+		public TestStruct(Nd nd, long record) {
+			super(nd, record);
+		}
+
+		public void printStringTo(StringBuilder builder) {
+			builder.append(getName().getString());
+			ElementNode related = RELATED_NODE.get(getNd(), getAddress());
+			if (related != null) {
+				builder.append("->[");
+				builder.append(related.getName().getString());
+				builder.append("]");
+			}
+
+			List<ElementNode> children = getChildren();
+			if (!children.isEmpty()) {
+				builder.append("(");
+				boolean isFirst = true;
+				for (ElementNode struct : children) {
+					if (!isFirst) {
+						builder.append(", ");
+					}
+					isFirst = false;
+					struct.printStringTo(builder);
+				}
+				builder.append(")");
+			}
+		}
+
+		public String toString() {
+			StringBuilder builder = new StringBuilder();
+			printStringTo(builder);
+			return builder.toString();
+		}
+
+		public IString getName() {
+			return NAME.get(getNd(), getAddress());
+		}
+
+		public void setRelatedNode(ElementNode elementNode) {
+			RELATED_NODE.put(getNd(), getAddress(), elementNode);
+		}
+
+		public ElementNode getRelatedNode() {
+			return RELATED_NODE.get(getNd(), getAddress());
+		}
+
+		public void setName(String name) {
+			NAME.put(getNd(), getAddress(), name);
+		}
+
+		public List<ElementNode> getChildren() {
+			return CHILD_NODES.asList(getNd(), getAddress());
+		}
+
+		public ElementNode createChild(String name) {
+			return new ElementNode(getNd(), name, this);
+		}
+
+		@Override
+		public void destruct() {
+			FieldListTest.structDeletions++;
+		}
+	}
+
+	TestStruct referencer;
+	TestStruct owner;
+	private Nd nd;
+	private ElementNode root;
+	private boolean nodeDeleted;
+
+	@Override
+	protected void setUp() throws Exception {
+		super.setUp();
+
+		structDeletions = 0;
+		nodeDeletions = 0;
+		NdNodeTypeRegistry<NdNode> registry = new NdNodeTypeRegistry<>();
+		registry.register(0, ElementNode.type.getFactory());
+		this.nd = DatabaseTestUtil.createEmptyNd(getName(), registry);
+		this.nd.getDB().setExclusiveLock();
+		this.root = new ElementNode(this.nd, "root", null);
+	}
+
+	protected void freeAllMemory() throws Exception {
+		if (this.nodeDeleted) {
+			return;
+		}
+		this.nodeDeleted = true;
+		this.root.delete();
+		this.nd.processDeletions();
+		long freed = this.nd.getDB().getBytesFreed();
+		long allocated = this.nd.getDB().getBytesAllocated();
+		assertEquals("We should have freed all the bytes we allocated and no more", allocated, freed);
+	}
+
+	@Override
+	protected void tearDown() throws Exception {
+		freeAllMemory();
+		super.tearDown();
+	}
+
+	public void testEmptyList() throws Exception {
+		assertTrue("isEmpty() should return true if no children inserted",
+				this.root.getChildren().isEmpty());
+		freeAllMemory();
+		assertEquals("No structs should have been disposed during this test", 0, structDeletions);
+		assertEquals("One node should have been disposed during this test", 1, nodeDeletions);
+	}
+
+	public void testOneChild() throws Exception {
+		TestStruct testStruct = this.root.createChild("child");
+		assertEquals("root should be initialized properly", "child", testStruct.getName().getString());
+		assertEquals("root should have correct contents", "root(child)", this.root.toString());
+		freeAllMemory();
+		assertEquals("No structs should have been disposed during this test", 1, structDeletions);
+		assertEquals("One node should have been disposed during this test", 1, nodeDeletions);
+	}
+
+	public void testElementsInBlock() throws Exception {
+		this.root.createChild("child1");
+		this.root.createChild("child2");
+		assertEquals("root should have correct contents", "root(child1, child2)", this.root.toString());
+		this.root.createChild("child3");
+		assertEquals("root should have correct contents", "root(child1, child2, child3)",
+				this.root.toString());
+		this.root.createChild("child4");
+		assertEquals("root should have correct contents", "root(child1, child2, child3, child4)",
+				this.root.toString());
+		this.root.createChild("child5");
+		assertEquals("root should have correct contents", "root(child1, child2, child3, child4, child5)",
+				this.root.toString());
+		freeAllMemory();
+		assertEquals("No structs should have been disposed during this test", 5, structDeletions);
+		assertEquals("One node should have been disposed during this test", 1, nodeDeletions);
+	}
+
+	public void testDestructorInPartiallyFilledBlock() throws Exception {
+		this.root.createChild("child1");
+		this.root.createChild("child2");
+		this.root.createChild("child3");
+		this.root.createChild("child4");
+		freeAllMemory();
+		assertEquals("No structs should have been disposed during this test", 4, structDeletions);
+		assertEquals("One node should have been disposed during this test", 1, nodeDeletions);
+	}
+
+	public void testListOwningNode() throws Exception {
+		TestStruct child1 = this.root.createChild("child1");
+		child1.createChild("grandchild1");
+
+		assertEquals("root should have correct contents", "root(child1(grandchild1))", this.root.toString());
+
+		freeAllMemory();
+		assertEquals("No structs should have been disposed during this test", 1, structDeletions);
+		assertEquals("One node should have been disposed during this test", 2, nodeDeletions);
+	}
+
+	public void testListWithManyToOneNode() throws Exception {
+		TestStruct child1 = this.root.createChild("child1");
+		ElementNode relatedNode = new ElementNode(this.nd, "relatedNode", null);
+		child1.setRelatedNode(relatedNode);
+
+		assertEquals("Related node should have been set", relatedNode, child1.getRelatedNode());
+		assertEquals("root should have correct contents", "root(child1->[relatedNode])", this.root.toString());
+
+		this.root.delete();
+		this.nodeDeleted = true;
+
+		assertEquals("Related node should be cleared", null, child1.getRelatedNode());
+	}
+
+	public static Test suite() {
+		return BaseTestCase.suite(FieldListTest.class);
+	}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/RunIndexTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/RunIndexTests.java
index f358ea0..9bb4a9f 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/RunIndexTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/RunIndexTests.java
@@ -30,6 +30,7 @@
 		ChunkWriterTests.class,
 		DatabaseTest.class,
 		FieldBackPointerTest.class,
+		FieldListTest.class,
 		FieldOneToOneTest.class,
 		IndexerTest.class,
 		InheritenceTests.class,
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdNode.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdNode.java
deleted file mode 100644
index ef24eb6..0000000
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdNode.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Google, Inc 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:
- *   Stefan Xenos (Google) - Initial implementation
- *******************************************************************************/
-package org.eclipse.jdt.internal.core.nd;
-
-import org.eclipse.jdt.internal.core.nd.db.IndexException;
-
-/**
- * Interface for all nodes that can be visited by a {@link INdVisitor}.
- * @noextend This interface is not intended to be extended by clients.
- * @noimplement This interface is not intended to be implemented by clients.
- */
-public interface INdNode {
-
-	/**
-	 * Visits the children of this node.
-	 */
-	public void accept(INdVisitor visitor) throws IndexException;
-}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdStruct.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdStruct.java
new file mode 100644
index 0000000..909fd56
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdStruct.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Google, Inc 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:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+/**
+ * Implementations of this interface wrap content in the database as a java object.
+ * All such objects have an address and a pointer to the database.
+ */
+public interface INdStruct {
+	/**
+	 * Returns the database address at which the struct begins.
+	 */
+	public long getAddress();
+
+	/**
+	 * Returns the database backing this struct.
+	 */
+	public Nd getNd();
+
+	/**
+	 * Given a nullable {@link INdStruct}, this returns the address of the struct
+	 * or 0 if the object was null.
+	 */
+	static long addressOf(INdStruct nullable) {
+		if (nullable == null) {
+			return 0;
+		}
+		return nullable.getAddress();
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdVisitor.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdVisitor.java
deleted file mode 100644
index 034e233..0000000
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdVisitor.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015, 2016 Google, Inc 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:
- *   Stefan Xenos (Google) - Initial implementation
- *******************************************************************************/
-package org.eclipse.jdt.internal.core.nd;
-
-import org.eclipse.core.runtime.CoreException;
-
-public interface INdVisitor {
-
-	/**
-	 * Walk the nodes in a {@link Nd}. Return true to visit the children of
-	 * this node, or false to skip to the next sibling of this node.
-	 * Throw CoreException to stop the visit.
-	 *  
-	 * @param node being visited
-	 * @return whether to visit children
-	 */
-	public boolean visit(INdNode node) throws CoreException;
-	
-	/**
-	 * All children have been visited, about to go back to the parent.
-	 * 
-	 * @param node that has just completed visitation
-	 * @throws CoreException
-	 */
-	public void leave(INdNode node) throws CoreException;
-	
-}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IndexExceptionBuilder.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IndexExceptionBuilder.java
index 42d9b5a..7994234 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IndexExceptionBuilder.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IndexExceptionBuilder.java
@@ -72,6 +72,9 @@
 	 */
 	public IndexException build(String description) {
 		IndexException toThrow = new IndexException(description);
+		if (this.db.getLog().enabled()) {
+			toThrow.setTime(this.db.getLog().getWriteCount());
+		}
 		attachTo(toThrow);
 		return toThrow;
 	}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Nd.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Nd.java
index 9b20c45..a8acff1 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Nd.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Nd.java
@@ -650,7 +650,7 @@
 	/**
 	 * Returns the type ID for the given class
 	 */
-	public short getNodeType(Class<? extends NdNode> toQuery) {
+	public short getNodeType(Class<?> toQuery) {
 		return this.fNodeTypeRegistry.getTypeForClass(toQuery);
 	}
 
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNode.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNode.java
index d550751..ddf646e 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNode.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNode.java
@@ -16,29 +16,22 @@
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
 
 /**
- * This is a basic node in the network database.
+ * This is a basic polymorphic node in the network database. Pointers to NdNode or any of their
+ * subclasses will be resolved to the correct subclass of NdNode such that the correct version of an
+ * overloaded method will be invoked.
  */
-public abstract class NdNode implements IDestructable {
+public abstract class NdNode extends NdStruct implements IDestructable {
 	public static final FieldShort NODE_TYPE;
 
+	@SuppressWarnings("hiding")
 	public static final StructDef<NdNode> type;
 
 	static {
-		type = StructDef.create(NdNode.class);
+		type = StructDef.create(NdNode.class, NdStruct.type);
 		NODE_TYPE = type.addShort();
 		type.done();
 	}
 
-	public final long address;
-	private Nd nd;
-
-	public static long addressOf(NdNode nullable) {
-		if (nullable == null) {
-			return 0;
-		}
-		return nullable.address;
-	}
-
 	/**
 	 * Load a node from the specified address in the given database.  Return null if a node cannot
 	 * be loaded.
@@ -63,7 +56,7 @@
 	}
 
 	@SuppressWarnings("unchecked")
-	public static <T extends NdNode> T load(Nd nd, long address, StructDef<T> targetType) {
+	public static <T extends INdStruct> T load(Nd nd, long address, StructDef<T> typeToLoad) {
 		if (address == 0) {
 			return null;
 		}
@@ -78,7 +71,7 @@
 			throw e;
 		}
 
-		Class<T> clazz = targetType.getStructClass();
+		Class<T> clazz = typeToLoad.getStructClass();
 		if (!clazz.isAssignableFrom(result.getClass())) {
 			throw nd.describeProblem()
 				.addProblemAddress(NODE_TYPE, address)
@@ -86,7 +79,7 @@
 					clazz + " but found " + result.getClass()); //$NON-NLS-1$
 		}
 
-		return (T)result;
+		return (T) result;
 	}
 
 	/**
@@ -97,13 +90,12 @@
 	}
 
 	protected NdNode(Nd nd, long address) {
-		this.nd = nd;
-		this.address = address;
+		super(nd, address);
 	}
 
 	protected NdNode(Nd nd) {
+		super(nd, 0);
 		Database db = nd.getDB();
-		this.nd = nd;
 
 		short nodeType = nd.getNodeType(getClass());
 		ITypeFactory<? extends NdNode> factory1 = nd.getTypeFactory(nodeType);
@@ -113,14 +105,6 @@
 		NODE_TYPE.put(nd, this.address, nodeType);
 	}
 
-	protected Database getDB() {
-		return this.nd.getDB();
-	}
-
-	public final Nd getNd() {
-		return this.nd;
-	}
-
 	/**
 	 * Return a value to uniquely identify the node within the factory that is responsible for loading
 	 * instances of this node from the {@link Nd}.
@@ -155,10 +139,6 @@
 		return (int) (this.address >> Database.BLOCK_SIZE_DELTA_BITS);
 	}
 
-	public void accept(INdVisitor visitor) {
-		// No children here.
-	}
-
 	/**
 	 * Return an value to globally identify the given node within the given linkage.  This value
 	 * can be used for comparison with other {@link NdNode}s.
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNodeTypeRegistry.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNodeTypeRegistry.java
index d15b779..12c2fd7 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNodeTypeRegistry.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNodeTypeRegistry.java
@@ -75,7 +75,11 @@
 		return typeFactory.create(nd, address);
 	}
 
-	public short getTypeForClass(Class<? extends R> toQuery) {
+	public boolean isRegisteredClass(Class<?> toQuery) {
+		return this.registeredClasses.containsKey(toQuery);
+	}
+
+	public short getTypeForClass(Class<?> toQuery) {
 		Short classId = this.registeredClasses.get(toQuery);
 
 		if (classId == null) {
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdStruct.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdStruct.java
new file mode 100644
index 0000000..5bdec2a
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdStruct.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Google, Inc 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:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+/**
+ * Base class for standard implementations of {@link INdStruct}. Holds the address of the struct
+ * and the pointer to the database.
+ */
+public class NdStruct implements INdStruct {
+	public long address;
+	protected final Nd nd;
+
+	public static final StructDef<NdStruct> type;
+
+	static {
+		type = StructDef.createAbstract(NdStruct.class);
+		type.done();
+	}
+
+	protected NdStruct(Nd nd, long address) {
+		this.nd = nd;
+		this.address = address;
+	}
+
+	@Override
+	public long getAddress() {
+		return this.address;
+	}
+
+	@Override
+	public Nd getNd() {
+		return this.nd;
+	}
+
+	protected final Database getDB() {
+		return this.nd.getDB();
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/RawGrowableArray.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/RawGrowableArray.java
index ac3ede1..6711752 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/RawGrowableArray.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/RawGrowableArray.java
@@ -16,6 +16,7 @@
 import org.eclipse.jdt.internal.core.nd.field.FieldPointer;
 import org.eclipse.jdt.internal.core.nd.field.FieldShort;
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.nd.util.MathUtils;
 
 /**
  * Implements a growable array of pointers that supports constant-time insertions and removals. Items are inserted at
@@ -585,7 +586,7 @@
 
 			// For sizes larger than the max block size, we need to use a metablock. In this case, the allocated size
 			// will be a multiple of the max block size.
-			return roundUpToMultipleOf(GrowableBlockHeader.MAX_GROWABLE_SIZE, growableRegionSize);
+			return MathUtils.roundUpToNearestMultiple(growableRegionSize, GrowableBlockHeader.MAX_GROWABLE_SIZE);
 		}
 
 		return nextGrowableSize;
@@ -618,15 +619,6 @@
 	}
 
 	/**
-	 * Rounds a value up to the nearest multiple of another value
-	 */
-	private static int roundUpToMultipleOf(int unit, int valueToRound) {
-		int numberOfMetablocks = (valueToRound + unit - 1) / unit;
-
-		return numberOfMetablocks * unit;
-	}
-
-	/**
 	 * Returns the record size for a RawGrowableSize with the given number of inline records
 	 */
 	public int getRecordSize() {
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IndexException.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IndexException.java
index aa195b8..f971fb8 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IndexException.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IndexException.java
@@ -23,6 +23,7 @@
 
 	private IStatus status;
 	private List<RelatedAddress> relatedAddresses = new ArrayList<>();
+	private long time = -1;
 
 	public IndexException(IStatus status) {
 		this.status = status;
@@ -32,6 +33,16 @@
 		this(new Status(IStatus.ERROR, "org.eclipse.jdt.core", message)); //$NON-NLS-1$
 	}
 
+	/**
+	 * Sets the time that the exception occurred at (in terms of the write number
+	 * from the modification log)
+	 * 
+	 * @param writeNumber 
+	 */
+	public void setTime(long writeNumber) {
+		this.time = writeNumber;
+	}
+
 	@Override
 	public synchronized Throwable getCause() {
 		return this.status.getException();
@@ -59,6 +70,11 @@
 	@Override
 	public String getMessage() {
 		StringBuilder result = new StringBuilder();
+		if (this.time != -1) {
+			result.append("(time "); //$NON-NLS-1$
+			result.append(this.time);
+			result.append(") "); //$NON-NLS-1$
+		}
 		result.append(this.status.getMessage());
 
 		if (!this.relatedAddresses.isEmpty()) {
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ModificationLog.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ModificationLog.java
index 1229343..b81b37f 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ModificationLog.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ModificationLog.java
@@ -311,7 +311,7 @@
 		}
 	}
 
-	private boolean enabled() {
+	public boolean enabled() {
 		return this.buffer0 != null;
 	}
 
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/Field.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/Field.java
index efd6e8e..f596d88 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/Field.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/Field.java
@@ -12,6 +12,7 @@
 
 import org.eclipse.jdt.internal.core.nd.ITypeFactory;
 import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
 
 /**
  * Used to represent a single field of an object stored in the database. Objects 
@@ -54,4 +55,31 @@
 	public int getRecordSize() {
 		return this.factory.getRecordSize();
 	}
+
+	@Override
+	public int getAlignment() {
+		// This sort of field is almost always used for embedding NdStructs within
+		// other data types. Since most NdStructs allow incoming record pointers, they need to
+		// be properly aligned. If we ever want to use this sort of field for other data types
+		// that don't require alignment, we may want to replace this with something smarter
+		// that can figure out the correct alignment based on the requirements of the actual
+		// data type.
+		return Database.BLOCK_SIZE_DELTA;
+	}
+
+	/**
+	 * Creates a new {@link Field} in the given struct with the given type.
+	 *
+	 * @param struct the struct that will contain the newly-created field (must not have had
+	 * {@link StructDef#done()} called on it yet).
+	 * @param fieldType the data type for the contents of the newly created field
+	 * @return the newly-constructed field
+	 */
+	public static <T> Field<T> create(StructDef<?> struct, StructDef<T> fieldType) {
+		Field<T> result = new Field<>(fieldType.getFactory(), struct.getStructName(), struct.getNumFields());
+		struct.add(result);
+		struct.addDestructableField(result);
+		fieldType.addDependency(struct);
+		return result;
+	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldList.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldList.java
new file mode 100644
index 0000000..b2c5ec7
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldList.java
@@ -0,0 +1,327 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Google, Inc 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:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.ITypeFactory;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.db.ModificationLog;
+import org.eclipse.jdt.internal.core.nd.db.ModificationLog.Tag;
+import org.eclipse.jdt.internal.core.nd.util.MathUtils;
+
+/**
+ * Stores a singly-linked list of blocks, each of which contains a variable number of embedded elements.
+ * Each block contains a header containing the size of the block and pointer to the next block, followed
+ * by the embedded elements themselves.
+ */
+public class FieldList<T> extends BaseField implements IDestructableField {
+	/**
+	 * Pointer to the first block.
+	 */
+	public final static FieldPointer FIRST_BLOCK;
+	/**
+	 * Pointer to the block where insertions are currently happening. This is only null if there are no allocated
+	 * blocks. If there are any blocks containing elements, this points to the last block with a nonzero number of
+	 * elements.
+	 */
+	public final static FieldPointer LAST_BLOCK_WITH_ELEMENTS;
+
+	@SuppressWarnings("rawtypes")
+	private static final StructDef<FieldList> type;
+	private static final int LIST_HEADER_BYTES;
+	private static final long MAX_BYTES_IN_A_CHUNK = Database.getBytesThatFitInChunks(1);
+
+	private final StructDef<T> elementType;
+	private final int elementsPerBlock;
+	private final StructDef<?> ownerType;
+	private final Tag allocateTag;
+	private final Tag appendTag;
+	private final Tag destructTag;
+
+	static {
+		type = StructDef.createAbstract(FieldList.class);
+		FIRST_BLOCK = type.addPointer();
+		LAST_BLOCK_WITH_ELEMENTS = type.addPointer();
+
+		type.done();
+		LIST_HEADER_BYTES = MathUtils.roundUpToNearestMultipleOfPowerOfTwo(type.size(), Database.BLOCK_SIZE_DELTA);
+	}
+
+	private static class BlockHeader {
+		// This points to the next block if there is one, or null if not.
+		public final static FieldPointer NEXT_BLOCK;
+		public final static FieldShort BLOCK_SIZE;
+		public final static FieldShort ELEMENTS_IN_USE;
+		public static final int BLOCK_HEADER_BYTES;
+
+		@SuppressWarnings("hiding")
+		private static final StructDef<BlockHeader> type;
+
+		static {
+			type = StructDef.createAbstract(BlockHeader.class);
+
+			NEXT_BLOCK = type.addPointer();
+			BLOCK_SIZE = type.addShort();
+			ELEMENTS_IN_USE = type.addShort();
+			type.done();
+
+			BLOCK_HEADER_BYTES = MathUtils.roundUpToNearestMultipleOfPowerOfTwo(type.size(), Database.BLOCK_SIZE_DELTA);
+		}
+	}
+
+	private FieldList(StructDef<?> ownerType, StructDef<T> elementType,  int elementsPerBlock) {
+		this.elementType = elementType;
+		this.elementsPerBlock = elementsPerBlock;
+		this.ownerType = ownerType;
+		int fieldNumber = ownerType.getNumFields();
+		setFieldName("field " + fieldNumber + ", a " + getClass().getSimpleName() //$NON-NLS-1$//$NON-NLS-2$
+				+ " in struct " + ownerType.getStructName());//$NON-NLS-1$
+		this.allocateTag = ModificationLog.createTag("Allocating elements for " + getFieldName()); //$NON-NLS-1$
+		this.appendTag = ModificationLog.createTag("Appending to " + getFieldName()); //$NON-NLS-1$
+		this.destructTag = ModificationLog.createTag("Deallocating " + getFieldName()); //$NON-NLS-1$
+	}
+
+	/**
+	 * Creates a new {@link FieldList} in the given struct which contains elements of the given type. The resulting list
+	 * will grow by 1 element each time it overflows.
+	 * 
+	 * @param ownerStruct
+	 *            the struct to which the new list field will be added. Must not have had {@link StructDef#done()}
+	 *            invoked on it yet.
+	 * @param elementType
+	 *            the type of elements that will be contained in the struct.
+	 * @return a newly-constructed list field in the given struct.
+	 */
+	public static <T> FieldList<T> create(StructDef<?> ownerStruct, StructDef<T> elementType) {
+		return create(ownerStruct, elementType, 1);
+	}
+
+	/**
+	 * Creates a new {@link FieldList} in the given struct which contains elements of the given type. The resulting list
+	 * will grow by the given number of elements each time it overflows.
+	 * 
+	 * @param ownerStruct
+	 *            the struct to which the new list field will be added. Must not have had {@link StructDef#done()}
+	 *            invoked on it yet.
+	 * @param elementType
+	 *            the type of elements that will be contained in the struct.
+	 * @param elementsPerBlock
+	 *            the number of elements that will be allocated each time the list overflows.
+	 * @return a newly-constructed list field in the given struct.
+	 */
+	public static <T> FieldList<T> create(StructDef<?> ownerStruct, StructDef<T> elementType, int elementsPerBlock) {
+		FieldList<T> result = new FieldList<>(ownerStruct, elementType, elementsPerBlock);
+		ownerStruct.add(result);
+		ownerStruct.addDestructableField(result);
+		return result;
+	}
+
+	private int getElementSize() {
+		int recordSize = this.elementType.getFactory().getRecordSize();
+		return MathUtils.roundUpToNearestMultipleOfPowerOfTwo(recordSize, Database.BLOCK_SIZE_DELTA);
+	}
+
+	@Override
+	public int getRecordSize() {
+		return LIST_HEADER_BYTES;
+	}
+
+	/**
+	 * Returns the contents of the receiver as a {@link List}.
+	 * 
+	 * @param nd the database to be queried.
+	 * @param address the address of the parent struct
+	 */
+	public List<T> asList(Nd nd, long address) {
+		long headerStartAddress = address + this.offset;
+		long firstBlockAddress = FIRST_BLOCK.get(nd, headerStartAddress);
+
+		List<T> result = new ArrayList<>();
+
+		long nextBlockAddress = firstBlockAddress;
+		while (nextBlockAddress != 0) {
+			long currentBlockAddress = nextBlockAddress;
+			nextBlockAddress = BlockHeader.NEXT_BLOCK.get(nd, currentBlockAddress);
+			int elementsInBlock = BlockHeader.ELEMENTS_IN_USE.get(nd, currentBlockAddress);
+			long firstElementInBlockAddress = currentBlockAddress + BlockHeader.BLOCK_HEADER_BYTES;
+
+			readElements(result, nd, firstElementInBlockAddress, elementsInBlock);
+		}
+
+		return result;
+	}
+
+	private void readElements(List<T> result, Nd nd, long nextElementAddress, int count) {
+		ITypeFactory<T> factory = this.elementType.getFactory();
+
+		int size = getElementSize();
+		for (; count > 0; count--) {
+			result.add(factory.create(nd, nextElementAddress));
+			nextElementAddress += size;
+		}
+	}
+
+	public T append(Nd nd, long address) {
+		Database db = nd.getDB();
+		db.getLog().start(this.appendTag);
+		try {
+			long headerStartAddress = address + this.offset;
+			long nextBlockAddress = LAST_BLOCK_WITH_ELEMENTS.get(nd, headerStartAddress);
+	
+			// Ensure that there's at least one block
+			long insertionBlockAddress = nextBlockAddress;
+			if (nextBlockAddress == 0) {
+				long newBlockAddress = allocateNewBlock(nd, this.elementsPerBlock);
+				LAST_BLOCK_WITH_ELEMENTS.put(nd, headerStartAddress, newBlockAddress);
+				FIRST_BLOCK.put(nd, headerStartAddress, newBlockAddress);
+				insertionBlockAddress = newBlockAddress;
+			}
+	
+			// Check if there's any free space in this block
+			int elementsInBlock = BlockHeader.ELEMENTS_IN_USE.get(nd, insertionBlockAddress);
+			int blockSize = BlockHeader.BLOCK_SIZE.get(nd, insertionBlockAddress);
+	
+			if (elementsInBlock >= blockSize) {
+				long nextBlock = BlockHeader.NEXT_BLOCK.get(nd, insertionBlockAddress);
+				if (nextBlock == 0) {
+					nextBlock = allocateNewBlock(nd, this.elementsPerBlock);
+					BlockHeader.NEXT_BLOCK.put(nd, insertionBlockAddress, nextBlock);
+				}
+				LAST_BLOCK_WITH_ELEMENTS.put(nd, headerStartAddress, nextBlock);
+				insertionBlockAddress = nextBlock;
+				elementsInBlock = BlockHeader.ELEMENTS_IN_USE.get(nd, insertionBlockAddress); 
+			}
+	
+			BlockHeader.ELEMENTS_IN_USE.put(nd, insertionBlockAddress, (short) (elementsInBlock + 1));
+			int elementSize = getElementSize();
+	
+			long resultAddress = insertionBlockAddress + BlockHeader.BLOCK_HEADER_BYTES + elementsInBlock * elementSize;
+			assert ((resultAddress - Database.BLOCK_HEADER_SIZE) & (Database.BLOCK_SIZE_DELTA - 1)) == 0;
+			return this.elementType.getFactory().create(nd, resultAddress);
+		} finally {
+			db.getLog().end(this.appendTag);
+		}
+	}
+
+	/**
+	 * Ensures that the receiver will have space for the given number of elements without additional
+	 * allocation. Callers should invoke this prior to a sequence of {@link FieldList#append(Nd, long)}
+	 * calls if they know in advance how many elements will be appended. Will create the minimum number
+	 * of extra blocks needed to the given number of additional elements.
+	 */
+	public void allocate(Nd nd, long address, int numElements) {
+		Database db = nd.getDB();
+		db.getLog().start(this.allocateTag);
+		try {
+			if (numElements == 0) {
+				// Not an error, but there's nothing to do if the caller didn't actually ask for anything to be allocated.
+				return;
+			}
+			long headerStartAddress = address + this.offset;
+			long nextBlockAddress = LAST_BLOCK_WITH_ELEMENTS.get(nd, headerStartAddress);
+	
+			int maxBlockSizeThatFitsInAChunk = (int) ((MAX_BYTES_IN_A_CHUNK - BlockHeader.BLOCK_HEADER_BYTES)
+					/ getElementSize());
+
+			// Ensure that there's at least one block
+			if (nextBlockAddress == 0) {
+				int firstAllocation = Math.min(numElements, maxBlockSizeThatFitsInAChunk);
+				nextBlockAddress = allocateNewBlock(nd, firstAllocation);
+				LAST_BLOCK_WITH_ELEMENTS.put(nd, headerStartAddress, nextBlockAddress);
+				FIRST_BLOCK.put(nd, headerStartAddress, nextBlockAddress);
+			}
+	
+			// Check if there's any free space in this block
+			int remainingToAllocate = numElements;
+			while (true) {
+				long currentBlockAddress = nextBlockAddress;
+				nextBlockAddress = BlockHeader.NEXT_BLOCK.get(nd, currentBlockAddress);
+				int elementsInUse = BlockHeader.ELEMENTS_IN_USE.get(nd, currentBlockAddress);
+				int blockSize = BlockHeader.BLOCK_SIZE.get(nd, currentBlockAddress);
+	
+				remainingToAllocate -= (blockSize - elementsInUse);
+				if (remainingToAllocate <= 0) {
+					break;
+				}
+	
+				if (nextBlockAddress == 0) {
+					nextBlockAddress = allocateNewBlock(nd, Math.min(maxBlockSizeThatFitsInAChunk, numElements));
+					BlockHeader.NEXT_BLOCK.put(nd, currentBlockAddress, nextBlockAddress);
+				}
+			}
+		} finally {
+			db.getLog().end(this.allocateTag);
+		}
+	}
+
+	private long allocateNewBlock(Nd nd, int blockSize) {
+		short poolId = getMemoryPoolId(nd);
+		int elementSize = getElementSize();
+		long bytesNeeded = BlockHeader.BLOCK_HEADER_BYTES + blockSize * elementSize;
+		// If we're close enough to filling the chunk that we wouldn't be able to fit any more elements anyway, allocate
+		// the entire chunk. Although it wastes a small amount of space, it ensures that the blocks can be more easily
+		// reused rather than being fragmented. It also allows freed blocks to be merged via the large block allocator.
+		if (MAX_BYTES_IN_A_CHUNK - bytesNeeded < elementSize) {
+			bytesNeeded = MAX_BYTES_IN_A_CHUNK;
+		}
+		long result = nd.getDB().malloc(bytesNeeded, poolId);
+		BlockHeader.BLOCK_SIZE.put(nd, result, (short) blockSize);
+		return result;
+	}
+
+	private short getMemoryPoolId(Nd nd) {
+		short poolId = Database.POOL_LINKED_LIST;
+		if (this.ownerType != null) {
+			Class<?> structClass = this.ownerType.getStructClass();
+			if (nd.getTypeRegistry().isRegisteredClass(structClass)) {
+				poolId = (short) (Database.POOL_FIRST_NODE_TYPE + nd.getNodeType(structClass));
+			}
+		}
+		return poolId;
+	}
+
+	@Override
+	public void destruct(Nd nd, long address) {
+		Database db = nd.getDB();
+		db.getLog().start(this.destructTag);
+		try {
+			short poolId = getMemoryPoolId(nd);
+			long headerStartAddress = address + this.offset;
+			long firstBlockAddress = FIRST_BLOCK.get(nd, headerStartAddress);
+	
+			long nextBlockAddress = firstBlockAddress;
+			while (nextBlockAddress != 0) {
+				long currentBlockAddress = nextBlockAddress;
+				nextBlockAddress = BlockHeader.NEXT_BLOCK.get(nd, currentBlockAddress);
+				int elementsInBlock = BlockHeader.ELEMENTS_IN_USE.get(nd, currentBlockAddress);
+				destructElements(nd, currentBlockAddress + BlockHeader.BLOCK_HEADER_BYTES, elementsInBlock);
+				db.free(currentBlockAddress, poolId);
+			}
+	
+			db.clearRange(headerStartAddress, getRecordSize());
+		} finally {
+			db.getLog().end(this.destructTag);
+		} 
+	}
+
+	private void destructElements(Nd nd, long nextElementAddress, int count) {
+		ITypeFactory<T> factory = this.elementType.getFactory();
+
+		int size = getElementSize();
+		while (--count >= 0) {
+			factory.destruct(nd, nextElementAddress);
+			nextElementAddress += size;
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldManyToOne.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldManyToOne.java
index 41216cf..4d3d4b1 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldManyToOne.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldManyToOne.java
@@ -10,6 +10,7 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.core.nd.field;
 
+import org.eclipse.jdt.internal.core.nd.INdStruct;
 import org.eclipse.jdt.internal.core.nd.ITypeFactory;
 import org.eclipse.jdt.internal.core.nd.Nd;
 import org.eclipse.jdt.internal.core.nd.NdNode;
@@ -23,12 +24,12 @@
  * {@link FieldManyToOne} points to an object, the inverse pointer is automatically inserted into the matching back
  * pointer list.
  */
-public class FieldManyToOne<T extends NdNode> extends BaseField implements IDestructableField, IRefCountedField {
+public class FieldManyToOne<T extends INdStruct> extends BaseField implements IDestructableField, IRefCountedField {
 	public final static FieldPointer TARGET;
 	public final static FieldInt BACKPOINTER_INDEX;
 
 	StructDef<T> targetType;
-	final StructDef<? extends NdNode> localType;
+	final StructDef<? extends INdStruct> localType;
 	FieldOneToMany<?> backPointer;
 	@SuppressWarnings("rawtypes")
 	private final static StructDef<FieldManyToOne> type;
@@ -38,6 +39,7 @@
 	public final boolean pointsToOwner;
 	private final Tag putTag;
 	private final Tag destructTag;
+	private boolean permitsNull = true;
 
 	static {
 		type = StructDef.createAbstract(FieldManyToOne.class);
@@ -47,7 +49,7 @@
 	}
 
 	@SuppressWarnings({ "unchecked", "rawtypes" })
-	private FieldManyToOne(StructDef<? extends NdNode> localType, FieldOneToMany<?> backPointer, boolean pointsToOwner) {
+	private FieldManyToOne(StructDef<? extends INdStruct> localType, FieldOneToMany<?> backPointer, boolean pointsToOwner) {
 		this.localType = localType;
 		this.pointsToOwner = pointsToOwner;
 
@@ -68,7 +70,14 @@
 		this.destructTag = ModificationLog.createTag("Destructing " + getFieldName()); //$NON-NLS-1$
 	}
 
-	public static <T extends NdNode, B extends NdNode> FieldManyToOne<T> create(StructDef<B> builder,
+	public static <T extends INdStruct, B extends INdStruct> FieldManyToOne<T> createNonNull(StructDef<B> builder,
+			FieldOneToMany<B> forwardPointer) {
+		FieldManyToOne<T> result = create(builder, forwardPointer);
+		result.permitsNull = false;
+		return result;
+	}
+
+	public static <T extends INdStruct, B extends INdStruct> FieldManyToOne<T> create(StructDef<B> builder,
 			FieldOneToMany<B> forwardPointer) {
 		FieldManyToOne<T> result = new FieldManyToOne<T>(builder, forwardPointer, false);
 		builder.add(result);
@@ -84,8 +93,17 @@
 	 * @param forwardPointer the field which holds the pointer in the other direction
 	 * @return a newly constructed field
 	 */
-	public static <T extends NdNode, B extends NdNode> FieldManyToOne<T> createOwner(StructDef<B> builder,
+	public static <T extends INdStruct, B extends INdStruct> FieldManyToOne<T> createOwner(StructDef<B> builder,
 			FieldOneToMany<B> forwardPointer) {
+		// Although it would work to have a non-NdNode owned in this manner, we currently have no legitimate use-cases
+		// for this to occur. If this happens it is almost certainly an accidental copy-paste error where someone
+		// intended to call create but called this method instead. If we ever discover a legitimate use-case for it,
+		// this could be removed and things would probably still work.
+		if (!NdNode.class.isAssignableFrom(builder.getStructClass())) {
+			throw new IllegalArgumentException(FieldManyToOne.class.getSimpleName() + " can't be the owner of " //$NON-NLS-1$
+					+ builder.getStructClass().getSimpleName() + " because the latter isn't a subclass of " //$NON-NLS-1$
+					+ NdNode.class.getSimpleName()); 
+		}
 
 		FieldManyToOne<T> result = new FieldManyToOne<T>(builder, forwardPointer, true);
 		builder.add(result);
@@ -94,12 +112,29 @@
 		return result;
 	}
 
+	/**
+	 * Sets whether or not this field permits nulls to be assigned.
+	 * 
+	 * @param permitted true iff the field permits nulls
+	 * @return this
+	 */
+	public FieldManyToOne<T> permitNull(boolean permitted) {
+		this.permitsNull = permitted;
+		return this;
+	}
+
 	public T get(Nd nd, long address) {
 		return NdNode.load(nd, getAddress(nd, address), this.targetType);
 	}
 
 	public long getAddress(Nd nd, long address) {
-		return nd.getDB().getRecPtr(address + this.offset);
+		long result = nd.getDB().getRecPtr(address + this.offset);
+		if (!this.permitsNull && result == 0) {
+			throw nd.describeProblem()
+				.addProblemAddress(this, address)
+				.build("Database contained a null in a non-null field"); //$NON-NLS-1$
+		}
+		return result;
 	}
 
 	/**
@@ -109,8 +144,10 @@
 	public void put(Nd nd, long address, T value) {
 		if (value != null) {
 			put(nd, address, value.getAddress());
-		} else {
+		} else if (this.permitsNull) {
 			put(nd, address, 0);
+		} else {
+			throw new IllegalArgumentException("Attempted to write a null into a non-null field"); //$NON-NLS-1$
 		}
 	}
 
@@ -153,13 +190,14 @@
 
 			this.backPointer.remove(nd, oldTargetAddress, oldIndex);
 
-			short targetTypeId = NdNode.NODE_TYPE.get(nd, oldTargetAddress);
+			if (this.targetType.isNdNode()) {
+				short targetTypeId = NdNode.NODE_TYPE.get(nd, oldTargetAddress);
+				ITypeFactory<? extends NdNode> typeFactory = nd.getTypeFactory(targetTypeId);
 
-			ITypeFactory<T> typeFactory = nd.getTypeFactory(targetTypeId);
-
-			if (typeFactory.getDeletionSemantics() == StructDef.DeletionSemantics.REFCOUNTED 
-					&& typeFactory.isReadyForDeletion(nd, oldTargetAddress)) {
-				nd.scheduleDeletion(oldTargetAddress);
+				if (typeFactory.getDeletionSemantics() == StructDef.DeletionSemantics.REFCOUNTED 
+						&& typeFactory.isReadyForDeletion(nd, oldTargetAddress)) {
+					nd.scheduleDeletion(oldTargetAddress);
+				}
 			}
 		}
 	}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToMany.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToMany.java
index 19c085d..5b3952f 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToMany.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToMany.java
@@ -13,6 +13,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import org.eclipse.jdt.internal.core.nd.INdStruct;
 import org.eclipse.jdt.internal.core.nd.Nd;
 import org.eclipse.jdt.internal.core.nd.NdNode;
 import org.eclipse.jdt.internal.core.nd.RawGrowableArray;
@@ -21,9 +22,9 @@
  * Holds the 1 side of a 1..n relationship between two objects. FieldNodePointer and FieldBackPointer fields always go
  * together in pairs.
  */
-public class FieldOneToMany<T extends NdNode> extends BaseField implements IDestructableField, IRefCountedField {
+public class FieldOneToMany<T extends INdStruct> extends BaseField implements IDestructableField, IRefCountedField {
 	public StructDef<T> targetType;
-	public final StructDef<? extends NdNode> localType;
+	public final StructDef<? extends INdStruct> localType;
 	private final RawGrowableArray backPointerArray;
 	FieldManyToOne<?> forwardPointer;
 
@@ -32,7 +33,7 @@
 	}
 
 	@SuppressWarnings({ "rawtypes", "unchecked" })
-	private FieldOneToMany(StructDef<? extends NdNode> localType, FieldManyToOne<? extends NdNode> forwardPointer,
+	private FieldOneToMany(StructDef<? extends INdStruct> localType, FieldManyToOne<? extends INdStruct> forwardPointer,
 			int inlineElements) {
 		this.localType = localType;
 
@@ -42,8 +43,8 @@
 					"Attempted to construct a FieldBackPointer referring to a forward pointer that is already in use" //$NON-NLS-1$
 						+ " by another field"); //$NON-NLS-1$
 			}
-			forwardPointer.targetType = (StructDef)localType;
-			this.targetType = (StructDef)forwardPointer.localType;
+			forwardPointer.targetType = (StructDef) localType;
+			this.targetType = (StructDef) forwardPointer.localType;
 			forwardPointer.backPointer = this;
 		}
 		this.forwardPointer = forwardPointer;
@@ -64,7 +65,7 @@
 	 * offer a performance improvement. For relationships that will normally be empty, this should be 0.
 	 * @return the newly constructed backpointer field
 	 */
-	public static <T extends NdNode, B extends NdNode> FieldOneToMany<T> create(StructDef<B> builder, 
+	public static <T extends INdStruct, B extends INdStruct> FieldOneToMany<T> create(StructDef<B> builder, 
 			FieldManyToOne<B> forwardPointer, int inlineElementCount) {
 		FieldOneToMany<T> result = new FieldOneToMany<T>(builder, forwardPointer, inlineElementCount);
 		builder.add(result);
@@ -73,11 +74,11 @@
 		return result;
 	}
 
-	public static <T extends NdNode, B extends NdNode> FieldOneToMany<T> create(StructDef<B> builder, 
+	public static <T extends INdStruct, B extends INdStruct> FieldOneToMany<T> create(StructDef<B> builder, 
 			FieldManyToOne<B> forwardPointer) {
 		return create(builder, forwardPointer, 0);
 	}
-	
+
 	public void accept(Nd nd, long address, Visitor<T> visitor) {
 		int size = size(nd, address);
 
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToOne.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToOne.java
index ca7fa19..47d1d71 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToOne.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToOne.java
@@ -10,6 +10,7 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.core.nd.field;
 
+import org.eclipse.jdt.internal.core.nd.INdStruct;
 import org.eclipse.jdt.internal.core.nd.Nd;
 import org.eclipse.jdt.internal.core.nd.NdNode;
 import org.eclipse.jdt.internal.core.nd.db.ModificationLog;
@@ -19,7 +20,7 @@
 /**
  * Represents a 1-to-0..1 relationship in a Nd database.
  */
-public class FieldOneToOne<T extends NdNode> extends BaseField implements IDestructableField, IRefCountedField {
+public class FieldOneToOne<T extends INdStruct> extends BaseField implements IDestructableField, IRefCountedField {
 	public final StructDef<T> nodeType; 
 	FieldOneToOne<?> backPointer;
 	private boolean pointsToOwner;
@@ -49,7 +50,7 @@
 		this.destructTag = ModificationLog.createTag("Destructing " + getFieldName()); //$NON-NLS-1$
 	}
 
-	public static <T extends NdNode, B extends NdNode> FieldOneToOne<T> create(StructDef<B> builder,
+	public static <T extends INdStruct, B extends INdStruct> FieldOneToOne<T> create(StructDef<B> builder,
 			StructDef<T> nodeType, FieldOneToOne<B> forwardPointer) {
 
 		FieldOneToOne<T> result = new FieldOneToOne<T>(nodeType, forwardPointer, false);
@@ -58,7 +59,7 @@
 		return result;
 	}
 
-	public static <T extends NdNode, B extends NdNode> FieldOneToOne<T> createOwner(StructDef<B> builder,
+	public static <T extends INdStruct, B extends INdStruct> FieldOneToOne<T> createOwner(StructDef<B> builder,
 			StructDef<T> nodeType, FieldOneToOne<B> forwardPointer) {
 
 		FieldOneToOne<T> result = new FieldOneToOne<T>(nodeType, forwardPointer, true);
@@ -84,8 +85,8 @@
 					nd.scheduleDeletion(address);
 				}
 			} else {
-				db.putRecPtr(address + this.offset, target.address);
-				db.putRecPtr(target.address + this.backPointer.offset, address);
+				db.putRecPtr(address + this.offset, target.getAddress());
+				db.putRecPtr(target.getAddress() + this.backPointer.offset, address);
 			}
 		} finally {
 			db.getLog().end(this.putTag);
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IField.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IField.java
index 6796a46..78a793e 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IField.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IField.java
@@ -14,7 +14,15 @@
 
 /**
  * Represents a single field of a struct in the {@link Database}. Holds metadata for that field
- * and permits laziy initialization of the field offset.
+ * and permits laziy initialization of the field offset. Fields are normally instantiated as static
+ * variables. Collectively, they describe the database schema but they are not associated with any
+ * particular instance of data in the database.
+ * <p>
+ * Fields are temporarily mutable. On construction, a number of attributes (such as offset) are
+ * computed in a second pass or are initialized as other fields are constructed. Generally such
+ * attributes can't be computed in the constructor since they depend on knowledge of other fields
+ * that must be instantiated first. However, once {@link StructDef#done()} has been called on the
+ * last {@link StructDef}, fields are immutable and should not ever be modified again.
  */
 public interface IField {
 	/**
@@ -22,12 +30,20 @@
 	 * after the sizes of all preceeding fields are known.
 	 */
 	void setOffset(int offset);
+
 	/**
 	 * Returns the size of the field, in bytes.
 	 */
 	int getRecordSize();
 
 	/**
+	 * Returns the required byte alignment for the field.
+	 */
+	default int getAlignment() {
+		return 1;
+	}
+
+	/**
 	 * Returns the name of the field. This is mainly used for error messages, debug output, and diagnostic tools.
 	 * Meant to be programmer-readable but not user-readable.
 	 */
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/StructDef.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/StructDef.java
index b5fddf3..9fe5571 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/StructDef.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/StructDef.java
@@ -15,14 +15,18 @@
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import org.eclipse.jdt.internal.core.nd.IDestructable;
 import org.eclipse.jdt.internal.core.nd.ITypeFactory;
 import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.db.ModificationLog;
+import org.eclipse.jdt.internal.core.nd.NdNode;
 import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.db.ModificationLog;
 import org.eclipse.jdt.internal.core.nd.db.ModificationLog.Tag;
+import org.eclipse.jdt.internal.core.nd.util.MathUtils;
 
 /**
  * Defines a data structure that will appear in the database.
@@ -46,10 +50,11 @@
 public final class StructDef<T> {
 	Class<T> clazz;
 	private StructDef<? super T> superClass;
+	private Set<StructDef<?>> dependencies = new HashSet<>();
 	private List<IField> fields = new ArrayList<>();
 	private boolean doneCalled;
 	private boolean offsetsComputed;
-	private List<StructDef<? extends T>> subClasses = new ArrayList<>();
+	private List<StructDef<? extends T>> dependents = new ArrayList<>();
 	private int size;
 	List<IDestructableField> destructableFields = new ArrayList<>();
 	boolean refCounted;
@@ -60,6 +65,7 @@
 	protected boolean hasUserDestructor;
 	private DeletionSemantics deletionSemantics;
 	final Tag destructTag;
+	private boolean isNdNode;
 
 	public static enum DeletionSemantics {
 		EXPLICIT, OWNED, REFCOUNTED
@@ -76,9 +82,10 @@
 	private StructDef(Class<T> clazz, StructDef<? super T> superClass, boolean isAbstract) {
 		this.destructTag = ModificationLog.createTag("Destructing struct " + clazz.getSimpleName()); //$NON-NLS-1$
 		this.clazz = clazz;
+		this.isNdNode = NdNode.class.isAssignableFrom(clazz);
 		this.superClass = superClass;
 		if (this.superClass != null) {
-			this.superClass.subClasses.add(this);
+			addDependency(this.superClass);
 		}
 		this.isAbstract = isAbstract;
 		final String fullyQualifiedClassName = clazz.getName();
@@ -162,6 +169,32 @@
 		};
 	}
 
+	public void addDependency(StructDef<?> newDependency) {
+		if (newDependency.hasIndirectDependent(new HashSet<>(), this)) {
+			throw new IllegalArgumentException("Circular dependency detected. Struct " //$NON-NLS-1$
+					+ getStructName() + " and struct " + newDependency.getStructName()  //$NON-NLS-1$
+					+ " both depend on one another"); //$NON-NLS-1$
+		}
+		if (this.dependencies.add(newDependency)) {
+			this.superClass.dependents.add(this);
+		}
+	}
+
+	private boolean hasIndirectDependent(Set<StructDef<?>> visited, StructDef<?> structDef) {
+		for (StructDef<?> next : this.dependents) {
+			if (!visited.add(next)) {
+				continue;
+			}
+			if (next.equals(structDef)) {
+				return true;
+			}
+			if (next.hasIndirectDependent(visited, structDef)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
 	public Class<T> getStructClass() {
 		return this.clazz;
 	}
@@ -217,6 +250,15 @@
 		return this.deletionSemantics;
 	}
 
+	private boolean areAllDependenciesResolved() {
+		for (StructDef<?> next : this.dependencies) {
+			if (!next.areOffsetsComputed()) {
+				return false;
+			}
+		}
+		return true;
+	}
+
 	/**
 	 * Call this once all the fields have been added to the struct definition and it is
 	 * ready to use.
@@ -227,7 +269,7 @@
 		}
 		this.doneCalled = true;
 
-		if (this.superClass == null || this.superClass.areOffsetsComputed()) {
+		if (areAllDependenciesResolved()) {
 			computeOffsets();
 		}
 	}
@@ -286,12 +328,13 @@
 
 	/**
 	 * Invoked on all StructDef after both {@link #done()} has been called on the struct and
-	 * {@link #computeOffsets()} has been called on their base class.
+	 * {@link #computeOffsets()} has been called on every dependency of this struct.
 	 */
 	private void computeOffsets() {
 		int offset = this.superClass == null ? 0 : this.superClass.size();
 
 		for (IField next : this.fields) {
+			offset = MathUtils.roundUpToNearestMultiple(offset, next.getAlignment());
 			next.setOffset(offset);
 			offset += next.getRecordSize();
 		}
@@ -320,7 +363,7 @@
 		
 		this.offsetsComputed = true;
 
-		for (StructDef<? extends T> next : this.subClasses) {
+		for (StructDef<? extends T> next : this.dependents) {
 			if (next.doneCalled) {
 				next.computeOffsets();
 			}
@@ -409,6 +452,10 @@
 		}
 	}
 
+	public boolean isNdNode() {
+		return this.isNdNode;
+	}
+
 	public int getNumFields() {
 		return this.fields.size();
 	}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/ClassFileToIndexConverter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/ClassFileToIndexConverter.java
index 38cacb1..2ba44b1 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/ClassFileToIndexConverter.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/ClassFileToIndexConverter.java
@@ -36,11 +36,6 @@
 import org.eclipse.jdt.internal.core.nd.java.JavaIndex;
 import org.eclipse.jdt.internal.core.nd.java.JavaNames;
 import org.eclipse.jdt.internal.core.nd.java.NdAnnotation;
-import org.eclipse.jdt.internal.core.nd.java.NdAnnotationInConstant;
-import org.eclipse.jdt.internal.core.nd.java.NdAnnotationInMethod;
-import org.eclipse.jdt.internal.core.nd.java.NdAnnotationInMethodParameter;
-import org.eclipse.jdt.internal.core.nd.java.NdAnnotationInType;
-import org.eclipse.jdt.internal.core.nd.java.NdAnnotationInVariable;
 import org.eclipse.jdt.internal.core.nd.java.NdAnnotationValuePair;
 import org.eclipse.jdt.internal.core.nd.java.NdBinding;
 import org.eclipse.jdt.internal.core.nd.java.NdComplexTypeSignature;
@@ -50,17 +45,12 @@
 import org.eclipse.jdt.internal.core.nd.java.NdConstantClass;
 import org.eclipse.jdt.internal.core.nd.java.NdConstantEnum;
 import org.eclipse.jdt.internal.core.nd.java.NdMethod;
-import org.eclipse.jdt.internal.core.nd.java.NdMethodException;
 import org.eclipse.jdt.internal.core.nd.java.NdMethodId;
 import org.eclipse.jdt.internal.core.nd.java.NdMethodParameter;
 import org.eclipse.jdt.internal.core.nd.java.NdResourceFile;
 import org.eclipse.jdt.internal.core.nd.java.NdType;
 import org.eclipse.jdt.internal.core.nd.java.NdTypeAnnotation;
-import org.eclipse.jdt.internal.core.nd.java.NdTypeAnnotationInMethod;
-import org.eclipse.jdt.internal.core.nd.java.NdTypeAnnotationInType;
-import org.eclipse.jdt.internal.core.nd.java.NdTypeAnnotationInVariable;
 import org.eclipse.jdt.internal.core.nd.java.NdTypeArgument;
-import org.eclipse.jdt.internal.core.nd.java.NdTypeBound;
 import org.eclipse.jdt.internal.core.nd.java.NdTypeId;
 import org.eclipse.jdt.internal.core.nd.java.NdTypeInterface;
 import org.eclipse.jdt.internal.core.nd.java.NdTypeParameter;
@@ -77,7 +67,7 @@
 	private static final char[] COMMA = new char[]{','};
 	private static final char[][] EMPTY_CHAR_ARRAY_ARRAY = new char[0][];
 	private static final boolean ENABLE_LOGGING = false;
-	private static final char[] EMPTY_CHAR_ARRAY = new char[0];
+	static final char[] EMPTY_CHAR_ARRAY = new char[0];
 	private static final char[] PATH_SEPARATOR = new char[]{'/'};
 	private static final char[] ARRAY_FIELD_DESCRIPTOR_PREFIX = new char[] { '[' };
 	private NdResourceFile resource;
@@ -135,8 +125,9 @@
 
 		IBinaryTypeAnnotation[] typeAnnotations = binaryType.getTypeAnnotations();
 		if (typeAnnotations != null) {
+			type.allocateTypeAnnotations(typeAnnotations.length);
 			for (IBinaryTypeAnnotation typeAnnotation : typeAnnotations) {
-				NdTypeAnnotationInType annotation = new NdTypeAnnotationInType(getNd(), type);
+				NdTypeAnnotation annotation = type.createTypeAnnotation();
 
 				initTypeAnnotation(annotation, typeAnnotation);
 			}
@@ -194,6 +185,7 @@
 		IBinaryField[] fields = binaryType.getFields();
 
 		if (fields != null) {
+			type.allocateVariables(fields.length);
 			for (IBinaryField nextField : fields) {
 				addField(type, nextField);
 			}
@@ -251,8 +243,9 @@
 
 	private void attachAnnotations(NdMethod method, IBinaryAnnotation[] annotations) {
 		if (annotations != null) {
+			method.allocateAnnotations(annotations.length);
 			for (IBinaryAnnotation next : annotations) {
-				NdAnnotationInMethod annotation = new NdAnnotationInMethod(getNd(), method);
+				NdAnnotation annotation = method.createAnnotation();
 				initAnnotation(annotation, next);
 			}
 		}
@@ -260,8 +253,9 @@
 
 	private void attachAnnotations(NdType type, IBinaryAnnotation[] annotations) {
 		if (annotations != null) {
+			type.allocateAnnotations(annotations.length);
 			for (IBinaryAnnotation next : annotations) {
-				NdAnnotationInType annotation = new NdAnnotationInType(getNd(), type);
+				NdAnnotation annotation = type.createAnnotation();
 				initAnnotation(annotation, next);
 			}
 		}
@@ -269,8 +263,9 @@
 
 	private void attachAnnotations(NdVariable variable, IBinaryAnnotation[] annotations) {
 		if (annotations != null) {
+			variable.allocateAnnotations(annotations.length);
 			for (IBinaryAnnotation next : annotations) {
-				NdAnnotationInVariable annotation = new NdAnnotationInVariable(getNd(), variable);
+				NdAnnotation annotation = variable.createAnnotation();
 				initAnnotation(annotation, next);
 			}
 		}
@@ -278,8 +273,9 @@
 
 	private void attachAnnotations(NdMethodParameter variable, IBinaryAnnotation[] annotations) {
 		if (annotations != null) {
+			variable.allocateAnnotations(annotations.length);
 			for (IBinaryAnnotation next : annotations) {
-				NdAnnotationInMethodParameter annotation = new NdAnnotationInMethodParameter(getNd(), variable);
+				NdAnnotation annotation = variable.createAnnotation();
 				initAnnotation(annotation, next);
 			}
 		}
@@ -306,8 +302,9 @@
 
 		IBinaryTypeAnnotation[] typeAnnotations = next.getTypeAnnotations();
 		if (typeAnnotations != null) {
+			method.allocateTypeAnnotations(typeAnnotations.length);
 			for (IBinaryTypeAnnotation typeAnnotation : typeAnnotations) {
-				NdTypeAnnotationInMethod annotation = new NdTypeAnnotationInMethod(getNd(), method);
+				NdTypeAnnotation annotation = method.createTypeAnnotation();
 
 				initTypeAnnotation(annotation, typeAnnotation);
 			}
@@ -349,6 +346,10 @@
 
 		int parameterNameIdx = 0;
 		int annotatedParametersCount = next.getAnnotatedParametersCount();
+		int namedParameterCount = parameterNames == null ? 0 : parameterNames.length;
+		int estimatedParameterCount = Math.max(Math.max(Math.max(numArgumentsInGenericSignature, namedParameterCount),
+				annotatedParametersCount), parameterFieldDescriptors.size());
+		method.allocateParameters(estimatedParameterCount);
 
 		short descriptorParameterIdx = 0;
 		char[] binaryTypeName = binaryType.getName();
@@ -367,8 +368,8 @@
 			if (isCompilerDefined && !compilerDefinedParametersAreIncludedInSignature) {
 				nextFieldSignature = new SignatureWrapper(nextFieldDescriptor);
 			}
-			NdMethodParameter parameter = new NdMethodParameter(method,
-					createTypeSignature(nextFieldSignature, nextFieldDescriptor));
+			NdMethodParameter parameter = method.createNewParameter();
+			parameter.setType(createTypeSignature(nextFieldSignature, nextFieldDescriptor));
 
 			parameter.setCompilerDefined(isCompilerDefined);
 
@@ -378,7 +379,7 @@
 
 				attachAnnotations(parameter, parameterAnnotations);
 			}
-			if (!isCompilerDefined && parameterNames != null && parameterNames.length > parameterNameIdx) {
+			if (!isCompilerDefined && namedParameterCount > parameterNameIdx) {
 				parameter.setName(parameterNames[parameterNameIdx++]);
 			}
 			descriptorParameterIdx++;
@@ -393,11 +394,12 @@
 		if (exceptionTypes == null) {
 			exceptionTypes = CharArrayUtils.EMPTY_ARRAY_OF_CHAR_ARRAYS;
 		}
+		method.allocateExceptions(exceptionTypes.length);
 		int throwsIdx = 0;
 		if (hasExceptionsInSignature) {
 			while (hasAnotherException(signature)) {
 				signature.start++;
-				new NdMethodException(method, createTypeSignature(signature,
+				method.createException(createTypeSignature(signature,
 						JavaNames.binaryNameToFieldDescriptor(exceptionTypes[throwsIdx])));
 				throwsIdx++;
 			}
@@ -405,7 +407,7 @@
 			for (;throwsIdx < exceptionTypes.length; throwsIdx++) {
 				char[] fieldDescriptor = JavaNames.binaryNameToFieldDescriptor(exceptionTypes[throwsIdx]);
 				SignatureWrapper convertedWrapper = new SignatureWrapper(fieldDescriptor);
-				new NdMethodException(method, createTypeSignature(convertedWrapper,
+				method.createException(createTypeSignature(convertedWrapper,
 						JavaNames.binaryNameToFieldDescriptor(exceptionTypes[throwsIdx])));
 			}
 		}
@@ -439,7 +441,7 @@
 	 * Adds the given field to the given type
 	 */
 	private void addField(NdType type, IBinaryField nextField) throws CoreException {
-		NdVariable variable = new NdVariable(type);
+		NdVariable variable = type.createVariable();
 
 		variable.setName(nextField.getName());
 
@@ -455,18 +457,23 @@
 
 		IBinaryTypeAnnotation[] typeAnnotations = nextField.getTypeAnnotations();
 		if (typeAnnotations != null) {
+			variable.allocateTypeAnnotations(typeAnnotations.length);
 			for (IBinaryTypeAnnotation next : typeAnnotations) {
-				NdTypeAnnotationInVariable annotation = new NdTypeAnnotationInVariable(getNd(), variable);
+				NdTypeAnnotation annotation = variable.createTypeAnnotation();
 	
 				initTypeAnnotation(annotation, next);
 			}
 		}
 		variable.setType(createTypeSignature(nextTypeSignature, nextField.getTypeName()));
 		variable.setTagBits(nextField.getTagBits());
+	}
 
-		// char[] fieldDescriptor = nextField.getTypeName();
-		// // DO NOT SUBMIT:
-		// IBinaryField bf = IndexBinaryType.createBinaryField(variable);
+	private static class TypeParameter {
+		public TypeParameter() {
+		}
+		public List<NdTypeSignature> bounds = new ArrayList<>();
+		public char[] identifier = ClassFileToIndexConverter.EMPTY_CHAR_ARRAY;
+		public boolean firstBoundIsClass;
 	}
 
 	/**
@@ -482,25 +489,40 @@
 			return;
 		}
 
+		List<TypeParameter> typeParameters = new ArrayList<>();
+
 		int indexOfClosingBracket = wrapper.skipAngleContents(wrapper.start) - 1;
 		wrapper.start++;
-		NdTypeParameter parameter = null;
+		TypeParameter parameter = null;
 		while (wrapper.start < indexOfClosingBracket) {
 			int colonPos = CharOperation.indexOf(':', genericSignature, wrapper.start, indexOfClosingBracket);
 
 			if (colonPos > wrapper.start) {
 				char[] identifier = CharOperation.subarray(genericSignature, wrapper.start, colonPos);
-				parameter = new NdTypeParameter(type, identifier);
+				parameter = new TypeParameter();
+				typeParameters.add(parameter);
+				parameter.identifier = identifier;
 				wrapper.start = colonPos + 1;
 				// The first bound is a class as long as it doesn't start with a double-colon
-				parameter.setFirstBoundIsClass(wrapper.charAtStart() != ':');
+				parameter.firstBoundIsClass = (wrapper.charAtStart() != ':');
 			}
 
 			skipChar(wrapper, ':');
 
 			NdTypeSignature boundSignature = createTypeSignature(wrapper, JAVA_LANG_OBJECT_FIELD_DESCRIPTOR);
 
-			new NdTypeBound(parameter, boundSignature);
+			parameter.bounds.add(boundSignature);
+		}
+
+		type.allocateTypeParameters(typeParameters.size());
+		for (TypeParameter param : typeParameters) {
+			NdTypeParameter ndParam = type.createTypeParameter();
+			ndParam.setIdentifier(param.identifier);
+			ndParam.setFirstBoundIsClass(param.firstBoundIsClass);
+			ndParam.allocateBounds(param.bounds.size());
+			for (NdTypeSignature bound : param.bounds) {
+				ndParam.createBound(bound);
+			}
 		}
 
 		skipChar(wrapper, '>');
@@ -854,8 +876,9 @@
 		IBinaryElementValuePair[] pairs = next.getElementValuePairs();
 
 		if (pairs != null) {
+			annotation.allocateValuePairs(pairs.length);
 			for (IBinaryElementValuePair element : pairs) {
-				NdAnnotationValuePair nextPair = new NdAnnotationValuePair(annotation, element.getName());
+				NdAnnotationValuePair nextPair = annotation.createValuePair(element.getName());
 				nextPair.setValue(createConstantFromMixedType(element.getValue()));
 			}
 		}
@@ -894,9 +917,9 @@
 		} else if (value instanceof IBinaryAnnotation) {
 			IBinaryAnnotation binaryAnnotation = (IBinaryAnnotation) value;
 
-			NdAnnotationInConstant annotation = new NdAnnotationInConstant(getNd());
-			initAnnotation(annotation, binaryAnnotation);
-			return NdConstantAnnotation.create(getNd(), annotation);
+			NdConstantAnnotation constant = new NdConstantAnnotation(getNd());
+			initAnnotation(constant.getValue(), binaryAnnotation);
+			return constant;
 		} else if (value instanceof Object[]) {
 			NdConstantArray result = new NdConstantArray(getNd());
 			Object[] array = (Object[]) value;
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Indexer.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Indexer.java
index 877713c..08bd027 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Indexer.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Indexer.java
@@ -75,7 +75,6 @@
 import org.eclipse.jdt.internal.core.nd.java.NdType;
 import org.eclipse.jdt.internal.core.nd.java.NdTypeId;
 import org.eclipse.jdt.internal.core.nd.java.NdWorkspaceLocation;
-import org.eclipse.jdt.internal.core.nd.java.NdZipEntry;
 import org.eclipse.jdt.internal.core.nd.java.TypeRef;
 import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeDescriptor;
 import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeFactory;
@@ -704,6 +703,13 @@
 				}
 				subMonitor.setWorkRemaining(zipFile.size());
 
+				// Preallocate memory for the zipfile entries
+				this.nd.acquireWriteLock(subMonitor.split(5));
+				try {
+					resourceFile.allocateZipEntries(zipFile.size());
+				} finally {
+					this.nd.releaseWriteLock();
+				}
 				for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements();) {
 					SubMonitor nextEntry = subMonitor.split(1).setWorkRemaining(2);
 					ZipEntry member = e.nextElement();
@@ -717,7 +723,7 @@
 									Package.logInfo("Inserting non-class file " + fileName + " into " //$NON-NLS-1$//$NON-NLS-2$
 											+ resourceFile.getLocation().getString() + " " + resourceFile.address); //$NON-NLS-1$
 								}
-								new NdZipEntry(resourceFile, fileName);
+								resourceFile.addZipEntry(fileName);
 
 								if (fileName.equals("META-INF/MANIFEST.MF")) { //$NON-NLS-1$
 									try (InputStream inputStream = zipFile.getInputStream(member)) {
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaIndex.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaIndex.java
index 2c416a6..9c9f94a 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaIndex.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaIndex.java
@@ -38,9 +38,9 @@
 
 public class JavaIndex {
 	// Version constants
-	static final int CURRENT_VERSION = Nd.version(1, 47);
-	static final int MAX_SUPPORTED_VERSION = Nd.version(1, 47);
-	static final int MIN_SUPPORTED_VERSION = Nd.version(1, 47);
+	static final int CURRENT_VERSION = Nd.version(1, 48);
+	static final int MAX_SUPPORTED_VERSION = Nd.version(1, 48);
+	static final int MIN_SUPPORTED_VERSION = Nd.version(1, 48);
 
 	// Fields for the search header
 	public static final FieldSearchIndex<NdResourceFile> FILES;
@@ -289,13 +289,7 @@
 
 	static NdNodeTypeRegistry<NdNode> createTypeRegistry() {
 		NdNodeTypeRegistry<NdNode> registry = new NdNodeTypeRegistry<>();
-		registry.register(0x0001, NdAnnotation.type.getFactory());
-		registry.register(0x0004, NdAnnotationInConstant.type.getFactory());
-		registry.register(0x0008, NdAnnotationInMethod.type.getFactory());
-		registry.register(0x000c, NdAnnotationInMethodParameter.type.getFactory());
-		registry.register(0x0010, NdAnnotationInType.type.getFactory());
-		registry.register(0x0014, NdAnnotationInVariable.type.getFactory());
-		registry.register(0x0020, NdAnnotationValuePair.type.getFactory());
+
 		registry.register(0x0028, NdBinding.type.getFactory());
 		registry.register(0x0030, NdComplexTypeSignature.type.getFactory());
 		registry.register(0x0038, NdConstant.type.getFactory());
@@ -314,25 +308,16 @@
 		registry.register(0x0100, NdConstantString.type.getFactory());
 		registry.register(0x0110, NdMethod.type.getFactory());
 		registry.register(0x0118, NdMethodAnnotationData.type.getFactory());
-		registry.register(0x0120, NdMethodException.type.getFactory());
 		registry.register(0x0130, NdMethodId.type.getFactory());
-		registry.register(0x0140, NdMethodParameter.type.getFactory());
 		registry.register(0x0150, NdResourceFile.type.getFactory());
 		registry.register(0x0170, NdType.type.getFactory());
-		registry.register(0x0180, NdTypeAnnotation.type.getFactory());
-		registry.register(0x0184, NdTypeAnnotationInMethod.type.getFactory());
-		registry.register(0x0188, NdTypeAnnotationInType.type.getFactory());
-		registry.register(0x018c, NdTypeAnnotationInVariable.type.getFactory());
 		registry.register(0x0190, NdTypeArgument.type.getFactory());
-		registry.register(0x0194, NdTypeBound.type.getFactory());
 		registry.register(0x01A0, NdTypeInterface.type.getFactory());
-		registry.register(0x01B0, NdTypeParameter.type.getFactory());
 		registry.register(0x01C0, NdTypeSignature.type.getFactory());
 		registry.register(0x01D0, NdTypeId.type.getFactory());
 		registry.register(0x01E0, NdTypeInterface.type.getFactory());
 		registry.register(0x01F0, NdVariable.type.getFactory());
 		registry.register(0x0200, NdWorkspaceLocation.type.getFactory());
-		registry.register(0x0210, NdZipEntry.type.getFactory());
 		return registry;
 	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotation.java
index 9715c10..7248ce4 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotation.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotation.java
@@ -13,22 +13,22 @@
 import java.util.List;
 
 import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.NdStruct;
+import org.eclipse.jdt.internal.core.nd.field.FieldList;
 import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
-import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
 
-public class NdAnnotation extends NdNode {
+public class NdAnnotation extends NdStruct {
 	public static final FieldManyToOne<NdTypeSignature> ANNOTATION_TYPE;
-	public static final FieldOneToMany<NdAnnotationValuePair> ELEMENT_VALUE_PAIRS;
+	public static final FieldList<NdAnnotationValuePair> ELEMENT_VALUE_PAIRS;
 
 	@SuppressWarnings("hiding")
 	public static final StructDef<NdAnnotation> type;
 
 	static {
-		type = StructDef.create(NdAnnotation.class, NdNode.type);
+		type = StructDef.create(NdAnnotation.class, NdStruct.type);
 		ANNOTATION_TYPE = FieldManyToOne.create(type, NdTypeSignature.ANNOTATIONS_OF_THIS_TYPE);
-		ELEMENT_VALUE_PAIRS = FieldOneToMany.create(type, NdAnnotationValuePair.APPLIES_TO);
+		ELEMENT_VALUE_PAIRS = FieldList.create(type, NdAnnotationValuePair.type);
 		type.done();
 	}
 
@@ -36,10 +36,6 @@
 		super(nd, address);
 	}
 
-	public NdAnnotation(Nd nd) {
-		super(nd);
-	}
-
 	public NdTypeSignature getType() {
 		return ANNOTATION_TYPE.get(getNd(), this.address);
 	}
@@ -51,4 +47,14 @@
 	public List<NdAnnotationValuePair> getElementValuePairs() {
 		return ELEMENT_VALUE_PAIRS.asList(getNd(), this.address);
 	}
+
+	public NdAnnotationValuePair createValuePair(char[] name) {
+		NdAnnotationValuePair result = ELEMENT_VALUE_PAIRS.append(getNd(), getAddress());
+		result.setName(name);
+		return result;
+	}
+
+	public void allocateValuePairs(int length) {
+		ELEMENT_VALUE_PAIRS.allocate(getNd(), getAddress(), length);
+	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInConstant.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInConstant.java
deleted file mode 100644
index d3ee914..0000000
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInConstant.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Google, Inc 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:
- *   Stefan Xenos (Google) - Initial implementation
- *******************************************************************************/
-package org.eclipse.jdt.internal.core.nd.java;
-
-import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
-import org.eclipse.jdt.internal.core.nd.field.StructDef;
-
-public class NdAnnotationInConstant extends NdAnnotation {
-	public static final FieldOneToOne<NdConstantAnnotation> OWNER;
-
-	@SuppressWarnings("hiding")
-	public static final StructDef<NdAnnotationInConstant> type;
-
-	static {
-		type = StructDef.create(NdAnnotationInConstant.class, NdAnnotation.type);
-		OWNER = FieldOneToOne.createOwner(type, NdConstantAnnotation.type, NdConstantAnnotation.VALUE);
-		type.done();
-	}
-
-	public NdAnnotationInConstant(Nd nd, long address) {
-		super(nd, address);
-	}
-
-	public NdAnnotationInConstant(Nd nd) {
-		super(nd);
-	}
-
-}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethod.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethod.java
deleted file mode 100644
index c1b524e..0000000
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethod.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Google, Inc 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:
- *   Stefan Xenos (Google) - Initial implementation
- *******************************************************************************/
-package org.eclipse.jdt.internal.core.nd.java;
-
-import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
-import org.eclipse.jdt.internal.core.nd.field.StructDef;
-
-public class NdAnnotationInMethod extends NdAnnotation {
-	public static final FieldManyToOne<NdMethodAnnotationData> OWNER;
-
-	@SuppressWarnings("hiding")
-	public static final StructDef<NdAnnotationInMethod> type;
-
-	static {
-		type = StructDef.create(NdAnnotationInMethod.class, NdAnnotation.type);
-		OWNER = FieldManyToOne.createOwner(type, NdMethodAnnotationData.ANNOTATIONS);
-		type.done();
-	}
-
-	public NdAnnotationInMethod(Nd nd, long address) {
-		super(nd, address);
-	}
-
-	public NdAnnotationInMethod(Nd nd, NdMethod owner) {
-		super(nd);
-
-		OWNER.put(getNd(), this.address, owner.createAnnotationData());
-	}
-
-}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethodParameter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethodParameter.java
deleted file mode 100644
index 0a4f3fb..0000000
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethodParameter.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Google, Inc 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:
- *   Stefan Xenos (Google) - Initial implementation
- *******************************************************************************/
-package org.eclipse.jdt.internal.core.nd.java;
-
-import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
-import org.eclipse.jdt.internal.core.nd.field.StructDef;
-
-public class NdAnnotationInMethodParameter extends NdAnnotation {
-	public static final FieldManyToOne<NdMethodParameter> OWNER;
-
-	@SuppressWarnings("hiding")
-	public static final StructDef<NdAnnotationInMethodParameter> type;
-
-	static {
-		type = StructDef.create(NdAnnotationInMethodParameter.class, NdAnnotation.type);
-		OWNER = FieldManyToOne.createOwner(type, NdMethodParameter.ANNOTATIONS);
-		type.done();
-	}
-
-	public NdAnnotationInMethodParameter(Nd nd, long address) {
-		super(nd, address);
-	}
-
-	public NdAnnotationInMethodParameter(Nd nd, NdMethodParameter owner) {
-		super(nd);
-
-		OWNER.put(getNd(), this.address, owner);
-	}
-
-}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInType.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInType.java
deleted file mode 100644
index c220ed9..0000000
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInType.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Google, Inc 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:
- *   Stefan Xenos (Google) - Initial implementation
- *******************************************************************************/
-package org.eclipse.jdt.internal.core.nd.java;
-
-import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
-import org.eclipse.jdt.internal.core.nd.field.StructDef;
-
-public class NdAnnotationInType extends NdAnnotation {
-	public static final FieldManyToOne<NdType> OWNER;
-
-	@SuppressWarnings("hiding")
-	public static final StructDef<NdAnnotationInType> type;
-
-	static {
-		type = StructDef.create(NdAnnotationInType.class, NdAnnotation.type);
-		OWNER = FieldManyToOne.createOwner(type, NdType.ANNOTATIONS);
-		type.done();
-	}
-
-	public NdAnnotationInType(Nd nd, long address) {
-		super(nd, address);
-	}
-
-	public NdAnnotationInType(Nd nd, NdType owner) {
-		super(nd);
-
-		OWNER.put(getNd(), this.address, owner);
-	}
-
-}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInVariable.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInVariable.java
deleted file mode 100644
index 378b2d4..0000000
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInVariable.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Google, Inc 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:
- *   Stefan Xenos (Google) - Initial implementation
- *******************************************************************************/
-package org.eclipse.jdt.internal.core.nd.java;
-
-import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
-import org.eclipse.jdt.internal.core.nd.field.StructDef;
-
-public class NdAnnotationInVariable extends NdAnnotation {
-	public static final FieldManyToOne<NdVariable> OWNER;
-
-	@SuppressWarnings("hiding")
-	public static final StructDef<NdAnnotationInVariable> type;
-
-	static {
-		type = StructDef.create(NdAnnotationInVariable.class, NdAnnotation.type);
-		OWNER = FieldManyToOne.createOwner(type, NdVariable.ANNOTATIONS);
-		type.done();
-	}
-
-	public NdAnnotationInVariable(Nd nd, long address) {
-		super(nd, address);
-	}
-
-	public NdAnnotationInVariable(Nd nd, NdVariable owner) {
-		super(nd);
-
-		OWNER.put(getNd(), this.address, owner);
-	}
-
-}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationValuePair.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationValuePair.java
index 303a0bc..eb5d3c2 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationValuePair.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationValuePair.java
@@ -11,15 +11,13 @@
 package org.eclipse.jdt.internal.core.nd.java;
 
 import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.NdStruct;
 import org.eclipse.jdt.internal.core.nd.db.IString;
-import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
 import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
 import org.eclipse.jdt.internal.core.nd.field.FieldString;
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
 
-public class NdAnnotationValuePair extends NdNode {
-	public static final FieldManyToOne<NdAnnotation> APPLIES_TO;
+public class NdAnnotationValuePair extends NdStruct {
 	public static final FieldString NAME;
 	public static final FieldOneToOne<NdConstant> VALUE;
 
@@ -27,8 +25,7 @@
 	public static final StructDef<NdAnnotationValuePair> type;
 
 	static {
-		type = StructDef.create(NdAnnotationValuePair.class, NdNode.type);
-		APPLIES_TO = FieldManyToOne.createOwner(type, NdAnnotation.ELEMENT_VALUE_PAIRS);
+		type = StructDef.create(NdAnnotationValuePair.class, NdStruct.type);
 		NAME = type.addString();
 		VALUE = FieldOneToOne.create(type, NdConstant.type, NdConstant.PARENT_ANNOTATION_VALUE);
 		type.done();
@@ -38,22 +35,11 @@
 		super(nd, address);
 	}
 
-	public NdAnnotationValuePair(NdAnnotation annotation, char[] name) {
-		super(annotation.getNd());
-		Nd nd = annotation.getNd();
-		APPLIES_TO.put(nd, this.address, annotation);
-		NAME.put(nd, this.address, name);
-	}
-
-	public NdAnnotation getAnnotation() {
-		return APPLIES_TO.get(getNd(), this.address);
-	}
-
 	public IString getName() {
 		return NAME.get(getNd(), this.address);
 	}
 
-	public void setName(String name) {
+	public void setName(char[] name) {
 		NAME.put(getNd(), this.address, name);
 	}
 
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdBinding.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdBinding.java
index 3c48fc9..6fb38a9 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdBinding.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdBinding.java
@@ -17,7 +17,7 @@
 import org.eclipse.jdt.internal.core.nd.Nd;
 import org.eclipse.jdt.internal.core.nd.NdNode;
 import org.eclipse.jdt.internal.core.nd.field.FieldInt;
-import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldList;
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
 import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
 
@@ -26,8 +26,7 @@
  */
 public abstract class NdBinding extends NdNode implements IAdaptable {
 	public static final FieldInt MODIFIERS;
-	public static final FieldOneToMany<NdTypeParameter> TYPE_PARAMETERS;
-	public static final FieldOneToMany<NdVariable> VARIABLES;
+	public static final FieldList<NdTypeParameter> TYPE_PARAMETERS;
 
 	@SuppressWarnings("hiding")
 	public static final StructDef<NdBinding> type;
@@ -35,8 +34,7 @@
 	static {
 		type = StructDef.create(NdBinding.class, NdNode.type);
 		MODIFIERS = type.addInt();
-		TYPE_PARAMETERS = FieldOneToMany.create(type, NdTypeParameter.PARENT);
-		VARIABLES = FieldOneToMany.create(type, NdVariable.PARENT);
+		TYPE_PARAMETERS = FieldList.create(type, NdTypeParameter.type);
 		type.done();
 	}
 
@@ -48,10 +46,6 @@
 		super(nd);
 	}
 
-	public List<NdVariable> getVariables() {
-		return VARIABLES.asList(getNd(), this.address);
-	}
-
 	/**
 	 * Tests whether this binding has one of the flags defined in {@link Flags}
 	 */
@@ -106,4 +100,12 @@
 	public List<NdTypeParameter> getTypeParameters() {
 		return TYPE_PARAMETERS.asList(getNd(), this.address);
 	}
+
+	public NdTypeParameter createTypeParameter() {
+		return TYPE_PARAMETERS.append(getNd(), getAddress());
+	}
+
+	public void allocateTypeParameters(int elements) {
+		TYPE_PARAMETERS.allocate(getNd(), getAddress(), elements);
+	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantAnnotation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantAnnotation.java
index a231458..35a0da4 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantAnnotation.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantAnnotation.java
@@ -12,18 +12,18 @@
 
 import org.eclipse.jdt.internal.compiler.impl.Constant;
 import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
+import org.eclipse.jdt.internal.core.nd.field.Field;
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
 
 public final class NdConstantAnnotation extends NdConstant {
-	public static final FieldOneToOne<NdAnnotationInConstant> VALUE;
+	public static final Field<NdAnnotation> VALUE;
 
 	@SuppressWarnings("hiding")
 	public static StructDef<NdConstantAnnotation> type;
 
 	static {
 		type = StructDef.create(NdConstantAnnotation.class, NdConstant.type);
-		VALUE = FieldOneToOne.create(type, NdAnnotationInConstant.type, NdAnnotationInConstant.OWNER);
+		VALUE = Field.create(type, NdAnnotation.type);
 		type.done();
 	}
 
@@ -31,20 +31,10 @@
 		super(nd, address);
 	}
 
-	protected NdConstantAnnotation(Nd nd) {
+	public NdConstantAnnotation(Nd nd) {
 		super(nd);
 	}
 
-	public static NdConstantAnnotation create(Nd nd, NdAnnotationInConstant value) {
-		NdConstantAnnotation result = new NdConstantAnnotation(nd);
-		result.setValue(value);
-		return result;
-	}
-
-	public void setValue(NdAnnotationInConstant value) {
-		VALUE.put(getNd(), this.address, value);
-	}
-
 	public NdAnnotation getValue() {
 		return VALUE.get(getNd(), this.address);
 	}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethod.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethod.java
index d7d49b8..2ff6c3e 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethod.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethod.java
@@ -14,6 +14,7 @@
 import java.util.List;
 
 import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldList;
 import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
 import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
 import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
@@ -27,9 +28,9 @@
 	public static final FieldShort METHOD_FLAGS;
 	public static final FieldManyToOne<NdType> PARENT;
 	public static final FieldOneToMany<NdVariable> DECLARED_VARIABLES;
-	public static final FieldOneToMany<NdMethodParameter> PARAMETERS;
+	public static final FieldList<NdMethodParameter> PARAMETERS;
 	public static final FieldOneToOne<NdConstant> DEFAULT_VALUE;
-	public static final FieldOneToMany<NdMethodException> EXCEPTIONS;
+	public static final FieldList<NdMethodException> EXCEPTIONS;
 	public static final FieldManyToOne<NdTypeSignature> RETURN_TYPE;
 	public static final FieldOneToOne<NdMethodAnnotationData> ANNOTATION_DATA;
 
@@ -41,10 +42,10 @@
 		METHOD_ID = FieldManyToOne.create(type, NdMethodId.METHODS);
 		METHOD_FLAGS = type.addShort();
 		PARENT = FieldManyToOne.createOwner(type, NdType.METHODS);
-		PARAMETERS = FieldOneToMany.create(type, NdMethodParameter.PARENT);
+		PARAMETERS = FieldList.create(type, NdMethodParameter.type);
 		DECLARED_VARIABLES = FieldOneToMany.create(type, NdVariable.DECLARING_METHOD);
 		DEFAULT_VALUE = FieldOneToOne.create(type, NdConstant.type, NdConstant.PARENT_METHOD);
-		EXCEPTIONS = FieldOneToMany.create(type, NdMethodException.PARENT);
+		EXCEPTIONS = FieldList.create(type, NdMethodException.type);
 		RETURN_TYPE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_RETURN_TYPE);
 		ANNOTATION_DATA = FieldOneToOne.create(type, NdMethodAnnotationData.type, NdMethodAnnotationData.METHOD);
 		type.done();
@@ -63,6 +64,14 @@
 		PARENT.put(getNd(), this.address, parent);
 	}
 
+	public NdMethodParameter createNewParameter() {
+		return PARAMETERS.append(getNd(), getAddress());
+	}
+
+	public void allocateParameters(int numParameters) {
+		PARAMETERS.allocate(this.nd, this.address, numParameters);
+	}
+
 	public NdMethodId getMethodId() {
 		return METHOD_ID.get(getNd(), this.address);
 	}
@@ -90,7 +99,7 @@
 		return PARAMETERS.asList(getNd(), this.address);
 	}
 
-	public List<NdAnnotationInMethod> getAnnotations() {
+	public List<NdAnnotation> getAnnotations() {
 		NdMethodAnnotationData annotationData = getAnnotationData();
 		if (annotationData != null) {
 			return annotationData.getAnnotations();
@@ -114,7 +123,7 @@
 		METHOD_ID.put(getNd(), this.address, methodId);
 	}
 
-	public List<NdTypeAnnotationInMethod> getTypeAnnotations() {
+	public List<NdTypeAnnotation> getTypeAnnotations() {
 		NdMethodAnnotationData annotationData = getAnnotationData();
 		if (annotationData != null) {
 			return annotationData.getTypeAnnotations();
@@ -220,4 +229,34 @@
 	private NdMethodAnnotationData getAnnotationData() {
 		return ANNOTATION_DATA.get(getNd(), getAddress());
 	}
+
+	public NdMethodException createException(NdTypeSignature createTypeSignature) {
+		NdMethodException result = EXCEPTIONS.append(getNd(), getAddress());
+		result.setExceptionType(createTypeSignature);
+		return result;
+	}
+
+	public void allocateExceptions(int length) {
+		EXCEPTIONS.allocate(this.nd, this.address, length);
+	}
+
+	public NdAnnotation createAnnotation() {
+		return createAnnotationData().createAnnotation();
+	}
+
+	public NdTypeAnnotation createTypeAnnotation() {
+		return createAnnotationData().createTypeAnnotation();
+	}
+
+	public void allocateAnnotations(int length) {
+		if (length > 0) {
+			createAnnotationData().allocateAnnotations(length);
+		}
+	}
+
+	public void allocateTypeAnnotations(int length) {
+		if (length > 0) {
+			createAnnotationData().allocateTypeAnnotations(length);
+		}
+	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodAnnotationData.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodAnnotationData.java
index d7d62a3..a06518a 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodAnnotationData.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodAnnotationData.java
@@ -14,8 +14,8 @@
 
 import org.eclipse.jdt.internal.core.nd.Nd;
 import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldList;
 import org.eclipse.jdt.internal.core.nd.field.FieldLong;
-import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
 import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
 
@@ -26,8 +26,8 @@
 public class NdMethodAnnotationData extends NdNode {
 	public static final FieldOneToOne<NdMethod> METHOD;
 	public static final FieldLong TAG_BITS;
-	public static final FieldOneToMany<NdAnnotationInMethod> ANNOTATIONS;
-	public static final FieldOneToMany<NdTypeAnnotationInMethod> TYPE_ANNOTATIONS;
+	public static final FieldList<NdAnnotation> ANNOTATIONS;
+	public static final FieldList<NdTypeAnnotation> TYPE_ANNOTATIONS;
 
 	@SuppressWarnings("hiding")
 	public static final StructDef<NdMethodAnnotationData> type;
@@ -36,8 +36,8 @@
 		type = StructDef.create(NdMethodAnnotationData.class, NdNode.type);
 		METHOD = FieldOneToOne.createOwner(type, NdMethod.type, NdMethod.ANNOTATION_DATA);
 		TAG_BITS = type.addLong();
-		ANNOTATIONS = FieldOneToMany.create(type, NdAnnotationInMethod.OWNER);
-		TYPE_ANNOTATIONS = FieldOneToMany.create(type, NdTypeAnnotationInMethod.OWNER);
+		ANNOTATIONS = FieldList.create(type, NdAnnotation.type);
+		TYPE_ANNOTATIONS = FieldList.create(type, NdTypeAnnotation.type);
 		type.done();
 	}
 
@@ -59,11 +59,27 @@
 		return TAG_BITS.get(getNd(), this.address);
 	}
 
-	public List<NdTypeAnnotationInMethod> getTypeAnnotations() {
+	public List<NdTypeAnnotation> getTypeAnnotations() {
 		return TYPE_ANNOTATIONS.asList(getNd(), this.address);
 	}
 
-	public List<NdAnnotationInMethod> getAnnotations() {
+	public List<NdAnnotation> getAnnotations() {
 		return ANNOTATIONS.asList(getNd(), this.address);
 	}
+
+	public NdAnnotation createAnnotation() {
+		return ANNOTATIONS.append(getNd(), getAddress());
+	}
+
+	public void allocateAnnotations(int length) {
+		ANNOTATIONS.allocate(getNd(), getAddress(), length);
+	}
+
+	public NdTypeAnnotation createTypeAnnotation() {
+		return TYPE_ANNOTATIONS.append(getNd(), getAddress());
+	}
+
+	public void allocateTypeAnnotations(int length) {
+		TYPE_ANNOTATIONS.allocate(getNd(), getAddress(), length);
+	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodException.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodException.java
index 4c7cfe5..a90d63d 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodException.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodException.java
@@ -11,21 +11,19 @@
 package org.eclipse.jdt.internal.core.nd.java;
 
 import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.NdStruct;
 import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
 
-public class NdMethodException extends NdNode {
+public class NdMethodException extends NdStruct {
 
-	public static final FieldManyToOne<NdMethod> PARENT;
 	public static final FieldManyToOne<NdTypeSignature> EXCEPTION_TYPE;
 
 	@SuppressWarnings("hiding")
 	public static StructDef<NdMethodException> type;
 
 	static {
-		type = StructDef.create(NdMethodException.class, NdNode.type);
-		PARENT = FieldManyToOne.createOwner(type, NdMethod.EXCEPTIONS);
+		type = StructDef.create(NdMethodException.class);
 		EXCEPTION_TYPE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_EXCEPTION);
 		type.done();
 	}
@@ -34,21 +32,14 @@
 		super(nd, address);
 	}
 
-	public NdMethodException(NdMethod method, NdTypeSignature createTypeSignature) {
-		super(method.getNd());
-
-		PARENT.put(getNd(), this.address, method);
-		EXCEPTION_TYPE.put(getNd(), this.address, createTypeSignature);
+	public void setExceptionType(NdTypeSignature signature) {
+		EXCEPTION_TYPE.put(getNd(), this.address, signature);
 	}
 
 	public NdTypeSignature getExceptionType() {
 		return EXCEPTION_TYPE.get(getNd(), this.address);
 	}
 
-	public NdMethod getParent() {
-		return PARENT.get(getNd(), this.address);
-	}
-
 	public String toString() {
 		try {
 			return getExceptionType().toString();
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodParameter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodParameter.java
index bdcd8324..47d5df2 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodParameter.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodParameter.java
@@ -13,20 +13,19 @@
 import java.util.List;
 
 import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.NdStruct;
 import org.eclipse.jdt.internal.core.nd.db.IString;
 import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.FieldList;
 import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
-import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
 import org.eclipse.jdt.internal.core.nd.field.FieldString;
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
 import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
 
-public class NdMethodParameter extends NdNode {
-	public static final FieldManyToOne<NdMethod> PARENT;
+public class NdMethodParameter extends NdStruct {
 	public static final FieldManyToOne<NdTypeSignature> ARGUMENT_TYPE;
 	public static final FieldString NAME;
-	public static final FieldOneToMany<NdAnnotationInMethodParameter> ANNOTATIONS;
+	public static final FieldList<NdAnnotation> ANNOTATIONS;
 	public static final FieldByte FLAGS;
 
 	private static final byte FLG_COMPILER_DEFINED = 0x01;
@@ -35,11 +34,10 @@
 	public static StructDef<NdMethodParameter> type;
 
 	static {
-		type = StructDef.create(NdMethodParameter.class, NdNode.type);
-		PARENT = FieldManyToOne.create(type, NdMethod.PARAMETERS);
+		type = StructDef.create(NdMethodParameter.class);
 		ARGUMENT_TYPE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_METHOD_ARGUMENT);
 		NAME = type.addString();
-		ANNOTATIONS = FieldOneToMany.create(type, NdAnnotationInMethodParameter.OWNER);
+		ANNOTATIONS = FieldList.create(type, NdAnnotation.type);
 		FLAGS = type.addByte();
 		type.done();
 	}
@@ -48,10 +46,7 @@
 		super(nd, address);
 	}
 
-	public NdMethodParameter(NdMethod parent, NdTypeSignature argumentType) {
-		super(parent.getNd());
-
-		PARENT.put(getNd(), this.address, parent);
+	public void setType(NdTypeSignature argumentType) {
 		ARGUMENT_TYPE.put(getNd(), this.address, argumentType);
 	}
 
@@ -67,7 +62,7 @@
 		return NAME.get(getNd(), this.address);
 	}
 
-	public List<NdAnnotationInMethodParameter> getAnnotations() {
+	public List<NdAnnotation> getAnnotations() {
 		return ANNOTATIONS.asList(getNd(), this.address);
 	}
 
@@ -102,4 +97,12 @@
 			return super.toString();
 		}
 	}
+
+	public NdAnnotation createAnnotation() {
+		return ANNOTATIONS.append(getNd(), getAddress());
+	}
+
+	public void allocateAnnotations(int length) {
+		ANNOTATIONS.allocate(getNd(), getAddress(), length);
+	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdResourceFile.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdResourceFile.java
index 8a81f10..9c8e979 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdResourceFile.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdResourceFile.java
@@ -22,6 +22,7 @@
 import org.eclipse.jdt.internal.core.nd.db.Database;
 import org.eclipse.jdt.internal.core.nd.db.IString;
 import org.eclipse.jdt.internal.core.nd.db.IndexException;
+import org.eclipse.jdt.internal.core.nd.field.FieldList;
 import org.eclipse.jdt.internal.core.nd.field.FieldLong;
 import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
 import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany.Visitor;
@@ -45,7 +46,7 @@
 	public static final FieldOneToMany<NdWorkspaceLocation> WORKSPACE_MAPPINGS;
 	public static final FieldString JAVA_ROOT;
 	public static final FieldLong JDK_LEVEL;
-	public static final FieldOneToMany<NdZipEntry> ZIP_ENTRIES;
+	public static final FieldList<NdZipEntry> ZIP_ENTRIES;
 	public static final FieldString MANIFEST_CONTENT;
 	public static final FieldShort FILE_FLAGS;
 
@@ -68,7 +69,7 @@
 		WORKSPACE_MAPPINGS = FieldOneToMany.create(type, NdWorkspaceLocation.RESOURCE);
 		JAVA_ROOT = type.addString();
 		JDK_LEVEL = type.addLong();
-		ZIP_ENTRIES = FieldOneToMany.create(type, NdZipEntry.JAR_FILE);
+		ZIP_ENTRIES = FieldList.create(type, NdZipEntry.type, 1);
 		MANIFEST_CONTENT = type.addString();
 		FILE_FLAGS = type.addShort();
 
@@ -148,16 +149,16 @@
 	 */
 	public boolean isInIndex() {
 		try {
-			Nd nd = getNd();
 			// In the common case where the resource file was deleted and the memory hasn't yet been reused,
 			// this will fail.
-			if (!nd.isValidAddress(this.address) || NODE_TYPE.get(nd, this.address) != nd.getNodeType(getClass())) {
+			if (!this.nd.isValidAddress(this.address)
+					|| NODE_TYPE.get(this.nd, this.address) != this.nd.getNodeType(getClass())) {
 				return false;
 			}
 
 			char[] filename = FILENAME.get(getNd(), this.address).getChars();
 
-			NdResourceFile result = JavaIndex.FILES.findBest(nd, Database.DATA_AREA_OFFSET,
+			NdResourceFile result = JavaIndex.FILES.findBest(this.nd, Database.DATA_AREA_OFFSET,
 					SearchCriteria.create(filename), new IResultRank() {
 						@Override
 						public long getRank(Nd testNd, long testAddress) {
@@ -327,4 +328,14 @@
 			return super.toString();
 		}
 	}
+
+	public void allocateZipEntries(int expectedNumberOfZipEntries) {
+		ZIP_ENTRIES.allocate(this.nd, this.address, expectedNumberOfZipEntries);
+	}
+
+	public NdZipEntry addZipEntry(String fileName) {
+		NdZipEntry result = ZIP_ENTRIES.append(getNd(), getAddress());
+		result.setFilename(fileName);
+		return result;
+	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdType.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdType.java
index d83f37a..2594136 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdType.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdType.java
@@ -12,11 +12,10 @@
 
 import java.util.List;
 
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.jdt.internal.core.nd.INdVisitor;
 import org.eclipse.jdt.internal.core.nd.Nd;
 import org.eclipse.jdt.internal.core.nd.db.IString;
 import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.FieldList;
 import org.eclipse.jdt.internal.core.nd.field.FieldLong;
 import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
 import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
@@ -32,8 +31,9 @@
 	public static final FieldManyToOne<NdTypeId> DECLARING_TYPE;
 	public static final FieldManyToOne<NdMethodId> DECLARING_METHOD;
 	public static final FieldOneToMany<NdMethod> METHODS;
-	public static final FieldOneToMany<NdTypeAnnotationInType> TYPE_ANNOTATIONS;
-	public static final FieldOneToMany<NdAnnotationInType> ANNOTATIONS;
+	public static final FieldList<NdTypeAnnotation> TYPE_ANNOTATIONS;
+	public static final FieldList<NdAnnotation> ANNOTATIONS;
+	public static final FieldList<NdVariable> VARIABLES;
 	public static final FieldString MISSING_TYPE_NAMES;
 	public static final FieldString SOURCE_FILE_NAME;
 	public static final FieldString INNER_CLASS_SOURCE_NAME;
@@ -58,8 +58,9 @@
 		SUPERCLASS = FieldManyToOne.create(type, NdTypeSignature.SUBCLASSES);
 		DECLARING_METHOD = FieldManyToOne.create(type, NdMethodId.DECLARED_TYPES);
 		METHODS = FieldOneToMany.create(type, NdMethod.PARENT, 6);
-		TYPE_ANNOTATIONS = FieldOneToMany.create(type, NdTypeAnnotationInType.OWNER);
-		ANNOTATIONS = FieldOneToMany.create(type, NdAnnotationInType.OWNER);
+		TYPE_ANNOTATIONS = FieldList.create(type, NdTypeAnnotation.type);
+		ANNOTATIONS = FieldList.create(type, NdAnnotation.type);
+		VARIABLES = FieldList.create(type, NdVariable.type);
 		MISSING_TYPE_NAMES = type.addString();
 		SOURCE_FILE_NAME = type.addString();
 		INNER_CLASS_SOURCE_NAME = type.addString();
@@ -84,13 +85,6 @@
 		FILE.put(nd, this.address, resource);
 	}
 
-	/**
-	 * Called to populate the cache for the bindings in the class scope.
-	 */
-	public void acceptUncached(INdVisitor visitor) throws CoreException {
-		super.accept(visitor);
-	}
-
 	public NdTypeId getTypeId() {
 		return TYPENAME.get(getNd(), this.address);
 	}
@@ -221,6 +215,10 @@
 		return JavaNames.simpleNameToSourceName(simpleName);
 	}
 
+	public List<NdVariable> getVariables() {
+		return VARIABLES.asList(getNd(), this.address);
+	}
+
 	public NdMethodId getDeclaringMethod() {
 		return DECLARING_METHOD.get(getNd(), this.address);
 	}
@@ -230,14 +228,22 @@
 		return TYPE_PARAMETERS.asList(getNd(), this.address);
 	}
 
-	public List<NdTypeAnnotationInType> getTypeAnnotations() {
+	public List<NdTypeAnnotation> getTypeAnnotations() {
 		return TYPE_ANNOTATIONS.asList(getNd(), this.address);
 	}
 
-	public List<NdAnnotationInType> getAnnotations() {
+	public List<NdAnnotation> getAnnotations() {
 		return ANNOTATIONS.asList(getNd(), this.address);
 	}
 
+	public NdAnnotation createAnnotation() {
+		return ANNOTATIONS.append(getNd(), getAddress());
+	}
+
+	public void allocateAnnotations(int length) {
+		ANNOTATIONS.allocate(getNd(), getAddress(), length);
+	}
+
 	public List<NdMethod> getMethods() {
 		return METHODS.asList(getNd(), this.address);
 	}
@@ -275,4 +281,20 @@
 		}
 		return descriptorFromClass;
 	}
+
+	public NdTypeAnnotation createTypeAnnotation() {
+		return TYPE_ANNOTATIONS.append(getNd(), getAddress());
+	}
+
+	public void allocateTypeAnnotations(int length) {
+		TYPE_ANNOTATIONS.allocate(getNd(), getAddress(), length);
+	}
+
+	public NdVariable createVariable() {
+		return VARIABLES.append(getNd(), getAddress());
+	}
+
+	public void allocateVariables(int length) {
+		VARIABLES.allocate(getNd(), getAddress(), length);
+	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotation.java
index e9bfa4a..b6c0908 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotation.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotation.java
@@ -11,13 +11,14 @@
 package org.eclipse.jdt.internal.core.nd.java;
 
 import org.eclipse.jdt.internal.compiler.codegen.AnnotationTargetTypeConstants;
+import org.eclipse.jdt.internal.core.nd.IDestructable;
 import org.eclipse.jdt.internal.core.nd.Nd;
 import org.eclipse.jdt.internal.core.nd.db.Database;
 import org.eclipse.jdt.internal.core.nd.field.FieldByte;
 import org.eclipse.jdt.internal.core.nd.field.FieldPointer;
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
 
-public class NdTypeAnnotation extends NdAnnotation {
+public class NdTypeAnnotation extends NdAnnotation implements IDestructable {
 	public static final FieldByte TARGET_TYPE;
 	public static final FieldByte TARGET_ARG0;
 	public static final FieldByte TARGET_ARG1;
@@ -43,18 +44,13 @@
 		super(nd, address);
 	}
 
-	public NdTypeAnnotation(Nd nd) {
-		super(nd);
-	}
-
 	public void setPath(byte[] path) {
 		freePath();
-		Nd nd = getNd();
-		PATH_LENGTH.put(nd, this.address, (byte)path.length);
+		PATH_LENGTH.put(this.nd, this.address, (byte) path.length);
 		if (path.length > 0) {
-			long pathArray = nd.getDB().malloc(path.length, Database.POOL_MISC);
-			PATH.put(nd, this.address, pathArray);
-			nd.getDB().putBytes(pathArray, path, path.length);
+			long pathArray = this.nd.getDB().malloc(path.length, Database.POOL_MISC);
+			PATH.put(this.nd, this.address, pathArray);
+			this.nd.getDB().putBytes(pathArray, path, path.length);
 		}
 	}
 
@@ -111,12 +107,10 @@
 	@Override
 	public void destruct() {
 		freePath();
-		super.destruct();
 	}
 
 	private void freePath() {
-		Nd nd = getNd();
-		long pathPointer = PATH.get(nd, this.address);
-		nd.getDB().free(pathPointer, Database.POOL_MISC);
+		long pathPointer = PATH.get(this.nd, this.address);
+		this.nd.getDB().free(pathPointer, Database.POOL_MISC);
 	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInMethod.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInMethod.java
deleted file mode 100644
index 06dcce3..0000000
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInMethod.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Google, Inc 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:
- *   Stefan Xenos (Google) - Initial implementation
- *******************************************************************************/
-package org.eclipse.jdt.internal.core.nd.java;
-
-import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
-import org.eclipse.jdt.internal.core.nd.field.StructDef;
-
-public class NdTypeAnnotationInMethod extends NdTypeAnnotation {
-	public static final FieldManyToOne<NdMethodAnnotationData> OWNER;
-
-	@SuppressWarnings("hiding")
-	public static final StructDef<NdTypeAnnotationInMethod> type;
-
-	static {
-		type = StructDef.create(NdTypeAnnotationInMethod.class, NdTypeAnnotation.type);
-		OWNER = FieldManyToOne.createOwner(type, NdMethodAnnotationData.TYPE_ANNOTATIONS);
-		type.done();
-	}
-
-	public NdTypeAnnotationInMethod(Nd nd, long address) {
-		super(nd, address);
-	}
-
-	public NdTypeAnnotationInMethod(Nd nd, NdMethod method) {
-		super(nd);
-
-		OWNER.put(getNd(), this.address, method.createAnnotationData());
-	}
-
-}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInType.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInType.java
deleted file mode 100644
index 7aff109..0000000
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInType.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Google, Inc 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:
- *   Stefan Xenos (Google) - Initial implementation
- *******************************************************************************/
-package org.eclipse.jdt.internal.core.nd.java;
-
-import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
-import org.eclipse.jdt.internal.core.nd.field.StructDef;
-
-public class NdTypeAnnotationInType extends NdTypeAnnotation {
-	public static final FieldManyToOne<NdType> OWNER;
-
-	@SuppressWarnings("hiding")
-	public static final StructDef<NdTypeAnnotationInType> type;
-
-	static {
-		type = StructDef.create(NdTypeAnnotationInType.class, NdTypeAnnotation.type);
-		OWNER = FieldManyToOne.createOwner(type, NdType.TYPE_ANNOTATIONS);
-		type.done();
-	}
-
-	public NdTypeAnnotationInType(Nd nd, long address) {
-		super(nd, address);
-	}
-
-	public NdTypeAnnotationInType(Nd nd, NdType type) {
-		super(nd);
-
-		OWNER.put(getNd(), this.address, type);
-	}
-
-}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInVariable.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInVariable.java
deleted file mode 100644
index eb591fe..0000000
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInVariable.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Google, Inc 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:
- *   Stefan Xenos (Google) - Initial implementation
- *******************************************************************************/
-package org.eclipse.jdt.internal.core.nd.java;
-
-import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
-import org.eclipse.jdt.internal.core.nd.field.StructDef;
-
-public class NdTypeAnnotationInVariable extends NdTypeAnnotation {
-	public static final FieldManyToOne<NdVariable> OWNER;
-
-	@SuppressWarnings("hiding")
-	public static final StructDef<NdTypeAnnotationInVariable> type;
-
-	static {
-		type = StructDef.create(NdTypeAnnotationInVariable.class, NdTypeAnnotation.type);
-		OWNER = FieldManyToOne.createOwner(type, NdVariable.TYPE_ANNOTATIONS);
-		type.done();
-	}
-
-	public NdTypeAnnotationInVariable(Nd nd, long address) {
-		super(nd, address);
-	}
-
-	public NdTypeAnnotationInVariable(Nd nd, NdVariable variable) {
-		super(nd);
-
-		OWNER.put(getNd(), this.address, variable);
-	}
-
-}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeBound.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeBound.java
index c6c827e..1a5e0ef 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeBound.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeBound.java
@@ -11,7 +11,7 @@
 package org.eclipse.jdt.internal.core.nd.java;
 
 import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.NdStruct;
 import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
 import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
@@ -20,16 +20,14 @@
  * Represents the bound on a generic parameter (a ClassBound or InterfaceBound in
  * the sense of the Java VM spec Java SE 8 Edition, section 4.7.9.1).
  */
-public class NdTypeBound extends NdNode {
-	public static final FieldManyToOne<NdTypeParameter> PARENT;
+public class NdTypeBound extends NdStruct {
 	public static final FieldManyToOne<NdTypeSignature> TYPE;
 
 	@SuppressWarnings("hiding")
 	public static final StructDef<NdTypeBound> type;
 
 	static {
-		type = StructDef.create(NdTypeBound.class, NdNode.type);
-		PARENT = FieldManyToOne.createOwner(type, NdTypeParameter.BOUNDS);
+		type = StructDef.create(NdTypeBound.class, NdStruct.type);
 		TYPE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_TYPE_BOUND);
 
 		type.done();
@@ -39,17 +37,10 @@
 		super(nd, address);
 	}
 
-	public NdTypeBound(NdTypeParameter parent, NdTypeSignature signature) {
-		super(parent.getNd());
-
-		PARENT.put(getNd(), this.address, parent);
+	public void setType(NdTypeSignature signature) {
 		TYPE.put(getNd(), this.address, signature);
 	}
 
-	public NdTypeParameter getParent() {
-		return PARENT.get(getNd(), this.address);
-	}
-
 	public NdTypeSignature getType() {
 		return TYPE.get(getNd(), this.address);
 	}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeParameter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeParameter.java
index fda4676..8809b84 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeParameter.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeParameter.java
@@ -13,10 +13,9 @@
 import java.util.List;
 
 import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.NdStruct;
 import org.eclipse.jdt.internal.core.nd.field.FieldByte;
-import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
-import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldList;
 import org.eclipse.jdt.internal.core.nd.field.FieldString;
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
 import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
@@ -24,10 +23,9 @@
 /**
  * Represents a TypeParameter, as described in Section 4.7.9.1 of the java VM specification, Java SE 8 edititon.
  */
-public class NdTypeParameter extends NdNode {
-	public static final FieldManyToOne<NdBinding> PARENT;
+public class NdTypeParameter extends NdStruct {
 	public static final FieldString IDENTIFIER;
-	public static final FieldOneToMany<NdTypeBound> BOUNDS;
+	public static final FieldList<NdTypeBound> BOUNDS;
 	public static final FieldByte TYPE_PARAMETER_FLAGS;
 
 	public static final byte FLG_FIRST_BOUND_IS_A_CLASS = 0x01;
@@ -36,10 +34,9 @@
 	public static final StructDef<NdTypeParameter> type;
 
 	static {
-		type = StructDef.create(NdTypeParameter.class, NdNode.type);
-		PARENT = FieldManyToOne.createOwner(type, NdBinding.TYPE_PARAMETERS);
+		type = StructDef.create(NdTypeParameter.class, NdStruct.type);
 		IDENTIFIER = type.addString();
-		BOUNDS = FieldOneToMany.create(type, NdTypeBound.PARENT);
+		BOUNDS = FieldList.create(type, NdTypeBound.type);
 		TYPE_PARAMETER_FLAGS = type.addByte();
 
 		type.done();
@@ -49,10 +46,7 @@
 		super(nd, address);
 	}
 
-	public NdTypeParameter(NdBinding parent, char[] identifier) {
-		super(parent.getNd());
-
-		PARENT.put(getNd(), this.address, parent);
+	public void setIdentifier(char[] identifier) {
 		IDENTIFIER.put(getNd(), this.address, identifier);
 	}
 
@@ -108,4 +102,12 @@
 			buffer.append('>');
 		}
 	}
+
+	public void createBound(NdTypeSignature boundSignature) {
+		BOUNDS.append(getNd(), getAddress()).setType(boundSignature);
+	}
+
+	public void allocateBounds(int numBounds) {
+		BOUNDS.allocate(getNd(), getAddress(), numBounds);
+	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdVariable.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdVariable.java
index 0ab3bee..1006637 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdVariable.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdVariable.java
@@ -16,9 +16,9 @@
 import org.eclipse.jdt.internal.core.nd.db.IString;
 import org.eclipse.jdt.internal.core.nd.field.FieldByte;
 import org.eclipse.jdt.internal.core.nd.field.FieldInt;
+import org.eclipse.jdt.internal.core.nd.field.FieldList;
 import org.eclipse.jdt.internal.core.nd.field.FieldLong;
 import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
-import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
 import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
 import org.eclipse.jdt.internal.core.nd.field.FieldString;
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
@@ -27,13 +27,12 @@
 	public static final FieldManyToOne<NdTypeSignature> TYPE;
 	public static final FieldInt VARIABLE_ID;
 	public static final FieldManyToOne<NdMethod> DECLARING_METHOD;
-	public static final FieldManyToOne<NdBinding> PARENT;
 	public static final FieldString NAME;
 	public static final FieldOneToOne<NdConstant> CONSTANT;
 	public static final FieldLong TAG_BITS;
 	public static final FieldByte VARIABLE_FLAGS;
-	public static final FieldOneToMany<NdAnnotationInVariable> ANNOTATIONS;
-	public static final FieldOneToMany<NdTypeAnnotationInVariable> TYPE_ANNOTATIONS;
+	public static final FieldList<NdAnnotation> ANNOTATIONS;
+	public static final FieldList<NdTypeAnnotation> TYPE_ANNOTATIONS;
 
 	@SuppressWarnings("hiding")
 	public static StructDef<NdVariable> type;
@@ -45,13 +44,12 @@
 		TYPE = FieldManyToOne.create(type, NdTypeSignature.VARIABLES_OF_TYPE);
 		VARIABLE_ID = type.addInt();
 		DECLARING_METHOD = FieldManyToOne.create(type, NdMethod.DECLARED_VARIABLES);
-		PARENT = FieldManyToOne.createOwner(type, NdBinding.VARIABLES);
 		NAME = type.addString();
 		CONSTANT = FieldOneToOne.create(type, NdConstant.type, NdConstant.PARENT_VARIABLE);
 		TAG_BITS = type.addLong();
 		VARIABLE_FLAGS = type.addByte();
-		ANNOTATIONS = FieldOneToMany.create(type, NdAnnotationInVariable.OWNER);
-		TYPE_ANNOTATIONS = FieldOneToMany.create(type, NdTypeAnnotationInVariable.OWNER);
+		ANNOTATIONS = FieldList.create(type, NdAnnotation.type);
+		TYPE_ANNOTATIONS = FieldList.create(type, NdTypeAnnotation.type);
 		type.done();
 	}
 
@@ -59,12 +57,6 @@
 		super(nd, bindingRecord);
 	}
 
-	public NdVariable(NdBinding parent) {
-		super(parent.getNd());
-
-		PARENT.put(getNd(), this.address, parent);
-	}
-
 	public boolean hasVariableFlag(int toTest) {
 		return (VARIABLE_FLAGS.get(getNd(), this.address) & toTest) != 0;
 	}
@@ -106,14 +98,22 @@
 		TAG_BITS.put(getNd(), this.address, tagBits);
 	}
 
-	public List<NdTypeAnnotationInVariable> getTypeAnnotations() {
+	public List<NdTypeAnnotation> getTypeAnnotations() {
 		return TYPE_ANNOTATIONS.asList(getNd(), this.address);
 	}
 
-	public List<NdAnnotationInVariable> getAnnotations() {
+	public List<NdAnnotation> getAnnotations() {
 		return ANNOTATIONS.asList(getNd(), this.address);
 	}
 
+	public NdAnnotation createAnnotation() {
+		return ANNOTATIONS.append(this.getNd(), this.getAddress());
+	}
+
+	public void allocateAnnotations(int length) {
+		ANNOTATIONS.allocate(getNd(), getAddress(), length);
+	}
+
 	public String toString() {
 		try {
 			StringBuilder result = new StringBuilder();
@@ -138,4 +138,12 @@
 			return super.toString();
 		}
 	}
+
+	public NdTypeAnnotation createTypeAnnotation() {
+		return TYPE_ANNOTATIONS.append(getNd(), getAddress());
+	}
+
+	public void allocateTypeAnnotations(int length) {
+		TYPE_ANNOTATIONS.allocate(getNd(), getAddress(), length);
+	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdZipEntry.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdZipEntry.java
index e0086ab..4e4f82c 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdZipEntry.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdZipEntry.java
@@ -11,25 +11,22 @@
 package org.eclipse.jdt.internal.core.nd.java;
 
 import org.eclipse.jdt.internal.core.nd.Nd;
-import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.NdStruct;
 import org.eclipse.jdt.internal.core.nd.db.IString;
-import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
 import org.eclipse.jdt.internal.core.nd.field.FieldString;
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
 
 /**
  * Stores a (non-class) file within a .jar file.
  */
-public class NdZipEntry extends NdNode {
-	public static final FieldManyToOne<NdResourceFile> JAR_FILE;
+public class NdZipEntry extends NdStruct {
 	public static final FieldString FILE_NAME;
 
 	@SuppressWarnings("hiding")
 	public static final StructDef<NdZipEntry> type;
 
 	static {
-		type = StructDef.create(NdZipEntry.class, NdNode.type);
-		JAR_FILE = FieldManyToOne.createOwner(type, NdResourceFile.ZIP_ENTRIES);
+		type = StructDef.create(NdZipEntry.class, NdStruct.type);
 		FILE_NAME = type.addString();
 
 		type.done();
@@ -39,14 +36,11 @@
 		super(nd, address);
 	}
 
-	public NdZipEntry(NdResourceFile nd, String path) {
-		super(nd.getNd());
-
-		JAR_FILE.put(nd.getNd(), getAddress(), nd);
-		FILE_NAME.put(nd.getNd(), getAddress(), path);
+	public void setFilename(String filename) {
+		FILE_NAME.put(this.nd, this.address, filename);
 	}
 
 	public IString getFileName() {
-		return FILE_NAME.get(getNd(), getAddress());
+		return FILE_NAME.get(this.nd, this.address);
 	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/MathUtils.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/MathUtils.java
new file mode 100644
index 0000000..25c94e8
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/MathUtils.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Google, Inc 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:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.util;
+
+public class MathUtils {
+	/**
+	 * Rounds the one number up to the nearest multiple of another
+	 *
+	 * @param numberToRound
+	 *            number to round
+	 * @param toMultipleOfThis the result will be divisible by this number
+	 * @return the result will be the smallest multiple of toMultipleOfThis that is no smaller than numberToRound
+	 */
+	public static int roundUpToNearestMultiple(int numberToRound, int toMultipleOfThis) {
+		return ((numberToRound + toMultipleOfThis - 1) / toMultipleOfThis) * toMultipleOfThis;
+	}
+
+	/**
+	 * Rounds the one number up to the nearest multiple of another, where the second number is a power of two.
+	 *
+	 * @param numberToRound
+	 *            number to round
+	 * @param aPowerOfTwo
+	 *            the result will be divisible by this
+	 * @return the result will be the smallest multiple of aPowerOfTwo that is no smaller than numberToRound
+	 */
+	public static int roundUpToNearestMultipleOfPowerOfTwo(int numberToRound, int aPowerOfTwo) {
+		return ((numberToRound + aPowerOfTwo - 1) & ~(aPowerOfTwo - 1));
+	}
+}