Update jdt.core to I20161005-1430
diff --git a/org.eclipse.jdt.core/.classpath b/org.eclipse.jdt.core/.classpath
index 6add95f..cfba090 100644
--- a/org.eclipse.jdt.core/.classpath
+++ b/org.eclipse.jdt.core/.classpath
@@ -9,7 +9,7 @@
 	<classpathentry kind="src" path="formatter"/>
 	<classpathentry kind="src" path="model"/>
 	<classpathentry kind="src" path="search"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/org.eclipse.jdt.core/.options b/org.eclipse.jdt.core/.options
index 83d4c30..c50fb79 100644
--- a/org.eclipse.jdt.core/.options
+++ b/org.eclipse.jdt.core/.options
@@ -47,6 +47,33 @@
 # Reports Java model elements opening/closing
 org.eclipse.jdt.core/debug/javamodel/cache=false
 
+# Reports changes in the Java classpath and classpath resolution
+org.eclipse.jdt.core/debug/javamodel/classpath=false
+
+# Reports all insertions and removals from the java model cache
+org.eclipse.jdt.core/debug/javamodel/insertions=false
+
+# Records information about the invalid archive cache
+org.eclipse.jdt.core/debug/javamodel/invalid_archives=false
+
+# Prints information about when the indexer runs and what files are being indexed
+org.eclipse.jdt.core/debug/index/indexer=false
+
+# Prints a line whenever a class is added to or removed from the index
+org.eclipse.jdt.core/debug/index/insertions=false
+
+# Prints diagnostic information about index database locks
+org.eclipse.jdt.core/debug/index/locks=false
+
+# Prints statistics about database memory usage
+org.eclipse.jdt.core/debug/index/space=false
+
+# Performs self-testing during indexing by reading back every class and comparing it with the original .class file
+org.eclipse.jdt.core/debug/index/selftest=false
+
+# Prints statistics about indexing time
+org.eclipse.jdt.core/debug/index/timing=false
+
 # Reports post actions addition/run
 org.eclipse.jdt.core/debug/postaction=false
 
diff --git a/org.eclipse.jdt.core/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jdt.core/.settings/org.eclipse.jdt.core.prefs
index c02097a..d2ef492 100644
--- a/org.eclipse.jdt.core/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jdt.core/.settings/org.eclipse.jdt.core.prefs
@@ -25,9 +25,9 @@
 org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
 org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.compliance=1.8
 org.eclipse.jdt.core.compiler.debug.lineNumber=generate
 org.eclipse.jdt.core.compiler.debug.localVariable=generate
 org.eclipse.jdt.core.compiler.debug.sourceFile=generate
@@ -131,7 +131,7 @@
 org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
 org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
-org.eclipse.jdt.core.compiler.source=1.7
+org.eclipse.jdt.core.compiler.source=1.8
 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
diff --git a/org.eclipse.jdt.core/META-INF/MANIFEST.MF b/org.eclipse.jdt.core/META-INF/MANIFEST.MF
index 4bb8b6f..0e9d5cf 100644
--- a/org.eclipse.jdt.core/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.core/META-INF/MANIFEST.MF
@@ -45,6 +45,13 @@
  org.eclipse.jdt.internal.core.hierarchy;x-friends:="org.eclipse.objectteams.otdt",
  org.eclipse.jdt.internal.core.index;x-internal:=true,
  org.eclipse.jdt.internal.core.jdom;x-internal:=true,
+ org.eclipse.jdt.internal.core.nd;x-internal:=true,
+ org.eclipse.jdt.internal.core.nd.db;x-internal:=true,
+ org.eclipse.jdt.internal.core.nd.field;x-internal:=true,
+ org.eclipse.jdt.internal.core.nd.indexer;x-internal:=true,
+ org.eclipse.jdt.internal.core.nd.java;x-internal:=true,
+ org.eclipse.jdt.internal.core.nd.java.model;x-internal:=true,
+ org.eclipse.jdt.internal.core.nd.util;x-internal:=true,
  org.eclipse.jdt.internal.core.search;x-internal:=true,
  org.eclipse.jdt.internal.core.search.indexing;x-internal:=true,
  org.eclipse.jdt.internal.core.search.matching;x-internal:=true,
@@ -79,7 +86,8 @@
  org.eclipse.core.filesystem;bundle-version="[1.0.0,2.0.0)",
  org.eclipse.text;bundle-version="[3.1.0,4.0.0)",
  org.eclipse.team.core;bundle-version="[3.1.0,4.0.0)";resolution:=optional,
+ com.ibm.icu;bundle-version="54.1.1",
  org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)"
-Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Eclipse-ExtensibleAPI: true
 Bundle-ActivationPolicy: lazy
diff --git a/org.eclipse.jdt.core/antadapter/org/eclipse/jdt/core/JDTCompilerAdapter.java b/org.eclipse.jdt.core/antadapter/org/eclipse/jdt/core/JDTCompilerAdapter.java
index 73664ae..c2bdf71 100644
--- a/org.eclipse.jdt.core/antadapter/org/eclipse/jdt/core/JDTCompilerAdapter.java
+++ b/org.eclipse.jdt.core/antadapter/org/eclipse/jdt/core/JDTCompilerAdapter.java
@@ -305,6 +305,7 @@
 		String source = this.attributes.getSource();
 		if (source != null) {
 			this.customDefaultOptions.put(CompilerOptions.OPTION_Source, source);
+			this.customDefaultOptions.put(CompilerOptions.OPTION_Compliance, source);
 		}
 
 		if (compilerArgs != null) {
diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java
index 77ad903..6245837 100644
--- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java
+++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java
@@ -27,9 +27,12 @@
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationDecorator;
 import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationProvider;
 import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
 import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
+import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.ExternalAnnotationStatus;
 import org.eclipse.jdt.internal.compiler.util.ManifestAnalyzer;
 import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
 import org.eclipse.jdt.internal.compiler.util.Util;
@@ -102,15 +105,20 @@
 		return null; // most common case
 
 	try {
-		ClassFileReader reader = ClassFileReader.read(this.zipFile, qualifiedBinaryFileName);
+		IBinaryType reader = ClassFileReader.read(this.zipFile, qualifiedBinaryFileName);
 		if (reader != null) {
 			if (this.annotationPaths != null) {
 				String qualifiedClassName = qualifiedBinaryFileName.substring(0, qualifiedBinaryFileName.length()-SuffixConstants.EXTENSION_CLASS.length()-1);
 				for (String annotationPath : this.annotationPaths) {
 					try {
-						this.annotationZipFile = reader.setExternalAnnotationProvider(annotationPath, qualifiedClassName, this.annotationZipFile, null);
-						if (reader.hasAnnotationProvider())
+						if (this.annotationZipFile == null) {
+							this.annotationZipFile = ExternalAnnotationDecorator.getAnnotationZipFile(annotationPath, null);
+						}
+						reader = ExternalAnnotationDecorator.create(reader, annotationPath, qualifiedClassName, this.annotationZipFile);
+
+						if (reader.getExternalAnnotationStatus() == ExternalAnnotationStatus.TYPE_IS_ANNOTATED) {
 							break;
+						}
 					} catch (IOException e) {
 						// don't let error on annotations fail class reading
 					}
diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/FileSystem.java b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/FileSystem.java
index b6d9e5f..7c7dbc3 100644
--- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/FileSystem.java
+++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/FileSystem.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation 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
@@ -24,6 +24,7 @@
 
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationDecorator;
 import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
 import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
 import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
@@ -275,9 +276,14 @@
 		for (int i = 0, length = this.classpaths.length; i < length; i++) {
 			Classpath classpathEntry = this.classpaths[i];
 			if (classpathEntry.hasAnnotationFileFor(qualifiedTypeName)) {
+				// in case of 'this.annotationsFromClasspath' we indeed search for .eea entries inside the main zipFile of the entry:
 				ZipFile zip = classpathEntry instanceof ClasspathJar ? ((ClasspathJar) classpathEntry).zipFile : null;
 				try {
-					((ClassFileReader) answer.getBinaryType()).setExternalAnnotationProvider(classpathEntry.getPath(), qualifiedTypeName, zip, null);
+					if (zip == null) {
+						zip = ExternalAnnotationDecorator.getAnnotationZipFile(classpathEntry.getPath(), null);
+					}
+					answer.setBinaryType(ExternalAnnotationDecorator.create(answer.getBinaryType(), classpathEntry.getPath(), 
+							qualifiedTypeName, zip));
 					break;
 				} catch (IOException e) {
 					// ignore broken entry, keep searching
diff --git a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/CompletionEngine.java b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/CompletionEngine.java
index 59b6c3a..5967f22 100644
--- a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/CompletionEngine.java
+++ b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/CompletionEngine.java
@@ -18,6 +18,7 @@
 package org.eclipse.jdt.internal.codeassist;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Map;
@@ -43,41 +44,171 @@
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.core.compiler.IProblem;
 import org.eclipse.jdt.core.search.IJavaSearchConstants;
-import org.eclipse.jdt.internal.codeassist.complete.*;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionNodeDetector;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionNodeFound;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnAnnotationOfType;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnArgumentName;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnBranchStatementLabel;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnClassLiteralAccess;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnExplicitConstructorCall;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnFieldName;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnFieldType;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnImportReference;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnJavadoc;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnJavadocAllocationExpression;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnJavadocFieldReference;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnJavadocMessageSend;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnJavadocParamNameReference;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnJavadocQualifiedTypeReference;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnJavadocSingleTypeReference;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnJavadocTag;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnJavadocTypeParamReference;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnKeyword;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnKeyword3;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnLocalName;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnMarkerAnnotationName;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnMemberAccess;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnMemberValueName;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnMessageSend;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnMessageSendName;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnMethodName;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnMethodReturnType;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnPackageReference;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnParameterizedQualifiedTypeReference;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnQualifiedAllocationExpression;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnQualifiedNameReference;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnQualifiedTypeReference;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnReferenceExpressionName;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnSingleNameReference;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnSingleTypeReference;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionOnStringLiteral;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionParser;
+import org.eclipse.jdt.internal.codeassist.complete.CompletionScanner;
+import org.eclipse.jdt.internal.codeassist.complete.InvalidCursorLocation;
 import org.eclipse.jdt.internal.codeassist.impl.AssistParser;
 import org.eclipse.jdt.internal.codeassist.impl.Engine;
 import org.eclipse.jdt.internal.codeassist.impl.Keywords;
 import org.eclipse.jdt.internal.compiler.CompilationResult;
 import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
 import org.eclipse.jdt.internal.compiler.ExtraFlags;
-import org.eclipse.jdt.internal.compiler.ast.*;
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
+import org.eclipse.jdt.internal.compiler.ast.ArrayReference;
+import org.eclipse.jdt.internal.compiler.ast.AssertStatement;
+import org.eclipse.jdt.internal.compiler.ast.Assignment;
+import org.eclipse.jdt.internal.compiler.ast.BinaryExpression;
+import org.eclipse.jdt.internal.compiler.ast.CaseStatement;
+import org.eclipse.jdt.internal.compiler.ast.CastExpression;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression;
+import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.ExpressionContext;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FieldReference;
+import org.eclipse.jdt.internal.compiler.ast.ForStatement;
+import org.eclipse.jdt.internal.compiler.ast.IfStatement;
+import org.eclipse.jdt.internal.compiler.ast.ImportReference;
+import org.eclipse.jdt.internal.compiler.ast.Initializer;
+import org.eclipse.jdt.internal.compiler.ast.InstanceOfExpression;
+import org.eclipse.jdt.internal.compiler.ast.Javadoc;
+import org.eclipse.jdt.internal.compiler.ast.JavadocImplicitTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.JavadocQualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.JavadocSingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.LambdaExpression;
+import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
+import org.eclipse.jdt.internal.compiler.ast.MessageSend;
+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.NameReference;
+import org.eclipse.jdt.internal.compiler.ast.NormalAnnotation;
+import org.eclipse.jdt.internal.compiler.ast.OperatorExpression;
+import org.eclipse.jdt.internal.compiler.ast.OperatorIds;
+import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.ReferenceExpression;
+import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
+import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.SuperReference;
+import org.eclipse.jdt.internal.compiler.ast.SwitchStatement;
+import org.eclipse.jdt.internal.compiler.ast.ThisReference;
+import org.eclipse.jdt.internal.compiler.ast.TryStatement;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.ast.UnaryExpression;
+import org.eclipse.jdt.internal.compiler.ast.UnionTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.WhileStatement;
+import org.eclipse.jdt.internal.compiler.ast.Wildcard;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
-import org.eclipse.jdt.internal.compiler.env.*;
+import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
+import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
+import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
+import org.eclipse.jdt.internal.compiler.env.ISourceType;
+import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
 import org.eclipse.jdt.internal.compiler.impl.ReferenceContext;
-import org.eclipse.jdt.internal.compiler.lookup.*;
+import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
+import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.Binding;
+import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
+import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
+import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
+import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
+import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ImportBinding;
+import org.eclipse.jdt.internal.compiler.lookup.InferenceContext18;
+import org.eclipse.jdt.internal.compiler.lookup.InvocationSite;
+import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
+import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
+import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
+import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ParameterizedMethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemMethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
+import org.eclipse.jdt.internal.compiler.lookup.Scope;
+import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TagBits;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
+import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
+import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding;
+import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
+import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding;
+import org.eclipse.jdt.internal.compiler.parser.JavadocTagConstants;
 import org.eclipse.jdt.internal.compiler.parser.ScannerHelper;
 import org.eclipse.jdt.internal.compiler.parser.SourceTypeConverter;
-import org.eclipse.jdt.internal.compiler.parser.JavadocTagConstants;
 import org.eclipse.jdt.internal.compiler.parser.TerminalTokens;
 import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
 import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
 import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
 import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
-import org.eclipse.jdt.internal.compiler.util.SimpleSetOfCharArray;
-import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
 import org.eclipse.jdt.internal.compiler.util.HashtableOfObject;
 import org.eclipse.jdt.internal.compiler.util.ObjectVector;
+import org.eclipse.jdt.internal.compiler.util.SimpleSetOfCharArray;
+import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
 import org.eclipse.jdt.internal.core.BasicCompilationUnit;
+import org.eclipse.jdt.internal.core.BinaryTypeConverter;
 import org.eclipse.jdt.internal.core.INamingRequestor;
 import org.eclipse.jdt.internal.core.InternalNamingConventions;
 import org.eclipse.jdt.internal.core.JavaModelManager;
+import org.eclipse.jdt.internal.core.SearchableEnvironment;
 import org.eclipse.jdt.internal.core.SourceMethod;
 import org.eclipse.jdt.internal.core.SourceMethodElementInfo;
 import org.eclipse.jdt.internal.core.SourceType;
-import org.eclipse.jdt.internal.core.BinaryTypeConverter;
-import org.eclipse.jdt.internal.core.SearchableEnvironment;
 import org.eclipse.jdt.internal.core.SourceTypeElementInfo;
-import org.eclipse.jdt.internal.core.search.matching.JavaSearchNameEnvironment;
+import org.eclipse.jdt.internal.core.search.matching.IndexBasedJavaSearchEnvironment;
 import org.eclipse.jdt.internal.core.util.Messages;
 import org.eclipse.jdt.internal.core.util.Util;
 import org.eclipse.objectteams.otdt.core.IOTType;
@@ -581,7 +712,7 @@
 	CompletionRequestor requestor;
 	CompletionProblemFactory problemFactory;
 	ProblemReporter problemReporter;
-	private JavaSearchNameEnvironment noCacheNameEnvironment;
+	private INameEnvironment noCacheNameEnvironment;
 	char[] source;
 	char[] completionToken;
 	char[] qualifiedCompletionToken;
@@ -12849,7 +12980,7 @@
 	private INameEnvironment getNoCacheNameEnvironment() {
 		if (this.noCacheNameEnvironment == null) {
 			JavaModelManager.getJavaModelManager().cacheZipFiles(this);
-			this.noCacheNameEnvironment = new JavaSearchNameEnvironment(this.javaProject, this.owner == null ? null : JavaModelManager.getJavaModelManager().getWorkingCopies(this.owner, true/*add primary WCs*/));
+			this.noCacheNameEnvironment = IndexBasedJavaSearchEnvironment.create(Collections.singletonList(this.javaProject), this.owner == null ? null : JavaModelManager.getJavaModelManager().getWorkingCopies(this.owner, true/*add primary WCs*/));
 		}
 		return this.noCacheNameEnvironment;
 	}
diff --git a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/RelevanceConstants.java b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/RelevanceConstants.java
index 53f1215..97f8a5e 100644
--- a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/RelevanceConstants.java
+++ b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/RelevanceConstants.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation 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
@@ -21,7 +21,7 @@
 	 * 4. The value of R_DEFAULT is maintained at a positive value such that the sum of all the negative relevance constants
 	 *    and R_DEFAULT must not be negative. 
 	 */
-	int R_DEFAULT = 5;
+	int R_DEFAULT = 30;
 	int R_INTERESTING = 5;
 	int R_CASE = 10;
 	int R_CAMEL_CASE = 5;
@@ -44,7 +44,7 @@
 	int R_NAME_FIRST_SUFFIX = 4;
 	int R_NAME_SUFFIX = 3;
 	int R_NAME_LESS_NEW_CHARACTERS = 15;
-	int R_SUBSTRING = -1;
+	int R_SUBSTRING = -20;
 	int R_METHOD_OVERIDE = 3;
 	int R_NON_RESTRICTED = 3;
 	int R_TRUE_OR_FALSE = 1;
diff --git a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/SelectionEngine.java b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/SelectionEngine.java
index 94e796d..83777f1 100644
--- a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/SelectionEngine.java
+++ b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/SelectionEngine.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation 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
@@ -17,7 +17,6 @@
 import java.util.Locale;
 import java.util.Map;
 
-import org.eclipse.core.resources.IFile;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.OperationCanceledException;
 import org.eclipse.jdt.core.IBuffer;
@@ -26,26 +25,76 @@
 import org.eclipse.jdt.core.IOpenable;
 import org.eclipse.jdt.core.ISourceRange;
 import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaCore;
 import org.eclipse.jdt.core.JavaModelException;
 import org.eclipse.jdt.core.Signature;
 import org.eclipse.jdt.core.WorkingCopyOwner;
-import org.eclipse.jdt.core.compiler.*;
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.core.compiler.IProblem;
+import org.eclipse.jdt.core.compiler.InvalidInputException;
 import org.eclipse.jdt.core.search.IJavaSearchConstants;
 import org.eclipse.jdt.core.search.IJavaSearchScope;
 import org.eclipse.jdt.core.search.SearchPattern;
 import org.eclipse.jdt.core.search.TypeNameMatch;
 import org.eclipse.jdt.core.search.TypeNameMatchRequestor;
-import org.eclipse.jdt.internal.codeassist.impl.*;
-import org.eclipse.jdt.internal.codeassist.select.*;
-import org.eclipse.jdt.internal.compiler.*;
+import org.eclipse.jdt.internal.codeassist.impl.AssistParser;
+import org.eclipse.jdt.internal.codeassist.impl.Engine;
+import org.eclipse.jdt.internal.codeassist.select.SelectionJavadocParser;
+import org.eclipse.jdt.internal.codeassist.select.SelectionNodeFound;
+import org.eclipse.jdt.internal.codeassist.select.SelectionOnImportReference;
+import org.eclipse.jdt.internal.codeassist.select.SelectionOnPackageReference;
+import org.eclipse.jdt.internal.codeassist.select.SelectionOnQualifiedTypeReference;
+import org.eclipse.jdt.internal.codeassist.select.SelectionOnSingleTypeReference;
+import org.eclipse.jdt.internal.codeassist.select.SelectionParser;
+import org.eclipse.jdt.internal.compiler.ASTVisitor;
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Expression.DecapsulationState;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ImportReference;
+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
-import org.eclipse.jdt.internal.compiler.env.*;
-import org.eclipse.jdt.internal.compiler.ast.*;
-import org.eclipse.jdt.internal.compiler.ast.Expression.DecapsulationState;
-import org.eclipse.jdt.internal.compiler.lookup.*;
-import org.eclipse.jdt.internal.compiler.parser.*;
-import org.eclipse.jdt.internal.compiler.problem.*;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
+import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
+import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
+import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.Binding;
+import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
+import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
+import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
+import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
+import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
+import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
+import org.eclipse.jdt.internal.compiler.lookup.MemberTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
+import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemFieldBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons;
+import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
+import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.SyntheticMethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding;
+import org.eclipse.jdt.internal.compiler.parser.Scanner;
+import org.eclipse.jdt.internal.compiler.parser.ScannerHelper;
+import org.eclipse.jdt.internal.compiler.parser.SourceTypeConverter;
+import org.eclipse.jdt.internal.compiler.parser.TerminalTokens;
+import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
+import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
+import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
 import org.eclipse.jdt.internal.compiler.util.HashtableOfObject;
 import org.eclipse.jdt.internal.compiler.util.ObjectVector;
 import org.eclipse.jdt.internal.core.BinaryTypeConverter;
@@ -55,6 +104,8 @@
 import org.eclipse.jdt.internal.core.SelectionRequestor;
 import org.eclipse.jdt.internal.core.SourceType;
 import org.eclipse.jdt.internal.core.SourceTypeElementInfo;
+import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeDescriptor;
+import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeFactory;
 import org.eclipse.jdt.internal.core.search.BasicSearchEngine;
 import org.eclipse.jdt.internal.core.search.TypeNameMatchRequestorWrapper;
 import org.eclipse.jdt.internal.core.util.ASTNodeFinder;
@@ -1350,7 +1401,7 @@
 			if(!isValuesOrValueOf && !methodBinding.isSynthetic()) {
 //{ObjectTeams: retrench enhanced callin signature:
 /* orig:
-                TypeBinding[] parameterTypes = methodBinding.original().parameters;
+				TypeBinding[] parameterTypes = methodBinding.original().parameters;
   :giro */
                 TypeBinding[] parameterTypes = methodBinding.original().getSourceParameters();
 // SH}
@@ -1402,11 +1453,11 @@
                             declaringClass,
                             parsedUnit);
 //haebor}
-                    this.requestor.acceptMethod(
+					this.requestor.acceptMethod(
 //{ObjectTeams
 /* orig:
-        				declaringClass.qualifiedPackageName(),
-        				declaringClass.qualifiedSourceName(),
+						declaringClass.qualifiedPackageName(),
+						declaringClass.qualifiedSourceName(),
 :giro */
                         packTypeName.qualifiedPackageName,
                         packTypeName.qualifiedSourceName,
@@ -1666,7 +1717,18 @@
 				}
 			} else { // binary type
 				ClassFile classFile = (ClassFile) context.getClassFile();
-				ClassFileReader reader = (ClassFileReader) classFile.getBinaryTypeInfo((IFile) classFile.resource(), false/*don't fully initialize so as to keep constant pool (used below)*/);
+				BinaryTypeDescriptor descriptor = BinaryTypeFactory.createDescriptor(classFile);
+				ClassFileReader reader = null;
+				try {
+					reader = BinaryTypeFactory.rawReadType(descriptor, false/*don't fully initialize so as to keep constant pool (used below)*/);
+				} catch (ClassFormatException e) {
+					if (JavaCore.getPlugin().isDebugging()) {
+						e.printStackTrace(System.err);
+					}
+				}
+				if (reader == null) {
+					throw classFile.newNotPresentException();
+				}
 				CompilationResult result = new CompilationResult(reader.getFileName(), 1, 1, this.compilerOptions.maxProblemsPerUnit);
 				parsedUnit = new CompilationUnitDeclaration(this.parser.problemReporter(), result, 0);
 				HashSetOfCharArrayArray typeNames = new HashSetOfCharArrayArray();
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java
index fb21a11..d3fc337 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java
@@ -92,6 +92,7 @@
 import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
 import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
 import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.PolymorphicMethodBinding;
 import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
 import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
 import org.eclipse.jdt.internal.compiler.lookup.SyntheticArgumentBinding;
@@ -3098,7 +3099,7 @@
 				this.contents[localContentsOffset++] = (byte) (functionalDescriptorIndex >> 8);
 				this.contents[localContentsOffset++] = (byte) functionalDescriptorIndex;
 	
-				int methodHandleIndex = this.constantPool.literalIndexForMethodHandle(functional.binding.original()); // Speak of " implementation" (erased) version here, adaptations described below.
+				int methodHandleIndex = this.constantPool.literalIndexForMethodHandle(functional.binding instanceof PolymorphicMethodBinding ? functional.binding : functional.binding.original()); // Speak of " implementation" (erased) version here, adaptations described below.
 				this.contents[localContentsOffset++] = (byte) (methodHandleIndex >> 8);
 				this.contents[localContentsOffset++] = (byte) methodHandleIndex;
 	
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java
index b816309..ca0453b 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java
@@ -50,8 +50,9 @@
 			currentScope,
 			flowContext,
 			analyseCode(currentScope, flowContext, flowInfo).unconditionalInits());
-	if ((this.resolvedType.tagBits & TagBits.AnnotationNonNull) != 0) {
-		int nullStatus = assignment.expression.nullStatus(flowInfo, flowContext);
+		if ((this.resolvedType.tagBits & TagBits.AnnotationNonNull) != 0 || 
+				(this.resolvedType.isFreeTypeVariable() && !assignment.expression.resolvedType.isFreeTypeVariable())) {
+			int nullStatus = assignment.expression.nullStatus(flowInfo, flowContext);
 		if (nullStatus != FlowInfo.NON_NULL) {
 			currentScope.problemReporter().nullityMismatch(this, assignment.expression.resolvedType, this.resolvedType, nullStatus, currentScope.environment().getNonNullAnnotationName());
 		}
@@ -60,8 +61,8 @@
 }
 
 public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
-	this.receiver.checkNPE(currentScope, flowContext, flowInfo, 1);
 	flowInfo = this.receiver.analyseCode(currentScope, flowContext, flowInfo);
+	this.receiver.checkNPE(currentScope, flowContext, flowInfo, 1);
 	flowInfo = this.position.analyseCode(currentScope, flowContext, flowInfo);
 	this.position.checkNPEbyUnboxing(currentScope, flowContext, flowInfo);
 	// account for potential ArrayIndexOutOfBoundsException:
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/BinaryExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/BinaryExpression.java
index 9404a33..f4d9d99 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/BinaryExpression.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/BinaryExpression.java
@@ -70,13 +70,13 @@
 								this.left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits())
 							.unconditionalInits();
 		} else {
-			this.left.checkNPE(currentScope, flowContext, flowInfo);
 			flowInfo = this.left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits();
+			this.left.checkNPE(currentScope, flowContext, flowInfo);
 			if (((this.bits & OperatorMASK) >> OperatorSHIFT) != AND) {
 				flowContext.expireNullCheckedFieldInfo();
 			}
-			this.right.checkNPE(currentScope, flowContext, flowInfo);
 			flowInfo = this.right.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits();
+			this.right.checkNPE(currentScope, flowContext, flowInfo);
 			if (((this.bits & OperatorMASK) >> OperatorSHIFT) != AND) {
 				flowContext.expireNullCheckedFieldInfo();
 			}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CombinedBinaryExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CombinedBinaryExpression.java
index 7ff2fec..51e2849 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CombinedBinaryExpression.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CombinedBinaryExpression.java
@@ -125,26 +125,26 @@
 	}
 	try {
 		BinaryExpression cursor;
-		if ((cursor = this.referencesTable[0]).resolvedType.id !=
-				TypeIds.T_JavaLangString) {
+		cursor = this.referencesTable[0];
+		flowInfo = cursor.left.analyseCode(currentScope, flowContext, flowInfo).
+				unconditionalInits();
+		if (cursor.resolvedType.id != TypeIds.T_JavaLangString) {
 			cursor.left.checkNPE(currentScope, flowContext, flowInfo);
 		}
-		flowInfo = cursor.left.analyseCode(currentScope, flowContext, flowInfo).
-			unconditionalInits();
 		for (int i = 0, end = this.arity; i < end; i ++) {
-			if ((cursor = this.referencesTable[i]).resolvedType.id !=
-					TypeIds.T_JavaLangString) {
+			cursor = this.referencesTable[i];
+			flowInfo = cursor.right.
+					analyseCode(currentScope, flowContext, flowInfo).
+						unconditionalInits();
+			if (cursor.resolvedType.id != TypeIds.T_JavaLangString) {
 				cursor.right.checkNPE(currentScope, flowContext, flowInfo);
 			}
-			flowInfo = cursor.right.
-				analyseCode(currentScope, flowContext, flowInfo).
-					unconditionalInits();
 		}
+		flowInfo = this.right.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits();
 		if (this.resolvedType.id != TypeIds.T_JavaLangString) {
 			this.right.checkNPE(currentScope, flowContext, flowInfo);
 		}
-		return this.right.analyseCode(currentScope, flowContext, flowInfo).
-			unconditionalInits();
+		return flowInfo;
 	} finally {
 		// account for exception possibly thrown by arithmetics
 		flowContext.recordAbruptExit();
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java
index 94110e5..93b2020 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java
@@ -136,6 +136,9 @@
 		this.receiver
 			.analyseCode(currentScope, flowContext, flowInfo, !this.binding.isStatic())
 			.unconditionalInits();
+	
+	this.receiver.checkNPE(currentScope, flowContext, flowInfo);
+	
 	if (assignment.expression != null) {
 		flowInfo =
 			assignment
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java
index 6c18cc8..c590ba9 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java
@@ -107,9 +107,9 @@
 		int initialComplaintLevel = (flowInfo.reachMode() & FlowInfo.UNREACHABLE) != 0 ? Statement.COMPLAINED_FAKE_REACHABLE : Statement.NOT_COMPLAINED;
 
 		// process the element variable and collection
-		this.collection.checkNPE(currentScope, flowContext, flowInfo, 1);
 		flowInfo = this.elementVariable.analyseCode(this.scope, flowContext, flowInfo);		
 		FlowInfo condInfo = this.collection.analyseCode(this.scope, flowContext, flowInfo.copy());
+		this.collection.checkNPE(currentScope, flowContext, condInfo.copy(), 1);
 		LocalVariableBinding elementVarBinding = this.elementVariable.binding;
 
 		// element variable will be assigned when iterating
@@ -126,7 +126,7 @@
 			condInfo.nullInfoLessUnconditionalCopy();
 		actionInfo.markAsDefinitelyUnknown(elementVarBinding);
 		if (currentScope.compilerOptions().isAnnotationBasedNullAnalysisEnabled) {
-			int elementNullStatus = FlowInfo.tagBitsToNullStatus(this.collectionElementType.tagBits);
+			int elementNullStatus = NullAnnotationMatching.nullStatusFromExpressionType(this.collectionElementType);
 			int nullStatus = NullAnnotationMatching.checkAssignment(currentScope, flowContext, elementVarBinding, null, // have no useful flowinfo for element var
 																		elementNullStatus, this.collection, this.collectionElementType);
 			if ((elementVarBinding.type.tagBits & TagBits.IsBaseType) == 0) {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java
index 2c76671..d6a9791 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java
@@ -878,7 +878,7 @@
 			this.genericTypeArguments = new TypeBinding[length];
 			for (int i = 0; i < length; i++) {
 				TypeReference typeReference = this.typeArguments[i];
-				if ((this.genericTypeArguments[i] = typeReference.resolveType(scope, true /* check bounds*/)) == null) {
+				if ((this.genericTypeArguments[i] = typeReference.resolveType(scope, true /* check bounds*/, Binding.DefaultLocationTypeArgument)) == null) {
 					this.argumentsHaveErrors = true;
 				}
 				if (this.argumentsHaveErrors && typeReference instanceof Wildcard) {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java
index ef63d2a..0d57976 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java
@@ -311,7 +311,7 @@
 						}
 					}
 					severity = severity.max(s);
-					if (!severity.isAnyMismatch() && (providedBits & TagBits.AnnotationNonNull) != 0)
+					if (!severity.isAnyMismatch() && (providedBits & TagBits.AnnotationNullMASK) == TagBits.AnnotationNonNull)
 						okStatus = okNonNullStatus(providedExpression);
 				}
 				if (severity != Severity.MISMATCH && nullStatus != FlowInfo.NULL) {  // null value has no details
@@ -479,6 +479,20 @@
 		return 0;
 	}
 
+	/**
+	 * Use only if no suitable flowInfo is available.
+	 */
+	public static int nullStatusFromExpressionType(TypeBinding type) {
+		if (type.isFreeTypeVariable())
+			return FlowInfo.FREE_TYPEVARIABLE;
+		long bits = type.tagBits & TagBits.AnnotationNullMASK;
+		if (bits == 0)
+			return FlowInfo.UNKNOWN;
+		if (bits == TagBits.AnnotationNonNull)
+			return FlowInfo.NON_NULL;
+		return FlowInfo.POTENTIALLY_NON_NULL | FlowInfo.POTENTIALLY_NULL;
+	}
+
 	public static long validNullTagBits(long bits) {
 		bits &= TagBits.AnnotationNullMASK;
 		return bits == TagBits.AnnotationNullMASK ? 0 : bits;
@@ -725,5 +739,5 @@
 		buf.append("Analysis result: severity="+this.severity);
 		buf.append(" nullStatus="+this.nullStatus);
 		return buf.toString();
-	} 
+	}
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java
index 331bd26..004ddaf 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReferenceExpression.java
@@ -55,7 +55,7 @@
 import org.eclipse.jdt.internal.compiler.codegen.CodeStream;
 import org.eclipse.jdt.internal.compiler.codegen.ConstantPool;
 import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
-import org.eclipse.jdt.internal.compiler.flow.ExceptionHandlingFlowContext;
+import org.eclipse.jdt.internal.compiler.flow.FieldInitsFakingFlowContext;
 import org.eclipse.jdt.internal.compiler.flow.FlowContext;
 import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
 import org.eclipse.jdt.internal.compiler.flow.UnconditionalFlowInfo;
@@ -243,7 +243,7 @@
 		IErrorHandlingPolicy oldPolicy = currentScope.problemReporter().switchErrorHandlingPolicy(silentErrorHandlingPolicy);
 		try {
 			implicitLambda.analyseCode(currentScope, 
-					new ExceptionHandlingFlowContext(null, this, Binding.NO_EXCEPTIONS, null, currentScope, FlowInfo.DEAD_END), 
+					new FieldInitsFakingFlowContext(null, this, Binding.NO_EXCEPTIONS, null, currentScope, FlowInfo.DEAD_END), 
 					UnconditionalFlowInfo.fakeInitializedFlowInfo(currentScope.outerMostMethodScope().analysisIndex, currentScope.referenceType().maxFieldCount));
 		} finally {
 			currentScope.problemReporter().switchErrorHandlingPolicy(oldPolicy);
@@ -288,7 +288,7 @@
 			if (this.binding != null && isMethodReference()) {
 				if (TypeBinding.notEquals(this.binding.declaringClass, this.lhs.resolvedType.erasure())) {
 					if (!this.binding.declaringClass.canBeSeenBy(currentScope)) {
-						this.binding = new MethodBinding(this.binding, (ReferenceBinding) this.lhs.resolvedType.erasure());
+						this.binding = new MethodBinding(this.binding.original(), (ReferenceBinding) this.lhs.resolvedType.erasure());
 					}
 				}
 			}
@@ -415,8 +415,8 @@
 	public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
 		// static methods with receiver value never get here
 		if (this.haveReceiver) {
-			this.lhs.checkNPE(currentScope, flowContext, flowInfo);
 			this.lhs.analyseCode(currentScope, flowContext, flowInfo, true);
+			this.lhs.checkNPE(currentScope, flowContext, flowInfo);
 		} else if (isConstructorReference()) {
 			TypeBinding type = this.receiverType.leafComponentType();
 			if (type.isNestedType() &&
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/UnaryExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/UnaryExpression.java
index ed03863..e099594 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/UnaryExpression.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/UnaryExpression.java
@@ -34,18 +34,18 @@
 		BlockScope currentScope,
 		FlowContext flowContext,
 		FlowInfo flowInfo) {
-	this.expression.checkNPE(currentScope, flowContext, flowInfo);
 	if (((this.bits & OperatorMASK) >> OperatorSHIFT) == NOT) {
 		flowContext.tagBits ^= FlowContext.INSIDE_NEGATION;
 		flowInfo = this.expression.
 			analyseCode(currentScope, flowContext, flowInfo).
 			asNegatedCondition();
 		flowContext.tagBits ^= FlowContext.INSIDE_NEGATION;
-		return flowInfo;
 	} else {
-		return this.expression.
+		flowInfo = this.expression.
 			analyseCode(currentScope, flowContext, flowInfo);
 	}
+	this.expression.checkNPE(currentScope, flowContext, flowInfo);
+	return flowInfo;
 }
 
 	public Constant optimizedBooleanConstant() {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/AnnotationInfo.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/AnnotationInfo.java
index 9081769..3538098 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/AnnotationInfo.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/AnnotationInfo.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2012 BEA Systems, Inc.
+ * Copyright (c) 2005, 2016 BEA Systems, Inc.
  * 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
@@ -377,20 +377,7 @@
 	return currentOffset;
 }
 public String toString() {
-	StringBuffer buffer = new StringBuffer();
-	buffer.append('@');
-	buffer.append(this.typename);
-	if (this.pairs != null) {
-		buffer.append('(');
-		buffer.append("\n\t"); //$NON-NLS-1$
-		for (int i = 0, len = this.pairs.length; i < len; i++) {
-			if (i > 0)
-				buffer.append(",\n\t"); //$NON-NLS-1$
-			buffer.append(this.pairs[i]);
-		}
-		buffer.append(')');
-	}
-	return buffer.toString();
+	return BinaryTypeFormatter.annotationToString(this);
 }
 public int hashCode() {
 	final int prime = 31;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/AnnotationMethodInfo.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/AnnotationMethodInfo.java
index 6ddba77..1a59874 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/AnnotationMethodInfo.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/AnnotationMethodInfo.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2009 BEA Systems, Inc.
+ * Copyright (c) 2005, 2016 BEA Systems, Inc.
  * 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
@@ -93,23 +93,4 @@
 public Object getDefaultValue() {
 	return this.defaultValue;
 }
-protected void toStringContent(StringBuffer buffer) {
-	super.toStringContent(buffer);
-	if (this.defaultValue != null) {
-		buffer.append(" default "); //$NON-NLS-1$
-		if (this.defaultValue instanceof Object[]) {
-			buffer.append('{');
-			Object[] elements = (Object[]) this.defaultValue;
-			for (int i = 0, len = elements.length; i < len; i++) {
-				if (i > 0)
-					buffer.append(", "); //$NON-NLS-1$
-				buffer.append(elements[i]);
-			}
-			buffer.append('}');
-		} else {
-			buffer.append(this.defaultValue);
-		}
-		buffer.append('\n');
-	}
-}
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/AnnotationMethodInfoWithAnnotations.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/AnnotationMethodInfoWithAnnotations.java
index bd1cce8..9ff47d3 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/AnnotationMethodInfoWithAnnotations.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/AnnotationMethodInfoWithAnnotations.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2007 BEA Systems, Inc.
+ * Copyright (c) 2005, 2016 BEA Systems, Inc.
  * 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
@@ -34,11 +34,4 @@
 			this.annotations[i].reset();
 	super.reset();
 }
-protected void toStringContent(StringBuffer buffer) {
-	super.toStringContent(buffer);
-	for (int i = 0, l = this.annotations == null ? 0 : this.annotations.length; i < l; i++) {
-		buffer.append(this.annotations[i]);
-		buffer.append('\n');
-	}
-}
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/BinaryTypeFormatter.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/BinaryTypeFormatter.java
new file mode 100644
index 0000000..99c5111
--- /dev/null
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/BinaryTypeFormatter.java
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * 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.compiler.classfmt;
+
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryElementValuePair;
+import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+import org.eclipse.jdt.internal.compiler.util.Util;
+
+public class BinaryTypeFormatter {
+
+	public static String annotationToString(IBinaryAnnotation annotation) {
+		StringBuffer buffer = new StringBuffer();
+		buffer.append('@');
+		buffer.append(annotation.getTypeName());
+		IBinaryElementValuePair[] valuePairs = annotation.getElementValuePairs();
+		if (valuePairs != null) {
+			buffer.append('(');
+			buffer.append("\n\t"); //$NON-NLS-1$
+			for (int i = 0, len = valuePairs.length; i < len; i++) {
+				if (i > 0)
+					buffer.append(",\n\t"); //$NON-NLS-1$
+				buffer.append(valuePairs[i]);
+			}
+			buffer.append(')');
+		}
+		return buffer.toString();
+	}
+
+	public static String annotationToString(IBinaryTypeAnnotation typeAnnotation) {
+		StringBuffer buffer = new StringBuffer();
+		buffer.append(typeAnnotation.getAnnotation());
+		buffer.append(' ');
+		// Not fully decoding it here, just including all the information in the string
+		buffer.append("target_type=").append(typeAnnotation.getTargetType()); //$NON-NLS-1$
+		buffer.append(", info=").append(typeAnnotation.getSupertypeIndex()); //$NON-NLS-1$
+		buffer.append(", info2=").append(typeAnnotation.getBoundIndex()); //$NON-NLS-1$
+		int[] theTypePath = typeAnnotation.getTypePath();
+		if (theTypePath != null && theTypePath.length != 0) {
+			buffer.append(", location=["); //$NON-NLS-1$
+			for (int i = 0, max = theTypePath.length; i < max; i += 2) {
+				if (i > 0) {
+					buffer.append(", "); //$NON-NLS-1$
+				}
+				switch (theTypePath[i]) {
+					case 0:
+						buffer.append("ARRAY"); //$NON-NLS-1$
+						break;
+					case 1:
+						buffer.append("INNER_TYPE"); //$NON-NLS-1$
+						break;
+					case 2:
+						buffer.append("WILDCARD"); //$NON-NLS-1$
+						break;
+					case 3:
+						buffer.append("TYPE_ARGUMENT(").append(theTypePath[i+1]).append(')'); //$NON-NLS-1$
+						break;
+				}
+			}
+			buffer.append(']');
+		}
+		return buffer.toString();
+	}
+
+	public static String methodToString(IBinaryMethod method) {
+		StringBuffer result = new StringBuffer();
+		methodToStringContent(result, method);
+		return result.toString();
+	}
+
+	public static void methodToStringContent(StringBuffer buffer, IBinaryMethod method) {
+		int modifiers = method.getModifiers();
+		char[] desc = method.getGenericSignature();
+		if (desc == null)
+			desc = method.getMethodDescriptor();
+		buffer
+			.append('{')
+			.append(
+				((modifiers & ClassFileConstants.AccDeprecated) != 0 ? "deprecated " : Util.EMPTY_STRING) //$NON-NLS-1$
+					+ ((modifiers & 0x0001) == 1 ? "public " : Util.EMPTY_STRING) //$NON-NLS-1$
+					+ ((modifiers & 0x0002) == 0x0002 ? "private " : Util.EMPTY_STRING) //$NON-NLS-1$
+					+ ((modifiers & 0x0004) == 0x0004 ? "protected " : Util.EMPTY_STRING) //$NON-NLS-1$
+					+ ((modifiers & 0x0008) == 0x000008 ? "static " : Util.EMPTY_STRING) //$NON-NLS-1$
+					+ ((modifiers & 0x0010) == 0x0010 ? "final " : Util.EMPTY_STRING) //$NON-NLS-1$
+					+ ((modifiers & 0x0040) == 0x0040 ? "bridge " : Util.EMPTY_STRING) //$NON-NLS-1$
+					+ ((modifiers & 0x0080) == 0x0080 ? "varargs " : Util.EMPTY_STRING)) //$NON-NLS-1$
+			.append(method.getSelector())
+			.append(desc)
+			.append('}');
+
+		Object defaultValue = method.getDefaultValue();
+		if (defaultValue != null) {
+			buffer.append(" default "); //$NON-NLS-1$
+			if (defaultValue instanceof Object[]) {
+				buffer.append('{');
+				Object[] elements = (Object[]) defaultValue;
+				for (int i = 0, len = elements.length; i < len; i++) {
+					if (i > 0)
+						buffer.append(", "); //$NON-NLS-1$
+					buffer.append(elements[i]);
+				}
+				buffer.append('}');
+			} else {
+				buffer.append(defaultValue);
+			}
+			buffer.append('\n');
+		}
+
+		IBinaryAnnotation[] annotations = method.getAnnotations();
+		for (int i = 0, l = annotations == null ? 0 : annotations.length; i < l; i++) {
+			buffer.append(annotations[i]);
+			buffer.append('\n');
+		}
+
+		int annotatedParameterCount = method.getAnnotatedParametersCount();
+		for (int i = 0; i < annotatedParameterCount; i++) {
+			buffer.append("param" + (i - 1)); //$NON-NLS-1$
+			buffer.append('\n');
+			IBinaryAnnotation[] infos = method.getParameterAnnotations(i, new char[0]);
+			for (int j = 0, k = infos == null ? 0 : infos.length; j < k; j++) {
+				buffer.append(infos[j]);
+				buffer.append('\n');
+			}
+		}
+	}
+
+}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java
index 5b5d2a1..526c325 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation 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
@@ -19,29 +19,31 @@
 package org.eclipse.jdt.internal.compiler.classfmt;
 
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedList;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
 
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.codegen.AnnotationTargetTypeConstants;
 import org.eclipse.jdt.internal.compiler.codegen.AttributeNamesConstants;
-import org.eclipse.jdt.internal.compiler.env.*;
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryElementValuePair;
+import org.eclipse.jdt.internal.compiler.env.IBinaryField;
+import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
+import org.eclipse.jdt.internal.compiler.env.IBinaryNestedType;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+import org.eclipse.jdt.internal.compiler.env.ITypeAnnotationWalker;
 import org.eclipse.jdt.internal.compiler.impl.Constant;
 import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.ExternalAnnotationStatus;
 import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
 import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
 import org.eclipse.jdt.internal.compiler.lookup.TagBits;
-import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
 import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
-import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.ExternalAnnotationStatus;
 import org.eclipse.jdt.internal.compiler.util.Util;
 import org.eclipse.objectteams.otdt.core.compiler.IOTConstants;
 import org.eclipse.objectteams.otdt.internal.core.compiler.bytecode.*;
@@ -94,8 +96,6 @@
 	private char[][][] missingTypeNames;
 	private int enclosingNameAndTypeIndex;
 	private char[] enclosingMethod;
-	private ExternalAnnotationProvider annotationProvider;
-	private ExternalAnnotationStatus externalAnnotationStatus = ExternalAnnotationStatus.NOT_EEA_CONFIGURED;
 
 private static String printTypeModifiers(int modifiers) {
 	java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
@@ -528,60 +528,9 @@
 	}
 }
 
-/** Auxiliary interface for {@link #setExternalAnnotationProvider(String,String,ZipFile,ZipFileProducer)}. */
-public interface ZipFileProducer { ZipFile produce() throws IOException; }
-
-/**
- * Create and remember a provider for external annotations using the given basePath,
- * which is either a directory holding .eea text files, or a zip file of entries of the same format.
- * @param basePath resolved filesystem path of either directory or zip file
- * @param qualifiedBinaryTypeName slash-separated type name
- * @param zipFile an existing zip file for the same basePath, or null. 
- * 		Output: wl be filled with 
- * @param producer an optional helper to produce the zipFile when needed.
- * @return the client provided zip file; 
- * 		or else a fresh new zip file, to let clients cache it, if desired; 
- * 		or null to signal that basePath is not a zip file, but a directory.
- * @throws IOException any unexpected errors during file access. File not found while
- *		accessing an individual file if basePath is a directory <em>is</em> expected,
- *		and simply answered with null. If basePath is neither a directory nor a zip file,
- *		this is unexpected.
- */
-public ZipFile setExternalAnnotationProvider(String basePath, String qualifiedBinaryTypeName, ZipFile zipFile, ZipFileProducer producer) throws IOException {
-	this.externalAnnotationStatus = ExternalAnnotationStatus.NO_EEA_FILE;
-	String qualifiedBinaryFileName = qualifiedBinaryTypeName + ExternalAnnotationProvider.ANNOTATION_FILE_SUFFIX;
-	if (zipFile == null) {
-		File annotationBase = new File(basePath);
-		if (annotationBase.isDirectory()) {
-			try {
-				String filePath = annotationBase.getAbsolutePath()+'/'+qualifiedBinaryFileName;
-				this.annotationProvider = new ExternalAnnotationProvider(new FileInputStream(filePath), String.valueOf(getName()));
-				this.externalAnnotationStatus = ExternalAnnotationStatus.TYPE_IS_ANNOTATED;
-			} catch (FileNotFoundException e) {
-				// expected, no need to report an error here
-			}
-			return null; // no zipFile
-		}
-		if (!annotationBase.exists())
-			return null; // no zipFile, treat as not-yet-created directory
-		zipFile = (producer != null ? producer.produce() : new ZipFile(annotationBase));
-	}
-	ZipEntry entry = zipFile.getEntry(qualifiedBinaryFileName);
-	if (entry != null) {
-		this.annotationProvider = new ExternalAnnotationProvider(zipFile.getInputStream(entry), String.valueOf(getName()));
-		this.externalAnnotationStatus = ExternalAnnotationStatus.TYPE_IS_ANNOTATED;
-	}
-	return zipFile;
-}
-public boolean hasAnnotationProvider() {
-	return this.annotationProvider != null;
-}
-public void markAsFromSource() {
-	this.externalAnnotationStatus = ExternalAnnotationStatus.FROM_SOURCE;
-}
 @Override
 public ExternalAnnotationStatus getExternalAnnotationStatus() {
-	return this.externalAnnotationStatus;
+	return ExternalAnnotationStatus.NOT_EEA_CONFIGURED;
 }
 /**
  * Conditionally add external annotations to the mix.
@@ -590,23 +539,6 @@
  */
 @Override
 public ITypeAnnotationWalker enrichWithExternalAnnotationsFor(ITypeAnnotationWalker walker, Object member, LookupEnvironment environment) {
-	if (walker == ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER && this.annotationProvider != null) {
-		if (member == null) {
-			return this.annotationProvider.forTypeHeader(environment);
-		} else if (member instanceof IBinaryField) {
-			IBinaryField field = (IBinaryField) member;
-			char[] fieldSignature = field.getGenericSignature();
-			if (fieldSignature == null)
-				fieldSignature = field.getTypeName();
-			return this.annotationProvider.forField(field.getName(), fieldSignature, environment);
-		} else if (member instanceof IBinaryMethod) {
-			IBinaryMethod method = (IBinaryMethod) member;
-			char[] methodSignature = method.getGenericSignature();
-			if (methodSignature == null)
-				methodSignature = method.getMethodDescriptor();
-			return this.annotationProvider.forMethod(method.isConstructor() ? TypeConstants.INIT : method.getSelector(), methodSignature, environment);
-		}
-	}
 	return walker;
 }
 
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ElementValuePairInfo.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ElementValuePairInfo.java
index 5a40c0d..b3a3872 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ElementValuePairInfo.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ElementValuePairInfo.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2010 BEA Systems, Inc.
+ * Copyright (c) 2005, 2016 BEA Systems, Inc.
  * 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
@@ -22,7 +22,7 @@
 	private char[] name;
 	private Object value;
 
-ElementValuePairInfo(char[] name, Object value) {
+public ElementValuePairInfo(char[] name, Object value) {
 	this.name = name;
 	this.value = value;
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationDecorator.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationDecorator.java
new file mode 100644
index 0000000..f379a7e
--- /dev/null
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationDecorator.java
@@ -0,0 +1,290 @@
+/*******************************************************************************
+ * 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 <sxenos@gmail.com> (Google) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.compiler.classfmt;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryField;
+import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
+import org.eclipse.jdt.internal.compiler.env.IBinaryNestedType;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+import org.eclipse.jdt.internal.compiler.env.ITypeAnnotationWalker;
+import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.ExternalAnnotationStatus;
+import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
+import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
+
+/**
+ * A decorator for {@link IBinaryType} that allows external annotations to be attached. This can be used to change the
+ * result of {@link #enrichWithExternalAnnotationsFor} or {@link #getExternalAnnotationStatus}.
+ */
+public class ExternalAnnotationDecorator implements IBinaryType {
+	private IBinaryType inputType;
+	private ExternalAnnotationProvider annotationProvider;
+	private boolean isFromSource;
+
+	/** Auxiliary interface for {@link #getAnnotationZipFile(String, ZipFileProducer)}. */
+	public interface ZipFileProducer { ZipFile produce() throws IOException; }
+
+	public ExternalAnnotationDecorator(IBinaryType toDecorate, ExternalAnnotationProvider externalAnnotationProvider) {
+		if (toDecorate == null) {
+			throw new NullPointerException("toDecorate"); //$NON-NLS-1$
+		}
+		this.inputType = toDecorate;
+		this.annotationProvider = externalAnnotationProvider;
+	}
+
+	public ExternalAnnotationDecorator(IBinaryType toDecorate, boolean isFromSource) {
+		if (toDecorate == null) {
+			throw new NullPointerException("toDecorate"); //$NON-NLS-1$
+		}
+		this.isFromSource = isFromSource;
+		this.inputType = toDecorate;
+	}
+
+	@Override
+	public char[] getFileName() {
+		return this.inputType.getFileName();
+	}
+
+	@Override
+	public boolean isBinaryType() {
+		return this.inputType.isBinaryType();
+	}
+
+	@Override
+	public IBinaryAnnotation[] getAnnotations() {
+		return this.inputType.getAnnotations();
+	}
+
+	@Override
+	public IBinaryTypeAnnotation[] getTypeAnnotations() {
+		return this.inputType.getTypeAnnotations();
+	}
+
+	@Override
+	public char[] getEnclosingMethod() {
+		return this.inputType.getEnclosingMethod();
+	}
+
+	@Override
+	public char[] getEnclosingTypeName() {
+		return this.inputType.getEnclosingTypeName();
+	}
+
+	@Override
+	public IBinaryField[] getFields() {
+		return this.inputType.getFields();
+	}
+
+	@Override
+	public char[] getGenericSignature() {
+		return this.inputType.getGenericSignature();
+	}
+
+	@Override
+	public char[][] getInterfaceNames() {
+		return this.inputType.getInterfaceNames();
+	}
+
+	@Override
+	public IBinaryNestedType[] getMemberTypes() {
+		return this.inputType.getMemberTypes();
+	}
+
+	@Override
+	public IBinaryMethod[] getMethods() {
+		return this.inputType.getMethods();
+	}
+
+	@Override
+	public char[][][] getMissingTypeNames() {
+		return this.inputType.getMissingTypeNames();
+	}
+
+	@Override
+	public char[] getName() {
+		return this.inputType.getName();
+	}
+
+	@Override
+	public char[] getSourceName() {
+		return this.inputType.getSourceName();
+	}
+
+	@Override
+	public char[] getSuperclassName() {
+		return this.inputType.getSuperclassName();
+	}
+
+	@Override
+	public long getTagBits() {
+		return this.inputType.getTagBits();
+	}
+
+	@Override
+	public boolean isAnonymous() {
+		return this.inputType.isAnonymous();
+	}
+
+	@Override
+	public boolean isLocal() {
+		return this.inputType.isLocal();
+	}
+
+	@Override
+	public boolean isMember() {
+		return this.inputType.isMember();
+	}
+
+	@Override
+	public char[] sourceFileName() {
+		return this.inputType.sourceFileName();
+	}
+
+	@Override
+	public int getModifiers() {
+		return this.inputType.getModifiers();
+	}
+
+	/**
+	 * Returns the zip file containing external annotations, if any. Returns null if there are no external annotations
+	 * or if the basePath refers to a directory.
+	 *
+	 * @param basePath
+	 *            resolved filesystem path of either directory or zip file
+	 * @param producer
+	 *            an optional helper to produce the zipFile when needed.
+	 * @return the client provided zip file; or else a fresh new zip file, to let clients cache it, if desired; or null
+	 *         to signal that basePath is not a zip file, but a directory.
+	 * @throws IOException
+	 *             any unexpected errors during file access. File not found while accessing an individual file if
+	 *             basePath is a directory <em>is</em> expected, and simply answered with null. If basePath is neither a
+	 *             directory nor a zip file, this is unexpected.
+	 */
+	public static ZipFile getAnnotationZipFile(String basePath, ZipFileProducer producer) throws IOException {
+		File annotationBase = new File(basePath);
+		if (!annotationBase.isFile()) {
+			return null;
+		}
+		return (producer != null ? producer.produce() : new ZipFile(annotationBase));
+	}
+
+	/**
+	 * Creates an external annotation provider for external annotations using the given basePath, which is either a
+	 * directory holding .eea text files, or a zip file of entries of the same format.
+	 *
+	 * @param basePath
+	 *            resolved filesystem path of either directory or zip file
+	 * @param qualifiedBinaryTypeName
+	 *            slash-separated type name
+	 * @param zipFile
+	 *            an existing zip file for the same basePath, or null.
+	 * @return the annotation provider or null if there are no external annotations.
+	 * @throws IOException
+	 *             any unexpected errors during file access. File not found while accessing an individual file if
+	 *             basePath is a directory <em>is</em> expected, and simply answered with null. If basePath is neither a
+	 *             directory nor a zip file, this is unexpected.
+	 */
+	public static ExternalAnnotationProvider externalAnnotationProvider(String basePath, String qualifiedBinaryTypeName,
+			ZipFile zipFile) throws IOException {
+		String qualifiedBinaryFileName = qualifiedBinaryTypeName + ExternalAnnotationProvider.ANNOTATION_FILE_SUFFIX;
+		if (zipFile == null) {
+			File annotationBase = new File(basePath);
+			if (annotationBase.isDirectory()) {
+				String filePath = annotationBase.getAbsolutePath() + '/' + qualifiedBinaryFileName;
+				try {
+					return new ExternalAnnotationProvider(new FileInputStream(filePath), qualifiedBinaryTypeName);
+				} catch (FileNotFoundException e) {
+					// Expected, no need to report an error here
+					return null;
+				}
+			}
+		} else {
+			ZipEntry entry = zipFile.getEntry(qualifiedBinaryFileName);
+			if (entry != null) {
+				return new ExternalAnnotationProvider(zipFile.getInputStream(entry), qualifiedBinaryTypeName);
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Possibly wrap the provided binary type in a ClassWithExternalAnnotations to which a fresh provider for external
+	 * annotations is associated. This provider is constructed using the given basePath, which is either a directory
+	 * holding .eea text files, or a zip file of entries of the same format. If no such provider could be constructed,
+	 * then the original binary type is returned unchanged.
+	 * 
+	 * @param toDecorate
+	 *            the binary type to wrap, if needed
+	 * @param basePath
+	 *            resolved filesystem path of either directory or zip file
+	 * @param qualifiedBinaryTypeName
+	 *            slash-separated type name
+	 * @param zipFile
+	 *            an existing zip file for the same basePath, or null.
+	 * @return either a fresh ClassWithExternalAnnotations or the original binary type unchanged.
+	 * @throws IOException
+	 *             any unexpected errors during file access. File not found while accessing an individual file if
+	 *             basePath is a directory <em>is</em> expected, and simply handled by not setting up an external
+	 *             annotation provider. If basePath is neither a directory nor a zip file, this is unexpected, resulting
+	 *             in an exception.
+	 */
+	public static IBinaryType create(IBinaryType toDecorate, String basePath,
+			String qualifiedBinaryTypeName, ZipFile zipFile) throws IOException {
+		ExternalAnnotationProvider externalAnnotationProvider = externalAnnotationProvider(basePath, qualifiedBinaryTypeName, zipFile);
+		if (externalAnnotationProvider == null)
+			return toDecorate;
+		return new ExternalAnnotationDecorator(toDecorate, externalAnnotationProvider);
+	}
+
+	@Override
+	public ITypeAnnotationWalker enrichWithExternalAnnotationsFor(ITypeAnnotationWalker walker, Object member,
+			LookupEnvironment environment) {
+		if (walker == ITypeAnnotationWalker.EMPTY_ANNOTATION_WALKER && this.annotationProvider != null) {
+			if (member == null) {
+				return this.annotationProvider.forTypeHeader(environment);
+			} else if (member instanceof IBinaryField) {
+				IBinaryField field = (IBinaryField) member;
+				char[] fieldSignature = field.getGenericSignature();
+				if (fieldSignature == null)
+					fieldSignature = field.getTypeName();
+				return this.annotationProvider.forField(field.getName(), fieldSignature, environment);
+			} else if (member instanceof IBinaryMethod) {
+				IBinaryMethod method = (IBinaryMethod) member;
+				char[] methodSignature = method.getGenericSignature();
+				if (methodSignature == null)
+					methodSignature = method.getMethodDescriptor();
+				return this.annotationProvider.forMethod(
+						method.isConstructor() ? TypeConstants.INIT : method.getSelector(), methodSignature,
+						environment);
+			}
+		}
+		return walker;
+	}
+
+	@Override
+	public ExternalAnnotationStatus getExternalAnnotationStatus() {
+		if (this.annotationProvider == null) {
+			if (this.isFromSource) {
+				return ExternalAnnotationStatus.FROM_SOURCE;
+			}
+			return ExternalAnnotationStatus.NO_EEA_FILE;
+		}
+		return ExternalAnnotationStatus.TYPE_IS_ANNOTATED;
+	}
+}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfo.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfo.java
index 4e2724d..6033dc8 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfo.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfo.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation 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
@@ -36,7 +36,7 @@
 import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding;
 import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
 import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
-import org.eclipse.jdt.internal.compiler.util.Util;
+import org.eclipse.jdt.internal.core.nd.java.JavaNames;
 import org.eclipse.objectteams.otdt.internal.core.compiler.bytecode.AbstractAttribute;
 import org.eclipse.objectteams.otdt.internal.core.compiler.bytecode.AnchorListAttribute;
 import org.eclipse.objectteams.otdt.internal.core.compiler.bytecode.CopyInheritanceSourceAttribute;
@@ -326,6 +326,7 @@
 	if (result != 0) return result;
 	return new String(getMethodDescriptor()).compareTo(new String(otherMethod.getMethodDescriptor()));
 }
+@Override
 public boolean equals(Object o) {
 	if (!(o instanceof MethodInfo)) {
 		return false;
@@ -334,6 +335,7 @@
 	return CharOperation.equals(getSelector(), otherMethod.getSelector())
 			&& CharOperation.equals(getMethodDescriptor(), otherMethod.getMethodDescriptor());
 }
+@Override
 public int hashCode() {
 	return CharOperation.hashCode(getSelector()) + CharOperation.hashCode(getMethodDescriptor());
 }
@@ -457,16 +459,14 @@
  * @return boolean
  */
 public boolean isClinit() {
-	char[] selector = getSelector();
-	return selector[0] == '<' && selector.length == 8; // Can only match <clinit>
+	return JavaNames.isClinit(getSelector());
 }
 /**
  * Answer true if the method is a constructor, false otherwise.
  * @return boolean
  */
 public boolean isConstructor() {
-	char[] selector = getSelector();
-	return selector[0] == '<' && selector.length == 6; // Can only match <init>
+	return JavaNames.isConstructor(getSelector());
 }
 /**
  * Return true if the field is a synthetic method, false otherwise.
@@ -599,6 +599,7 @@
 	return this.attributeBytes;
 }
 
+@Override
 public String toString() {
 	StringBuffer buffer = new StringBuffer();
 	toString(buffer);
@@ -609,24 +610,7 @@
 	toStringContent(buffer);
 }
 protected void toStringContent(StringBuffer buffer) {
-	int modifiers = getModifiers();
-	char[] desc = getGenericSignature();
-	if (desc == null)
-		desc = getMethodDescriptor();
-	buffer
-	.append('{')
-	.append(
-		((modifiers & ClassFileConstants.AccDeprecated) != 0 ? "deprecated " : Util.EMPTY_STRING) //$NON-NLS-1$
-			+ ((modifiers & 0x0001) == 1 ? "public " : Util.EMPTY_STRING) //$NON-NLS-1$
-			+ ((modifiers & 0x0002) == 0x0002 ? "private " : Util.EMPTY_STRING) //$NON-NLS-1$
-			+ ((modifiers & 0x0004) == 0x0004 ? "protected " : Util.EMPTY_STRING) //$NON-NLS-1$
-			+ ((modifiers & 0x0008) == 0x000008 ? "static " : Util.EMPTY_STRING) //$NON-NLS-1$
-			+ ((modifiers & 0x0010) == 0x0010 ? "final " : Util.EMPTY_STRING) //$NON-NLS-1$
-			+ ((modifiers & 0x0040) == 0x0040 ? "bridge " : Util.EMPTY_STRING) //$NON-NLS-1$
-			+ ((modifiers & 0x0080) == 0x0080 ? "varargs " : Util.EMPTY_STRING)) //$NON-NLS-1$
-	.append(getSelector())
-	.append(desc)
-	.append('}');
+	BinaryTypeFormatter.methodToStringContent(buffer, this);
 }
 private void readCodeAttribute() {
 	int attributesCount = u2At(6);
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfoWithAnnotations.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfoWithAnnotations.java
index 4db5b95..c2621f4 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfoWithAnnotations.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfoWithAnnotations.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2007 BEA Systems, Inc.
+ * Copyright (c) 2005, 2016 BEA Systems, Inc.
  * 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
@@ -43,11 +43,4 @@
 			this.annotations[i].reset();
 	super.reset();
 }
-protected void toStringContent(StringBuffer buffer) {
-	super.toStringContent(buffer);
-	for (int i = 0, l = this.annotations == null ? 0 : this.annotations.length; i < l; i++) {
-		buffer.append(this.annotations[i]);
-		buffer.append('\n');
-	}
-}
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfoWithParameterAnnotations.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfoWithParameterAnnotations.java
index d29206f..3fa81eb 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfoWithParameterAnnotations.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfoWithParameterAnnotations.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2015 BEA Systems, Inc.
+ * Copyright (c) 2005, 2016 BEA Systems, Inc.
  * 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
@@ -61,16 +61,4 @@
 	}
 	super.reset();
 }
-protected void toStringContent(StringBuffer buffer) {
-	super.toStringContent(buffer);
-	for (int i = 0, l = this.parameterAnnotations == null ? 0 : this.parameterAnnotations.length; i < l; i++) {
-		buffer.append("param" + (i - 1)); //$NON-NLS-1$
-		buffer.append('\n');
-		AnnotationInfo[] infos = this.parameterAnnotations[i];
-		for (int j = 0, k = infos == null ? 0 : infos.length; j < k; j++) {
-			buffer.append(infos[j]);
-			buffer.append('\n');
-		}
-	}
-}
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfoWithTypeAnnotations.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfoWithTypeAnnotations.java
index 54c75dc..d2f7db6 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfoWithTypeAnnotations.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/MethodInfoWithTypeAnnotations.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2013 GoPivotal, Inc. All Rights Reserved.
+ * Copyright (c) 2016 GoPivotal, Inc. All Rights Reserved.
  * 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
@@ -36,12 +36,4 @@
 	}
 	super.reset();
 }
-protected void toStringContent(StringBuffer buffer) {
-	super.toStringContent(buffer);
-	buffer.append("type annotations = \n");//$NON-NLS-1$
-	for (int i = 0, l = this.typeAnnotations == null ? 0 : this.typeAnnotations.length; i < l; i++) {
-		buffer.append(this.typeAnnotations[i].toString());
-		buffer.append('\n');
-	}
-}
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/TypeAnnotationInfo.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/TypeAnnotationInfo.java
index f58de40..1472b3c 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/TypeAnnotationInfo.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/TypeAnnotationInfo.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2013 GoPivotal, Inc. All Rights Reserved.
+ * Copyright (c) 2016 GoPivotal, Inc. All Rights Reserved.
  * 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
@@ -123,40 +123,9 @@
 }
 
 public String toString() {
-	StringBuffer buffer = new StringBuffer();
-	buffer.append(this.annotation);
-	buffer.append(' ');
-	// Not fully decoding it here, just including all the information in the string
-	buffer.append("target_type=").append(this.targetType); //$NON-NLS-1$
-	buffer.append(", info=").append(this.info); //$NON-NLS-1$
-	buffer.append(", info2=").append(this.info2); //$NON-NLS-1$
-	if (this.typePath != NO_TYPE_PATH) {
-		buffer.append(", location=["); //$NON-NLS-1$
-		for (int i = 0, max = this.typePath.length; i < max; i += 2) {
-			if (i > 0) {
-				buffer.append(", "); //$NON-NLS-1$
-			}
-			switch (this.typePath[i]) {
-				case 0:
-					buffer.append("ARRAY"); //$NON-NLS-1$
-					break;
-				case 1:
-					buffer.append("INNER_TYPE"); //$NON-NLS-1$
-					break;
-				case 2:
-					buffer.append("WILDCARD"); //$NON-NLS-1$
-					break;
-				case 3:
-					buffer.append("TYPE_ARGUMENT(").append(this.typePath[i+1]).append(')'); //$NON-NLS-1$
-					break;
-			}
-		}
-		buffer.append(']');
-	}
-	return buffer.toString();
+	return BinaryTypeFormatter.annotationToString(this);
 }
 
-
 public int getTargetType() {
 	return this.targetType;
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java
index f49d64c..f7709c4 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java
@@ -2716,6 +2716,8 @@
 				methodKind = ClassFileConstants.MethodHandleRefKindInvokeSpecial;
 			} else if (mb.isConstructor()) {
 				methodKind = ClassFileConstants.MethodHandleRefKindNewInvokeSpecial;
+			} else if (mb.declaringClass.isInterface()) {
+				methodKind = ClassFileConstants.MethodHandleRefKindInvokeInterface;
 			} else {
 				methodKind = ClassFileConstants.MethodHandleRefKindInvokeVirtual;
 			}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/env/NameEnvironmentAnswer.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/env/NameEnvironmentAnswer.java
index bc79c2f..03b8563 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/env/NameEnvironmentAnswer.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/env/NameEnvironmentAnswer.java
@@ -34,15 +34,44 @@
 		this.accessRestriction = accessRestriction;
 		this.externalAnnotationPath = externalAnnotationPath;
 	}
+	
+	@Override
+	public String toString() {
+		String baseString = ""; //$NON-NLS-1$
+		if (this.binaryType != null) {
+			char[] fileNameChars = this.binaryType.getFileName();
+			String fileName = fileNameChars == null ? "" : new String(fileNameChars); //$NON-NLS-1$
+			baseString = "IBinaryType " + fileName; //$NON-NLS-1$
+		}
+		if (this.compilationUnit != null) {
+			baseString = "ICompilationUnit " + this.compilationUnit.toString(); //$NON-NLS-1$
+		}
+		if (this.sourceTypes != null) {
+			baseString = this.sourceTypes.toString();
+		}
+		if (this.accessRestriction != null) {
+			baseString += " " + this.accessRestriction.toString(); //$NON-NLS-1$
+		}
+		if (this.externalAnnotationPath != null) {
+			baseString += " extPath=" + this.externalAnnotationPath.toString(); //$NON-NLS-1$
+		}
+		return baseString;
+	}
+	
 	/**
 	 * Returns the associated access restriction, or null if none.
 	 */
 	public AccessRestriction getAccessRestriction() {
 		return this.accessRestriction;
 	}
+
+	public void setBinaryType(IBinaryType newType) {
+		this.binaryType = newType;
+	}
+
 	/**
-	 * Answer the resolved binary form for the type or null if the
-	 * receiver represents a compilation unit or source type.
+	 * Answer the resolved binary form for the type or null if the receiver represents a compilation unit or source
+	 * type.
 	 */
 	public IBinaryType getBinaryType() {
 		return this.binaryType;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/ExceptionInferenceFlowContext.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/ExceptionInferenceFlowContext.java
index a94d4d0..172e28e 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/ExceptionInferenceFlowContext.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/ExceptionInferenceFlowContext.java
@@ -19,7 +19,7 @@
  * try statements, exception handlers, etc...
  */
 
-public class ExceptionInferenceFlowContext extends ExceptionHandlingFlowContext {
+public class ExceptionInferenceFlowContext extends FieldInitsFakingFlowContext {
 	public ExceptionInferenceFlowContext(
 			FlowContext parent,
 			ASTNode associatedNode,
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FieldInitsFakingFlowContext.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FieldInitsFakingFlowContext.java
new file mode 100644
index 0000000..35e1b94
--- /dev/null
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FieldInitsFakingFlowContext.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Till Brychcy 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:
+ *     Till Brychcy - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.compiler.flow;
+
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
+import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
+
+/**
+ * For instances of this class,
+ * {@link FlowContext#getInitsForFinalBlankInitializationCheck(org.eclipse.jdt.internal.compiler.lookup.TypeBinding, FlowInfo)}
+ * will returns a {@link FlowInfo#DEAD_END}, which for which
+ * {@link FlowInfo#isDefinitelyAssigned(org.eclipse.jdt.internal.compiler.lookup.FieldBinding)} returns true for all
+ * fields.
+ */
+
+public class FieldInitsFakingFlowContext extends ExceptionHandlingFlowContext {
+	public FieldInitsFakingFlowContext(
+			FlowContext parent,
+			ASTNode associatedNode,
+			ReferenceBinding[] handledExceptions,
+			FlowContext initializationParent,
+			BlockScope scope,
+			UnconditionalFlowInfo flowInfo) {
+	super(parent, associatedNode, handledExceptions, initializationParent, scope, flowInfo);
+}
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java
index e75997d..ce65992 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java
@@ -514,6 +514,9 @@
 			inits = initializationContext.initsBeforeContext;
 			current = initializationContext.initializationParent;
 		} else if (current instanceof ExceptionHandlingFlowContext) {
+			if(current instanceof FieldInitsFakingFlowContext) {
+				return FlowInfo.DEAD_END; // isDefinitelyAssigned will return true for all fields
+			}
 			ExceptionHandlingFlowContext exceptionContext = (ExceptionHandlingFlowContext) current;
 			current = exceptionContext.initializationParent == null ? exceptionContext.parent : exceptionContext.initializationParent;
 		} else {
@@ -521,7 +524,7 @@
 		}
 	} while (current != null);
 	// not found
-	return null;
+	throw new IllegalStateException(declaringType.debugName());
 }
 
 /*
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java
index 0cc4f11..11a78f3 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java
@@ -1648,8 +1648,7 @@
 		int length = end - start + 1;
 		int count = 0;
 		for (int i = start; i <= end; i++) {
-			int len = this.methods[i].parameters.length;
-			if (len <= suggestedParameterLength || (this.methods[i].isVarargs() && len == suggestedParameterLength + 1))
+			if (this.methods[i].doesParameterLengthMatch(suggestedParameterLength))
 				count++;
 		}
 		if (count == 0) {
@@ -1662,8 +1661,7 @@
 			MethodBinding[] result = new MethodBinding[count];
 			// iterate methods to resolve them
 			for (int i = start, index = 0; i <= end; i++) {
-				int len = this.methods[i].parameters.length;
-				if (len <= suggestedParameterLength || (this.methods[i].isVarargs() && len == suggestedParameterLength + 1))
+				if (this.methods[i].doesParameterLengthMatch(suggestedParameterLength))
 					result[index++] = resolveTypesFor(this.methods[i]);
 			}
 			return result;
@@ -1671,6 +1669,7 @@
 	}
 	return Binding.NO_METHODS;
 }
+
 public boolean hasMemberTypes() {
 	if (!isPrototype())
 		return this.prototype.hasMemberTypes();
@@ -2126,7 +2125,9 @@
 		}
 	}
 	if (useNullTypeAnnotations && this.externalAnnotationStatus.isPotentiallyUnannotatedLib()) {
-		if (methodBinding.returnType.hasNullTypeAnnotations()) {
+		if (methodBinding.returnType.hasNullTypeAnnotations() 
+				|| (methodBinding.tagBits & TagBits.AnnotationNullMASK) != 0 
+				|| methodBinding.parameterNonNullness != null) {
 			this.externalAnnotationStatus = ExternalAnnotationStatus.TYPE_IS_ANNOTATED;
 		} else {
 			for (TypeBinding parameter : parameters) {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java
index a004158..b8ebe83 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java
@@ -849,19 +849,27 @@
 		//  α = S and T <: α imply ⟨T <: S⟩
 		InferenceVariable alpha = boundS.left;
 		TypeBinding s = boundS.right;
-		if (TypeBinding.equalsEquals(alpha,boundT.left))
-			return ConstraintTypeFormula.create(s, boundT.right, boundT.relation, boundT.isSoft||boundS.isSoft);
-		if (TypeBinding.equalsEquals(alpha, boundT.right))
-			return ConstraintTypeFormula.create(boundT.right, s, boundT.relation, boundT.isSoft||boundS.isSoft);
+		if (TypeBinding.equalsEquals(alpha, boundT.left)) {
+			TypeBinding t = boundT.right;
+			return ConstraintTypeFormula.create(s, t, boundT.relation, boundT.isSoft||boundS.isSoft);
+		}
+		if (TypeBinding.equalsEquals(alpha, boundT.right)) {
+			TypeBinding t = boundT.left;
+			return ConstraintTypeFormula.create(t, s, boundT.relation, boundT.isSoft||boundS.isSoft);
+		}
 
 		if (boundS.right instanceof InferenceVariable) {
 			// reverse:
 			alpha = (InferenceVariable) boundS.right;
 			s = boundS.left;
-			if (TypeBinding.equalsEquals(alpha, boundT.left))
-				return ConstraintTypeFormula.create(s, boundT.right, boundT.relation, boundT.isSoft||boundS.isSoft);
-			if (TypeBinding.equalsEquals(alpha, boundT.right))
-				return ConstraintTypeFormula.create(boundT.right, s, boundT.relation, boundT.isSoft||boundS.isSoft);			
+			if (TypeBinding.equalsEquals(alpha, boundT.left)) {
+				TypeBinding t = boundT.right;
+				return ConstraintTypeFormula.create(s, t, boundT.relation, boundT.isSoft||boundS.isSoft);
+			}
+			if (TypeBinding.equalsEquals(alpha, boundT.right)) {
+				TypeBinding t = boundT.left;
+				return ConstraintTypeFormula.create(t, s, boundT.relation, boundT.isSoft||boundS.isSoft);
+			}			
 		}
 		
 		//  α = U and S <: T imply ⟨S[α:=U] <: T[α:=U]⟩ 
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CaptureBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CaptureBinding.java
index 59baf79..d9d49ee 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CaptureBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CaptureBinding.java
@@ -147,6 +147,7 @@
 	 * e.g. given X<U, V extends X<U, V>>,     capture(X<E,?>) = X<E,capture>, where capture extends X<E,capture>
 	 */
 	public void initializeBounds(Scope scope, ParameterizedTypeBinding capturedParameterizedType) {
+		boolean is18plus = scope.compilerOptions().complianceLevel >= ClassFileConstants.JDK1_8;
 		TypeVariableBinding wildcardVariable = this.wildcard.typeVariable();
 		if (wildcardVariable == null) {
 			// error resilience when capturing Zork<?>
@@ -155,7 +156,9 @@
 			switch (this.wildcard.boundKind) {
 				case Wildcard.EXTENDS :
 					// still need to capture bound supertype as well so as not to expose wildcards to the outside (111208)
-					TypeBinding capturedWildcardBound = originalWildcardBound.capture(scope, this.start, this.end);
+					TypeBinding capturedWildcardBound = is18plus
+							? originalWildcardBound // as spec'd
+							: originalWildcardBound.capture(scope, this.start, this.end); // for compatibility with old behavior at 1.7-
 					if (originalWildcardBound.isInterface()) {
 						this.setSuperClass(scope.getJavaLangObject());
 						this.setSuperInterfaces(new ReferenceBinding[] { (ReferenceBinding) capturedWildcardBound });
@@ -207,7 +210,9 @@
 		switch (this.wildcard.boundKind) {
 			case Wildcard.EXTENDS :
 				// still need to capture bound supertype as well so as not to expose wildcards to the outside (111208)
-				TypeBinding capturedWildcardBound = originalWildcardBound.capture(scope, this.start, this.end);
+				TypeBinding capturedWildcardBound = is18plus
+							? originalWildcardBound // as spec'd
+							: originalWildcardBound.capture(scope, this.start, this.end); // for compatibility with old behavior at 1.7-
 //{ObjectTeams: is the bound a role type requiring wrapping?
 				if (capturedWildcardBound.isRole())
 					capturedWildcardBound = RoleTypeCreator.maybeWrapUnqualifiedRoleType(scope, capturedWildcardBound.enclosingType(), capturedWildcardBound, scope.methodScope().referenceMethod(), scope.problemReporter());
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintTypeFormula.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintTypeFormula.java
index 7ce5578..f1a3933 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintTypeFormula.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ConstraintTypeFormula.java
@@ -108,6 +108,9 @@
 				if (this.left.kind() != Binding.WILDCARD_TYPE) {
 					return ConstraintTypeFormula.create(this.left, this.right, SAME, this.isSoft);						
 				} else {
+					// TODO: speculative addition:
+					if (this.right instanceof InferenceVariable)
+						return new TypeBound((InferenceVariable) this.right, this.left, SAME, this.isSoft);
 					return FALSE;
 				}
 			} else {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ImplicitNullAnnotationVerifier.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ImplicitNullAnnotationVerifier.java
index ad48663..4d5d04e 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ImplicitNullAnnotationVerifier.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ImplicitNullAnnotationVerifier.java
@@ -15,6 +15,7 @@
 import java.util.List;
 import java.util.Set;
 
+import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.ast.ASTNode;
 import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.Argument;
@@ -202,11 +203,15 @@
 	private void collectOverriddenMethods(MethodBinding original, char[] selector, int suggestedParameterLength,
 			ReferenceBinding superType, Set ifcsSeen, List result) 
 	{
-		MethodBinding [] ifcMethods = superType.getMethods(selector, suggestedParameterLength);
+		MethodBinding [] ifcMethods = superType.unResolvedMethods();
 		int length = ifcMethods.length;
 		boolean added = false;
 		for  (int i=0; i<length; i++) {
 			MethodBinding currentMethod = ifcMethods[i];
+			if (!CharOperation.equals(selector, currentMethod.selector))
+				continue;
+			if (!currentMethod.doesParameterLengthMatch(suggestedParameterLength))
+				continue;
 			if (currentMethod.isStatic())
 				continue;
 			if (MethodVerifier.doesMethodOverride(original, currentMethod, this.environment)) {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java
index 6451ae5..e3995ce 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java
@@ -1880,7 +1880,7 @@
 	// type must be a ReferenceBinding at this point, cannot be a BaseTypeBinding or ArrayTypeBinding
 	ReferenceBinding actualType = (ReferenceBinding) type;
 	if (actualType instanceof UnresolvedReferenceBinding)
-		if (CharOperation.indexOf('$', actualType.compoundName[actualType.compoundName.length - 1]) > 0)
+		if (actualType.depth() > 0)
 			actualType = (ReferenceBinding) BinaryTypeBinding.resolveType(actualType, this, false /* no raw conversion */); // must resolve member types before asking for enclosingType
 	ReferenceBinding actualEnclosing = actualType.enclosingType();
 
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java
index e8b1119..a52c037 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java
@@ -1922,4 +1922,8 @@
 public boolean isVoidMethod() {
 	return this.returnType == TypeBinding.VOID;
 }
+public boolean doesParameterLengthMatch(int suggestedParameterLength) {
+	int len = this.parameters.length;
+	return len <= suggestedParameterLength || (isVarargs() && len == suggestedParameterLength + 1);
+}
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodScope.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodScope.java
index 35727fe..fd2072a 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodScope.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodScope.java
@@ -250,6 +250,7 @@
 				problemReporter().illegalModifierForAnnotationMember((AbstractMethodDeclaration) this.referenceContext);
 			else
 				problemReporter().illegalModifierForInterfaceMethod((AbstractMethodDeclaration) this.referenceContext, isJDK18orGreater);
+			methodBinding.modifiers &= (expectedModifiers | ~ExtraCompilerModifiers.AccJustFlag);
 		}
 		return;
 	}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java
index 31928d6..41307d3 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2016 IBM Corporation and others.
+ * Copyright (c) 2000, 2015 IBM Corporation 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
@@ -207,20 +207,7 @@
 	if (packageBinding != null && packageBinding != LookupEnvironment.TheNotFoundPackage) {
 		return packageBinding;
 	}
-
-	if (packageBinding == null) { // have not looked for it before
-		if ((packageBinding = findPackage(name)) != null) {
-			return packageBinding;
-		}
-		if (referenceBinding != null && referenceBinding != LookupEnvironment.TheNotFoundType) {
-			return referenceBinding; // found cached missing type - check if package conflict
-		}
-		addNotFoundPackage(name);
-	}
-
 	if (referenceBinding == null) { // have not looked for it before
-		//This call (to askForType) should be the last option to call, because the call is very expensive regarding performance
-		// (a search for secondary types may get triggered which requires to parse all classes of a package).
 		if ((referenceBinding = this.environment.askForType(this, name)) != null) {
 			if (referenceBinding.isNestedType()) {
 				return new ProblemReferenceBinding(new char[][]{name}, referenceBinding, ProblemReasons.InternalNameProvided);
@@ -233,6 +220,16 @@
 		addNotFoundType(name);
 	}
 
+	if (packageBinding == null) { // have not looked for it before
+		if ((packageBinding = findPackage(name)) != null) {
+			return packageBinding;
+		}
+		if (referenceBinding != null && referenceBinding != LookupEnvironment.TheNotFoundType) {
+			return referenceBinding; // found cached missing type - check if package conflict
+		}
+		addNotFoundPackage(name);
+	}
+
 	return null;
 }
 public final boolean isViewedAsDeprecated() {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SignatureWrapper.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SignatureWrapper.java
index c76163a..a967cf2 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SignatureWrapper.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SignatureWrapper.java
@@ -192,6 +192,15 @@
 		return CharOperation.subarray(this.signature, this.start, this.signature.length);
 	}
 	public String toString() {
+		if (this.start >= 0 && this.start <= this.signature.length) {
+			return new String(CharOperation.subarray(this.signature, 0, this.start)) + " ^ " //$NON-NLS-1$
+					+ new String(CharOperation.subarray(this.signature, this.start, this.signature.length));
+		}
+
 		return new String(this.signature) + " @ " + this.start; //$NON-NLS-1$
 	}
+
+	public char charAtStart() {
+		return this.signature[this.start];
+	}
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java
index 3a8231a..e059649 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java
@@ -182,9 +182,9 @@
 	private int nullnessDefaultInitialized = 0; // 0: nothing; 1: type; 2: package
 	private int lambdaOrdinal = 0;
 	private ReferenceBinding containerAnnotationType = null;
+
+	public ExternalAnnotationProvider externalAnnotationProvider;
 	
-	public ExternalAnnotationProvider externalAnnotationProvider;

-	

 public SourceTypeBinding(char[][] compoundName, PackageBinding fPackage, ClassScope scope) {
 //{ObjectTeams:	// share model from TypeDeclaration:
 	super(scope.referenceContext.getModel());
@@ -2430,7 +2430,7 @@
 }
 public MethodBinding resolveTypesFor(MethodBinding method, boolean fromSynthetic) {
 // SH}
-
+	
 	if (!isPrototype())
 		return this.prototype.resolveTypesFor(method);
 	
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/UnresolvedReferenceBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/UnresolvedReferenceBinding.java
index 6cbccb6..6d95fe9 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/UnresolvedReferenceBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/UnresolvedReferenceBinding.java
@@ -75,8 +75,9 @@
 }
 public int depth() {
 	// we don't yet have our enclosing types wired, but we know the nesting depth from our compoundName:
+	// (NOTE: this an upper bound, because class names may contain '$')
 	int last = this.compoundName.length-1;
-	return CharOperation.occurencesOf('$', this.compoundName[last]);
+	return CharOperation.occurencesOf('$', this.compoundName[last], 1); // leading '$' must be part of the class name, so start at 1.
 }
 public boolean hasTypeBit(int bit) {
 	// shouldn't happen since we are not called before analyseCode(), but play safe:
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/RecoveredInitializer.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/RecoveredInitializer.java
index 09290e1..fdc72b2 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/RecoveredInitializer.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/RecoveredInitializer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation 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
@@ -62,9 +62,11 @@
 		this.foundOpeningBrace = true;
 		this.bracketBalance++;
 	}
-	this.initializerBody = new RecoveredBlock(nestedBlockDeclaration, this, bracketBalanceValue);
+	if (this.initializerBody == null) {
+		return this.initializerBody = new RecoveredBlock(nestedBlockDeclaration, this, bracketBalanceValue);
+	}
 	if (nestedBlockDeclaration.sourceEnd == 0) return this.initializerBody;
-	return this;
+	return this.initializerBody.add(nestedBlockDeclaration, bracketBalanceValue, true);
 }
 /*
  * Record a field declaration (act like inside method body)
@@ -109,18 +111,26 @@
 		return this.parent.add(localDeclaration, bracketBalanceValue);
 	}
 	/* method body should have been created */
-	Block block = new Block(0);
-	block.sourceStart = ((Initializer)this.fieldDeclaration).sourceStart;
-	RecoveredElement element = this.add(block, 1);
-	if (this.initializerBody != null) {
-		this.initializerBody.attachPendingModifiers(
+	if (this.initializerBody == null) {
+		Block block = new Block(0);
+		block.sourceStart = ((Initializer)this.fieldDeclaration).sourceStart;
+		RecoveredElement element = this.add(block, 1);
+		if (this.bracketBalance > 0){
+			for (int i = 0; i < this.bracketBalance - 1; i++){
+				element = element.add(new Block(0), 1);
+			}
+			this.bracketBalance = 1;
+		}
+		return element.add(localDeclaration, bracketBalanceValue);
+	}
+	this.initializerBody.attachPendingModifiers(
 				this.pendingAnnotations,
 				this.pendingAnnotationCount,
 				this.pendingModifiers,
 				this.pendingModifersSourceStart);
-	}
 	resetPendingModifiers();
-	return element.add(localDeclaration, bracketBalanceValue);
+
+	return this.initializerBody.add(localDeclaration, bracketBalanceValue, true);
 }
 /*
  * Record a statement - regular method should have been created a block body
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/RecoveredMethod.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/RecoveredMethod.java
index 7dd7b54..7e79ea1 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/RecoveredMethod.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/RecoveredMethod.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation 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
@@ -99,7 +99,11 @@
 		this.bracketBalance++;
 	}
 
-	this.methodBody = new RecoveredBlock(nestedBlockDeclaration, this, bracketBalanceValue);
+	if (this.methodBody != null) {
+		this.methodBody.addBlockStatement(new RecoveredBlock(nestedBlockDeclaration, this, bracketBalanceValue));
+	} else {
+		this.methodBody = new RecoveredBlock(nestedBlockDeclaration, this, bracketBalanceValue);
+	}
 	if (nestedBlockDeclaration.sourceEnd == 0) return this.methodBody;
 	return this;
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java
index 827613a..8e7d097 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java
@@ -4,7 +4,7 @@
  * 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:
  *     IBM Corporation - initial API and implementation
  *     Benjamin Muskalla - Contribution for bug 239066
@@ -10090,7 +10090,11 @@
 					nullityMismatchSpecdNullable(expression, requiredType, annotationName);
 					return;
 				}
-		nullityMismatchPotentiallyNull(expression, requiredType, annotationName);
+			if (expression instanceof ArrayReference && expression.resolvedType.isFreeTypeVariable()) {
+				nullityMismatchingTypeAnnotation(expression, providedType, requiredType, NullAnnotationMatching.NULL_ANNOTATIONS_MISMATCH);
+				return;
+			}
+			nullityMismatchPotentiallyNull(expression, requiredType, annotationName);
 		return;
 	}
 	if (this.options.usesNullTypeAnnotations())
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/LineBreaksPreparator.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/LineBreaksPreparator.java
index 96540d0..a5a11f3 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/LineBreaksPreparator.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/LineBreaksPreparator.java
@@ -289,18 +289,14 @@
 		if (node.getBody() == null)
 			return true;
 
-		if (node.isConstructor()) {
-			handleBracedCode(node.getBody(), null, this.options.brace_position_for_constructor_declaration,
-					this.options.indent_statements_compare_to_body,
-					this.options.insert_new_line_in_empty_method_body);
-		} else {
-			handleBracedCode(node.getBody(), null, this.options.brace_position_for_method_declaration,
-					this.options.indent_statements_compare_to_body,
-					this.options.insert_new_line_in_empty_method_body);
-			Token openBrace = this.tm.firstTokenIn(node.getBody(), TokenNameLBRACE);
-			if (openBrace.getLineBreaksAfter() > 0) // if not, these are empty braces
-				openBrace.putLineBreaksAfter(this.options.blank_lines_at_beginning_of_method_body + 1);
-		}
+		String bracePosition = node.isConstructor() ? this.options.brace_position_for_constructor_declaration
+				: this.options.brace_position_for_method_declaration;
+		handleBracedCode(node.getBody(), null, bracePosition,
+				this.options.indent_statements_compare_to_body,
+				this.options.insert_new_line_in_empty_method_body);
+		Token openBrace = this.tm.firstTokenIn(node.getBody(), TokenNameLBRACE);
+		if (openBrace.getLineBreaksAfter() > 0) // if not, these are empty braces
+			openBrace.putLineBreaksAfter(this.options.blank_lines_at_beginning_of_method_body + 1);
 		return true;
 	}
 
@@ -448,10 +444,6 @@
 	@Override
 	public boolean visit(NormalAnnotation node) {
 		handleAnnotation(node);
-
-		int lParen = this.tm.firstIndexAfter(node.getTypeName(), TokenNameLPAREN);
-		int rParen = this.tm.lastIndexIn(node, TokenNameRPAREN);
-		handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_annotation);
 		return true;
 	}
 
@@ -551,6 +543,12 @@
 		}
 		if (breakAfter)
 			this.tm.lastTokenIn(node, -1).breakAfter();
+
+		if (!(node instanceof MarkerAnnotation)) {
+			int lParen = this.tm.firstIndexAfter(node.getTypeName(), TokenNameLPAREN);
+			int rParen = this.tm.lastIndexIn(node, TokenNameRPAREN);
+			handleParenthesesPositions(lParen, rParen, this.options.parenthesis_positions_in_annotation);
+		}
 	}
 
 	@Override
@@ -819,7 +817,7 @@
 				//$FALL-THROUGH$
 			case DefaultCodeFormatterConstants.SEPARATE_LINES:
 			case DefaultCodeFormatterConstants.PRESERVE_POSITIONS:
-				boolean always = positionsSetting != DefaultCodeFormatterConstants.PRESERVE_POSITIONS;
+				boolean always = !positionsSetting.equals(DefaultCodeFormatterConstants.PRESERVE_POSITIONS);
 				Token afterOpening = this.tm.get(openingParenIndex + 1);
 				if (always || this.tm.countLineBreaksBetween(this.tm.get(openingParenIndex), afterOpening) > 0) {
 					afterOpening.setWrapPolicy(
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapExecutor.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapExecutor.java
index 9783803..bd12cf7 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapExecutor.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapExecutor.java
@@ -122,6 +122,7 @@
 		 */
 		public int analyzeLine(int startIndex, int indent) {
 			Token startToken = WrapExecutor.this.tm.get(startIndex);
+			assert startToken.getLineBreaksBefore() > 0;
 			this.counter = WrapExecutor.this.tm.toIndent(indent, startToken.isWrappable());
 			this.lineIndent = indent;
 			this.firstPotentialWrap = -1;
@@ -170,11 +171,11 @@
 			if (this.lineExceeded && this.firstPotentialWrap >= 0) {
 				return false;
 			}
-			if (!token.isNextLineOnWrap())
-				token.setIndent(this.lineIndent);
+			token.setIndent(this.lineIndent);
 
-			boolean isLineEnd = getLineBreaksAfter() > 0 || getNext() == null;
-			assert !(token.isNextLineOnWrap() && !isLineEnd);
+			boolean isLineEnd = getLineBreaksAfter() > 0 || getNext() == null
+					|| (getNext().isNextLineOnWrap() && WrapExecutor.this.tm
+							.get(WrapExecutor.this.tm.findFirstTokenInLine(index)).isWrappable());
 			return !isLineEnd;
 		}
 
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java
index f745dd8..d59106d 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java
@@ -586,13 +586,18 @@
 			prepareElementsList(expressions, TokenNameCOMMA, TokenNameLBRACE);
 			handleWrap(this.options.alignment_for_expressions_in_array_initializer, node);
 		}
+		int openingBraceIndex = this.tm.firstIndexIn(node, TokenNameLBRACE);
+		Token openingBrace = this.tm.get(openingBraceIndex);
+		if (openingBrace.isNextLineOnWrap() && openingBrace.getWrapPolicy() == null && openingBraceIndex > 0) {
+			// add fake wrap policy to make sure the brace indentation is right
+			openingBrace.setWrapPolicy(new WrapPolicy(WrapMode.DISABLED, openingBraceIndex - 1, 0));
+		}
 		if (!this.options.join_wrapped_lines
 				&& !this.options.insert_new_line_before_closing_brace_in_array_initializer) {
 			// if there is a line break before the closing brace, formatter should treat it as a valid wrap to preserve
 			int closingBraceIndex = this.tm.lastIndexIn(node, TokenNameRBRACE);
 			Token closingBrace = this.tm.get(closingBraceIndex);
 			if (this.tm.countLineBreaksBetween(this.tm.get(closingBraceIndex - 1), closingBrace) == 1) {
-				int openingBraceIndex = this.tm.firstIndexIn(node, TokenNameLBRACE);
 				closingBrace.setWrapPolicy(new WrapPolicy(WrapMode.WHERE_NECESSARY, openingBraceIndex,
 						closingBraceIndex, 0, this.currentDepth, 1, true, false));
 			}
@@ -899,7 +904,7 @@
 		if (policy == null)
 			return;
 
-		setTokenWrapPolicy(this.wrapIndexes.get(0), policy, true);
+		setTokenWrapPolicy(0, policy, true);
 
 		boolean wrapPreceedingComments = !(parentNode instanceof InfixExpression)
 				|| !this.options.wrap_before_binary_operator;
@@ -907,7 +912,7 @@
 			penalty = this.wrapPenalties.size() > i ? this.wrapPenalties.get(i) : 1;
 			if (penalty != policy.penaltyMultiplier || i == 1)
 				policy = getWrapPolicy(wrappingOption, penalty, false, parentNode);
-			setTokenWrapPolicy(this.wrapIndexes.get(i), policy, wrapPreceedingComments);
+			setTokenWrapPolicy(i, policy, wrapPreceedingComments);
 		}
 
 		boolean forceWrap = (wrappingOption & Alignment.M_FORCE) != 0;
@@ -939,7 +944,8 @@
 		}
 	}
 
-	private void setTokenWrapPolicy(int index, WrapPolicy policy, boolean wrapPreceedingComments) {
+	private void setTokenWrapPolicy(int wrapIndexesIndex, WrapPolicy policy, boolean wrapPreceedingComments) {
+		int index = this.wrapIndexes.get(wrapIndexesIndex);
 		if (wrapPreceedingComments) {
 			for (int i = index - 1; i >= 0; i--) {
 				Token previous = this.tm.get(i);
@@ -950,6 +956,7 @@
 				if (previous.getLineBreaksBefore() > 0)
 					previous.setWrapPolicy(policy);
 			}
+			this.wrapIndexes.set(wrapIndexesIndex, index);
 		}
 
 		Token token = this.tm.get(index);
@@ -972,6 +979,8 @@
 		} else if (parentNode instanceof EnumDeclaration) {
 			// special behavior for compatibility with legacy formatter
 			extraIndent = ((wrappingOption & Alignment.M_INDENT_BY_ONE) != 0) ? 2 : 1;
+			if (!this.options.indent_body_declarations_compare_to_enum_declaration_header)
+				extraIndent--;
 			isAlreadyWrapped = isFirst;
 		} else if (parentNode instanceof IfStatement) {
 			extraIndent = 1;
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/IOpenable.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/IOpenable.java
index 0702061..515a8ff 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/IOpenable.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/IOpenable.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation 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
@@ -10,6 +10,7 @@
  *******************************************************************************/
 package org.eclipse.jdt.core;
 
+import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.IProgressMonitor;
 
 /**
@@ -46,7 +47,7 @@
  * Closes this element and its buffer (if any).
  * Closing an element which is not open has no effect.
  *
- * <p>Note: although {@link #close} is exposed in the API, clients are
+ * <p>Note: Although {@link #close} is exposed in the API, clients are
  * not expected to open and close elements - the Java model does this automatically
  * as elements are accessed.
  *
@@ -114,6 +115,13 @@
 boolean isConsistent() throws JavaModelException;
 /**
  * Returns whether this openable is open. This is a handle-only method.
+ * 
+ * <p>Note: This method doesn't tell whether an {@link IJavaProject}'s {@link IJavaProject#getProject() getProject()} is open.
+ * It is <b>not</b> equivalent to {@link IProject#isOpen()}!</p>
+ * 
+ * <p>Note: Although {@link #isOpen} is exposed in the API, clients
+ * rarely have a need to rely on this internal state of the Java model.</p>
+
  * @return true if this openable is open, false otherwise
  */
 boolean isOpen();
@@ -142,7 +150,7 @@
  * Opens this element and all parent elements that are not already open.
  * For compilation units, a buffer is opened on the contents of the underlying resource.
  *
- * <p>Note: although {@link #open} is exposed in the API, clients are
+ * <p>Note: Although {@link #open} is exposed in the API, clients are
  * not expected to open and close elements - the Java model does this automatically
  * as elements are accessed.
  *
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/IType.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/IType.java
index 5ee63a6..f215642 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/IType.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/IType.java
@@ -36,6 +36,11 @@
  * <code>IMethod</code>, <code>IInitializer</code> and <code>IType</code>.
  * The children are listed in the order in which they appear in the source or class file.
  * </p>
+ * <p>
+ * Caveat: The {@link #getChildren() children} of a {@link #isBinary() binary} type include
+ * nested types. However, the {@link #getParent() parent} of such a nested binary type is
+ * <em>not</em> the enclosing type, but that nested type's {@link IClassFile}!
+ * </p>
  *
  * @noimplement This interface is not intended to be implemented by clients.
  */
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java
index 84bd065..5eef533 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java
@@ -157,6 +157,7 @@
 import org.eclipse.jdt.internal.core.*;
 import org.eclipse.jdt.internal.core.builder.JavaBuilder;
 import org.eclipse.jdt.internal.core.builder.State;
+import org.eclipse.jdt.internal.core.nd.indexer.Indexer;
 import org.eclipse.jdt.internal.core.util.MementoTokenizer;
 import org.eclipse.jdt.internal.core.util.Messages;
 import org.eclipse.jdt.internal.core.util.Util;
@@ -5877,5 +5878,6 @@
 		super.start(context);
 		JavaModelManager.registerDebugOptionsListener(context);
 		JavaModelManager.getJavaModelManager().startup();
+		Indexer.getInstance().rescanAll();
 	}
 }
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/CompilationParticipant.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/CompilationParticipant.java
index c40c139..34ae1a2 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/CompilationParticipant.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/CompilationParticipant.java
@@ -101,8 +101,11 @@
  * given project should return <code>false</code> for that project.
  * </p><p>
  * Note: In {@link org.eclipse.jdt.core.WorkingCopyOwner#newWorkingCopy(String, org.eclipse.jdt.core.IClasspathEntry[], org.eclipse.core.runtime.IProgressMonitor)
- * special cases}, the project may be closed and not exist. Participants typically return false for projects that are
- * !{@link IJavaProject#isOpen()}.
+ * special cases}, the project may be closed and not exist. Participants typically return false when the
+ * underlying project is closed. I.e. when the following check returns false:
+ *  <pre>
+ * 	javaProject.getProject().isOpen();
+ * </pre>
  * </p>
  * @param project the project to participate in
  * @return whether this participant is active for a given project
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/util/ExternalAnnotationUtil.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/util/ExternalAnnotationUtil.java
index 6d15fb2..dad8397 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/util/ExternalAnnotationUtil.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/util/ExternalAnnotationUtil.java
@@ -294,7 +294,7 @@
 						newContent.append('\n');
 						continue;
 					}
-					if (!Character.isJavaIdentifierStart(line.charAt(0))) {
+					if (!Character.isJavaIdentifierStart(line.charAt(0)) && line.charAt(0) != '<') {
 						newContent.append(line).append('\n');
 						continue;
 					}
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/BinaryType.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/BinaryType.java
index f895be9..978704c 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/BinaryType.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/BinaryType.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation 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
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClassFile.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClassFile.java
index 000c463..9871f47 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClassFile.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClassFile.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation 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
@@ -37,11 +37,15 @@
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jdt.core.*;
 import org.eclipse.jdt.core.compiler.IProblem;
-import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationDecorator;
+import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationProvider;
 import org.eclipse.jdt.internal.compiler.env.IBinaryType;
-import org.eclipse.jdt.internal.compiler.env.IDependent;
 import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
+import org.eclipse.jdt.internal.core.nd.java.JavaNames;
+import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeDescriptor;
+import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeFactory;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
 import org.eclipse.jdt.internal.core.util.MementoTokenizer;
 import org.eclipse.jdt.internal.core.util.Util;
 import org.eclipse.objectteams.otdt.core.OTModelManager;
@@ -93,8 +97,9 @@
  * @see Openable
  * @see Signature
  */
+@Override
 protected boolean buildStructure(OpenableElementInfo info, IProgressMonitor pm, Map newElements, IResource underlyingResource) throws JavaModelException {
-	IBinaryType typeInfo = getBinaryTypeInfo((IFile) underlyingResource);
+	IBinaryType typeInfo = getBinaryTypeInfo();
 	if (typeInfo == null) {
 		// The structure of a class file is unknown if a class file format errors occurred
 		//during the creation of the diet class file representative of this ClassFile.
@@ -124,6 +129,7 @@
  * @see ICodeAssist#codeComplete(int, ICompletionRequestor)
  * @deprecated
  */
+@Deprecated
 public void codeComplete(int offset, ICompletionRequestor requestor) throws JavaModelException {
 	codeComplete(offset, requestor, DefaultWorkingCopyOwner.PRIMARY);
 }
@@ -131,6 +137,7 @@
  * @see ICodeAssist#codeComplete(int, ICompletionRequestor, WorkingCopyOwner)
  * @deprecated
  */
+@Deprecated
 public void codeComplete(int offset, ICompletionRequestor requestor, WorkingCopyOwner owner) throws JavaModelException {
 	if (requestor == null) {
 		throw new IllegalArgumentException("Completion requestor cannot be null"); //$NON-NLS-1$
@@ -197,9 +204,11 @@
 /**
  * Returns a new element info for this element.
  */
+@Override
 protected Object createElementInfo() {
 	return new ClassFileInfo();
 }
+@Override
 public boolean equals(Object o) {
 	if (!(o instanceof ClassFile)) return false;
 	ClassFile other = (ClassFile) o;
@@ -227,7 +236,7 @@
 			return false;
 		}
 		try {
-			info = getJarBinaryTypeInfo((PackageFragment) getParent(), true/*fully initialize so as to not keep a reference to the byte array*/);
+			info = getJarBinaryTypeInfo();
 		} catch (CoreException e) {
 			// leave info null
 		} catch (IOException e) {
@@ -277,6 +286,7 @@
 	}
 	return null;
 }
+@Override
 public String getAttachedJavadoc(IProgressMonitor monitor) throws JavaModelException {
 	return getType().getAttachedJavadoc(monitor);
 }
@@ -292,40 +302,26 @@
  * @exception JavaModelException when the IFile resource or JAR is not available
  * or when this class file is not present in the JAR
  */
-public IBinaryType getBinaryTypeInfo(IFile file) throws JavaModelException {
-	return getBinaryTypeInfo(file, true/*fully initialize so as to not keep a reference to the byte array*/);
-}
-public IBinaryType getBinaryTypeInfo(IFile file, boolean fullyInitialize) throws JavaModelException {
-	JavaElement pkg = (JavaElement) getParent();
-	if (pkg instanceof JarPackageFragment) {
-		try {
-			IBinaryType info = getJarBinaryTypeInfo((PackageFragment) pkg, fullyInitialize);
-			if (info == null) {
-				throw newNotPresentException();
-			}
-			return info;
-		} catch (ClassFormatException cfe) {
-			//the structure remains unknown
-			if (JavaCore.getPlugin().isDebugging()) {
-				cfe.printStackTrace(System.err);
-			}
-			return null;
-		} catch (IOException ioe) {
-			throw new JavaModelException(ioe, IJavaModelStatusConstants.IO_EXCEPTION);
-		} catch (CoreException e) {
-			if (e instanceof JavaModelException) {
-				throw (JavaModelException)e;
-			} else {
-				throw new JavaModelException(e);
-			}
+public IBinaryType getBinaryTypeInfo() throws JavaModelException {
+	try {
+		IBinaryType info = getJarBinaryTypeInfo();
+		if (info == null) {
+			throw newNotPresentException();
 		}
-	} else {
-		byte[] contents = Util.getResourceContentsAsByteArray(file);
-		try {
-			return new ClassFileReader(contents, file.getFullPath().toString().toCharArray(), fullyInitialize);
-		} catch (ClassFormatException cfe) {
-			//the structure remains unknown
-			return null;
+		return info;
+	} catch (ClassFormatException cfe) {
+		//the structure remains unknown
+		if (JavaCore.getPlugin().isDebugging()) {
+			cfe.printStackTrace(System.err);
+		}
+		return null;
+	} catch (IOException ioe) {
+		throw new JavaModelException(ioe, IJavaModelStatusConstants.IO_EXCEPTION);
+	} catch (CoreException e) {
+		if (e instanceof JavaModelException) {
+			throw (JavaModelException)e;
+		} else {
+			throw new JavaModelException(e);
 		}
 	}
 }
@@ -359,44 +355,58 @@
 		return Util.getResourceContentsAsByteArray(file);
 	}
 }
-private IBinaryType getJarBinaryTypeInfo(PackageFragment pkg, boolean fullyInitialize) throws CoreException, IOException, ClassFormatException {
-	JarPackageFragmentRoot root = (JarPackageFragmentRoot) pkg.getParent();
-	ZipFile zip = null;
-	ZipFile annotationZip = null;
-	try {
-		zip = root.getJar();
-		String entryName = Util.concatWith(pkg.names, getElementName(), '/');
-		ZipEntry ze = zip.getEntry(entryName);
-		if (ze != null) {
-			byte contents[] = org.eclipse.jdt.internal.compiler.util.Util.getZipEntryByteContent(ze, zip);
-			String fileName = root.getHandleIdentifier() + IDependent.JAR_FILE_ENTRY_SEPARATOR + entryName;
-			ClassFileReader reader = new ClassFileReader(contents, fileName.toCharArray(), fullyInitialize);
-			if (root.getKind() == IPackageFragmentRoot.K_BINARY) {
-				JavaProject javaProject = (JavaProject) getAncestor(IJavaElement.JAVA_PROJECT);
-				IClasspathEntry entry = javaProject.getClasspathEntryFor(getPath());
-				if (entry != null) {
-					IProject project = javaProject.getProject();
-					IPath externalAnnotationPath = ClasspathEntry.getExternalAnnotationPath(entry, project, false); // unresolved for use in ExternalAnnotationTracker
-					if (externalAnnotationPath != null) {
-						setupExternalAnnotationProvider(project, externalAnnotationPath, annotationZip, reader, 
-								entryName.substring(0, entryName.length() - SuffixConstants.SUFFIX_CLASS.length));
-					} else if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
-						reader.markAsFromSource();
-					}
-				}
-			}
-			return reader;
-		}
-	} finally {
-		JavaModelManager.getJavaModelManager().closeZipFile(zip);
-		JavaModelManager.getJavaModelManager().closeZipFile(annotationZip);
-	}
-	return null;
+
+public String getName() {
+	return this.name;
 }
 
-private void setupExternalAnnotationProvider(IProject project, final IPath externalAnnotationPath,
-		ZipFile annotationZip, ClassFileReader reader, final String typeName)
+private IBinaryType getJarBinaryTypeInfo() throws CoreException, IOException, ClassFormatException {
+	BinaryTypeDescriptor descriptor = BinaryTypeFactory.createDescriptor(this);
+
+	if (descriptor == null) {
+		return null;
+	}
+
+	IBinaryType result = BinaryTypeFactory.readType(descriptor, null);
+
+	if (result == null) {
+		return null;
+	}
+
+	// TODO(sxenos): setup the external annotation provider if the IBinaryType came from the index
+	// TODO(sxenos): the old code always passed null as the third argument to setupExternalAnnotationProvider,
+	// but this looks like a bug. I've preserved it for now but we need to figure out what was supposed to go
+	// there.
+	PackageFragment pkg = (PackageFragment) getParent();
+	IJavaElement grandparent = pkg.getParent();
+	if (grandparent instanceof JarPackageFragmentRoot) {
+		JarPackageFragmentRoot root = (JarPackageFragmentRoot) grandparent;
+
+		if (root.getKind() == IPackageFragmentRoot.K_BINARY) {
+			JavaProject javaProject = (JavaProject) getAncestor(IJavaElement.JAVA_PROJECT);
+			IClasspathEntry entry = javaProject.getClasspathEntryFor(getPath());
+			if (entry != null) {
+				String entryName = new String(CharArrayUtils.concat(
+						JavaNames.fieldDescriptorToBinaryName(descriptor.fieldDescriptor), SuffixConstants.SUFFIX_CLASS));
+				IProject project = javaProject.getProject();
+				IPath externalAnnotationPath = ClasspathEntry.getExternalAnnotationPath(entry, project, false); // unresolved for use in ExternalAnnotationTracker
+				if (externalAnnotationPath != null) {
+					result = setupExternalAnnotationProvider(project, externalAnnotationPath, null, result, 
+						entryName.substring(0, entryName.length() - SuffixConstants.SUFFIX_CLASS.length));
+				} else if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
+					result = new ExternalAnnotationDecorator(result, true);
+				}
+			}
+		}
+	}
+
+	return result;
+}
+
+private IBinaryType setupExternalAnnotationProvider(IProject project, final IPath externalAnnotationPath,
+		ZipFile annotationZip, IBinaryType reader, final String typeName)
 {
+	IBinaryType result = reader;
 	// try resolve path within the workspace:
 	IWorkspaceRoot root = project.getWorkspace().getRoot();
 	IResource resource;
@@ -410,26 +420,32 @@
 	String resolvedPath;
 	if (resource.exists()) {
 		if (resource.isVirtual()) {
-			Util.log(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, 
+			Util.log(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID,
 					"Virtual resource "+externalAnnotationPath+" cannot be used as annotationpath for project "+project.getName())); //$NON-NLS-1$ //$NON-NLS-2$
-			return;
+			return reader;
 		}
 		resolvedPath = resource.getLocation().toString(); // workspace lookup succeeded -> resolve it
 	} else {
 		resolvedPath = externalAnnotationPath.toString(); // not in workspace, use as is
 	}
 	try {
-		annotationZip = reader.setExternalAnnotationProvider(resolvedPath, typeName, annotationZip, new ClassFileReader.ZipFileProducer() {
-			@Override public ZipFile produce() throws IOException {
-				try {
-					return JavaModelManager.getJavaModelManager().getZipFile(externalAnnotationPath); // use (absolute, but) unresolved path here
-				} catch (CoreException e) {
-					throw new IOException("Failed to read annotation file for "+typeName+" from "+externalAnnotationPath.toString(), e); //$NON-NLS-1$ //$NON-NLS-2$
-				}
-			}});
+		if (annotationZip == null) {
+			annotationZip = ExternalAnnotationDecorator.getAnnotationZipFile(resolvedPath, new ExternalAnnotationDecorator.ZipFileProducer() {
+				@Override public ZipFile produce() throws IOException {
+					try {
+						return JavaModelManager.getJavaModelManager().getZipFile(externalAnnotationPath); // use (absolute, but) unresolved path here
+					} catch (CoreException e) {
+						throw new IOException("Failed to read annotation file for "+typeName+" from "+externalAnnotationPath.toString(), e); //$NON-NLS-1$ //$NON-NLS-2$
+					}
+				}});
+		}
+
+		ExternalAnnotationProvider annotationProvider = ExternalAnnotationDecorator
+				.externalAnnotationProvider(resolvedPath, typeName, annotationZip);
+		result = new ExternalAnnotationDecorator(reader, annotationProvider);
 	} catch (IOException e) {
 		Util.log(e);
-		return;
+		return result;
 	}
 	if (annotationZip == null) {
 		// Additional change listening for individual types only when annotations are in individual files.
@@ -437,6 +453,7 @@
 		this.externalAnnotationBase = externalAnnotationPath; // remember so we can unregister later
 		ExternalAnnotationTracker.registerClassFile(externalAnnotationPath, new Path(typeName), this);
 	}
+	return result;
 }
 void closeAndRemoveFromJarTypeCache() throws JavaModelException {
 	super.close();
@@ -451,6 +468,7 @@
 	}
 	super.close();
 }
+@Override
 public IBuffer getBuffer() throws JavaModelException {
 	IStatus status = validateClassFile();
 	if (status.isOK()) {
@@ -468,6 +486,7 @@
 /**
  * @see IMember
  */
+@Override
 public IClassFile getClassFile() {
 	return this;
 }
@@ -483,6 +502,7 @@
  *
  * @see IJavaElement
  */
+@Override
 public IResource getCorrespondingResource() throws JavaModelException {
 	IPackageFragmentRoot root= (IPackageFragmentRoot)getParent().getParent();
 	if (root.isArchive()) {
@@ -554,6 +574,7 @@
 		return null;
 	}
 }
+@Override
 public String getElementName() {
 	return this.name + SuffixConstants.SUFFIX_STRING_class;
 }
@@ -566,6 +587,7 @@
 /*
  * @see JavaElement
  */
+@Override
 public IJavaElement getHandleFromMemento(String token, MementoTokenizer memento, WorkingCopyOwner owner) {
 	switch (token.charAt(0)) {
 		case JEM_TYPE:
@@ -579,6 +601,7 @@
 /**
  * @see JavaElement#getHandleMemento()
  */
+@Override
 protected char getHandleMementoDelimiter() {
 	return JavaElement.JEM_CLASSFILE;
 }
@@ -596,6 +619,7 @@
 /*
  * @see IJavaElement
  */
+@Override
 public IResource resource(PackageFragmentRoot root) {
 	return ((IContainer) ((Openable) this.parent).resource(root)).getFile(new Path(getElementName()));
 }
@@ -707,15 +731,18 @@
  * @see IClassFile
  * @deprecated
  */
+@Deprecated
 public IJavaElement getWorkingCopy(IProgressMonitor monitor, org.eclipse.jdt.core.IBufferFactory factory) throws JavaModelException {
 	return getWorkingCopy(BufferFactoryWrapper.create(factory), monitor);
 }
 /**
  * @see Openable
  */
+@Override
 protected boolean hasBuffer() {
 	return true;
 }
+@Override
 public int hashCode() {
 	return Util.combineHashCodes(this.name.hashCode(), this.parent.hashCode());
 }
@@ -734,6 +761,7 @@
 /**
  * Returns true - class files are always read only.
  */
+@Override
 public boolean isReadOnly() {
 	return true;
 }
@@ -756,6 +784,7 @@
  *
  * @see Openable
  */
+@Override
 protected IBuffer openBuffer(IProgressMonitor pm, Object info) throws JavaModelException {
 	// Check the cache for the top-level type first
 	IType outerMostEnclosingType = getOuterMostEnclosingType();
@@ -896,6 +925,7 @@
  * @see ICodeAssist#codeComplete(int, ICodeCompletionRequestor)
  * @deprecated - should use codeComplete(int, ICompletionRequestor) instead
  */
+@Deprecated
 public void codeComplete(int offset, final org.eclipse.jdt.core.ICodeCompletionRequestor requestor) throws JavaModelException {
 
 	if (requestor == null){
@@ -951,6 +981,7 @@
 		});
 }
 
+@Override
 protected IStatus validateExistence(IResource underlyingResource) {
 	// check whether the class file can be opened
 	IStatus status = validateClassFile();
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DeltaProcessingState.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DeltaProcessingState.java
index 1854aee..a8e3b27 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DeltaProcessingState.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DeltaProcessingState.java
@@ -24,13 +24,16 @@
 import org.eclipse.core.runtime.*;
 import org.eclipse.jdt.core.*;
 import org.eclipse.jdt.internal.core.JavaModelManager.PerProjectInfo;
+import org.eclipse.jdt.internal.core.nd.indexer.Indexer;
+import org.eclipse.jdt.internal.core.nd.indexer.IndexerEvent;
+import org.eclipse.jdt.internal.core.nd.java.JavaIndex;
 import org.eclipse.jdt.internal.core.util.Util;
 
 /**
  * Keep the global states used during Java element delta processing.
  */
 @SuppressWarnings({ "rawtypes", "unchecked" })
-public class DeltaProcessingState implements IResourceChangeListener {
+public class DeltaProcessingState implements IResourceChangeListener, Indexer.Listener {
 
 	/*
 	 * Collection of listeners for Java element deltas
@@ -643,4 +646,15 @@
 		}
 	}
 
+	@Override
+	public void consume(IndexerEvent event) {
+		if (JavaIndex.isEnabled()) {
+			DeltaProcessor processor = getDeltaProcessor();
+			JavaElementDelta delta = (JavaElementDelta) event.getDelta();
+			delta.ignoreFromTests = true;
+			processor.notifyAndFire(delta);
+			this.deltaProcessors.set(null);
+		}
+	}
+
 }
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DeltaProcessor.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DeltaProcessor.java
index 18f5b75..0e2c598 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DeltaProcessor.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DeltaProcessor.java
@@ -502,8 +502,24 @@
 
 				break;
 			case IResource.FOLDER:
-				if (delta.getKind() == IResourceDelta.CHANGED) { // look for .jar file change to update classpath
-					children = delta.getAffectedChildren();
+				switch (delta.getKind()) {
+					case IResourceDelta.ADDED:
+					case IResourceDelta.REMOVED:
+						// Close the containing package fragment root to reset its cached children.
+						// See http://bugs.eclipse.org/500714
+						IPackageFragmentRoot root = findContainingPackageFragmentRoot(resource);
+						if (root != null && root.isOpen()) {
+							try {
+								root.close();
+							} catch (JavaModelException e) {
+								Util.log(e);
+							}
+						}
+						break;
+
+					case IResourceDelta.CHANGED: // look for .jar file change to update classpath
+						children = delta.getAffectedChildren();
+						break;
 				}
 				break;
 			case IResource.FILE :
@@ -548,6 +564,27 @@
 		}
 	}
 
+	private IPackageFragmentRoot findContainingPackageFragmentRoot(IResource resource) {
+		IProject project = resource.getProject();
+		if (JavaProject.hasJavaNature(project)) {
+			IJavaProject javaProject = JavaCore.create(project);
+			try {
+				IPath path = resource.getProjectRelativePath();
+				IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots();
+				for (IPackageFragmentRoot root : roots) {
+					IResource rootResource = root.getUnderlyingResource();
+					if (rootResource != null && !resource.equals(rootResource) &&
+							rootResource.getProjectRelativePath().isPrefixOf(path)) {
+						return root;
+					}
+				}
+			} catch (JavaModelException e) {
+				Util.log(e);
+			}
+		}
+		return null;
+	}
+
 	private void checkExternalFolderChange(IProject project, JavaProject javaProject) {
 		ClasspathChange change = this.state.getClasspathChange(project);
 		this.state.addExternalFolderChange(javaProject, change == null ? null : change.oldResolvedClasspath);
@@ -1020,6 +1057,9 @@
 							if (VERBOSE){
 								System.out.println("- External JAR CHANGED, affecting root: "+root.getElementName()); //$NON-NLS-1$
 							}
+							// TODO(sxenos): this is causing each change event for an external jar file to be fired twice.
+							// We need to preserve the clearing of cached information in the jar but defer the actual firing of
+							// the event until after the indexer has processed the jar.
 							contentChanged(root);
 							deltaContainsModifiedJar = true;
 							hasDelta = true;
@@ -1908,7 +1948,7 @@
 	 * caches and their dependents
 	 */
 	public void resetProjectCaches() {
-		if (this.projectCachesToReset.size() == 0)
+		if (this.projectCachesToReset.isEmpty())
 			return;
 
 		JavaModelManager.getJavaModelManager().resetJarTypeCache();
@@ -2064,14 +2104,7 @@
 							this.sourceElementParserCache = null; // don't hold onto parser longer than necessary
 							startDeltas();
 						}
-						IElementChangedListener[] listeners;
-						int listenerCount;
-						synchronized (this.state) {
-							listeners = this.state.elementChangedListeners;
-							listenerCount = this.state.elementChangedListenerCount;
-						}
-						notifyTypeHierarchies(listeners, listenerCount);
-						fire(null, ElementChangedEvent.POST_CHANGE);
+						notifyAndFire(null);
 					} finally {
 						// workaround for bug 15168 circular errors not reported
 						this.state.resetOldJavaProjectNames();
@@ -2180,6 +2213,17 @@
 		}
 	}
 
+	public void notifyAndFire(IJavaElementDelta delta) {
+		IElementChangedListener[] listeners;
+		int listenerCount;
+		synchronized (this.state) {
+			listeners = this.state.elementChangedListeners;
+			listenerCount = this.state.elementChangedListenerCount;
+		}
+		notifyTypeHierarchies(listeners, listenerCount);
+		fire(delta, ElementChangedEvent.POST_CHANGE);
+	}
+
 	/*
 	 * Returns the root info for the given path. Look in the old roots table if kind is REMOVED.
 	 */
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaElementDelta.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaElementDelta.java
index a7beb47..86abfaf 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaElementDelta.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaElementDelta.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation 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
@@ -82,6 +82,8 @@
 	 */
 	Map<Key, Integer> childIndex;
 
+	public boolean ignoreFromTests = false;
+
 	/**
 	 * The delta key
 	 */
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaModelCache.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaModelCache.java
index c2ad7c3..5c6ca12 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaModelCache.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaModelCache.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation 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
@@ -28,6 +28,7 @@
 @SuppressWarnings({"rawtypes", "unchecked"})
 public class JavaModelCache {
 	public static boolean VERBOSE = false;
+	public static boolean DEBUG_CACHE_INSERTIONS = false;
 
 	public static final int DEFAULT_PROJECT_SIZE = 5;  // average 25552 bytes per project.
 	public static final int DEFAULT_ROOT_SIZE = 50; // average 2590 bytes per root -> maximum size : 25900*BASE_VALUE bytes
@@ -224,6 +225,9 @@
  * Remember the info for the element.
  */
 protected void putInfo(IJavaElement element, Object info) {
+	if (DEBUG_CACHE_INSERTIONS) {
+		System.out.println(Thread.currentThread() + " cache putInfo (" + getElementType(element) + " " + element.toString() + ", " + info + ")");  //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$
+	}
 	switch (element.getElementType()) {
 		case IJavaElement.JAVA_MODEL:
 			this.modelInfo = info;
@@ -248,10 +252,39 @@
 			this.childrenCache.put(element, info);
 	}
 }
+
+public static String getElementType(IJavaElement element) {
+	String elementType;
+	switch (element.getElementType()) {
+		case IJavaElement.JAVA_PROJECT:
+			elementType = "project"; //$NON-NLS-1$
+			break;
+		case IJavaElement.PACKAGE_FRAGMENT_ROOT:
+			elementType = "root"; //$NON-NLS-1$
+			break;
+		case IJavaElement.PACKAGE_FRAGMENT:
+			elementType = "package"; //$NON-NLS-1$
+			break;
+		case IJavaElement.CLASS_FILE:
+			elementType = "class file"; //$NON-NLS-1$
+			break;
+		case IJavaElement.COMPILATION_UNIT:
+			elementType = "compilation unit"; //$NON-NLS-1$
+			break;
+		default:
+			elementType = "element"; //$NON-NLS-1$
+	}
+	return elementType;
+}
+
 /**
  * Removes the info of the element from the cache.
  */
 protected void removeInfo(JavaElement element) {
+	if (DEBUG_CACHE_INSERTIONS) {
+		String elementToString = element.toString();
+		System.out.println(Thread.currentThread() + " cache removeInfo " + getElementType(element) + " " + elementToString);  //$NON-NLS-1$//$NON-NLS-2$
+	}
 	switch (element.getElementType()) {
 		case IJavaElement.JAVA_MODEL:
 			this.modelInfo = null;
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaModelManager.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaModelManager.java
index cdb4157..8b4feaa 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaModelManager.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaModelManager.java
@@ -130,6 +130,8 @@
 import org.eclipse.jdt.internal.core.dom.SourceRangeVerifier;
 import org.eclipse.jdt.internal.core.dom.rewrite.RewriteEventStore;
 import org.eclipse.jdt.internal.core.hierarchy.TypeHierarchy;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.indexer.Indexer;
 import org.eclipse.jdt.internal.core.search.AbstractSearchScope;
 import org.eclipse.jdt.internal.core.search.BasicSearchEngine;
 import org.eclipse.jdt.internal.core.search.IRestrictedAccessTypeRequestor;
@@ -176,6 +178,14 @@
 	private static final String EXTERNAL_FILES_CACHE = "externalFilesCache";  //$NON-NLS-1$
 	private static final String ASSUMED_EXTERNAL_FILES_CACHE = "assumedExternalFilesCache";  //$NON-NLS-1$
 
+	public static enum ArchiveValidity {
+		BAD_FORMAT, UNABLE_TO_READ, VALID;
+
+		public boolean isValid() {
+			return this == VALID;
+		}
+	}
+
 	/**
 	 * Define a zip cache object.
 	 */
@@ -333,8 +343,11 @@
 	private static final String INDEX_MANAGER_DEBUG = JavaCore.PLUGIN_ID + "/debug/indexmanager" ; //$NON-NLS-1$
 	private static final String INDEX_MANAGER_ADVANCED_DEBUG = JavaCore.PLUGIN_ID + "/debug/indexmanager/advanced" ; //$NON-NLS-1$
 	private static final String COMPILER_DEBUG = JavaCore.PLUGIN_ID + "/debug/compiler" ; //$NON-NLS-1$
+	private static final String JAVAMODEL_CLASSPATH = JavaCore.PLUGIN_ID + "/debug/javamodel/classpath" ; //$NON-NLS-1$
 	private static final String JAVAMODEL_DEBUG = JavaCore.PLUGIN_ID + "/debug/javamodel" ; //$NON-NLS-1$
+	private static final String JAVAMODEL_INVALID_ARCHIVES = JavaCore.PLUGIN_ID + "/debug/javamodel/invalid_archives" ; //$NON-NLS-1$
 	private static final String JAVAMODELCACHE_DEBUG = JavaCore.PLUGIN_ID + "/debug/javamodel/cache" ; //$NON-NLS-1$
+	private static final String JAVAMODELCACHE_INSERTIONS_DEBUG = JavaCore.PLUGIN_ID + "/debug/javamodel/insertions" ; //$NON-NLS-1$
 	private static final String CP_RESOLVE_DEBUG = JavaCore.PLUGIN_ID + "/debug/cpresolution" ; //$NON-NLS-1$
 	private static final String CP_RESOLVE_ADVANCED_DEBUG = JavaCore.PLUGIN_ID + "/debug/cpresolution/advanced" ; //$NON-NLS-1$
 	private static final String CP_RESOLVE_FAILURE_DEBUG = JavaCore.PLUGIN_ID + "/debug/cpresolution/failure" ; //$NON-NLS-1$
@@ -354,6 +367,12 @@
 	private static final String SEARCH_DEBUG = JavaCore.PLUGIN_ID + "/debug/search" ; //$NON-NLS-1$
 	private static final String SOURCE_MAPPER_DEBUG_VERBOSE = JavaCore.PLUGIN_ID + "/debug/sourcemapper" ; //$NON-NLS-1$
 	private static final String FORMATTER_DEBUG = JavaCore.PLUGIN_ID + "/debug/formatter" ; //$NON-NLS-1$
+	private static final String INDEX_INDEXER_DEBUG = JavaCore.PLUGIN_ID + "/debug/index/indexer" ; //$NON-NLS-1$
+	private static final String INDEX_INDEXER_INSERTIONS = JavaCore.PLUGIN_ID + "/debug/index/insertions" ; //$NON-NLS-1$
+	private static final String INDEX_INDEXER_SELFTEST = JavaCore.PLUGIN_ID + "/debug/index/selftest" ; //$NON-NLS-1$
+	private static final String INDEX_LOCKS_DEBUG = JavaCore.PLUGIN_ID + "/debug/index/locks" ; //$NON-NLS-1$
+	private static final String INDEX_INDEXER_SPACE = JavaCore.PLUGIN_ID + "/debug/index/space" ; //$NON-NLS-1$
+	private static final String INDEX_INDEXER_TIMING = JavaCore.PLUGIN_ID + "/debug/index/timing" ; //$NON-NLS-1$
 
 	public static final String COMPLETION_PERF = JavaCore.PLUGIN_ID + "/perf/completion" ; //$NON-NLS-1$
 	public static final String SELECTION_PERF = JavaCore.PLUGIN_ID + "/perf/selection" ; //$NON-NLS-1$
@@ -1290,6 +1309,16 @@
 		}
 
 		private ClasspathChange setClasspath(IClasspathEntry[] newRawClasspath, IClasspathEntry[] referencedEntries, IPath newOutputLocation, IJavaModelStatus newRawClasspathStatus, IClasspathEntry[] newResolvedClasspath, Map newRootPathToRawEntries, Map newRootPathToResolvedEntries, IJavaModelStatus newUnresolvedEntryStatus, boolean addClasspathChange) {
+			if (DEBUG_CLASSPATH) {
+				System.out.println("Setting resolved classpath for " + this.project.getFullPath()); //$NON-NLS-1$
+				if (newResolvedClasspath == null) {
+					System.out.println("New classpath = null"); //$NON-NLS-1$
+				} else { 
+					for (IClasspathEntry next : newResolvedClasspath) {
+						System.out.println("    " + next); //$NON-NLS-1$
+					}
+				}
+			}
 			ClasspathChange classpathChange = addClasspathChange ? addClasspathChange() : null;
 
 			if (referencedEntries != null)	this.referencedEntries = referencedEntries;
@@ -1510,6 +1539,8 @@
 	}
 
 	public static boolean VERBOSE = false;
+	public static boolean DEBUG_CLASSPATH = false;
+	public static boolean DEBUG_INVALID_ARCHIVES = false;
 	public static boolean CP_RESOLVE_VERBOSE = false;
 	public static boolean CP_RESOLVE_VERBOSE_ADVANCED = false;
 	public static boolean CP_RESOLVE_VERBOSE_FAILURE = false;
@@ -1531,10 +1562,29 @@
 	// The amount of time from when an invalid archive is first sensed until that state is considered stale.
 	private static long INVALID_ARCHIVE_TTL_MILLISECONDS = 2 * 60 * 1000;
 
+	private static class InvalidArchiveInfo {
+		/**
+		 * Time at which this entry will be removed from the invalid archive list.
+		 */
+		final long evictionTimestamp;
+
+		/**
+		 * Reason the entry was added to the invalid archive list.
+		 */
+		final ArchiveValidity reason;
+
+		InvalidArchiveInfo(long evictionTimestamp, ArchiveValidity reason) {
+			this.evictionTimestamp = evictionTimestamp;
+			this.reason = reason;
+		}
+	}
+
 	/*
 	 * A map of IPaths for jars that are known to be invalid (such as not being in a valid/known format), to an eviction timestamp.
+	 * Synchronize on invalidArchivesMutex before accessing.
 	 */
-	private Map<IPath, Long> invalidArchives;
+	private final Map<IPath, InvalidArchiveInfo> invalidArchives = new HashMap<IPath, InvalidArchiveInfo>();
+	private final Object invalidArchivesMutex = new Object();
 
 	/*
 	 * A set of IPaths for files that are known to be external to the workspace.
@@ -1698,12 +1748,13 @@
 			this.nonChainingJars.add(path);
 	}
 	
-	public void addInvalidArchive(IPath path) {
-		// unlikely to be null
-		if (this.invalidArchives == null) {
-			this.invalidArchives = Collections.synchronizedMap(new HashMap());
+	public void addInvalidArchive(IPath path, ArchiveValidity reason) {
+		if (DEBUG_INVALID_ARCHIVES) {
+			System.out.println("Invalid JAR cache: adding " + path + ", reason: " + reason);  //$NON-NLS-1$//$NON-NLS-2$
 		}
-		this.invalidArchives.put(path, System.currentTimeMillis() + INVALID_ARCHIVE_TTL_MILLISECONDS);
+		synchronized (this.invalidArchivesMutex) {
+			this.invalidArchives.put(path, new InvalidArchiveInfo(System.currentTimeMillis() + INVALID_ARCHIVE_TTL_MILLISECONDS, reason));
+		}
 	}
 
 	/**
@@ -1773,8 +1824,11 @@
 				TypeHierarchy.DEBUG = debug && options.getBooleanOption(HIERARCHY_DEBUG, false);
 				JobManager.VERBOSE = debug && options.getBooleanOption(INDEX_MANAGER_DEBUG, false);
 				IndexManager.DEBUG = debug && options.getBooleanOption(INDEX_MANAGER_ADVANCED_DEBUG, false);
+				JavaModelManager.DEBUG_CLASSPATH = debug && options.getBooleanOption(JAVAMODEL_CLASSPATH, false);
+				JavaModelManager.DEBUG_INVALID_ARCHIVES = debug && options.getBooleanOption(JAVAMODEL_INVALID_ARCHIVES, false);
 				JavaModelManager.VERBOSE = debug && options.getBooleanOption(JAVAMODEL_DEBUG, false);
 				JavaModelCache.VERBOSE = debug && options.getBooleanOption(JAVAMODELCACHE_DEBUG, false);
+				JavaModelCache.DEBUG_CACHE_INSERTIONS = debug && options.getBooleanOption(JAVAMODELCACHE_INSERTIONS_DEBUG, false);
 				JavaModelOperation.POST_ACTION_VERBOSE = debug && options.getBooleanOption(POST_ACTION_DEBUG, false);
 				NameLookup.VERBOSE = debug && options.getBooleanOption(RESOLUTION_DEBUG, false);
 				BasicSearchEngine.VERBOSE = debug && options.getBooleanOption(SEARCH_DEBUG, false);
@@ -1782,6 +1836,12 @@
 				JavaModelManager.ZIP_ACCESS_VERBOSE = debug && options.getBooleanOption(ZIP_ACCESS_DEBUG, false);
 				SourceMapper.VERBOSE = debug && options.getBooleanOption(SOURCE_MAPPER_DEBUG_VERBOSE, false);
 				DefaultCodeFormatter.DEBUG = debug && options.getBooleanOption(FORMATTER_DEBUG, false);
+				Indexer.DEBUG = debug && options.getBooleanOption(INDEX_INDEXER_DEBUG, false);
+				Indexer.DEBUG_INSERTIONS = debug  && options.getBooleanOption(INDEX_INDEXER_INSERTIONS, false);
+				Indexer.DEBUG_ALLOCATIONS = debug && options.getBooleanOption(INDEX_INDEXER_SPACE, false);
+				Indexer.DEBUG_TIMING = debug && options.getBooleanOption(INDEX_INDEXER_TIMING, false);
+				Indexer.DEBUG_SELFTEST = debug && options.getBooleanOption(INDEX_INDEXER_SELFTEST, false);
+				Nd.sDEBUG_LOCKS = debug && options.getBooleanOption(INDEX_LOCKS_DEBUG, false);
 		
 				// configure performance options
 				if(PerformanceStats.ENABLED) {
@@ -2702,9 +2762,7 @@
 	}
 
 	public void verifyArchiveContent(IPath path) throws CoreException {
-		if (isInvalidArchive(path)) {
-			throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.status_IOException, new ZipException()));			
-		}
+		throwExceptionIfArchiveInvalid(path);
 		ZipFile file = getZipFile(path);
 		closeZipFile(file);
 	}
@@ -2724,16 +2782,47 @@
 		return getZipFile(path, true);
 	}
 
+	/**
+	 * For use in the JDT unit tests only. Used for testing error handling. Causes an
+	 * {@link IOException} to be thrown in {@link #getZipFile} whenever it attempts to
+	 * read a zip file.
+	 * 
+	 * @noreference This field is not intended to be referenced by clients.
+	 */
+	public static boolean throwIoExceptionsInGetZipFile = false;
+
 	private ZipFile getZipFile(IPath path, boolean checkInvalidArchiveCache) throws CoreException {
-		if (checkInvalidArchiveCache && isInvalidArchive(path))
-			throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.status_IOException, new ZipException()));
-		
+		if (checkInvalidArchiveCache) {
+			throwExceptionIfArchiveInvalid(path);
+		}
 		ZipCache zipCache;
 		ZipFile zipFile;
 		if ((zipCache = (ZipCache)this.zipFiles.get()) != null
 				&& (zipFile = zipCache.getCache(path)) != null) {
 			return zipFile;
 		}
+		File localFile = getLocalFile(path);
+
+		try {
+			if (ZIP_ACCESS_VERBOSE) {
+				System.out.println("(" + Thread.currentThread() + ") [JavaModelManager.getZipFile(IPath)] Creating ZipFile on " + localFile ); //$NON-NLS-1$ //$NON-NLS-2$
+			}
+			if (throwIoExceptionsInGetZipFile) {
+				throw new IOException();
+			}
+			zipFile = new ZipFile(localFile);
+			if (zipCache != null) {
+				zipCache.setCache(path, zipFile);
+			}
+			return zipFile;
+		} catch (IOException e) {
+			ArchiveValidity reason = (e instanceof ZipException) ? ArchiveValidity.BAD_FORMAT : ArchiveValidity.UNABLE_TO_READ;
+			addInvalidArchive(path, reason);
+			throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.status_IOException, e));
+		}
+	}
+
+	public static File getLocalFile(IPath path) throws CoreException {
 		File localFile = null;
 		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
 		IResource file = root.findMember(path);
@@ -2750,19 +2839,19 @@
 			// external resource -> it is ok to use toFile()
 			localFile= path.toFile();
 		}
+		return localFile;
+	}
 
-		try {
-			if (ZIP_ACCESS_VERBOSE) {
-				System.out.println("(" + Thread.currentThread() + ") [JavaModelManager.getZipFile(IPath)] Creating ZipFile on " + localFile ); //$NON-NLS-1$ //$NON-NLS-2$
+	private void throwExceptionIfArchiveInvalid(IPath path) throws CoreException {
+		ArchiveValidity validity = getArchiveValidity(path);
+		if (!validity.isValid()) {
+			IOException reason;
+			if (validity == ArchiveValidity.BAD_FORMAT) {
+				reason = new ZipException();
+			} else {
+				reason = new IOException();
 			}
-			zipFile = new ZipFile(localFile);
-			if (zipCache != null) {
-				zipCache.setCache(path, zipFile);
-			}
-			return zipFile;
-		} catch (IOException e) {
-			addInvalidArchive(path);
-			throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.status_IOException, e));
+			throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.status_IOException, reason));
 		}
 	}
 
@@ -3186,31 +3275,36 @@
 		return this.nonChainingJars != null && this.nonChainingJars.contains(path);
 	}
 	
-	public boolean isInvalidArchive(IPath path) {
-		if (this.invalidArchives == null)
-			return false;
-		Long evictionTime = this.invalidArchives.get(path);
-		if (evictionTime == null)
-			return false;
+	public ArchiveValidity getArchiveValidity(IPath path) {
+		InvalidArchiveInfo invalidArchiveInfo;
+		synchronized (this.invalidArchivesMutex) {
+			invalidArchiveInfo = this.invalidArchives.get(path);
+		}
+		if (invalidArchiveInfo == null)
+			return ArchiveValidity.VALID;
 		long now = System.currentTimeMillis();
 
 		// If the TTL for this cache entry has expired, directly check whether the archive is still invalid.
 		// If it transitioned to being valid, remove it from the cache and force an update to project caches.
-		if (now > evictionTime) {
+		if (now > invalidArchiveInfo.evictionTimestamp) {
 			try {
 				getZipFile(path, false);
 				removeFromInvalidArchiveCache(path);
-				return false;
 			} catch (CoreException e) {
 				// Archive is still invalid, fall through to reporting it is invalid.
 			}
+			// Retry the test from the start, now that we have an up-to-date result
+			return getArchiveValidity(path);
 		}
-		return true;
+		return invalidArchiveInfo.reason;
 	}
 
 	public void removeFromInvalidArchiveCache(IPath path) {
-		if (this.invalidArchives != null) {
+		synchronized(this.invalidArchivesMutex) {
 			if (this.invalidArchives.remove(path) != null) {
+				if (DEBUG_INVALID_ARCHIVES) {
+					System.out.println("Invalid JAR cache: removed " + path);  //$NON-NLS-1$
+				}
 				try {
 					// Bug 455042: Force an update of the JavaProjectElementInfo project caches.
 					for (IJavaProject project : getJavaModel().getJavaProjects()) {
@@ -3985,26 +4079,7 @@
 			boolean wasVerbose = false;
 			try {
 				if (JavaModelCache.VERBOSE) {
-					String elementType;
-					switch (element.getElementType()) {
-						case IJavaElement.JAVA_PROJECT:
-							elementType = "project"; //$NON-NLS-1$
-							break;
-						case IJavaElement.PACKAGE_FRAGMENT_ROOT:
-							elementType = "root"; //$NON-NLS-1$
-							break;
-						case IJavaElement.PACKAGE_FRAGMENT:
-							elementType = "package"; //$NON-NLS-1$
-							break;
-						case IJavaElement.CLASS_FILE:
-							elementType = "class file"; //$NON-NLS-1$
-							break;
-						case IJavaElement.COMPILATION_UNIT:
-							elementType = "compilation unit"; //$NON-NLS-1$
-							break;
-						default:
-							elementType = "element"; //$NON-NLS-1$
-					}
+					String elementType = JavaModelCache.getElementType(element);
 					System.out.println(Thread.currentThread() + " CLOSING "+ elementType + " " + element.toStringWithAncestors());  //$NON-NLS-1$//$NON-NLS-2$
 					wasVerbose = true;
 					JavaModelCache.VERBOSE = false;
@@ -4085,8 +4160,16 @@
 	public void resetClasspathListCache() {
 		if (this.nonChainingJars != null) 
 			this.nonChainingJars.clear();
-		if (this.invalidArchives != null) 
+		if (DEBUG_INVALID_ARCHIVES) {
+			synchronized(this.invalidArchivesMutex) {
+				if (!this.invalidArchives.isEmpty()) {
+					System.out.println("Invalid JAR cache: clearing cache"); //$NON-NLS-1$
+				}
+			}
+		}
+		synchronized(this.invalidArchivesMutex) {
 			this.invalidArchives.clear();
+		}
 		if (this.externalFiles != null)
 			this.externalFiles.clear();
 		if (this.assumedExternalFiles != null)
@@ -5163,6 +5246,8 @@
 					| IResourceChangeEvent.PRE_CLOSE
 					| IResourceChangeEvent.PRE_REFRESH);
 
+			Indexer.getInstance().addListener(this.deltaState);
+
 			// listen to resource changes affecting external annotations
 			ExternalAnnotationTracker.start(workspace);
 
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavadocConstants.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavadocConstants.java
index ed53500..e52d2e1 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavadocConstants.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavadocConstants.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2014 IBM Corporation and others.
+ * Copyright (c) 2005, 2016 IBM Corporation 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
@@ -14,7 +14,9 @@
 
 	String ANCHOR_PREFIX_END = "\""; //$NON-NLS-1$
 	char[] ANCHOR_PREFIX_START = "<A NAME=\"".toCharArray(); //$NON-NLS-1$
-	int ANCHOR_PREFIX_START_LENGHT = ANCHOR_PREFIX_START.length;
+	char[] ANCHOR_PREFIX_START_2 = "<A ID=\"".toCharArray(); //$NON-NLS-1$
+	int ANCHOR_PREFIX_START_LENGTH = ANCHOR_PREFIX_START.length;
+	int ANCHOR_PREFIX_START2_LENGTH = ANCHOR_PREFIX_START_2.length;
 	char[] ANCHOR_SUFFIX = "</A>".toCharArray(); //$NON-NLS-1$
 	int ANCHOR_SUFFIX_LENGTH = JavadocConstants.ANCHOR_SUFFIX.length;
 	char[] CONSTRUCTOR_DETAIL = "<!-- ========= CONSTRUCTOR DETAIL ======== -->".toCharArray(); //$NON-NLS-1$
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavadocContents.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavadocContents.java
index 02341d5..e29b96d 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavadocContents.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavadocContents.java
@@ -185,14 +185,15 @@
 		}
 		
 		int fromIndex = this.tempLastAnchorFoundIndex;
-		int index;
+		int[] index;
 		
 		// check each next unknown anchor locations
-		while ((index = CharOperation.indexOf(JavadocConstants.ANCHOR_PREFIX_START, this.content, false, fromIndex)) != -1 && (index < indexOfSectionBottom || indexOfSectionBottom == -1)) {
-			fromIndex = index + 1;
-			
-			int anchorEndStart = index + JavadocConstants.ANCHOR_PREFIX_START_LENGHT;
-			
+		index = getAnchorIndex(fromIndex);
+		while (index[0] != -1 && (index[0] < indexOfSectionBottom || indexOfSectionBottom == -1)) {
+			fromIndex = index[0] + 1;
+
+			int anchorEndStart = index[0] + index[1];
+
 			this.tempLastAnchorFoundIndex = anchorEndStart;
 			
 			if (CharOperation.prefixEquals(anchor, this.content, false, anchorEndStart)) {
@@ -204,11 +205,25 @@
 				
 				this.tempAnchorIndexes[this.tempAnchorIndexesCount++] = anchorEndStart;
 			}
+			index = getAnchorIndex(fromIndex);
 		}
 		
 		return null;
 	}
-	
+	private int[] getAnchorIndex(int fromIndex) {
+		int index = CharOperation.indexOf(JavadocConstants.ANCHOR_PREFIX_START, this.content, false, fromIndex);
+		if (index != -1) {
+			return new int[]{index, JavadocConstants.ANCHOR_PREFIX_START_LENGTH};
+		}
+		if (index == -1) {
+			index = CharOperation.indexOf(JavadocConstants.ANCHOR_PREFIX_START_2, this.content, false, fromIndex);
+		}
+		if (index == -1) {
+			return new int[]{-1, -1};
+		} else {
+			return new int[]{index, JavadocConstants.ANCHOR_PREFIX_START2_LENGTH};
+		}
+	}
 	private int[] computeChildRange(int anchorEndStart, char[] anchor, int indexOfBottom) {
 		int[] range = null;
 				
@@ -218,7 +233,7 @@
 			int indexOfEndLink = CharOperation.indexOf(JavadocConstants.ANCHOR_SUFFIX, this.content, false, anchorEndStart + anchor.length);
 			if (indexOfEndLink != -1) {
 				// try to find the next anchor
-				int indexOfNextElement = CharOperation.indexOf(JavadocConstants.ANCHOR_PREFIX_START, this.content, false, indexOfEndLink);
+				int indexOfNextElement = getAnchorIndex(indexOfEndLink)[0];
 				
 				int javadocStart = indexOfEndLink + JavadocConstants.ANCHOR_SUFFIX_LENGTH;
 				int javadocEnd = indexOfNextElement == -1 ? indexOfBottom : Math.min(indexOfNextElement, indexOfBottom);
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathDirectory.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathDirectory.java
index 72917d5..fa1e11d 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathDirectory.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathDirectory.java
@@ -15,10 +15,10 @@
 
 import org.eclipse.core.resources.*;
 import org.eclipse.core.runtime.*;
-
-import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationDecorator;
 import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
 import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
 import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable;
 import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
@@ -107,7 +107,7 @@
 public NameEnvironmentAnswer findClass(String binaryFileName, String qualifiedPackageName, String qualifiedBinaryFileName) {
 	if (!doesFileExist(binaryFileName, qualifiedPackageName, qualifiedBinaryFileName)) return null; // most common case
 
-	ClassFileReader reader = null;
+	IBinaryType reader = null;
 	try {
 		reader = Util.newClassFileReader(this.binaryFolder.getFile(new Path(qualifiedBinaryFileName)));
 	} catch (CoreException e) {
@@ -121,7 +121,12 @@
 		String fileNameWithoutExtension = qualifiedBinaryFileName.substring(0, qualifiedBinaryFileName.length() - SuffixConstants.SUFFIX_CLASS.length);
 		if (this.externalAnnotationPath != null) {
 			try {
-				this.annotationZipFile = reader.setExternalAnnotationProvider(this.externalAnnotationPath, fileNameWithoutExtension, this.annotationZipFile, null);
+				if (this.annotationZipFile == null) {
+					this.annotationZipFile = ExternalAnnotationDecorator
+							.getAnnotationZipFile(this.externalAnnotationPath, null);
+				}
+				reader = ExternalAnnotationDecorator.create(reader, this.externalAnnotationPath,
+						fileNameWithoutExtension, this.annotationZipFile);
 			} catch (IOException e) {
 				// don't let error on annotations fail class reading
 			}
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJar.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJar.java
index 1bda5fb..6575675 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJar.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJar.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation 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
@@ -13,22 +13,27 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.core.builder;
 
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.runtime.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationDecorator;
 import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
 import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
 import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable;
 import org.eclipse.jdt.internal.compiler.util.SimpleSet;
 import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
 import org.eclipse.jdt.internal.core.util.Util;
 
-import java.io.*;
-import java.util.*;
-import java.util.zip.*;
-
 @SuppressWarnings("rawtypes")
 public class ClasspathJar extends ClasspathLocation {
 
@@ -165,12 +170,18 @@
 	if (!isPackage(qualifiedPackageName)) return null; // most common case
 
 	try {
-		ClassFileReader reader = ClassFileReader.read(this.zipFile, qualifiedBinaryFileName);
+		IBinaryType reader = ClassFileReader.read(this.zipFile, qualifiedBinaryFileName);
 		if (reader != null) {
 			String fileNameWithoutExtension = qualifiedBinaryFileName.substring(0, qualifiedBinaryFileName.length() - SuffixConstants.SUFFIX_CLASS.length);
 			if (this.externalAnnotationPath != null) {
 				try {
-					this.annotationZipFile = reader.setExternalAnnotationProvider(this.externalAnnotationPath, fileNameWithoutExtension, this.annotationZipFile, null);
+					if (this.annotationZipFile == null) {
+						this.annotationZipFile = ExternalAnnotationDecorator
+								.getAnnotationZipFile(this.externalAnnotationPath, null);
+					}
+
+					reader = ExternalAnnotationDecorator.create(reader, this.externalAnnotationPath,
+							fileNameWithoutExtension, this.annotationZipFile);
 				} catch (IOException e) {
 					// don't let error on annotations fail class reading
 				}
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/BindingMap.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/BindingMap.java
new file mode 100644
index 0000000..3070193
--- /dev/null
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/BindingMap.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * 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.hierarchy;
+
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
+
+/**
+ * Maps a {@link TypeBinding} onto values. Two {@link TypeBinding}s are considered equivalent
+ * if their IDs are the same or if they have TypeIds.NoId and they are identical objects.
+ * <p>
+ * Takes into account the fact that a ReferenceBinding may have its ID change from NoId
+ * to a real ID at any time without notice. (This is a behavior that was observed in
+ * TypeHierarchyTests.testAnonymousType01 -- if type IDs could be made invariant then it
+ * would be possible to implement a more efficient map that never needs to perform an
+ * exhaustive search.)
+ */
+public class BindingMap<V> {
+	private Map<TypeBinding, V> identityMap = new IdentityHashMap<>();
+	private Object[] mapIdToValue = new Object[0];
+	private Set<TypeBinding> bindingsWithoutAnId = new HashSet<>();
+
+	public void put(TypeBinding key, V value) {
+		this.identityMap.put(key, value);
+		if (key.id != TypeIds.NoId) {
+			int targetId = key.id;
+			insertIntoIdMap(targetId, value);
+		} else {
+			this.bindingsWithoutAnId.add(key);
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	public V get(TypeBinding key) {
+		// Check if we can find this binding by identity
+		V value = this.identityMap.get(key);
+		if (value != null) {
+			return value;
+		}
+		int targetId = key.id;
+		if (targetId != TypeIds.NoId) {
+			// Check if we can find this binding by value
+			if (targetId < this.mapIdToValue.length) {
+				value = (V)this.mapIdToValue[targetId];
+			}
+			if (value != null) {
+				return value;
+			}
+
+			// Check if there are any bindings that previously had no ID that have
+			// subsequently been assigned one.
+			for (Iterator<TypeBinding> bindingIter = this.bindingsWithoutAnId.iterator(); bindingIter.hasNext();) {
+				TypeBinding nextBinding = bindingIter.next();
+
+				if (nextBinding.id != TypeIds.NoId) {
+					insertIntoIdMap(nextBinding.id, this.identityMap.get(nextBinding));
+					bindingIter.remove();
+				}
+			}
+
+			// Now look again to see if this binding can be found
+			if (targetId < this.mapIdToValue.length) {
+				value = (V)this.mapIdToValue[targetId];
+			}
+		}
+
+		return value;
+	}
+
+	private void insertIntoIdMap(int targetId, V value) {
+		int requiredSize = targetId + 1;
+		if (this.mapIdToValue.length < requiredSize) {
+			int newSize = requiredSize * 2;
+			Object[] newArray = new Object[newSize];
+			System.arraycopy(this.mapIdToValue, 0, newArray, 0, this.mapIdToValue.length);
+			this.mapIdToValue = newArray;
+		}
+		this.mapIdToValue[targetId] = value;
+	}
+
+	public void clear() {
+		this.identityMap.clear();
+		this.bindingsWithoutAnId.clear();
+		this.mapIdToValue = new Object[0];
+	}
+}
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyBinaryType.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyBinaryType.java
index 932c049..0427355 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyBinaryType.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyBinaryType.java
@@ -53,6 +53,23 @@
 	this.typeParameterSignatures = typeParameterSignatures;
 	CharOperation.replace(this.name, '.', '/');
 }
+
+public HierarchyBinaryType(int modifiers, char[] binaryName, char[] sourceName, char[] enclosingTypeBinaryName, char[][] typeParameterSignatures) {
+	this.modifiers = modifiers;
+	this.sourceName = sourceName;
+	this.name = binaryName;
+	this.enclosingTypeName = enclosingTypeBinaryName;
+	this.typeParameterSignatures = typeParameterSignatures;
+
+	if (typeParameterSignatures != null) {
+		for (char[] next : typeParameterSignatures) {
+			if (next == null) {
+				throw new IllegalArgumentException("Parameter's type signature must not be null"); //$NON-NLS-1$
+			}
+		}
+	}
+}
+
 /**
  * @see org.eclipse.jdt.internal.compiler.env.IBinaryType
  */
@@ -197,6 +214,7 @@
 	return false;  // index did not record this information (since unused for hierarchies)
 }
 
+
 public void recordSuperType(char[] superTypeName, char[] superQualification, char superClassOrInterface){
 
 	// index encoding of p.A$B was B/p.A$, rebuild the proper name
@@ -215,17 +233,25 @@
 		if (TypeDeclaration.kind(this.modifiers) == TypeDeclaration.INTERFACE_DECL) return;
 		char[] encodedName = CharOperation.concat(superQualification, superTypeName, '/');
 		CharOperation.replace(encodedName, '.', '/');
-		this.superclass = encodedName;
+		recordSuperclass(encodedName);
 	} else {
 		char[] encodedName = CharOperation.concat(superQualification, superTypeName, '/');
 		CharOperation.replace(encodedName, '.', '/');
-		if (this.superInterfaces == NoInterface){
-			this.superInterfaces = new char[][] { encodedName };
-		} else {
-			int length = this.superInterfaces.length;
-			System.arraycopy(this.superInterfaces, 0, this.superInterfaces = new char[length+1][], 0, length);
-			this.superInterfaces[length] = encodedName;
-		}
+		recordInterface(encodedName);
+	}
+}
+
+public void recordSuperclass(char[] binaryName) {
+	this.superclass = binaryName;
+}
+
+public void recordInterface(char[] binaryName) {
+	if (this.superInterfaces == NoInterface){
+		this.superInterfaces = new char[][] { binaryName };
+	} else {
+		int length = this.superInterfaces.length;
+		System.arraycopy(this.superInterfaces, 0, this.superInterfaces = new char[length+1][], 0, length);
+		this.superInterfaces[length] = binaryName;
 	}
 }
 
@@ -235,6 +261,7 @@
 public char[] sourceFileName() {
 	return null;
 }
+@Override
 public String toString() {
 	StringBuffer buffer = new StringBuffer();
 	if (this.modifiers == ClassFileConstants.AccPublic) {
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyBuilder.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyBuilder.java
index 41fb754..46fd72d 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyBuilder.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyBuilder.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation 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
@@ -18,15 +18,25 @@
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.jdt.core.IClassFile;
 import org.eclipse.jdt.core.IType;
 import org.eclipse.jdt.core.JavaModelException;
 import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
 import org.eclipse.jdt.internal.compiler.env.IBinaryType;
 import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
 import org.eclipse.jdt.internal.compiler.env.IGenericType;
 import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
 import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
-import org.eclipse.jdt.internal.core.*;
+import org.eclipse.jdt.internal.core.ClassFile;
+import org.eclipse.jdt.internal.core.JavaElement;
+import org.eclipse.jdt.internal.core.JavaProject;
+import org.eclipse.jdt.internal.core.NameLookup;
+import org.eclipse.jdt.internal.core.Openable;
+import org.eclipse.jdt.internal.core.ResolvedBinaryType;
+import org.eclipse.jdt.internal.core.SearchableEnvironment;
+import org.eclipse.jdt.internal.core.SourceTypeElementInfo;
+import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeFactory;
 import org.eclipse.jdt.internal.core.util.ResourceCompilationUnit;
 import org.eclipse.jdt.internal.core.util.Util;
 
@@ -280,6 +290,7 @@
 protected ICompilationUnit createCompilationUnitFromPath(Openable handle, IFile file) {
 	final char[] elementName = handle.getElementName().toCharArray();
 	return new ResourceCompilationUnit(file) {
+		@Override
 		public char[] getFileName() {
 			return elementName;
 		}
@@ -316,33 +327,17 @@
  * Create a type info from the given class file in a jar and adds it to the given list of infos.
  */
 protected IBinaryType createInfoFromClassFileInJar(Openable classFile) {
-	PackageFragment pkg = (PackageFragment) classFile.getParent();
-	String classFilePath = Util.concatWith(pkg.names, classFile.getElementName(), '/');
-	IBinaryType info = null;
-	java.util.zip.ZipFile zipFile = null;
+	IClassFile cf = (IClassFile)classFile;
+	IBinaryType info;
 	try {
-		zipFile = ((JarPackageFragmentRoot)pkg.getParent()).getJar();
-		info = org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader.read(
-			zipFile,
-			classFilePath);
-	} catch (org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException e) {
+		info = BinaryTypeFactory.create(cf, null);
+	} catch (JavaModelException | ClassFormatException e) {
 		if (TypeHierarchy.DEBUG) {
 			e.printStackTrace();
 		}
 		return null;
-	} catch (java.io.IOException e) {
-		if (TypeHierarchy.DEBUG) {
-			e.printStackTrace();
-		}
-		return null;
-	} catch (CoreException e) {
-		if (TypeHierarchy.DEBUG) {
-			e.printStackTrace();
-		}
-		return null;
-	} finally {
-		JavaModelManager.getJavaModelManager().closeZipFile(zipFile);
 	}
+
 	this.infoToHandle.put(info, classFile);
 	return info;
 }
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyResolver.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyResolver.java
index b57e2d5..d9a99ff 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyResolver.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyResolver.java
@@ -87,6 +87,7 @@
 	private CompilerOptions options;
 	HierarchyBuilder builder;
 	private ReferenceBinding[] typeBindings;
+	private BindingMap<IGenericType> bindingMap = new BindingMap<>();
 
 	private int typeIndex;
 	private IGenericType[] typeModels;
@@ -230,10 +231,9 @@
 				}
 			}
 		}
-		for (int t = this.typeIndex; t >= 0; t--) {
-			if (TypeBinding.equalsEquals(this.typeBindings[t], superBinding)) {
-				return this.builder.getHandle(this.typeModels[t], superBinding);
-			}
+		IGenericType typeModel = this.bindingMap.get(superBinding);
+		if (typeModel != null) {
+			return this.builder.getHandle(typeModel, superBinding);
 		}
 	}
 	return null;
@@ -336,13 +336,12 @@
 			// ensure that the binding corresponds to the interface defined by the user
 			if (CharOperation.equals(simpleName, interfaceBinding.sourceName)) {
 				bindingIndex++;
-				for (int t = this.typeIndex; t >= 0; t--) {
-					if (TypeBinding.equalsEquals(this.typeBindings[t], interfaceBinding)) {
-						IType handle = this.builder.getHandle(this.typeModels[t], interfaceBinding);
-						if (handle != null) {
-							superinterfaces[index++] = handle;
-							continue next;
-						}
+				IGenericType genericType = this.bindingMap.get(interfaceBinding);
+				if (genericType != null) {
+					IType handle = this.builder.getHandle(genericType, interfaceBinding);
+					if (handle != null) {
+						superinterfaces[index++] = handle;
+						continue next;
 					}
 				}
 			}
@@ -438,6 +437,7 @@
 	}
 	this.typeModels[this.typeIndex] = suppliedType;
 	this.typeBindings[this.typeIndex] = typeBinding;
+	this.bindingMap.put(typeBinding, suppliedType);
 }
 private void remember(IType type, ReferenceBinding typeBinding) {
 //{ObjectTeams: for phantom roles avoid hitting the JME (phantom has no info) but proceed into else as to record what we have
@@ -740,6 +740,7 @@
 	this.typeIndex = -1;
 	this.typeModels = new IGenericType[5];
 	this.typeBindings = new ReferenceBinding[5];
+	this.bindingMap.clear();
 }
 
 /**
@@ -1062,6 +1063,7 @@
 	this.typeIndex = -1;
 	this.typeModels = new IGenericType[5];
 	this.typeBindings = new ReferenceBinding[5];
+	this.bindingMap.clear();
 }
 
 /*
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/IndexBasedHierarchyBuilder.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/IndexBasedHierarchyBuilder.java
index 4c9d1e7..a2342eb 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/IndexBasedHierarchyBuilder.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/IndexBasedHierarchyBuilder.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation 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
@@ -10,6 +10,7 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.core.hierarchy;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
@@ -17,9 +18,12 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
 
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.SubMonitor;
@@ -50,9 +54,19 @@
 import org.eclipse.jdt.internal.core.Openable;
 import org.eclipse.jdt.internal.core.PackageFragment;
 import org.eclipse.jdt.internal.core.SearchableEnvironment;
+import org.eclipse.jdt.internal.core.nd.IReader;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.indexer.Indexer;
+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.NdType;
+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.NdTypeSignature;
 import org.eclipse.jdt.internal.core.search.IndexQueryRequestor;
 import org.eclipse.jdt.internal.core.search.JavaSearchParticipant;
 import org.eclipse.jdt.internal.core.search.SubTypeSearchJob;
+import org.eclipse.jdt.internal.core.search.UnindexedSearchScope;
 import org.eclipse.jdt.internal.core.search.indexing.IIndexConstants;
 import org.eclipse.jdt.internal.core.search.indexing.IndexManager;
 import org.eclipse.jdt.internal.core.search.matching.MatchLocator;
@@ -467,7 +481,103 @@
 	int waitingPolicy,	// WaitUntilReadyToSearch | ForceImmediateSearch | CancelIfNotReadyToSearch
 	final IProgressMonitor monitor) {
 
-	SubMonitor subMonitor = SubMonitor.convert(monitor);
+	if (JavaIndex.isEnabled()) {
+		SubMonitor subMonitor = SubMonitor.convert(monitor, 2);
+		newSearchAllPossibleSubTypes(type, scope, binariesFromIndexMatches, pathRequestor, waitingPolicy,
+				subMonitor.split(1));
+		legacySearchAllPossibleSubTypes(type, UnindexedSearchScope.filterEntriesCoveredByTheNewIndex(scope),
+				binariesFromIndexMatches, pathRequestor, waitingPolicy, subMonitor.split(1));
+	} else {
+		legacySearchAllPossibleSubTypes(type, scope, binariesFromIndexMatches, pathRequestor, waitingPolicy,
+				monitor);
+	}
+}
+
+private static void newSearchAllPossibleSubTypes(IType type, IJavaSearchScope scope2, Map binariesFromIndexMatches2,
+		IPathRequestor pathRequestor, int waitingPolicy, IProgressMonitor progressMonitor) {
+	SubMonitor subMonitor = SubMonitor.convert(progressMonitor, 2);
+	JavaIndex index = JavaIndex.getIndex();
+
+	Indexer.getInstance().waitForIndex(waitingPolicy, subMonitor.split(1));
+
+	Nd nd = index.getNd();
+	char[] fieldDefinition = JavaNames.fullyQualifiedNameToFieldDescriptor(type.getFullyQualifiedName().toCharArray());
+
+	IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+
+	try (IReader reader = nd.acquireReadLock()) {
+		NdTypeId foundType = index.findType(fieldDefinition);
+
+		if (foundType == null) {
+			return;
+		}
+
+		ArrayDeque<NdType> typesToVisit = new ArrayDeque<>();
+		Set<NdType> discoveredTypes = new HashSet<>();
+		typesToVisit.addAll(foundType.getTypes());
+		discoveredTypes.addAll(typesToVisit);
+
+		while (!typesToVisit.isEmpty()) {
+			NdType nextType = typesToVisit.removeFirst();
+			NdTypeId typeId = nextType.getTypeId();
+
+			String typePath = new String(JavaNames.getIndexPathFor(nextType, root));
+			if (!scope2.encloses(typePath)) {
+				continue;
+			}
+
+			subMonitor.setWorkRemaining(Math.max(typesToVisit.size(), 3000)).split(1);
+
+			boolean isLocalClass = nextType.isLocal() || nextType.isAnonymous();
+			pathRequestor.acceptPath(typePath, isLocalClass);
+
+			HierarchyBinaryType binaryType = (HierarchyBinaryType)binariesFromIndexMatches2.get(typePath);
+			if (binaryType == null) {
+				binaryType = createBinaryTypeFrom(nextType);
+				binariesFromIndexMatches2.put(typePath, binaryType);
+			}
+
+			for (NdType subType : typeId.getSubTypes()) {
+				if (discoveredTypes.add(subType)) {
+					typesToVisit.add(subType);
+				}
+			}
+		}
+	}
+}
+
+private static HierarchyBinaryType createBinaryTypeFrom(NdType type) {
+	char[] enclosingTypeName = null;
+	NdTypeSignature enclosingType = type.getDeclaringType();
+	if (enclosingType != null) {
+		enclosingTypeName = enclosingType.getRawType().getBinaryName();
+	}
+	char[][] typeParameters = type.getTypeParameterSignatures();
+	NdTypeId typeId = type.getTypeId();
+	HierarchyBinaryType result = new HierarchyBinaryType(type.getModifiers(), typeId.getBinaryName(),
+		type.getSourceName(), enclosingTypeName, typeParameters.length == 0 ? null : typeParameters);
+
+	NdTypeSignature superClass = type.getSuperclass();
+	if (superClass != null) {
+		result.recordSuperclass(superClass.getRawType().getBinaryName());
+	}
+
+	for (NdTypeInterface interf : type.getInterfaces()) {
+		result.recordInterface(interf.getInterface().getRawType().getBinaryName());
+	}
+	return result;
+}
+
+private static void legacySearchAllPossibleSubTypes(
+	IType type,
+	IJavaSearchScope scope,
+	final Map binariesFromIndexMatches,
+	final IPathRequestor pathRequestor,
+	int waitingPolicy,	// WaitUntilReadyToSearch | ForceImmediateSearch | CancelIfNotReadyToSearch
+	final IProgressMonitor progressMonitor) {
+
+	SubMonitor subMonitor = SubMonitor.convert(progressMonitor, 100);
+
 	/* embed constructs inside arrays so as to pass them to (inner) collector */
 	final Queue queue = new Queue();
 	final HashtableOfObject foundSuperNames = new HashtableOfObject(5);
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/AbstractTypeFactory.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/AbstractTypeFactory.java
new file mode 100644
index 0000000..957e7cf
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/AbstractTypeFactory.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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;
+
+import org.eclipse.jdt.internal.core.nd.field.StructDef.DeletionSemantics;
+
+public abstract class AbstractTypeFactory<T> implements ITypeFactory<T> {
+	@Override
+	public void destructFields(Nd dom, long address) {
+		// No nested fields by default
+	}
+
+	@Override
+	public void destruct(Nd dom, long address) {
+		// Nothing to destruct by default
+	}
+
+	@Override
+	public boolean hasDestructor() {
+		return false;
+	}
+
+	@Override
+	public boolean isReadyForDeletion(Nd dom, long address) {
+		return false;
+	}
+
+	@Override
+	public DeletionSemantics getDeletionSemantics() {
+		return DeletionSemantics.EXPLICIT;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/DatabaseRef.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/DatabaseRef.java
new file mode 100644
index 0000000..044fe24
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/DatabaseRef.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * 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 java.util.function.Supplier;
+
+/**
+ * Holds a reference to a database entity that may be retained across read locks. In normal circumstances, it
+ * is unsafe to retain a database address after a read lock is released since the object pointed to at that
+ * address may have been deleted in the meantime. This class addresses this problem by remembering both the
+ * address itself and enough information to determine whether that address is invalid and search for an
+ * equivalent object if the original is lost.
+ */
+public class DatabaseRef<T extends NdNode> implements Supplier<T> {
+	private final Nd nd;
+	private T lastResult;
+	private long writeCounter;
+	private final Supplier<T> searchFunction;
+
+	/**
+	 * Constructs a new {@link DatabaseRef} that will search for its target using the given search function.
+	 */
+	public DatabaseRef(Nd nd, Supplier<T> searchFunction) {
+		this.nd = nd;
+		this.searchFunction = searchFunction;
+		this.writeCounter = -1;
+	}
+
+	/**
+	 * Constructs a new {@link DatabaseRef} that will search for its target using the given search function.
+	 */
+	public DatabaseRef(Nd nd, Supplier<T> searchFunction, T initialResult) {
+		this.nd = nd;
+		this.searchFunction = searchFunction;
+		this.lastResult = initialResult;
+		this.writeCounter = this.nd.getWriteNumber();
+	}
+
+	/**
+	 * Returns the referenced object or null if the object is no longer present in the database.
+	 */
+	public T get() {
+		long ndWriteNumber = this.nd.getWriteNumber();
+		if (this.writeCounter == ndWriteNumber) {
+			return this.lastResult;
+		}
+
+		T result = this.searchFunction.get();
+		this.writeCounter = ndWriteNumber;
+		this.lastResult = result;
+		return result;
+	}
+
+	public Nd getNd() {
+		return this.nd;
+	}
+
+	/**
+	 * Acquires a read lock. Callers must invoke close() on the result when done.
+	 */
+	public IReader lock() {
+		return this.nd.acquireReadLock();
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IDestructable.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IDestructable.java
new file mode 100644
index 0000000..5bc78e0
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IDestructable.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * This mix-in interface is implemented by database objects that require a custom
+ * destruction step.
+ */
+public interface IDestructable {
+	/**
+	 * Intended to be implemented by objects which require a custom destruction step.
+	 * This should normally not be invoked by clients, since custom destruction is just
+	 * one step in tearing down an object. The normal way to tear down an object is
+	 * {@link NdNode#delete}
+	 * <p>
+	 * If you are writing code that must run as part of delete (or are implementing part
+	 * of the destruct method on a custom ITypeFactory)the correct steps to destructing
+	 * an object are:
+	 * <ul>
+	 * <li>Invoke this destruct method (which serves the same purpose as the user-implemented
+	 *     portion of a C++ destructor)</li>
+	 * <li>Invoke ITypeFactory.destructFields to destruct its fields (which serves the same
+	 *     purpose as the compiler-implemented portion of a C++ destructor)</li>
+	 * <li>Invoke Database.free on its address to free up memory allocated for the object
+	 *     itself. (Which serves the same purpose as the memory deallocation step in
+	 *     the C++ delete operator)</li>
+	 * </ul>
+	 * <p>
+	 * Normally, first two steps are performed together as part of ITypeFactory.destruct
+	 */
+	void destruct();
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IIndexFileLocation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IIndexFileLocation.java
new file mode 100644
index 0000000..8a805d0
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IIndexFileLocation.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * 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;
+
+import java.net.URI;
+
+/**
+ * Files in the index are (conceptually) partitioned into workspace and non-workspace (external) files. Two index file
+ * locations are considered equal if their URIs are equal.
+ *
+ * @noextend This interface is not intended to be extended by clients.
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface IIndexFileLocation {
+	/**
+	 * Returns the URI of the indexed file (non-{@code null}).
+	 */
+	public URI getURI();
+
+	/**
+	 * Returns the workspace relative path of the file in the index or {@code null} if the file is not in the workspace.
+	 */
+	public String getFullPath();
+}
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
new file mode 100644
index 0000000..ef24eb6
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdNode.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * 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/INdVisitor.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdVisitor.java
new file mode 100644
index 0000000..034e233
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdVisitor.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * 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/IReader.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IReader.java
new file mode 100644
index 0000000..3e1c321
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IReader.java
@@ -0,0 +1,16 @@
+/*******************************************************************************
+ * 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;
+
+public interface IReader extends AutoCloseable {
+	@Override
+	void close();
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/ITypeFactory.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/ITypeFactory.java
new file mode 100644
index 0000000..e387a40
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/ITypeFactory.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * 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.field.StructDef.DeletionSemantics;
+
+// TODO(sxenos): rename this to something like "StructDescriptor" -- it's more than a factory and the word
+// type is overloaded in JDT.
+public interface ITypeFactory<T> {
+	/**
+	 * Invokes the delete method on all the fields of the object, and calls deleteFields on the superclass' type (if
+	 * any). Does not perform any higher-level cleanup operations. This is only intended to be called from the
+	 * deleteFields methods of a subtype or the delete method of this class.
+	 * <p>
+	 * When destructing a type with a superclass, the correct destruction behavior is:
+	 * <ul>
+	 * <li>External code invokes the delete method on ITypeFactory
+	 * <li>The ITypeFactory.delete method calls an instance method on the class (typically called T#delete()), which
+	 * performs high-level deletion operations (if any).
+	 * <li>T.delete also calls T.super.delete() (if any)
+	 * <li>ITypeFactory.delete calls ITypeFactory.deleteFields, which performs low-level deletion operations on the
+	 * fields, then calls ITypeFactory.deleteFields on the base type.
+	 * </ul>
+	 */
+	void destructFields(Nd dom, long address);
+
+	T create(Nd dom, long address);
+
+	/**
+	 * Invokes any cleanup code for this object. In particular, it deallocates any memory allocated by the type's
+	 * fields. Does not free the memory at address, though. This is used for both objects which were allocated their own
+	 * memory block and objects which are embedded as fields within a larger object. If the object was given its own
+	 * memory block, it is the caller's responsibility to invoke free after calling this method.
+	 */
+	void destruct(Nd dom, long address);
+
+	/**
+	 * If this returns false, the delete and deleteFields methods both always do nothing.
+	 */
+	boolean hasDestructor();
+
+	int getRecordSize();
+
+	Class<?> getElementClass();
+
+	/**
+	 * Returns true if this object is orphaned. If the object is refcounted, this means the refcount is 0. If
+	 * the object is deleted via an owner pointer, this means the owner pointer is null.
+	 */
+	boolean isReadyForDeletion(Nd dom, long address);
+
+	/**
+	 * Returns the deletion semantics used for this object.
+	 */
+	DeletionSemantics getDeletionSemantics();
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IndexFileLocation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IndexFileLocation.java
new file mode 100644
index 0000000..e311214
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IndexFileLocation.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * 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 java.net.URI;
+
+/**
+ * An implementation of IIndexFileLocation.
+ */
+public class IndexFileLocation implements IIndexFileLocation {
+	private final URI uri;
+	private final String fullPath;
+
+	public IndexFileLocation(URI uri, String fullPath) {
+		if (uri == null)
+			throw new IllegalArgumentException();
+		this.uri = uri;
+		this.fullPath = fullPath;
+	}
+
+	@Override
+	public String getFullPath() {
+		return this.fullPath;
+	}
+
+	@Override
+	public URI getURI() {
+		return this.uri;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (obj instanceof IIndexFileLocation) {
+			return this.uri.equals(((IIndexFileLocation) obj).getURI());
+		}
+		return false;
+	}
+
+	@Override
+	public int hashCode() {
+		return this.uri.hashCode();
+	}
+
+	@Override
+	public String toString() {
+		if (this.fullPath == null) {
+			return this.uri.toString();
+		}
+		return this.fullPath.toString() + " (" + this.uri.toString() + ')'; //$NON-NLS-1$
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/LongArray.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/LongArray.java
new file mode 100644
index 0000000..11d28f8
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/LongArray.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * Represents an array of long.
+ */
+public class LongArray {
+	private static final int MIN_CAPACITY = 8;
+	private long[] contents;
+	private int size;
+
+	long get(int index) {
+		if (index >= this.size) {
+			throw new ArrayIndexOutOfBoundsException(index);
+		}
+
+		return this.contents[index];
+	}
+
+	long removeLast() {
+		return this.contents[--this.size];
+	}
+
+	void addLast(long toAdd) {
+		ensureCapacity(this.size + 1);
+		this.contents[this.size++] = toAdd;
+	}
+
+	private void ensureCapacity(int capacity) {
+		if (this.contents == null) {
+			this.contents = new long[Math.max(MIN_CAPACITY, capacity)];
+		}
+
+		if (this.contents.length >= capacity) {
+			return;
+		}
+
+		int newSize = capacity * 2;
+		long[] newContents = new long[newSize];
+
+		System.arraycopy(this.contents, 0, newContents, 0, this.contents.length);
+		this.contents = newContents;
+	}
+
+	int size() {
+		return this.size;
+	}
+
+	public boolean isEmpty() {
+		return this.size == 0;
+	}
+}
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
new file mode 100644
index 0000000..fb02f13
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Nd.java
@@ -0,0 +1,602 @@
+/*******************************************************************************
+ * 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 java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.jdt.internal.core.nd.db.ChunkCache;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+
+/**
+ * Network Database for storing semantic information.
+ */
+public class Nd {
+	private static final int CANCELLATION_CHECK_INTERVAL = 500;
+	private static final int BLOCKED_WRITE_LOCK_OUTPUT_INTERVAL = 30000;
+	private static final int LONG_WRITE_LOCK_REPORT_THRESHOLD = 1000;
+	private static final int LONG_READ_LOCK_WAIT_REPORT_THRESHOLD = 1000;
+	public static boolean sDEBUG_LOCKS= false;
+	public static boolean DEBUG_DUPLICATE_DELETIONS = false;
+
+	private final int currentVersion;
+	private final int maxVersion;
+	private final int minVersion;
+
+	public static int version(int major, int minor) {
+		return (major << 16) + minor;
+	}
+
+	/**
+	 * Returns the version that shall be used when creating new databases.
+	 */
+	public int getDefaultVersion() {
+		return this.currentVersion;
+	}
+
+	public boolean isSupportedVersion(int vers) {
+		return vers >= this.minVersion && vers <= this.maxVersion;
+	}
+
+	public int getMinSupportedVersion() {
+		return this.minVersion;
+	}
+
+	public int getMaxSupportedVersion() {
+		return this.maxVersion;
+	}
+
+	public static String versionString(int version) {
+		final int major= version >> 16;
+		final int minor= version & 0xffff;
+		return "" + major + '.' + minor; //$NON-NLS-1$
+	}
+
+	// Local caches
+	protected Database db;
+	private File fPath;
+	private final HashMap<Object, Object> fResultCache = new HashMap<>();
+
+	private final NdNodeTypeRegistry<NdNode> fNodeTypeRegistry;
+	private HashMap<Long, Object> pendingDeletions = new HashMap<>();
+
+	private IReader fReader = new IReader() {
+		@Override
+		public void close() {
+			releaseReadLock();
+		}
+	};
+
+	/**
+	 * This long is incremented every time a change is written to the database. Can be used to determine if the database
+	 * has changed.
+	 */
+	private long fWriteNumber;
+
+	public Nd(File dbPath, NdNodeTypeRegistry<NdNode> nodeTypes, int minVersion, int maxVersion,
+			int currentVersion) throws IndexException {
+		this(dbPath, ChunkCache.getSharedInstance(), nodeTypes, minVersion, maxVersion, currentVersion);
+	}
+
+	public Nd(File dbPath, ChunkCache chunkCache, NdNodeTypeRegistry<NdNode> nodeTypes, int minVersion,
+			int maxVersion, int currentVersion) throws IndexException {
+		this.currentVersion = currentVersion;
+		this.maxVersion = maxVersion;
+		this.minVersion = minVersion;
+		this.fNodeTypeRegistry = nodeTypes;
+		loadDatabase(dbPath, chunkCache);
+		if (sDEBUG_LOCKS) {
+			this.fLockDebugging = new HashMap<>();
+			System.out.println("Debugging database Locks"); //$NON-NLS-1$
+		}
+	}
+
+	public File getPath() {
+		return this.fPath;
+	}
+
+	public long getWriteNumber() {
+		return this.fWriteNumber;
+	}
+
+	public void scheduleDeletion(long addressOfNodeToDelete) {
+		if (this.pendingDeletions.containsKey(addressOfNodeToDelete)) {
+			logDoubleDeletion(addressOfNodeToDelete);
+			return;
+		}
+
+		Object data = Boolean.TRUE;
+		if (DEBUG_DUPLICATE_DELETIONS) {
+			data = new RuntimeException();
+		}
+		this.pendingDeletions.put(addressOfNodeToDelete, data);
+	}
+
+	protected void logDoubleDeletion(long addressOfNodeToDelete) {
+		// Sometimes an object can be scheduled for deletion twice, if it is created and then discarded shortly
+		// afterward during indexing. This may indicate an inefficiency in the indexer but is not necessarily
+		// a bug.
+		// If you're debugging issues related to duplicate deletions, set DEBUG_DUPLICATE_DELETIONS to true
+		Package.log("Database object queued for deletion twice", new RuntimeException()); //$NON-NLS-1$
+		Object earlierData = this.pendingDeletions.get(addressOfNodeToDelete);
+		if (earlierData instanceof RuntimeException) {
+			RuntimeException exception = (RuntimeException) earlierData;
+
+			Package.log("Data associated with earlier deletion stack was:", exception); //$NON-NLS-1$
+		}
+	}
+
+	/**
+	 * Synchronously processes all pending deletions
+	 */
+	public void processDeletions() {
+		while (!this.pendingDeletions.isEmpty()) {
+			long next = this.pendingDeletions.keySet().iterator().next();
+
+			deleteIfUnreferenced(next);
+
+			this.pendingDeletions.remove(next);
+		}
+	}
+
+	/**
+	 * Returns whether this {@link Nd} can never be written to. Writable subclasses should return false.
+	 */
+	protected boolean isPermanentlyReadOnly() {
+		return false;
+	}
+
+	private void loadDatabase(File dbPath, ChunkCache cache) throws IndexException {
+		this.fPath= dbPath;
+		final boolean lockDB= this.db == null || this.lockCount != 0;
+
+		clearCaches();
+		this.db = new Database(this.fPath, cache, getDefaultVersion(), isPermanentlyReadOnly());
+
+		this.db.setLocked(lockDB);
+		if (!isSupportedVersion()) {
+			Package.log("Index database is uses an unsupported version " + this.db.getVersion() //$NON-NLS-1$
+				+ " Deleting and recreating.", null); //$NON-NLS-1$
+			this.db.close();
+			this.fPath.delete();
+			this.db = new Database(this.fPath, cache, getDefaultVersion(), isPermanentlyReadOnly());
+			this.db.setLocked(lockDB);
+		}
+		this.fWriteNumber = this.db.getLong(Database.WRITE_NUMBER_OFFSET);
+		this.db.setLocked(this.lockCount != 0);
+	}
+
+	public Database getDB() {
+		return this.db;
+	}
+
+	// Read-write lock rules. Readers don't conflict with other readers,
+	// Writers conflict with readers, and everyone conflicts with writers.
+	private final Object mutex = new Object();
+	private int lockCount;
+	private int waitingReaders;
+	private long lastWriteAccess= 0;
+	//private long lastReadAccess= 0;
+	private long timeWriteLockAcquired;
+
+	public IReader acquireReadLock() {
+		try {
+			long t = sDEBUG_LOCKS ? System.nanoTime() : 0;
+			synchronized (this.mutex) {
+				++this.waitingReaders;
+				try {
+					while (this.lockCount < 0)
+						this.mutex.wait();
+				} finally {
+					--this.waitingReaders;
+				}
+				++this.lockCount;
+				this.db.setLocked(true);
+
+				if (sDEBUG_LOCKS) {
+					t = (System.nanoTime() - t) / 1000000;
+					if (t >= LONG_READ_LOCK_WAIT_REPORT_THRESHOLD) {
+						System.out.println("Acquired index read lock after " + t + " ms wait."); //$NON-NLS-1$//$NON-NLS-2$
+					}
+					incReadLock(this.fLockDebugging);
+				}
+				return this.fReader;
+			}
+		} catch (InterruptedException e) {
+			throw new OperationCanceledException();
+		}
+	}
+
+	public void releaseReadLock() {
+		synchronized (this.mutex) {
+			assert this.lockCount > 0: "No lock to release"; //$NON-NLS-1$
+			if (sDEBUG_LOCKS) {
+				decReadLock(this.fLockDebugging);
+			}
+
+			//this.lastReadAccess= System.currentTimeMillis();
+			if (this.lockCount > 0)
+				--this.lockCount;
+			this.mutex.notifyAll();
+			this.db.setLocked(this.lockCount != 0);
+		}
+		// A lock release probably means that some AST is going away. The result cache has to be
+		// cleared since it may contain objects belonging to the AST that is going away. A failure
+		// to release an AST object would cause a memory leak since the whole AST would remain
+		// pinned to memory.
+		// TODO(sprigogin): It would be more efficient to replace the global result cache with
+		// separate caches for each AST.
+		//clearResultCache();
+	}
+
+	/**
+	 * Acquire a write lock on this {@link Nd}. Blocks until any existing read/write locks are released.
+	 * @throws OperationCanceledException
+	 * @throws IllegalStateException if this {@link Nd} is not writable
+	 */
+	public void acquireWriteLock(IProgressMonitor monitor) {
+		try {
+			acquireWriteLock(0, monitor);
+		} catch (InterruptedException e) {
+			throw new OperationCanceledException();
+		}
+	}
+
+	/**
+	 * Acquire a write lock on this {@link Nd}, giving up the specified number of read locks first. Blocks
+	 * until any existing read/write locks are released.
+	 * @throws InterruptedException
+	 * @throws IllegalStateException if this {@link Nd} is not writable
+	 */
+	public void acquireWriteLock(int giveupReadLocks, IProgressMonitor monitor) throws InterruptedException {
+		assert !isPermanentlyReadOnly();
+		synchronized (this.mutex) {
+			if (sDEBUG_LOCKS) {
+				incWriteLock(giveupReadLocks);
+			}
+
+			if (giveupReadLocks > 0) {
+				// give up on read locks
+				assert this.lockCount >= giveupReadLocks: "Not enough locks to release"; //$NON-NLS-1$
+				if (this.lockCount < giveupReadLocks) {
+					giveupReadLocks= this.lockCount;
+				}
+			} else {
+				giveupReadLocks= 0;
+			}
+
+			// Let the readers go first
+			long start= sDEBUG_LOCKS ? System.currentTimeMillis() : 0;
+			while (this.lockCount > giveupReadLocks || this.waitingReaders > 0) {
+				this.mutex.wait(CANCELLATION_CHECK_INTERVAL);
+				if (monitor != null && monitor.isCanceled()) {
+					throw new OperationCanceledException();
+				}
+				if (sDEBUG_LOCKS) {
+					start = reportBlockedWriteLock(start, giveupReadLocks);
+				}
+			}
+			this.lockCount= -1;
+			if (sDEBUG_LOCKS)
+				this.timeWriteLockAcquired = System.currentTimeMillis();
+			this.db.setExclusiveLock();
+		}
+	}
+
+	public final void releaseWriteLock() {
+		releaseWriteLock(0, true);
+	}
+
+	@SuppressWarnings("nls")
+	public void releaseWriteLock(int establishReadLocks, boolean flush) {
+		boolean wasInterrupted = false;
+		// When all locks are released we can clear the result cache.
+		if (establishReadLocks == 0) {
+			processDeletions();
+			this.db.putLong(Database.WRITE_NUMBER_OFFSET, ++this.fWriteNumber);
+			clearResultCache();
+		}
+		try {
+			wasInterrupted = this.db.giveUpExclusiveLock(flush) || wasInterrupted;
+		} catch (IndexException e) {
+			Package.log(e);
+		}
+		assert this.lockCount == -1;
+		this.lastWriteAccess= System.currentTimeMillis();
+		synchronized (this.mutex) {
+			if (sDEBUG_LOCKS) {
+				long timeHeld = this.lastWriteAccess - this.timeWriteLockAcquired;
+				if (timeHeld >= LONG_WRITE_LOCK_REPORT_THRESHOLD) {
+					System.out.println("Index write lock held for " + timeHeld + " ms");
+				}
+				decWriteLock(establishReadLocks);
+			}
+
+			if (this.lockCount < 0)
+				this.lockCount= establishReadLocks;
+			this.mutex.notifyAll();
+			this.db.setLocked(this.lockCount != 0);
+		}
+
+		if (wasInterrupted) {
+			throw new OperationCanceledException();
+		}
+	}
+
+	public boolean hasWaitingReaders() {
+		synchronized (this.mutex) {
+			return this.waitingReaders > 0;
+		}
+	}
+
+	public long getLastWriteAccess() {
+		return this.lastWriteAccess;
+	}
+
+	public boolean isSupportedVersion() throws IndexException {
+		final int version = this.db.getVersion();
+		return version >= this.minVersion && version <= this.maxVersion;
+	}
+
+	public void close() throws IndexException {
+		this.db.close();
+		clearCaches();
+	}
+
+	private void clearCaches() {
+//		fileIndex= null;
+//		tagIndex = null;
+//		indexOfDefectiveFiles= null;
+//		indexOfFiledWithUnresolvedIncludes= null;
+//		fLinkageIDCache.clear();
+		clearResultCache();
+	}
+
+	public void clearResultCache() {
+		synchronized (this.fResultCache) {
+			this.fResultCache.clear();
+		}
+	}
+
+	public Object getCachedResult(Object key) {
+		synchronized (this.fResultCache) {
+			return this.fResultCache.get(key);
+		}
+	}
+
+	public void putCachedResult(Object key, Object result) {
+		putCachedResult(key, result, true);
+	}
+
+	public Object putCachedResult(Object key, Object result, boolean replace) {
+		synchronized (this.fResultCache) {
+			Object old= this.fResultCache.put(key, result);
+			if (old != null && !replace) {
+				this.fResultCache.put(key, old);
+				return old;
+			}
+			return result;
+		}
+	}
+
+	public void removeCachedResult(Object key) {
+		synchronized (this.fResultCache) {
+			this.fResultCache.remove(key);
+		}
+	}
+
+	// For debugging lock issues
+	static class DebugLockInfo {
+		int fReadLocks;
+		int fWriteLocks;
+		List<StackTraceElement[]> fTraces= new ArrayList<>();
+
+		public int addTrace() {
+			this.fTraces.add(Thread.currentThread().getStackTrace());
+			return this.fTraces.size();
+		}
+
+		@SuppressWarnings("nls")
+		public void write(String threadName) {
+			System.out.println("Thread: '" + threadName + "': " + this.fReadLocks + " readlocks, " + this.fWriteLocks + " writelocks");
+			for (StackTraceElement[] trace : this.fTraces) {
+				System.out.println("  Stacktrace:");
+				for (StackTraceElement ste : trace) {
+					System.out.println("    " + ste);
+				}
+			}
+		}
+
+		public void inc(DebugLockInfo val) {
+			this.fReadLocks+= val.fReadLocks;
+			this.fWriteLocks+= val.fWriteLocks;
+			this.fTraces.addAll(val.fTraces);
+		}
+	}
+
+	// For debugging lock issues
+	private Map<Thread, DebugLockInfo> fLockDebugging;
+
+	// For debugging lock issues
+	private static DebugLockInfo getLockInfo(Map<Thread, DebugLockInfo> lockDebugging) {
+		assert sDEBUG_LOCKS;
+
+		Thread key = Thread.currentThread();
+		DebugLockInfo result= lockDebugging.get(key);
+		if (result == null) {
+			result= new DebugLockInfo();
+			lockDebugging.put(key, result);
+		}
+		return result;
+	}
+
+	// For debugging lock issues
+	static void incReadLock(Map<Thread, DebugLockInfo> lockDebugging) {
+		DebugLockInfo info = getLockInfo(lockDebugging);
+		info.fReadLocks++;
+		if (info.addTrace() > 10) {
+			outputReadLocks(lockDebugging);
+		}
+	}
+
+	// For debugging lock issues
+	@SuppressWarnings("nls")
+	static void decReadLock(Map<Thread, DebugLockInfo> lockDebugging) throws AssertionError {
+		DebugLockInfo info = getLockInfo(lockDebugging);
+		if (info.fReadLocks <= 0) {
+			outputReadLocks(lockDebugging);
+			throw new AssertionError("Superfluous releaseReadLock");
+		}
+		if (info.fWriteLocks != 0) {
+			outputReadLocks(lockDebugging);
+			throw new AssertionError("Releasing readlock while holding write lock");
+		}
+		if (--info.fReadLocks == 0) {
+			lockDebugging.remove(Thread.currentThread());
+		} else {
+			info.addTrace();
+		}
+	}
+
+	// For debugging lock issues
+	@SuppressWarnings("nls")
+	private void incWriteLock(int giveupReadLocks) throws AssertionError {
+		DebugLockInfo info = getLockInfo(this.fLockDebugging);
+		if (info.fReadLocks != giveupReadLocks) {
+			outputReadLocks(this.fLockDebugging);
+			throw new AssertionError("write lock with " + giveupReadLocks + " readlocks, expected " + info.fReadLocks);
+		}
+		if (info.fWriteLocks != 0)
+			throw new AssertionError("Duplicate write lock");
+		info.fWriteLocks++;
+	}
+
+	// For debugging lock issues
+	private void decWriteLock(int establishReadLocks) throws AssertionError {
+		DebugLockInfo info = getLockInfo(this.fLockDebugging);
+		if (info.fReadLocks != establishReadLocks)
+			throw new AssertionError("release write lock with " + establishReadLocks + " readlocks, expected " + info.fReadLocks); //$NON-NLS-1$ //$NON-NLS-2$
+		if (info.fWriteLocks != 1)
+			throw new AssertionError("Wrong release write lock"); //$NON-NLS-1$
+		info.fWriteLocks= 0;
+		if (info.fReadLocks == 0) {
+			this.fLockDebugging.remove(Thread.currentThread());
+		}
+	}
+
+	// For debugging lock issues
+	@SuppressWarnings("nls")
+	private long reportBlockedWriteLock(long start, int giveupReadLocks) {
+		long now= System.currentTimeMillis();
+		if (now >= start + BLOCKED_WRITE_LOCK_OUTPUT_INTERVAL) {
+			System.out.println();
+			System.out.println("Blocked writeLock");
+			System.out.println("  lockcount= " + this.lockCount + ", giveupReadLocks=" + giveupReadLocks + ", waitingReaders=" + this.waitingReaders);
+			outputReadLocks(this.fLockDebugging);
+			start= now;
+		}
+		return start;
+	}
+
+	// For debugging lock issues
+	@SuppressWarnings("nls")
+	private static void outputReadLocks(Map<Thread, DebugLockInfo> lockDebugging) {
+		System.out.println("---------------------  Lock Debugging -------------------------");
+		for (Thread th: lockDebugging.keySet()) {
+			DebugLockInfo info = lockDebugging.get(th);
+			info.write(th.getName());
+		}
+		System.out.println("---------------------------------------------------------------");
+	}
+
+	// For debugging lock issues
+	public void adjustThreadForReadLock(Map<Thread, DebugLockInfo> lockDebugging) {
+		for (Thread th : lockDebugging.keySet()) {
+			DebugLockInfo val= lockDebugging.get(th);
+			if (val.fReadLocks > 0) {
+				DebugLockInfo myval= this.fLockDebugging.get(th);
+				if (myval == null) {
+					myval= new DebugLockInfo();
+					this.fLockDebugging.put(th, myval);
+				}
+				myval.inc(val);
+				for (int i = 0; i < val.fReadLocks; i++) {
+					decReadLock(this.fLockDebugging);
+				}
+			}
+		}
+	}
+
+    public NdNode getNode(long address, short nodeType) throws IndexException {
+    	return this.fNodeTypeRegistry.createNode(this, address, nodeType);
+    }
+
+    public <T extends NdNode> ITypeFactory<T> getTypeFactory(short nodeType) {
+    	return this.fNodeTypeRegistry.getTypeFactory(nodeType);
+    }
+
+	/**
+	 * Returns the type ID for the given class
+	 */
+	public short getNodeType(Class<? extends NdNode> toQuery) {
+		return this.fNodeTypeRegistry.getTypeForClass(toQuery);
+	}
+
+	private void deleteIfUnreferenced(long address) {
+		if (address == 0) {
+			return;
+		}
+		short nodeType = NdNode.NODE_TYPE.get(this, address);
+
+		// Look up the type
+		ITypeFactory<? extends NdNode> factory1 = getTypeFactory(nodeType);
+
+		if (factory1.isReadyForDeletion(this, address)) {
+			// Call its destructor
+			factory1.destruct(this, address);
+
+			// Free up its memory
+			getDB().free(address, (short)(Database.POOL_FIRST_NODE_TYPE + nodeType));
+		}
+	}
+
+	public void delete(long address) {
+		if (address == 0) {
+			return;
+		}
+		short nodeType = NdNode.NODE_TYPE.get(this, address);
+
+		// Look up the type
+		ITypeFactory<? extends NdNode> factory1 = getTypeFactory(nodeType);
+
+		// Call its destructor
+		factory1.destruct(this, address);
+
+		// Free up its memory
+		getDB().free(address, (short)(Database.POOL_FIRST_NODE_TYPE + nodeType));
+
+		// If this node was in the list of pending deletions, remove it since it's now been deleted
+		if (this.pendingDeletions.containsKey(address)) {
+			logDoubleDeletion(address);
+			this.pendingDeletions.remove(address);
+		}
+	}
+
+	public NdNodeTypeRegistry<NdNode> getTypeRegistry() {
+		return this.fNodeTypeRegistry;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdLinkedList.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdLinkedList.java
new file mode 100644
index 0000000..cfce208
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdLinkedList.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * 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;
+
+public final class NdLinkedList<T> {
+	private final NdRawLinkedList rawList;
+	final ITypeFactory<T> elementFactory;
+
+	public static interface ILinkedListVisitor<T> {
+		public void visit(T record, short metadataBits, int index) throws IndexException;
+	}
+
+	public NdLinkedList(Nd nd, long address, ITypeFactory<T> elementFactory, int recordsInFirstBlock,
+			int recordsInSubsequentBlocks) {
+		this(nd, address, elementFactory, recordsInFirstBlock, recordsInSubsequentBlocks, 0);
+	}
+
+	public NdLinkedList(Nd nd, long address, ITypeFactory<T> elementFactory, int recordsInFirstBlock,
+			int recordsInSubsequentBlocks, int metadataBitsPerElement) {
+		this.rawList = new NdRawLinkedList(nd, address, elementFactory.getRecordSize(), recordsInFirstBlock,
+				recordsInSubsequentBlocks, metadataBitsPerElement);
+		this.elementFactory = elementFactory;
+	}
+
+	/**
+	 * Computes the size of this list. This is an O(n) operation.
+	 *
+	 * @return the size of this list
+	 * @throws IndexException
+	 */
+	public int size() throws IndexException {
+		return this.rawList.size();
+	}
+
+	public T addMember(short metadataBits) throws IndexException {
+		long address = this.rawList.addMember(metadataBits);
+
+		return this.elementFactory.create(this.rawList.getNd(), address);
+	}
+
+	public void accept(final ILinkedListVisitor<T> visitor) throws IndexException {
+		final NdRawLinkedList localRawList = this.rawList;
+		final ITypeFactory<T> localElementFactory = this.elementFactory;
+		localRawList.accept(new NdRawLinkedList.ILinkedListVisitor() {
+			@Override
+			public void visit(long address, short metadataBits, int index) throws IndexException {
+				visitor.visit(localElementFactory.create(localRawList.getNd(),
+						address), metadataBits, index);
+			}
+		});
+	}
+
+	public static <T> ITypeFactory<NdLinkedList<T>> getFactoryFor(
+			final ITypeFactory<T> elementFactory, final int recordsInFirstBlock, final int recordsInSubsequentBlocks) {
+		return getFactoryFor(elementFactory, recordsInSubsequentBlocks, 0);
+	}
+
+	public static <T> ITypeFactory<NdLinkedList<T>> getFactoryFor(
+			final ITypeFactory<T> elementFactory, final int recordsInFirstBlock, final int recordsInSubsequentBlocks,
+			final int metadataBitsPerElement) {
+
+		return new AbstractTypeFactory<NdLinkedList<T>>() {
+			public NdLinkedList<T> create(Nd dom, long address) {
+				return new NdLinkedList<T>(dom, address, elementFactory, recordsInFirstBlock, recordsInSubsequentBlocks, metadataBitsPerElement);
+			}
+
+			@Override
+			public int getRecordSize() {
+				return NdRawLinkedList.recordSize(elementFactory.getRecordSize(), recordsInFirstBlock,
+						metadataBitsPerElement);
+			}
+
+			@Override
+			public Class<?> getElementClass() {
+				return NdLinkedList.class;
+			}
+
+			@Override
+			public boolean hasDestructor() {
+				return true;
+			}
+
+			@Override
+			public void destructFields(Nd dom, long address) {
+				create(dom, address).destruct();
+			}
+
+			@Override
+			public void destruct(Nd dom, long address) {
+				destructFields(dom, address);
+			}
+		};
+	}
+
+	/**
+	 *
+	 */
+	protected void destruct() {
+		if (this.elementFactory.hasDestructor()) {
+			final Nd nd = this.rawList.getNd();
+			this.rawList.accept(new NdRawLinkedList.ILinkedListVisitor() {
+				@Override
+				public void visit(long address, short metadataBits, int index) throws IndexException {
+					NdLinkedList.this.elementFactory.destruct(nd, address);
+				}
+			});
+		}
+		this.rawList.destruct();
+	}
+}
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
new file mode 100644
index 0000000..67d039b
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNode.java
@@ -0,0 +1,185 @@
+/*******************************************************************************
+ * 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.Database;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+import org.eclipse.jdt.internal.core.nd.field.FieldShort;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+/**
+ * This is a basic node in the network database.
+ */
+public abstract class NdNode implements IDestructable {
+	public static final FieldShort NODE_TYPE;
+
+	public static final StructDef<NdNode> type;
+
+	static {
+		type = StructDef.create(NdNode.class);
+		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.
+	 *
+	 * @param nd The {@link Nd} from which to load the node.
+	 * @param address The address of the node in the given {@link Nd}.
+	 * @return The {@link NdNode} at the specified location or null if a node cannot be loaded.
+	 * @When there is a problem reading the given {@link Nd}'s Database
+	 */
+	public static NdNode load(Nd nd, long address) {
+		if (address == 0) {
+			return null;
+		}
+
+		return nd.getNode(address, NODE_TYPE.get(nd, address));
+	}
+
+	@SuppressWarnings("unchecked")
+	public static <T extends NdNode> T load(Nd nd, long address, Class<T> clazz) {
+		if (address == 0) {
+			return null;
+		}
+
+		NdNode result = nd.getNode(address, NODE_TYPE.get(nd, address));
+
+		if (!clazz.isAssignableFrom(result.getClass())) {
+			throw new IndexException("Found wrong data type at address " + address + ". Expected a subclass of " +  //$NON-NLS-1$//$NON-NLS-2$
+					clazz + " but found " + result.getClass()); //$NON-NLS-1$
+		}
+
+		return (T)result;
+	}
+
+	/**
+	 * Invokes the destructor on this node and frees up its memory
+	 */
+	public final void delete() {
+		getNd().delete(this.address);
+	}
+
+	protected NdNode(Nd nd, long address) {
+		this.nd = nd;
+		this.address = address;
+	}
+
+	protected NdNode(Nd nd) {
+		Database db = nd.getDB();
+		this.nd = nd;
+
+		short nodeType = nd.getNodeType(getClass());
+		ITypeFactory<? extends NdNode> factory1 = nd.getTypeFactory(nodeType);
+
+		this.address = db.malloc(factory1.getRecordSize(), (short)(Database.POOL_FIRST_NODE_TYPE + nodeType));
+
+		NODE_TYPE.put(nd, this.address, nodeType);
+	}
+
+	protected Database getDB() {
+		return this.nd.getDB();
+	}
+
+	public 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}.
+	 * <b>
+	 */
+	public short getNodeType() {
+		return this.nd.getNodeType(getClass());
+	}
+
+	public final long getAddress() {
+		return this.address;
+	}
+
+	public final long getBindingID() {
+		return this.address;
+	}
+
+	@Override
+	public final boolean equals(Object obj) {
+		if (obj == this)
+			return true;
+		if (obj instanceof NdNode) {
+			NdNode other = (NdNode) obj;
+			return getNd() == other.getNd() && this.address == other.address;
+		}
+
+		return super.equals(obj);
+	}
+
+	@Override
+	public final int hashCode() {
+		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.
+	 */
+	public static int getNodeId(int linkageID, int nodeType) {
+		return (linkageID << 16) | (nodeType & 0xffff);
+	}
+
+	/**
+	 * Convenience method for fetching a byte from the database.
+	 * @param offset Location of the byte.
+	 * @return a byte from the database.
+	 */
+	protected byte getByte(long offset) {
+		return getDB().getByte(offset);
+	}
+
+	/**
+	 * Returns the bit at the specified offset in a bit vector.
+	 * @param bitVector Bits.
+	 * @param offset The position of the desired bit.
+	 * @return the bit at the specified offset.
+	 */
+	protected static boolean getBit(int bitVector, int offset) {
+		int mask = 1 << offset;
+		return (bitVector & mask) != 0;
+	}
+
+	/**
+	 * Dispose this {@link NdNode}. Subclasses should extend this method to perform any high-level node-specific cleanup.
+	 * This will be invoked prior to disposing the fields. Implementations must invoke their parent's destruct method
+	 * and should not destruct the fields.
+	 * <p>
+	 * If an external object wants to destroy a node, they should invoke {@link NdNode#delete} rather than this
+	 * method.
+	 */
+	public void destruct() {
+		// Nothing to do by default. Subclasses will provide an implementation if necessary.
+	}
+
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..b76057d
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNodeTypeRegistry.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * 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 java.util.BitSet;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+
+/**
+ * Maps integer constants onto factories for {@link NdNode} objects.
+ */
+public class NdNodeTypeRegistry<R> {
+	private final Map<Short, ITypeFactory<? extends R>> types = new HashMap<>();
+	private final BitSet reserved = new BitSet();
+	private final Map<Class<?>, Short> registeredClasses = new HashMap<>();
+
+	/**
+	 * Registers a class to be used with this node type registry. Note that if we ever want to stop registering a type
+	 * name in the future, its fully-qualified class name should be passed to reserve(...) to prevent its hashfrom being
+	 * reused in the future.
+	 */
+	public <T extends R> void register(int typeId, ITypeFactory<T> toRegister) {
+		if ((typeId & 0xFFFF0000) != 0) {
+			throw new IllegalArgumentException("The typeId " + typeId + " does not fit within a short int");  //$NON-NLS-1$//$NON-NLS-2$
+		}
+		short shortTypeId = (short)typeId;
+		String fullyQualifiedClassName = toRegister.getElementClass().getName();
+
+		if (this.types.containsKey(typeId) || this.reserved.get(typeId)) {
+			throw new IllegalArgumentException(
+					"The type id " + typeId + " for class " + fullyQualifiedClassName + " is already in use."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+		}
+
+		this.types.put(shortTypeId, toRegister);
+		this.registeredClasses.put(toRegister.getElementClass(), shortTypeId);
+	}
+
+	/**
+	 * Reserves the given node class name, such that its hash cannot be used by any other node registered with
+	 * "register". If we ever want to unregister a given Class from the type registry, its class name should be reserved
+	 * using this method. Doing so will prevent its type ID from being reused by another future class.
+	 */
+	public void reserve(short typeId) {
+		if (this.types.containsKey(typeId) || this.reserved.get(typeId)) {
+			throw new IllegalArgumentException("The type ID " + typeId + " is already in use"); //$NON-NLS-1$ //$NON-NLS-2$
+		}
+		this.reserved.set(typeId);
+	}
+
+	/**
+	 * Returns the class associated with the given type or null if the given type ID is not known
+	 */
+	public ITypeFactory<? extends R> getClassForType(short type) {
+		return this.types.get(type);
+	}
+
+	public R createNode(Nd nd, long address, short nodeType) throws IndexException {
+		ITypeFactory<? extends R> typeFactory = this.types.get(nodeType);
+
+		return typeFactory.create(nd, address);
+	}
+
+	public short getTypeForClass(Class<? extends R> toQuery) {
+		Short classId = this.registeredClasses.get(toQuery);
+
+		if (classId == null) {
+			throw new IllegalArgumentException(toQuery.getName() + " was not registered as a node type"); //$NON-NLS-1$
+		}
+		return classId;
+	}
+
+	@SuppressWarnings("unchecked")
+	public <T extends R> ITypeFactory<T> getTypeFactory(short nodeType) {
+		ITypeFactory<T> result = (ITypeFactory<T>) this.types.get(nodeType);
+
+		if (result == null) {
+			throw new IllegalArgumentException("The node type " + nodeType  //$NON-NLS-1$
+				+ " is not registered with this database"); //$NON-NLS-1$
+		}
+
+		return result;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdRawLinkedList.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdRawLinkedList.java
new file mode 100644
index 0000000..969a893
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdRawLinkedList.java
@@ -0,0 +1,271 @@
+/*******************************************************************************
+ * 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.Database;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+
+/**
+ * {@link NdRawLinkedList} stores a list of fixed-sized records. Along with the records themselves, there is also
+ * a bit field associated with each record which can hold a small number of bits of metadata per record.
+ * The underlying format is as follows:
+ *
+ * <pre>
+ * Bytes       Content
+ * ----------------
+ * 4           Pointer to the next block. If this is 0, this is the last block and it is not yet full. The number of
+ *             elements will be stored at the position where the last element would normally start. If this points back
+ *             to the start of the block, this is the last block and it is full. If this holds any other value, the
+ *             block is full and this points to the next block.
+ * headerSize  Bit field for this block (the bits for each element are tightly packed)
+ * recordSize  The content of the first element in the block
+ * recordSize  The content of the second element in the block
+ * ...         repeated recordsPerBlock times
+ * recordSize  If the block is full, this holds the last
+ * </pre>
+ *
+ * stored in linked blocks where each block is an array of record pointers. Each block contains a pointer to the
+ * subsequent block, so they can be chained.
+ * <p>
+ * The size of the blocks are generally hardcoded. All blocks are the same size except for the first block whose size
+ * may be configured independently. The size of the first block may be zero, in which case the first "block" is
+ * simply a pointer to the following block or null.
+ */
+public class NdRawLinkedList {
+	private static final int NEXT_MEMBER_BLOCK = 0;
+	private static final int ELEMENT_START_POSITION = NEXT_MEMBER_BLOCK + Database.PTR_SIZE;
+
+	private final long address;
+	private final Nd nd;
+	private final int firstBlockRecordCount;
+	private final int recordCount;
+	private final int elementRecordSize;
+	private final int metadataBitsPerRecord;
+
+	// Derived data. Holds the address for the last block we know about
+	private long lastKnownBlock;
+
+	public static interface ILinkedListVisitor {
+		public void visit(long address, short metadataBits, int index) throws IndexException;
+	}
+
+	/**
+	 * @param nd the Nd object
+	 * @param address pointer to the start of the linked list
+	 * @param recordsPerBlock number of records per block. This is normally a hardcoded value.
+	 */
+	public NdRawLinkedList(Nd nd, long address, int elementRecordSize, int firstBlockRecordCount, int recordsPerBlock,
+			int metadataBitsPerRecord) {
+		assert(recordsPerBlock > 0);
+		assert(firstBlockRecordCount >= 0);
+		this.nd = nd;
+		this.address = address;
+		this.firstBlockRecordCount = firstBlockRecordCount;
+		this.recordCount = recordsPerBlock;
+		this.elementRecordSize = elementRecordSize;
+		this.lastKnownBlock = address;
+		this.metadataBitsPerRecord = metadataBitsPerRecord;
+	}
+
+	/**
+	 * Returns the record size for a linked list with the given element record size and number of
+	 * records per block
+	 */
+	public static int recordSize(int elementRecordSize, int recordsPerBlock, int metadataBitsPerRecord) {
+		int metadataSize = 0;
+
+		if (metadataBitsPerRecord > 0) {
+			int metadataRecordsPerShort = 16 / metadataBitsPerRecord;
+			int numberOfShorts = (recordsPerBlock + metadataRecordsPerShort - 1) / metadataRecordsPerShort;
+
+			metadataSize = 2 * numberOfShorts;
+		}
+
+		return Database.PTR_SIZE + elementRecordSize * recordsPerBlock + metadataSize;
+	}
+
+	public Nd getNd() {
+		return this.nd;
+	}
+
+	private int getElementsInBlock(long currentRecord, long ptr, int currentRecordCount) throws IndexException {
+		if (ptr == 0 && currentRecordCount > 0) {
+			return getDB().getInt(getAddressOfElement(currentRecord, currentRecordCount - 1));
+		}
+		return currentRecordCount;
+	}
+
+	private Database getDB() {
+		return this.nd.getDB();
+	}
+
+	public long getAddress() {
+		return this.address;
+	}
+
+	/**
+	 * Adds a new element to the list and returns the record pointer to the start of the newly-allocated object
+	 *
+	 * @param metadataBits the metadata bits to attach to the new member. Use 0 if this list does not use metadata.
+	 */
+	public long addMember(short metadataBits) throws IndexException {
+		Database db = getDB();
+		long current = this.lastKnownBlock;
+		int thisBlockRecordCount = this.firstBlockRecordCount;
+		while (true) {
+			long ptr = db.getRecPtr(current + NEXT_MEMBER_BLOCK);
+			int elementsInBlock = getElementsInBlock(current, ptr, thisBlockRecordCount);
+
+			// If there's room in this block
+			if (elementsInBlock < thisBlockRecordCount) {
+				long positionOfElementCount = getAddressOfElement(current, thisBlockRecordCount - 1);
+				// If there's only one space left
+				if (elementsInBlock == thisBlockRecordCount - 1) {
+					// We use the fact that the next pointer points to itself as a sentinel to indicate that the
+					// block is full and there are no further blocks
+					db.putRecPtr(current + NEXT_MEMBER_BLOCK, current);
+					// Zero out the int we've been using to hold the count of elements
+					db.putInt(positionOfElementCount, 0);
+				} else {
+					// Increment the element count
+					db.putInt(positionOfElementCount, elementsInBlock + 1);
+				}
+
+				if (this.metadataBitsPerRecord > 0) {
+					int metadataMask = (1 << this.metadataBitsPerRecord) - 1;
+					int metadataRecordsPerShort = this.metadataBitsPerRecord == 0 ? 0
+							: (16 / this.metadataBitsPerRecord);
+					metadataBits &= metadataMask;
+
+					int metadataBitOffset = elementsInBlock % metadataRecordsPerShort;
+					long metadataStart = getAddressOfMetadata(current, thisBlockRecordCount);
+					int whichShort = elementsInBlock / metadataRecordsPerShort;
+					long metadataOffset = metadataStart + 2 * whichShort;
+					short metadataValue = db.getShort(metadataOffset);
+
+					// Resetting the previous visibility bits of the target member.
+					metadataValue &= ~(metadataMask << metadataBitOffset * this.metadataBitsPerRecord);
+					// Setting the new visibility bits of the target member.
+					metadataValue |= metadataBits << metadataBitOffset * this.metadataBitsPerRecord;
+
+					getDB().putShort(metadataOffset, metadataValue);
+				}
+
+				this.lastKnownBlock = current;
+				return getAddressOfElement(current, elementsInBlock);
+			} else {
+				// When ptr == current, this is a sentinel indicating that the block is full and there are no
+				// further blocks. If this is the case, create a new block
+				if (isLastBlock(current, ptr)) {
+					current = db.malloc(
+							recordSize(this.elementRecordSize, this.recordCount, this.metadataBitsPerRecord), Database.POOL_LINKED_LIST);
+					db.putRecPtr(current + NEXT_MEMBER_BLOCK, current);
+				} else {
+					thisBlockRecordCount = this.recordCount;
+					// Else, there are more blocks following this one so advance
+					current = ptr;
+				}
+			}
+		}
+	}
+
+	private long getAddressOfElement(long blockRecordStart, int elementNumber) {
+		return blockRecordStart + ELEMENT_START_POSITION + elementNumber * this.elementRecordSize;
+	}
+
+	private long getAddressOfMetadata(long blockRecordStart, int blockRecordCount) {
+		return getAddressOfElement(blockRecordStart, blockRecordCount);
+	}
+
+	public void accept(ILinkedListVisitor visitor) throws IndexException {
+		int count = 0;
+		Database db = getDB();
+
+		int blockRecordCount = this.firstBlockRecordCount;
+		int metadataMask = (1 << this.metadataBitsPerRecord) - 1;
+		int metadataRecordsPerShort = this.metadataBitsPerRecord == 0 ? 0 : (16 / this.metadataBitsPerRecord);
+		long current = this.address;
+		while (true) {
+			long ptr = db.getRecPtr(current + NEXT_MEMBER_BLOCK);
+			int elementsInBlock = getElementsInBlock(current, ptr, blockRecordCount);
+
+			long metadataStart = getAddressOfMetadata(current, blockRecordCount);
+			for (int idx = 0; idx < elementsInBlock; idx++) {
+				long elementRecord = getAddressOfElement(current, idx);
+
+				short metadataBits = 0;
+
+				if (metadataRecordsPerShort > 0) {
+					int metadataBitOffset = idx % metadataRecordsPerShort;
+					int whichShort = idx / metadataRecordsPerShort;
+					long metadataOffset = metadataStart + 2 * whichShort;
+					metadataBits = getDB().getShort(metadataOffset);
+
+					metadataBits >>>= metadataBits * metadataBitOffset;
+					metadataBits &= metadataMask;
+				}
+
+				visitor.visit(elementRecord, metadataBits, count++);
+			}
+
+			blockRecordCount = this.recordCount;
+
+			if (isLastBlock(current, ptr)) {
+				return;
+			}
+
+			current = ptr;
+		}
+	}
+
+	public void destruct() throws IndexException {
+		Database db = getDB();
+		long current = this.address;
+		while (true) {
+			long ptr = db.getRecPtr(current + NEXT_MEMBER_BLOCK);
+			db.free(current, Database.POOL_LINKED_LIST);
+
+			if (isLastBlock(current, ptr)) {
+				return;
+			}
+
+			current = ptr;
+		}
+	}
+
+	private boolean isLastBlock(long blockAddress, long pointerToNextBlock) {
+		return pointerToNextBlock == 0 || pointerToNextBlock == blockAddress;
+	}
+
+	/**
+	 * Returns the number of elements in this list. This is an O(n) operation.
+	 * @throws IndexException
+	 */
+	public int size() throws IndexException {
+		int count = 0;
+		Database db = getDB();
+		int currentRecordCount = this.firstBlockRecordCount;
+		long current = this.address;
+		while (true) {
+			long ptr = db.getRecPtr(current + NEXT_MEMBER_BLOCK);
+			count += getElementsInBlock(current, ptr, currentRecordCount);
+
+			if (isLastBlock(current, ptr)) {
+				break;
+			}
+
+			currentRecordCount = this.recordCount;
+			current = ptr;
+		}
+
+		return count;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Package.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Package.java
new file mode 100644
index 0000000..1782bd4
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Package.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * 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.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.JavaCore;
+
+/* package */ class Package {
+	public static String PLUGIN_ID = JavaCore.PLUGIN_ID;
+
+	public static void log(Throwable e) {
+		String msg = e.getMessage();
+		if (msg == null) {
+			log("Error", e); //$NON-NLS-1$
+		} else {
+			log("Error: " + msg, e); //$NON-NLS-1$
+		}
+	}
+
+	public static void log(String message, Throwable e) {
+		log(createStatus(message, e));
+	}
+
+	public static void logInfo(String message) {
+		log(createStatus(IStatus.INFO, message, null));
+	}
+
+	public static IStatus createStatus(int statusCode, String msg, Throwable e) {
+		return new Status(statusCode, PLUGIN_ID, msg, e);
+	}
+
+	public static IStatus createStatus(String msg, Throwable e) {
+		return new Status(IStatus.ERROR, PLUGIN_ID, msg, e);
+	}
+
+	public static IStatus createStatus(String msg) {
+		return new Status(IStatus.ERROR, PLUGIN_ID, msg);
+	}
+
+	public static void log(IStatus status) {
+		JavaCore.getPlugin().getLog().log(status);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Pointer.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Pointer.java
new file mode 100644
index 0000000..bd8a597
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Pointer.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+  * 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.Database;
+
+/**
+ * Points to a concrete type, NOT one of its subclasses. This should not be used for node
+ * pointers, since they are stored as a pointer to the base class. If you want a pointer to
+ * a node, use a NodeFieldDefinition instead.
+ */
+public class Pointer<T> {
+	private final Nd nd;
+	private final long address;
+	private ITypeFactory<T> targetFactory;
+
+	public Pointer(Nd nd, long address, ITypeFactory<T> targetFactory) {
+		this.nd = nd;
+		this.address = address;
+		this.targetFactory = targetFactory;
+	}
+
+	public T get() {
+		long ptr = this.nd.getDB().getRecPtr(this.address);
+
+		if (ptr == 0) {
+			return null;
+		}
+
+		return this.targetFactory.create(this.nd, ptr);
+	}
+
+	public static <T> ITypeFactory<Pointer<T>> getFactory(final ITypeFactory<T> targetFactory) {
+		if (NdNode.class.isAssignableFrom(targetFactory.getElementClass())) {
+			throw new IllegalArgumentException("Don't use Pointer<T> for references to NdNode"); //$NON-NLS-1$
+		}
+		return new AbstractTypeFactory<Pointer<T>>() {
+			@Override
+			public Pointer<T> create(Nd dom, long address) {
+				return new Pointer<T>(dom, address, targetFactory);
+			}
+
+			@Override
+			public int getRecordSize() {
+				return Database.PTR_SIZE;
+			}
+
+			@Override
+			public Class<?> getElementClass() {
+				return Pointer.class;
+			}
+		};
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/PrimitiveTypes.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/PrimitiveTypes.java
new file mode 100644
index 0000000..0185068
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/PrimitiveTypes.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * 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.Database;
+
+/**
+ * Holds type factories for all primitive types known to the Database.
+ */
+public class PrimitiveTypes {
+	public static final ITypeFactory<Long> Pointer = new AbstractTypeFactory<Long>() {
+		@Override
+		public Long create(Nd dom, long address) {
+			return dom.getDB().getRecPtr(address);
+		}
+
+		@Override
+		public int getRecordSize() {
+			return Database.PTR_SIZE;
+		}
+
+		@Override
+		public Class<?> getElementClass() {
+			return Long.class;
+		}
+	};
+
+	public static final ITypeFactory<Short> Short = new AbstractTypeFactory<Short>() {
+		@Override
+		public Short create(Nd dom, long address) {
+			return dom.getDB().getShort(address);
+		}
+
+		@Override
+		public int getRecordSize() {
+			return Database.SHORT_SIZE;
+		}
+
+		@Override
+		public Class<?> getElementClass() {
+			return Short.class;
+		}
+	};
+
+	public static final ITypeFactory<Integer> Integer = new AbstractTypeFactory<Integer>() {
+		@Override
+		public Integer create(Nd dom, long address) {
+			return dom.getDB().getInt(address);
+		}
+
+		@Override
+		public int getRecordSize() {
+			return Database.INT_SIZE;
+		}
+
+		@Override
+		public Class<?> getElementClass() {
+			return Integer.class;
+		}
+	};
+}
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
new file mode 100644
index 0000000..df7ca4e
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/RawGrowableArray.java
@@ -0,0 +1,593 @@
+/*******************************************************************************
+ * 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.Database;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+import org.eclipse.jdt.internal.core.nd.field.FieldInt;
+import org.eclipse.jdt.internal.core.nd.field.FieldPointer;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+/**
+ * Implements a growable array of pointers that supports constant-time insertions and removals. Items are inserted at
+ * the end of the array, and each insertion hands back a unique identifier that can be used to remove the item quickly
+ * at a later time.
+ * <p>
+ * The memory format contains a header is as follows:
+ * <p>
+ * 
+ * <pre>
+ * Byte				Meaning
+ * --------------------------------------------
+ * 0..3				Pointer to the growable block. Null if the number of records <= inlineRecordCount
+ * 4..7				Record [0]
+ * 8..11			Record [1]
+ * ...
+ * k...k+4			Record [inlineRecordCount-1]
+ * </pre>
+ * 
+ * As shown above, the first few records are stored inline with the array. inlineRecordCount is a tunable parameter
+ * which may be 0. If there are fewer than inlineRecordCount records, there is no growable block and all records are
+ * stored in the header. Storing the first few records in the header is intended as an optimization for very small
+ * arrays in the case where small arrays are expected to be a common case. If there are fewer than inlineRecordCount
+ * records stored in the array, the size of the array is not stored explicitly. It is computed on demand by searching
+ * for the first null entry among the inline records.
+ *
+ * <p>
+ * The memory format for a growable block is as follows:
+ * <p>
+ * 
+ * <pre>
+ * Byte				Meaning
+ * --------------------------------------------
+ * 0..3				Size of the array, including all inline records. This is also the index at which the next entry will
+ *					be inserted
+ * 4..7				Capacity of this growable block.
+ * 8..11			Record [n]
+ * 12..15			Record [n+1]
+ * ...
+ * k...k+4			Record [blockSize-1]
+ * </pre>
+ * 
+ * <p>
+ * The growable block itself begins with a 4-byte int holding the size of the array, followed by a 4-byte int holding
+ * the capacity of the growable block. In the event that the array is larger than
+ * {@link GrowableBlockHeader#MAX_GROWABLE_SIZE} enough to be using a metablock, there will be multiple growable blocks
+ * in use. In this case, the size and capacity stored in the metablock is used for the array and the size and capacity
+ * stored in each growable block will be filled in with 0s.
+ * <p>
+ * If capacity <= MAX_BLOCK_SIZE then this is a normal block containing a flat array of record pointers starting from
+ * the element numbered inlineRecordCount. If capacity > MAX_BLOCK_SIZE then then it is a metablock which holds record
+ * pointers to separate growable blocks, each of which holds exactly MAX_BLOCK_SIZE elements.
+ * <p>
+ * Every time an element is inserted in the array, the add method returns the element's index. Indices can be used to
+ * remove elements in constant time, but will be reassigned by the RawGrowableArray during removes by swapping the
+ * removed element with the last element in the array. If the owner of the array is keeping track of indices, it should
+ * update the relevant indices on remove.
+ * <p>
+ * The array itself is tightly packed. When an element is removed, the last element in the array is swapped into its
+ * location. Anyone keeping track of indices may rely on the fact that they are consecutive integers.
+ * <p>
+ * These arrays preserve insertion order until the first call to "remove". If element order matters, you should not
+ * remove individual elements but should instead destroy and rebuild the entire array.
+ * <p>
+ * Element additions and removals run in constant amortized time.
+ * <p>
+ * There are a lot of ints and longs used in the implementation of this class. In order to help clarify their function,
+ * they get the following suffixes:
+ * <ul>
+ * <li>index - holds an index into the array
+ * <li>size - holds a count of the number of indices
+ * <li>value - holds one of the pointer values inserted into the array
+ * <li>address - holds a pointer into the database (refers to a full 8-byte long, not the compressed 4-byte version)
+ * <li>bytes - holds the size (in bytes) of something in the database
+ * <li>block - holds a block number (in the case where a metablock is in use, the growable blocks are identified by
+ * block numbers).
+ * <li>blockCount - holds a number of blocks
+ * </ul>
+ */
+public final class RawGrowableArray {
+	private static final FieldPointer GROWABLE_BLOCK_ADDRESS;
+	private static final int ARRAY_HEADER_BYTES;
+
+	private static final StructDef<RawGrowableArray> type; 
+
+	static {
+		type = StructDef.createAbstract(RawGrowableArray.class);
+		GROWABLE_BLOCK_ADDRESS = type.addPointer();
+		type.done();
+
+		ARRAY_HEADER_BYTES = type.size();
+	}
+
+	private static final class GrowableBlockHeader {
+		public static final FieldInt ARRAY_SIZE;
+		public static final FieldInt ALLOCATED_SIZE;
+		public static final int GROWABLE_BLOCK_HEADER_BYTES;
+		public static final int MAX_GROWABLE_SIZE;
+
+		@SuppressWarnings("hiding")
+		private static final StructDef<GrowableBlockHeader> type;
+
+		static {
+			type = StructDef.createAbstract(GrowableBlockHeader.class);
+
+			ARRAY_SIZE = type.addInt();
+			ALLOCATED_SIZE = type.addInt();
+			type.done();
+
+			GROWABLE_BLOCK_HEADER_BYTES = type.size();
+
+			MAX_GROWABLE_SIZE = (Database.MAX_MALLOC_SIZE - GROWABLE_BLOCK_HEADER_BYTES)
+					/ Database.PTR_SIZE;
+		}
+	}
+
+	private final int inlineSize;
+
+	public RawGrowableArray(int inlineRecords) {
+		this.inlineSize = inlineRecords;
+	}
+
+	public static int getMaxGrowableBlockSize() {
+		return GrowableBlockHeader.MAX_GROWABLE_SIZE;
+	}
+
+	/**
+	 * Returns the size of the array.
+	 * 
+	 * @param address address of the array
+	 * @return the array size, in number elements
+	 */
+	public int size(Nd nd, long address) {
+		Database db = nd.getDB();
+		long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+
+		if (growableBlockAddress == 0) {
+			// If there is no growable block or metablock, then the size is determined by the position of the first
+			// null pointer among the inline records.
+			long inlineRecordStartAddress = address + ARRAY_HEADER_BYTES;
+			for (int index = 0; index < this.inlineSize; index++) {
+				long nextAddress = inlineRecordStartAddress + index * Database.PTR_SIZE;
+
+				long nextValue = db.getRecPtr(nextAddress);
+				if (nextValue == 0) {
+					return index;
+				}
+			}
+			return this.inlineSize;
+		}
+		return GrowableBlockHeader.ARRAY_SIZE.get(nd, growableBlockAddress);
+	}
+
+	/**
+	 * Adds the given value to the array. Returns an index which can be later passed into remove in order to
+	 * remove the element at a later time.
+	 */
+	public int add(Nd nd, long address, long value) {
+		if (value == 0) {
+			throw new IllegalArgumentException("Null pointers cannot be inserted into " + getClass().getName()); //$NON-NLS-1$
+		}
+		Database db = nd.getDB();
+
+		int insertionIndex = size(nd, address);
+		int newSize = insertionIndex + 1;
+
+		ensureCapacity(nd, address, newSize);
+		long recordAddress = getAddressOfRecord(nd, address, insertionIndex);
+		db.putRecPtr(recordAddress, value);
+		setSize(nd, address, newSize);
+		return insertionIndex;
+	}
+
+	/**
+	 * Returns the element at the given index (nonzero). The given index must be < size().
+	 */
+	public long get(Nd nd, long address, int index) {
+		long recordAddress = getAddressOfRecord(nd, address, index);
+		return nd.getDB().getRecPtr(recordAddress);
+	}
+
+	/**
+	 * Ensures that the array contains at least enough space allocated to fit the given number of new elements.
+	 */
+	public void ensureCapacity(Nd nd, long address, int desiredSize) {
+		int growableBlockNeededSize = desiredSize - this.inlineSize;
+		long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+		int growableBlockCurrentSize = growableBlockAddress == 0 ? 0
+				: GrowableBlockHeader.ALLOCATED_SIZE.get(nd, growableBlockAddress);
+
+		// The growable region is already large enough.
+		if (growableBlockNeededSize <= growableBlockCurrentSize) {
+			return;
+		}
+
+		Database db = nd.getDB();
+
+		int neededBlockSize = getGrowableRegionSizeFor(desiredSize); 
+		if (neededBlockSize > GrowableBlockHeader.MAX_GROWABLE_SIZE) {
+			// We need a metablock.
+			long metablockAddress = growableBlockAddress;
+
+			if (!(growableBlockCurrentSize > GrowableBlockHeader.MAX_GROWABLE_SIZE)) {
+				// We weren't using a metablock previously
+				int currentSize = size(nd, address);
+				// Need to convert to using metablocks.
+				long firstGrowableBlockAddress = resizeBlock(nd, address, GrowableBlockHeader.MAX_GROWABLE_SIZE);
+
+				metablockAddress = db.malloc(computeBlockBytes(GrowableBlockHeader.MAX_GROWABLE_SIZE), Database.POOL_GROWABLE_ARRAY);
+				GrowableBlockHeader.ARRAY_SIZE.put(nd, metablockAddress, currentSize);
+				GrowableBlockHeader.ALLOCATED_SIZE.put(nd, metablockAddress,
+						GrowableBlockHeader.MAX_GROWABLE_SIZE);
+
+				// Link the first block into the metablock.
+				db.putRecPtr(metablockAddress + GrowableBlockHeader.GROWABLE_BLOCK_HEADER_BYTES,
+						firstGrowableBlockAddress);
+				GROWABLE_BLOCK_ADDRESS.put(nd, address, metablockAddress);
+			}
+
+			// neededBlockSize should always be a multiple of the max block size when metablocks are in use
+			assert neededBlockSize % GrowableBlockHeader.MAX_GROWABLE_SIZE == 0;
+			// Create extra growable blocks if necessary.
+			int requiredBlockCount = neededBlockSize / GrowableBlockHeader.MAX_GROWABLE_SIZE;
+			int currentAllocatedSize = GrowableBlockHeader.ALLOCATED_SIZE.get(nd, metablockAddress);
+			assert currentAllocatedSize % GrowableBlockHeader.MAX_GROWABLE_SIZE == 0;
+			int currentBlockCount = currentAllocatedSize / GrowableBlockHeader.MAX_GROWABLE_SIZE;
+
+			for (int nextBlock = currentBlockCount; nextBlock < requiredBlockCount; nextBlock++) {
+				long nextBlockAddress = db.malloc(computeBlockBytes(GrowableBlockHeader.MAX_GROWABLE_SIZE), Database.POOL_GROWABLE_ARRAY);
+
+				db.putRecPtr(metablockAddress + GrowableBlockHeader.GROWABLE_BLOCK_HEADER_BYTES
+						+ nextBlock * Database.PTR_SIZE, nextBlockAddress);
+			}
+
+			GrowableBlockHeader.ALLOCATED_SIZE.put(nd, metablockAddress, neededBlockSize);
+		} else {
+			long newBlockAddress = resizeBlock(nd, address, neededBlockSize);
+
+			GROWABLE_BLOCK_ADDRESS.put(nd, address, newBlockAddress);
+		}
+	}
+
+	/**
+	 * Allocates a new normal block, copies the contents of the old block to it, and deletes the old block. Should not
+	 * be used if the array is using metablocks. Returns the address of the newly-allocated block.
+	 */
+	private long resizeBlock(Nd nd, long address, int newBlockSize) {
+		Database db = nd.getDB();
+		long oldGrowableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+
+		// Check if the existing block is already exactly the right size
+		if (oldGrowableBlockAddress != 0) {
+			if (newBlockSize == 0) {
+				db.free(oldGrowableBlockAddress, Database.POOL_GROWABLE_ARRAY);
+				return 0;
+			}
+
+			int oldAllocatedSize = GrowableBlockHeader.ALLOCATED_SIZE.get(nd, oldGrowableBlockAddress);
+			if (oldAllocatedSize == newBlockSize) {
+				return oldGrowableBlockAddress;
+			}
+		}
+
+		int arraySize = size(nd, address);
+		int numToCopySize = Math.min(Math.max(0, arraySize - this.inlineSize), newBlockSize);
+		long newGrowableBlockAddress = db.malloc(computeBlockBytes(newBlockSize), Database.POOL_GROWABLE_ARRAY);
+
+		if (oldGrowableBlockAddress != 0) {
+			db.memcpy(newGrowableBlockAddress, oldGrowableBlockAddress, computeBlockBytes(numToCopySize));
+			db.free(oldGrowableBlockAddress, Database.POOL_GROWABLE_ARRAY);
+		}
+
+		GrowableBlockHeader.ARRAY_SIZE.put(nd, newGrowableBlockAddress, arraySize);
+		GrowableBlockHeader.ALLOCATED_SIZE.put(nd, newGrowableBlockAddress, newBlockSize);
+		return newGrowableBlockAddress;
+	}
+
+	private int computeBlockBytes(int size) {
+		return size * Database.PTR_SIZE + GrowableBlockHeader.GROWABLE_BLOCK_HEADER_BYTES;
+	}
+
+	/**
+	 * @param size
+	 */
+	private void setSize(Nd nd, long address, int size) {
+		long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+
+		// If we're not using a growable block, we don't explicitly store the size
+		if (growableBlockAddress == 0) {
+			return;
+		}
+
+		GrowableBlockHeader.ARRAY_SIZE.put(nd, growableBlockAddress, size);
+	}
+
+	/**
+	 * Returns a record address given a record number
+	 */
+	private long getAddressOfRecord(Nd nd, long address, int index) {
+		int growableBlockRelativeIndex = index - this.inlineSize;
+
+		if (growableBlockRelativeIndex >= 0) {
+			Database db = nd.getDB();
+			// This record is located within the growable region
+			long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+			int size = size(nd, address);
+
+			// We use reads of 1 past the end of the array to handle insertions.
+			if (index > size) {
+				throw new IndexException(
+						"Record index " + index + " out of range. Array contains " + size + " elements"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+			}
+
+			int growableBlockSize = GrowableBlockHeader.ALLOCATED_SIZE.get(nd, growableBlockAddress);
+			long dataStartAddress = growableBlockAddress + GrowableBlockHeader.GROWABLE_BLOCK_HEADER_BYTES;
+
+			if (growableBlockSize > GrowableBlockHeader.MAX_GROWABLE_SIZE) {
+				// If this array is so big that it's using a metablock, look up the correct sub-block and use the
+				// correct address within the sub-block
+				int blockRelativeIndex = growableBlockRelativeIndex % GrowableBlockHeader.MAX_GROWABLE_SIZE;
+				int block = growableBlockRelativeIndex / GrowableBlockHeader.MAX_GROWABLE_SIZE;
+
+				dataStartAddress = db.getRecPtr(dataStartAddress + block * Database.PTR_SIZE)
+						+ GrowableBlockHeader.GROWABLE_BLOCK_HEADER_BYTES;
+				growableBlockRelativeIndex = blockRelativeIndex;
+			}
+
+			return dataStartAddress + growableBlockRelativeIndex * Database.PTR_SIZE;
+		} else {
+			// This record is one of the ones inlined in the header
+			return address + ARRAY_HEADER_BYTES + index * Database.PTR_SIZE;
+		}
+	}
+
+	/**
+	 * Removes an entry from the array, given an element index. If the given index is not the last element
+	 * in the list, the last element will have its index swapped with the removed element. If another element
+	 * was swapped into the position of the removed element, this returns the value of that element. Otherwise,
+	 * it returns 0.
+	 */
+	public long remove(Nd nd, long address, int index) {
+		int currentSize = size(nd, address);
+		int lastElementIndex = currentSize - 1;
+
+		Database db = nd.getDB();
+		if (index > lastElementIndex || index < 0) {
+			throw new IndexException("Attempt to remove nonexistent element " + index //$NON-NLS-1$
+					+ " from an array of size " + (lastElementIndex + 1)); //$NON-NLS-1$
+		}
+
+		long toRemoveAddress = getAddressOfRecord(nd, address, index);
+		long returnValue;
+		// If we're removing the last element
+		if (index == lastElementIndex) {
+			returnValue = 0;
+			// Clear out the removed element
+			db.putRecPtr(toRemoveAddress, 0);
+		} else {
+			long lastElementAddress = getAddressOfRecord(nd, address, lastElementIndex);
+			long lastElementValue = db.getRecPtr(lastElementAddress);
+
+			// Move the last element into the position occupied by the element being removed (this is a noop if
+			// removing the last element)
+			db.putRecPtr(toRemoveAddress, lastElementValue);
+
+			// Clear out the last element
+			db.putRecPtr(lastElementAddress, 0);
+
+			returnValue = lastElementValue;
+		}
+
+		// Update the array size
+		setSize(nd, address, currentSize - 1);
+		repackIfNecessary(nd, address, currentSize);
+
+		return returnValue;
+	}
+
+	/**
+	 * Checks if we should reduce the amount of allocated in the growable region, such that the array can hold the given
+	 * number of elements.
+	 * 
+	 * @param desiredSize
+	 *            the new current size of the array or 0 to free up all memory
+	 */
+	private void repackIfNecessary(Nd nd, long address, int desiredSize) {
+		long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+
+		// If there is no growable block then the array is already as small as we can make it. Nothing to do.
+		if (growableBlockAddress == 0) {
+			return;
+		}
+
+		int desiredGrowableSize = desiredSize - this.inlineSize;
+
+		int currentGrowableSize = GrowableBlockHeader.ALLOCATED_SIZE.get(nd, growableBlockAddress);
+		int newGrowableSize = getGrowableRegionSizeFor(desiredSize);
+
+		// We only need to repack if the new size is smaller than the old one
+		if (newGrowableSize >= currentGrowableSize) {
+			return;
+		}
+
+		Database db = nd.getDB();
+		if (currentGrowableSize > GrowableBlockHeader.MAX_GROWABLE_SIZE) {
+			// We are currently using a metablock
+			int desiredBlockCount = (newGrowableSize + GrowableBlockHeader.MAX_GROWABLE_SIZE - 1)
+					/ GrowableBlockHeader.MAX_GROWABLE_SIZE;
+			int currentBlockCount = currentGrowableSize / GrowableBlockHeader.MAX_GROWABLE_SIZE;
+
+			// Only deallocate memory if either there are either two full unused blocks
+			// or the desired size is less than or equal to half of a block + 1. We add one to ensure
+			// that the newly-shrunk array will still be about double the size of the used elements.
+			boolean needsRepacking = (currentBlockCount - desiredBlockCount > 1)
+					|| (newGrowableSize <= (GrowableBlockHeader.MAX_GROWABLE_SIZE / 2 + 1));
+			if (!needsRepacking) {
+				return;
+			}
+
+			long metablockRecordsAddress = growableBlockAddress + GrowableBlockHeader.GROWABLE_BLOCK_HEADER_BYTES;
+			int currentBlock = currentBlockCount;
+			while (--currentBlock >= desiredBlockCount) {
+				long nextAddress = metablockRecordsAddress + currentBlock * Database.PTR_SIZE;
+				long oldBlockAddress = db.getRecPtr(nextAddress);
+				db.free(oldBlockAddress, Database.POOL_GROWABLE_ARRAY);
+				db.putRecPtr(nextAddress, 0);
+			}
+
+			// If we still need to be using a metablock, we're done
+			if (newGrowableSize > GrowableBlockHeader.MAX_GROWABLE_SIZE) {
+				// First record the new growable region size
+				GrowableBlockHeader.ALLOCATED_SIZE.put(nd, growableBlockAddress, newGrowableSize);
+				return;
+			}
+
+			// Else we need to stop using a metablock.
+			// Dispose the metablock and replace it with the first growable block
+			long firstBlockAddress = db.getRecPtr(metablockRecordsAddress);
+			int oldSize = GrowableBlockHeader.ARRAY_SIZE.get(nd, growableBlockAddress);
+			db.free(growableBlockAddress, Database.POOL_GROWABLE_ARRAY);
+
+			GROWABLE_BLOCK_ADDRESS.put(nd, address, firstBlockAddress);
+
+			if (firstBlockAddress != 0) {
+				currentGrowableSize = GrowableBlockHeader.MAX_GROWABLE_SIZE;
+				GrowableBlockHeader.ARRAY_SIZE.put(nd, firstBlockAddress, oldSize);
+				GrowableBlockHeader.ALLOCATED_SIZE.put(nd, firstBlockAddress,
+						GrowableBlockHeader.MAX_GROWABLE_SIZE);
+			}
+
+			// Then we'll fall through to the normal (non-metablock) case, which may shrink the size of the last
+			// growable block further
+		}
+
+		// If we're not using metablocks, we only resize the growable region once the size of the array shrinks
+		// such that we're only using 1/4 of it.
+		if (desiredGrowableSize <= (currentGrowableSize / 4 + 1)) {
+			long newBlockAddress = resizeBlock(nd, address, newGrowableSize);
+
+			GROWABLE_BLOCK_ADDRESS.put(nd, address, newBlockAddress);
+		}
+	}
+
+	/**
+	 * Returns the number of elements that should actually be allocated in the growable region for an array of the given
+	 * size
+	 */
+	private int getGrowableRegionSizeFor(int arraySize) {
+		int growableRegionSize = arraySize - this.inlineSize;
+
+		if (growableRegionSize <= 0) {
+			return 0;
+		}
+
+		// Find the next power of two that is equal or greater than the required size. We use inlineSize
+		// as the minimum growable block size since we tend to assign a large inlineSize to lists with a large
+		// average number of elements, and these are also the lists that will benefit from a larger initial block size.
+		int nextGrowableSize = getNextPowerOfTwo(Math.max(growableRegionSize, this.inlineSize));
+
+		if (nextGrowableSize > GrowableBlockHeader.MAX_GROWABLE_SIZE) {
+			// If the next power of two is greater than the max block size but the requested size is smaller than it,
+			// clamp it to the the max block size
+			if (growableRegionSize <= GrowableBlockHeader.MAX_GROWABLE_SIZE) {
+				return GrowableBlockHeader.MAX_GROWABLE_SIZE;
+			}
+
+			// 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 nextGrowableSize;
+	}
+
+	/**
+	 * Returns the largest power of two that is less than or equal to the given integer
+	 */
+	private static int getPrevPowerOfTwo(int n) {
+		n |= (n >> 1);
+		n |= (n >> 2);
+		n |= (n >> 4);
+		n |= (n >> 8);
+		n |= (n >> 16);
+		return n - (n >> 1);
+	}
+
+	/**
+	 * Returns the next power of two that is equal to or greater than the given int.
+	 */
+	private static int getNextPowerOfTwo(int toTest) {
+		int highBit = getPrevPowerOfTwo(toTest);
+		int nextGrowableSize = highBit;
+
+		if (highBit != toTest) {
+			assert (nextGrowableSize << 1) != 0;
+			nextGrowableSize <<= 1;
+		}
+		return nextGrowableSize;
+	}
+
+	/**
+	 * 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() {
+		return ARRAY_HEADER_BYTES + Database.PTR_SIZE * this.inlineSize;
+	}
+
+	public void destruct(Nd nd, long address) {
+		repackIfNecessary(nd, address, 0);
+	}
+
+	
+	/**
+	 * Returns true iff the size of the array is 0
+	 * 
+	 * @param address address of the array
+	 * @return the array size, in number elements
+	 */
+	public boolean isEmpty(Nd nd, long address) {
+		Database db = nd.getDB();
+		long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+
+		if (growableBlockAddress == 0) {
+			if (this.inlineSize == 0) {
+				return true;
+			}
+			// If there is no growable block or metablock, then the size is determined by the position of the first
+			// null pointer among the inline records.
+			long firstValue = db.getRecPtr(address + ARRAY_HEADER_BYTES);
+
+			return firstValue == 0;
+		}
+		return GrowableBlockHeader.ARRAY_SIZE.get(nd, growableBlockAddress) == 0;
+	}
+
+	public int getCapacity(Nd nd, long address) {
+		long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+
+		if (growableBlockAddress == 0) {
+			return this.inlineSize;
+		}
+
+		int growableBlockCurrentSize = GrowableBlockHeader.ALLOCATED_SIZE.get(nd, growableBlockAddress);
+
+		return growableBlockCurrentSize + this.inlineSize;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/StreamHasher.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/StreamHasher.java
new file mode 100644
index 0000000..ab1642d
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/StreamHasher.java
@@ -0,0 +1,235 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 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:
+ * 	   Sergey Prigogin (Google) - initial API and implementation
+ *
+ * Based on lookup3.c, by Bob Jenkins {@link "http://burtleburtle.net/bob/c/lookup3.c"}
+ *
+ * Here is the original comment by Bob Jenkins:
+ * -------------------------------------------------------------------------------
+ * lookup3.c, by Bob Jenkins, May 2006, Public Domain.
+ *
+ * These are functions for producing 32-bit hashes for hash table lookup.
+ * hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final()
+ * are externally useful functions.  Routines to test the hash are included
+ * if SELF_TEST is defined.  You can use this free for any purpose.  It's in
+ * the public domain.  It has no warranty.
+ *
+ * You probably want to use hashlittle().  hashlittle() and hashbig()
+ * hash byte arrays.  hashlittle() is is faster than hashbig() on
+ * little-endian machines.  Intel and AMD are little-endian machines.
+ * On second thought, you probably want hashlittle2(), which is identical to
+ * hashlittle() except it returns two 32-bit hashes for the price of one.
+ * You could implement hashbig2() if you wanted but I haven't bothered here.
+ *
+ * If you want to find a hash of, say, exactly 7 integers, do
+ *   a = i1;  b = i2;  c = i3;
+ *   mix(a, b, c);
+ *   a += i4; b += i5; c += i6;
+ *   mix(a, b, c);
+ *   a += i7;
+ *   finalMix(a, b, c);
+ * then use c as the hash value.  If you have a variable length array of
+ * 4-byte integers to hash, use hashword().  If you have a byte array (like
+ * a character string), use hashlittle().  If you have several byte arrays, or
+ * a mix of things, see the comments above hashlittle().
+ *
+ * Why is this so big?  I read 12 bytes at a time into 3 4-byte integers,
+ * then mix those integers.  This is fast (you can do a lot more thorough
+ * mixing with 12*3 instructions on 3 integers than you can with 3 instructions
+ * on 1 byte), but shoehorning those bytes into integers efficiently is messy.
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+/**
+ * Computes a 64-bit hash value of a character stream that can be supplied one chunk at a time.
+ * Usage:
+ * <pre>
+ *   StreamHasher hasher = new StreamHasher();
+ *   for (long offset = 0; offset < streamLength; offset += chunkLength) {
+ *     hasher.addChunk(offset, chunkOfCharacters);
+ *   }
+ *   int64 hashValue = hasher.computeHash();
+ * </pre>
+ *
+ * Based on lookup3.c by Bob Jenkins from {@link "http://burtleburtle.net/bob/c/lookup3.c"}
+ */
+public final class StreamHasher {
+	private static final long SEED = 3141592653589793238L;  // PI
+	private static final long EMPTY_STRING_HASH = new StreamHasher().computeHashInternal();
+
+	long hashedOffset;  // Current position in the stream of characters.
+	int state;  // Current position in the stream of characters modulo 6, or -1 after computeHash is called.
+	int a;
+	int b;
+	int c;
+	char previousCharacter;
+
+	public StreamHasher() {
+		// Set up the internal state.
+		this.hashedOffset = 0;
+		this.state = 0;
+		this.a = this.b = this.c = (int) SEED;
+		this.c += SEED >>> 32;
+	}
+
+	/**
+	 * Adds a chunk of data to the hasher.
+	 * @param chunk Contents of the chunk.
+	 */
+	public void addChunk(char[] chunk) {
+		for (int pos = 0; pos < chunk.length; pos++, this.hashedOffset++) {
+			char cc = chunk[pos];
+			switch (this.state++) {
+			case -1:
+				throw new IllegalStateException("addChunk is called after computeHash."); //$NON-NLS-1$
+			case 0:
+			case 2:
+			case 4:
+				this.previousCharacter = cc;
+				break;
+			case 1:
+				this.a += this.previousCharacter | (cc << 16);
+				break;
+			case 3:
+				this.b += this.previousCharacter | (cc << 16);
+				break;
+			case 5:
+				this.c += this.previousCharacter | (cc << 16);
+				mix();
+				this.state = 0;
+				break;
+			}
+		}
+	}
+
+	/**
+	 * Computes and returns the hash value. Must be called once after the last chunk.
+	 * @return The hash value of the character stream.
+	 */
+	public long computeHash() {
+		if (this.state < 0) {
+			throw new IllegalStateException("computeHash method is called more than once."); //$NON-NLS-1$
+		}
+		return computeHashInternal() ^ EMPTY_STRING_HASH;
+	}
+
+	private long computeHashInternal() {
+		switch (this.state) {
+		case 1:
+			this.a += this.previousCharacter;
+			break;
+		case 3:
+			this.b += this.previousCharacter;
+			break;
+		case 5:
+			this.c += this.previousCharacter;
+			break;
+		}
+		this.state = -1;  // Protect against subsequent calls.
+		finalMix();
+		return (this.c & 0xFFFFFFFFL) | ((long) this.b << 32);
+	}
+
+	/**
+	 * Computes a 64-bit hash value of a String. The resulting hash value
+	 * is zero if the string is empty.
+	 *
+	 * @param str The string to hash.
+	 * @return The hash value.
+	 */
+	public static long hash(String str) {
+		StreamHasher hasher = new StreamHasher();
+		hasher.addChunk(str.toCharArray());
+		return hasher.computeHash();
+	}
+
+	/**
+	 * Mixes three 32-bit values reversibly.
+	 *
+	 * This is reversible, so any information in a, b, c before mix() is
+	 * still in a, b, c after mix().
+     *
+     * If four pairs of a, b, c inputs are run through mix(), or through
+	 * mix() in reverse, there are at least 32 bits of the output that
+	 * are sometimes the same for one pair and different for another pair.
+	 * This was tested for:
+	 * * pairs that differed by one bit, by two bits, in any combination
+	 *   of top bits of a, b, c, or in any combination of bottom bits of
+	 *   a, b, c.
+	 * * "differ" is defined as +, -, ^, or ~^.  For + and -, I transformed
+	 *   the output delta to a Gray code (a ^ (a >> 1)) so a string of 1's
+	 *   (as is commonly produced by subtraction) look like a single 1-bit
+	 *   difference.
+	 * * the base values were pseudo-random, all zero but one bit set, or
+	 *   all zero plus a counter that starts at zero.
+     *
+	 * Some k values for my "a -= c; a ^= Integer.rotateLeft(c, k); c += b;"
+	 * arrangement that satisfy this are
+	 *     4  6  8 16 19  4
+	 *     9 15  3 18 27 15
+	 *    14  9  3  7 17  3
+	 * Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
+	 * for "differ" defined as + with a one-bit base and a two-bit delta.
+	 * I used http://burtleburtle.net/bob/hash/avalanche.html to choose
+	 * the operations, constants, and arrangements of the variables.
+     *
+	 * This does not achieve avalanche.  There are input bits of a, b, c
+	 * that fail to affect some output bits of a, b, c, especially of a.
+	 * The most thoroughly mixed value is c, but it doesn't really even
+	 * achieve avalanche in c.
+     *
+	 * This allows some parallelism.  Read-after-writes are good at doubling
+	 * the number of bits affected, so the goal of mixing pulls in the opposite
+	 * direction as the goal of parallelism.  I did what I could.  Rotates
+	 * seem to cost as much as shifts on every machine I could lay my hands
+	 * on, and rotates are much kinder to the top and bottom bits, so I used
+	 * rotates.
+	 */
+	private void mix() {
+		this.a -= this.c;  this.a ^= Integer.rotateLeft(this.c, 4);  this.c += this.b;
+		this.b -= this.a;  this.b ^= Integer.rotateLeft(this.a, 6);  this.a += this.c;
+		this.c -= this.b;  this.c ^= Integer.rotateLeft(this.b, 8);  this.b += this.a;
+		this.a -= this.c;  this.a ^= Integer.rotateLeft(this.c, 16); this.c += this.b;
+		this.b -= this.a;  this.b ^= Integer.rotateLeft(this.a, 19); this.a += this.c;
+		this.c -= this.b;  this.c ^= Integer.rotateLeft(this.b, 4);  this.b += this.a;
+	}
+
+	/**
+	 * Final mixing of 3 32-bit values a, b, c into c
+     *
+	 * Pairs of a, b, c values differing in only a few bits will usually
+	 * produce values of c that look totally different.  This was tested for
+	 * * pairs that differed by one bit, by two bits, in any combination
+	 *   of top bits of a, b, c, or in any combination of bottom bits of
+	 *   a, b, c.
+	 * * "differ" is defined as +, -, ^, or ~^.  For + and -, I transformed
+	 *   the output delta to a Gray code (a ^ (a >> 1)) so a string of 1's (as
+	 *   is commonly produced by subtraction) look like a single 1-bit
+	 *   difference.
+	 * * the base values were pseudo-random, all zero but one bit set, or
+	 *   all zero plus a counter that starts at zero.
+	 *
+	 * These constants passed:
+	 *  14 11 25 16 4 14 24
+	 *  12 14 25 16 4 14 24
+	 * and these came close:
+	 *   4  8 15 26 3 22 24
+	 *  10  8 15 26 3 22 24
+	 *  11  8 15 26 3 22 24
+	 */
+	private void finalMix() {
+		this.c ^= this.b; this.c -= Integer.rotateLeft(this.b, 14);
+		this.a ^= this.c; this.a -= Integer.rotateLeft(this.c, 11);
+		this.b ^= this.a; this.b -= Integer.rotateLeft(this.a, 25);
+		this.c ^= this.b; this.c -= Integer.rotateLeft(this.b, 16);
+		this.a ^= this.c; this.a -= Integer.rotateLeft(this.c, 4);
+		this.b ^= this.a; this.b -= Integer.rotateLeft(this.a, 14);
+		this.c ^= this.b; this.c -= Integer.rotateLeft(this.b, 24);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/BTree.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/BTree.java
new file mode 100644
index 0000000..0d04788
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/BTree.java
@@ -0,0 +1,773 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2016 QNX Software Systems 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:
+ *     QNX - Initial API and implementation
+ *     Andrew Ferguson (Symbian) - Provide B-tree deletion routine
+ *     Markus Schorn (Wind River Systems)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import java.text.MessageFormat;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.internal.core.nd.AbstractTypeFactory;
+import org.eclipse.jdt.internal.core.nd.ITypeFactory;
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+/**
+ * Implements B-Tree search structure.
+ */
+public class BTree {
+	private static final int DEFAULT_DEGREE = 8;
+	// Constants for internal deletion routine (see deleteImp doc).
+	private static final int DELMODE_NORMAL = 0;
+	private static final int DELMODE_DELETE_MINIMUM = 1;
+	private static final int DELMODE_DELETE_MAXIMUM = 2;
+
+	public static final int RECORD_SIZE = Database.PTR_SIZE;
+
+	private final Nd nd;
+	protected final Database db;
+	protected final long rootPointer;
+
+	protected final int degree;
+	protected final int maxRecords;
+	protected final int maxChildren;
+	protected final int minRecords;
+	protected final int offsetChildren;
+	protected final int medianRecord;
+
+	protected final IBTreeComparator cmp;
+
+	public BTree(Nd nd, long rootPointer, IBTreeComparator cmp) {
+		this(nd, rootPointer, DEFAULT_DEGREE, cmp);
+	}
+
+	/**
+	 * Constructor.
+	 *
+	 * @param nd the database containing the btree
+	 * @param rootPointer offset into database of the pointer to the root node
+	 */
+	public BTree(Nd nd, long rootPointer, int degree, IBTreeComparator cmp) {
+		this.nd = nd;
+		if (degree < 2)
+			throw new IllegalArgumentException("Illegal degree " + degree + " in tree"); //$NON-NLS-1$ //$NON-NLS-2$
+
+		this.db = nd.getDB();
+		this.rootPointer = rootPointer;
+		this.cmp = cmp;
+
+		this.degree = degree;
+		this.minRecords = this.degree - 1;
+		this.maxRecords = 2*this.degree - 1;
+		this.maxChildren = 2*this.degree;
+		this.offsetChildren = this.maxRecords * Database.INT_SIZE;
+		this.medianRecord = this.degree - 1;
+	}
+
+	public static ITypeFactory<BTree> getFactory(final IBTreeComparator cmp) {
+		return getFactory(8, cmp);
+	}
+
+	public static ITypeFactory<BTree> getFactory(final int degree, final IBTreeComparator cmp) {
+		return new AbstractTypeFactory<BTree>() {
+			@Override
+			public BTree create(Nd dom, long address) {
+				return new BTree(dom, address, degree, cmp);
+			}
+
+			@Override
+			public int getRecordSize() {
+				return RECORD_SIZE;
+			}
+
+			@Override
+			public Class<?> getElementClass() {
+				return BTree.class;
+			}
+
+			@Override
+			public void destruct(Nd dom, long address) {
+				destructFields(dom, address);
+			}
+
+			@Override
+			public void destructFields(Nd dom, long address) {
+				create(dom, address).destruct();
+			}
+		};
+	}
+
+	protected long getRoot() throws IndexException {
+		return this.db.getRecPtr(this.rootPointer);
+	}
+
+	protected final void putRecord(Chunk chunk, long node, int index, long record) {
+		chunk.putRecPtr(node + index * Database.INT_SIZE, record);
+	}
+
+	protected final long getRecord(Chunk chunk, long node, int index) {
+		return chunk.getRecPtr(node + index * Database.INT_SIZE);
+	}
+
+	protected final void putChild(Chunk chunk, long node, int index, long child) {
+		chunk.putRecPtr(node + this.offsetChildren + index * Database.INT_SIZE, child);
+	}
+
+	protected final long getChild(Chunk chunk, long node, int index) {
+		return chunk.getRecPtr(node + this.offsetChildren + index * Database.INT_SIZE);
+	}
+
+	public void destruct() {
+		long root = getRoot();
+
+		if (root == 0) {
+			return;
+		}
+
+		deallocateChildren(root);
+	}
+
+	private void deallocateChildren(long record) {
+		Chunk chunk = this.db.getChunk(record);
+
+		// Copy all the children pointers to an array of longs so all the reads will happen on the same chunk
+		// consecutively
+		long[] children = new long[this.maxRecords + 1];
+
+		for (int idx = 0; idx < children.length; idx++) {
+			children[idx] = getChild(chunk, record, idx);
+		}
+
+		this.db.free(record, Database.POOL_BTREE);
+
+		chunk = null;
+
+		for (long nextChild : children) {
+			if (nextChild != 0) {
+				deallocateChildren(nextChild);
+			}
+		}
+	}
+
+	/**
+	 * Inserts the record into the b-tree. We don't insert if the key was already there,
+	 * in which case we return the record that matched. In other cases, we just return
+	 * the record back.
+	 *
+	 * @param record  offset of the record
+	 */
+	public long insert(long record) throws IndexException {
+		long root = getRoot();
+
+		// Is this our first time in.
+		if (root == 0) {
+			firstInsert(record);
+			return record;
+		}
+
+		return insert(null, 0, 0, root, record);
+	}
+
+	private long insert(Chunk pChunk, long parent, int iParent, long node, long record) throws IndexException {
+		Chunk chunk = this.db.getChunk(node);
+
+		// If this node is full (last record isn't null), split it.
+		if (getRecord(chunk, node, this.maxRecords - 1) != 0) {
+			long median = getRecord(chunk, node, this.medianRecord);
+			if (median == record) {
+				// Found it, never mind.
+				return median;
+			} else {
+				// Split it.
+				// Create the new node and move the larger records over.
+				long newnode = allocateNode();
+				Chunk newchunk = this.db.getChunk(newnode);
+				for (int i = 0; i < this.medianRecord; ++i) {
+					putRecord(newchunk, newnode, i, getRecord(chunk, node, this.medianRecord + 1 + i));
+					putRecord(chunk, node, this.medianRecord + 1 + i, 0);
+					putChild(newchunk, newnode, i, getChild(chunk, node, this.medianRecord + 1 + i));
+					putChild(chunk, node, this.medianRecord + 1 + i, 0);
+				}
+				putChild(newchunk, newnode, this.medianRecord, getChild(chunk, node, this.maxRecords));
+				putChild(chunk, node, this.maxRecords, 0);
+
+				if (parent == 0) {
+					// Create a new root
+					parent = allocateNode();
+					pChunk = this.db.getChunk(parent);
+					this.db.putRecPtr(this.rootPointer, parent);
+					putChild(pChunk, parent, 0, node);
+				} else {
+					// Insert the median into the parent.
+					for (int i = this.maxRecords - 2; i >= iParent; --i) {
+						long r = getRecord(pChunk, parent, i);
+						if (r != 0) {
+							putRecord(pChunk, parent, i + 1, r);
+							putChild(pChunk, parent, i + 2, getChild(pChunk, parent, i + 1));
+						}
+					}
+				}
+				putRecord(pChunk, parent, iParent, median);
+				putChild(pChunk, parent, iParent + 1, newnode);
+
+				putRecord(chunk, node, this.medianRecord, 0);
+
+				// Set the node to the correct one to follow.
+				if (this.cmp.compare(this.nd, record, median) > 0) {
+					node = newnode;
+					chunk = newchunk;
+				}
+			}
+		}
+
+		// Binary search to find the insert point.
+		int lower= 0;
+		int upper= this.maxRecords - 1;
+		while (lower < upper && getRecord(chunk, node, upper - 1) == 0) {
+			upper--;
+		}
+
+		while (lower < upper) {
+			int middle= (lower + upper) / 2;
+			long checkRec= getRecord(chunk, node, middle);
+			if (checkRec == 0) {
+				upper= middle;
+			} else {
+				int compare= this.cmp.compare(this.nd, checkRec, record);
+				if (compare > 0) {
+					upper= middle;
+				} else if (compare < 0) {
+					lower= middle + 1;
+				} else {
+					// Found it, no insert, just return the matched record.
+					return checkRec;
+				}
+			}
+		}
+		final int i= lower;
+		long child = getChild(chunk, node, i);
+		if (child != 0) {
+			// Visit the children.
+			return insert(chunk, node, i, child, record);
+		} else {
+			// We are at the leaf, add us in.
+			// First copy everything after over one.
+			for (int j = this.maxRecords - 2; j >= i; --j) {
+				long r = getRecord(chunk, node, j);
+				if (r != 0)
+					putRecord(chunk, node, j + 1, r);
+			}
+			putRecord(chunk, node, i, record);
+			return record;
+		}
+	}
+
+	private void firstInsert(long record) throws IndexException {
+		// Create the node and save it as root.
+		long root = allocateNode();
+		this.db.putRecPtr(this.rootPointer, root);
+		// Put the record in the first slot of the node.
+		putRecord(this.db.getChunk(root), root, 0, record);
+	}
+
+	private long allocateNode() throws IndexException {
+		return this.db.malloc((2 * this.maxRecords + 1) * Database.INT_SIZE, Database.POOL_BTREE);
+	}
+
+	/**
+	 * Deletes the specified record from the B-tree.
+	 * <p>
+	 * If the specified record is not present then this routine has no effect.
+	 * <p>
+	 * Specifying a record r for which there is another record q existing in the B-tree
+	 * where cmp.compare(r,q)==0 && r!=q will also have no effect
+	 * <p>
+	 * N.B. The record is not deleted itself - its storage is not deallocated.
+	 * The reference to the record in the btree is deleted.
+	 *
+	 * @param record the record to delete
+	 * @throws IndexException
+	 */
+	public void delete(long record) throws IndexException {
+		try {
+			deleteImp(record, getRoot(), DELMODE_NORMAL);
+		} catch (BTreeKeyNotFoundException e) {
+			// Contract of this method is to NO-OP upon this event.
+		}
+	}
+
+	private class BTreeKeyNotFoundException extends Exception {
+		private static final long serialVersionUID = 9065438266175091670L;
+		public BTreeKeyNotFoundException(String msg) {
+			super(msg);
+		}
+	}
+
+	/**
+	 * Used in implementation of delete routines
+	 */
+	private class BTNode {
+		final long node;
+		final int keyCount;
+		final Chunk chunk;
+
+		BTNode(long node) throws IndexException {
+			this.node = node;
+			this.chunk = BTree.this.db.getChunk(node);
+			int i= 0;
+			while (i < BTree.this.maxRecords && getRecord(this.chunk, node, i) != 0)
+				i++;
+			this.keyCount = i;
+		}
+
+		BTNode getChild(int index) throws IndexException {
+			if (0 <= index && index < BTree.this.maxChildren) {
+				long child = BTree.this.getChild(this.chunk, this.node, index);
+				if (child != 0)
+					return new BTNode(child);
+			}
+			return null;
+		}
+	}
+
+	/**
+	 * Implementation for deleting a key/record from the B-tree.
+	 * <p>
+	 * There is no distinction between keys and records.
+	 * <p>
+	 * This implements a single downward pass (with minor exceptions) deletion
+	 * <p>
+	 * @param key the address of the record to delete
+	 * @param nodeRecord a node that (directly or indirectly) contains the specified key/record
+	 * @param mode one of DELMODE_NORMAL, DELMODE_DELETE_MINIMUM, DELMODE_DELETE_MAXIMUM
+	 * 	where DELMODE_NORMAL: locates the specified key/record using the comparator provided
+	 *        DELMODE_DELETE_MINIMUM: locates and deletes the minimum element in the subtree rooted at nodeRecord
+	 *        DELMODE_DELETE_MAXIMUM: locates and deletes the maximum element in the subtree rooted at nodeRecord
+	 * @return the address of the record removed from the B-tree
+	 * @throws IndexException
+	 */
+	private long deleteImp(long key, long nodeRecord, int mode)
+	throws IndexException, BTreeKeyNotFoundException {
+		BTNode node = new BTNode(nodeRecord);
+
+		// Determine index of key in current node, or -1 if its not in this node.
+		int keyIndexInNode = -1;
+		if (mode == DELMODE_NORMAL)
+			for (int i= 0; i < node.keyCount; i++)
+				if (getRecord(node.chunk, node.node, i) == key) {
+					keyIndexInNode = i;
+					break;
+				}
+
+		if (getChild(node.chunk, node.node, 0) == 0) {
+			/* Case 1: leaf node containing the key (by method precondition) */
+			if (keyIndexInNode != -1) {
+				nodeContentDelete(node, keyIndexInNode, 1);
+				return key;
+			} else {
+				if (mode == DELMODE_DELETE_MINIMUM) {
+					long subst = getRecord(node.chunk, node.node, 0);
+					nodeContentDelete(node, 0, 1);
+					return subst;
+				} else if (mode == DELMODE_DELETE_MAXIMUM) {
+					long subst = getRecord(node.chunk, node.node, node.keyCount - 1);
+					nodeContentDelete(node, node.keyCount - 1, 1);
+					return subst;
+				}
+				throw new BTreeKeyNotFoundException("Deletion on absent key " + key + ", mode = " + mode);  //$NON-NLS-1$//$NON-NLS-2$
+			}
+		} else {
+			if (keyIndexInNode != -1) {
+				/* Case 2: non-leaf node which contains the key itself */
+
+				BTNode succ = node.getChild(keyIndexInNode + 1);
+				if (succ != null && succ.keyCount > this.minRecords) {
+					/* Case 2a: Delete key by overwriting it with its successor (which occurs in a leaf node) */
+					long subst = deleteImp(-1, succ.node, DELMODE_DELETE_MINIMUM);
+					putRecord(node.chunk, node.node, keyIndexInNode, subst);
+					return key;
+				}
+
+				BTNode pred = node.getChild(keyIndexInNode);
+				if (pred != null && pred.keyCount > this.minRecords) {
+					/* Case 2b: Delete key by overwriting it with its predecessor (which occurs in a leaf node) */
+					long subst = deleteImp(-1, pred.node, DELMODE_DELETE_MAXIMUM);
+					putRecord(node.chunk, node.node, keyIndexInNode, subst);
+					return key;
+				}
+
+				/* Case 2c: Merge successor and predecessor */
+				// assert(pred != null && succ != null);
+				if (pred != null) {
+					mergeNodes(succ, node, keyIndexInNode, pred);
+					return deleteImp(key, pred.node, mode);
+				}
+				return key;
+			} else {
+				/* Case 3: non-leaf node which does not itself contain the key */
+
+				/* Determine root of subtree that should contain the key */
+				int subtreeIndex;
+				switch(mode) {
+				case DELMODE_NORMAL:
+					subtreeIndex = node.keyCount;
+					for (int i= 0; i < node.keyCount; i++)
+						if (this.cmp.compare(this.nd, getRecord(node.chunk, node.node, i), key)>0) {
+							subtreeIndex = i;
+							break;
+						}
+					break;
+				case DELMODE_DELETE_MINIMUM: subtreeIndex = 0; break;
+				case DELMODE_DELETE_MAXIMUM: subtreeIndex = node.keyCount; break;
+				default: throw new IndexException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, IStatus.OK, "Unknown delete mode " + mode, null)); //$NON-NLS-1$
+				}
+
+				BTNode child = node.getChild(subtreeIndex);
+				if (child == null) {
+					throw new IndexException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, IStatus.OK,
+							"BTree integrity error (null child found)", null)); //$NON-NLS-1$
+				}
+
+				if (child.keyCount > this.minRecords) {
+					return deleteImp(key, child.node, mode);
+				} else {
+					BTNode sibR = node.getChild(subtreeIndex + 1);
+					if (sibR != null && sibR.keyCount > this.minRecords) {
+						/* Case 3a (i): child will underflow upon deletion, take a key from rightSibling */
+						long rightKey = getRecord(node.chunk, node.node, subtreeIndex);
+						long leftmostRightSiblingKey = getRecord(sibR.chunk, sibR.node, 0);
+						append(child, rightKey, getChild(sibR.chunk, sibR.node, 0));
+						nodeContentDelete(sibR, 0, 1);
+						putRecord(node.chunk, node.node, subtreeIndex, leftmostRightSiblingKey);
+						return deleteImp(key, child.node, mode);
+					}
+
+					BTNode sibL = node.getChild(subtreeIndex - 1);
+					if (sibL != null && sibL.keyCount > this.minRecords) {
+						/* Case 3a (ii): child will underflow upon deletion, take a key from leftSibling */
+						long leftKey = getRecord(node.chunk, node.node, subtreeIndex - 1);
+						prepend(child, leftKey, getChild(sibL.chunk, sibL.node, sibL.keyCount));
+						long rightmostLeftSiblingKey = getRecord(sibL.chunk, sibL.node, sibL.keyCount - 1);
+						putRecord(sibL.chunk, sibL.node, sibL.keyCount - 1, 0);
+						putChild(sibL.chunk, sibL.node, sibL.keyCount, 0);
+						putRecord(node.chunk, node.node, subtreeIndex - 1, rightmostLeftSiblingKey);
+						return deleteImp(key, child.node, mode);
+					}
+
+					/* Case 3b (i,ii): leftSibling, child, rightSibling all have minimum number of keys */
+
+					if (sibL != null) { // merge child into leftSibling
+						mergeNodes(child, node, subtreeIndex - 1, sibL);
+						return deleteImp(key, sibL.node, mode);
+					}
+
+					if (sibR != null) { // merge rightSibling into child
+						mergeNodes(sibR, node, subtreeIndex, child);
+						return deleteImp(key, child.node, mode);
+					}
+
+					throw new BTreeKeyNotFoundException(
+							MessageFormat.format("Deletion of key not in btree: {0} mode={1}", //$NON-NLS-1$
+									new Object[]{new Long(key), new Integer(mode)}));
+				}
+			}
+		}
+	}
+
+	/**
+	 * Merge node 'src' onto the right side of node 'dst' using node
+	 * 'keyProvider' as the source of the median key. Bounds checking is not
+	 * performed.
+	 * @param src the key to merge into dst
+	 * @param keyProvider the node that provides the median key for the new node
+	 * @param kIndex the index of the key in the node <i>mid</i> which is to become the new node's median key
+	 * @param dst the node which is the basis and result of the merge
+	 */
+	public void mergeNodes(BTNode src, BTNode keyProvider, int kIndex, BTNode dst)
+	throws IndexException {
+		nodeContentCopy(src, 0, dst, dst.keyCount + 1, src.keyCount + 1);
+		long midKey = getRecord(keyProvider.chunk, keyProvider.node, kIndex);
+		putRecord(dst.chunk, dst.node, dst.keyCount, midKey);
+		long keySucc = kIndex + 1 == this.maxRecords ? 0 : getRecord(keyProvider.chunk, keyProvider.node, kIndex + 1);
+		this.db.free(getChild(keyProvider.chunk, keyProvider.node,  kIndex + 1), Database.POOL_BTREE);
+		nodeContentDelete(keyProvider, kIndex + 1, 1);
+		putRecord(keyProvider.chunk, keyProvider.node, kIndex, keySucc);
+		if (kIndex == 0 && keySucc == 0) {
+			/*
+			 * The root node is excused from the property that a node must have a least MIN keys
+			 * This means we must special case it at the point when its had all of its keys deleted
+			 * entirely during merge operations (which push one of its keys down as a pivot)
+			 */
+			long rootNode = getRoot();
+			if (rootNode == keyProvider.node) {
+				this.db.putRecPtr(this.rootPointer, dst.node);
+				this.db.free(rootNode, Database.POOL_BTREE);
+			}
+		}
+	}
+
+	/**
+	 * Insert the key and (its predecessor) child at the left side of the specified node. Bounds checking
+	 * is not performed.
+	 * @param node the node to prepend to
+	 * @param key the new leftmost (least) key
+	 * @param child the new leftmost (least) subtree root
+	 */
+	private void prepend(BTNode node, long key, long child) {
+		nodeContentCopy(node, 0, node, 1, node.keyCount + 1);
+		putRecord(node.chunk, node.node, 0, key);
+		putChild(node.chunk, node.node, 0, child);
+	}
+
+	/**
+	 * Insert the key and (its successor) child at the right side of the specified node. Bounds
+	 * checking is not performed.
+	 * @param node
+	 * @param key
+	 * @param child
+	 */
+	private void append(BTNode node, long key, long child) {
+		putRecord(node.chunk, node.node, node.keyCount, key);
+		putChild(node.chunk, node.node, node.keyCount + 1, child);
+	}
+
+	/**
+	 * Overwrite a section of the specified node (dst) with the specified section of the source
+	 * node. Bounds checking is not performed. To allow just copying of the final child (which has
+	 * no corresponding key) the routine behaves as though there were a corresponding key existing
+	 * with value zero.<p>
+	 * Copying from a node to itself is permitted.
+	 * @param src the node to read from
+	 * @param srcPos the initial index to read from (inclusive)
+	 * @param dst the node to write to
+	 * @param dstPos the initial index to write to (inclusive)
+	 * @param length the number of (key,(predecessor)child) nodes to write
+	 */
+	private void nodeContentCopy(BTNode src, int srcPos, BTNode dst, int dstPos, int length) {
+		for (int i=length - 1; i >= 0; i--) { // this order is important when src == dst!
+			int srcIndex = srcPos + i;
+			int dstIndex = dstPos + i;
+
+			if (srcIndex < src.keyCount + 1) {
+				long srcChild = getChild(src.chunk, src.node, srcIndex);
+				putChild(dst.chunk, dst.node, dstIndex, srcChild);
+
+				if (srcIndex < src.keyCount) {
+					long srcKey = getRecord(src.chunk, src.node, srcIndex);
+					putRecord(dst.chunk, dst.node, dstIndex, srcKey);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Delete a section of node content - (key, (predecessor)child) pairs. Bounds checking
+	 * is not performed. To allow deletion of the final child (which has no corresponding key)
+	 * the routine behaves as though there were a corresponding key existing with value zero.<p>
+	 * Content is deleted and remaining content is moved leftward the appropriate amount.
+	 * @param node the node to delete content from
+	 * @param i the start index (inclusive) to delete from
+	 * @param length the length of the sequence to delete
+	 */
+	private void nodeContentDelete(BTNode node, int i, int length) {
+		for (int index= i; index <= this.maxRecords; index++) {
+			long newKey = (index + length) < node.keyCount ? getRecord(node.chunk, node.node, index + length) : 0;
+			long newChild = (index + length) < node.keyCount + 1 ? getChild(node.chunk, node.node, index + length) : 0;
+			if (index < this.maxRecords) {
+				putRecord(node.chunk, node.node, index, newKey);
+			}
+			if (index < this.maxChildren) {
+				putChild(node.chunk, node.node, index, newChild);
+			}
+		}
+	}
+
+	/**
+	 * Visit all nodes beginning when the visitor comparator
+	 * returns >= 0 until the visitor visit returns falls.
+	 *
+	 * @param visitor
+	 */
+	public boolean accept(IBTreeVisitor visitor) throws IndexException {
+		return accept(this.db.getRecPtr(this.rootPointer), visitor);
+	}
+
+	private boolean accept(long node, IBTreeVisitor visitor) throws IndexException {
+		// If found is false, we are still in search mode.
+		// Once found is true visit everything.
+		// Return false when ready to quit.
+
+		if (node == 0) {
+			return true;
+		}
+		if (visitor instanceof IBTreeVisitor2) {
+			((IBTreeVisitor2) visitor).preNode(node);
+		}
+
+		try {
+			Chunk chunk = this.db.getChunk(node);
+
+			// Binary search to find first record greater or equal.
+			int lower= 0;
+			int upper= this.maxRecords - 1;
+			while (lower < upper && getRecord(chunk, node, upper - 1) == 0) {
+				upper--;
+			}
+			while (lower < upper) {
+				int middle= (lower + upper) / 2;
+				long checkRec = getRecord(chunk, node, middle);
+				if (checkRec == 0) {
+					upper= middle;
+				} else {
+					int compare= visitor.compare(checkRec);
+					if (compare >= 0) {
+						upper= middle;
+					} else {
+						lower= middle + 1;
+					}
+				}
+			}
+
+			// Start with first record greater or equal, reuse comparison results.
+			int i= lower;
+			for (; i < this.maxRecords; ++i) {
+				long record = getRecord(chunk, node, i);
+				if (record == 0)
+					break;
+
+				int compare= visitor.compare(record);
+				if (compare > 0) {
+					// Start point is to the left.
+					return accept(getChild(chunk, node, i), visitor);
+				}  else if (compare == 0) {
+					if (!accept(getChild(chunk, node, i), visitor))
+						return false;
+					if (!visitor.visit(record))
+						return false;
+				}
+			}
+			return accept(getChild(chunk, node, i), visitor);
+		} finally {
+			if (visitor instanceof IBTreeVisitor2) {
+				((IBTreeVisitor2) visitor).postNode(node);
+			}
+		}
+	}
+
+	/*
+	 * TODO: It would be good to move these into IBTreeVisitor and eliminate
+	 * IBTreeVisitor2 if this is acceptable.
+	 */
+	private interface IBTreeVisitor2 extends IBTreeVisitor {
+		void preNode(long node) throws IndexException;
+		void postNode(long node) throws IndexException;
+	}
+
+	/**
+	 * Debugging method for checking B-tree invariants
+	 * @return the empty String if B-tree invariants hold, otherwise
+	 * a human readable report
+	 * @throws IndexException
+	 */
+	public String getInvariantsErrorReport() throws IndexException {
+		InvariantsChecker checker = new InvariantsChecker();
+		accept(checker);
+		return checker.isValid() ? "" : checker.getMsg(); //$NON-NLS-1$
+	}
+
+	/**
+	 * A B-tree visitor for checking some B-tree invariants.
+	 * Note ordering invariants are not checked here.
+	 */
+	private class InvariantsChecker implements IBTreeVisitor2 {
+		boolean valid = true;
+		String msg = ""; //$NON-NLS-1$
+		Integer leafDepth;
+		int depth;
+
+		public InvariantsChecker() {}
+		public String getMsg() { return this.msg; }
+		public boolean isValid() { return this.valid; }
+		@Override
+		public void postNode(long node) throws IndexException { this.depth--; }
+		@Override
+		public int compare(long record) throws IndexException { return 0; }
+		@Override
+		public boolean visit(long record) throws IndexException { return true; }
+
+		@Override
+		public void preNode(long node) throws IndexException {
+			this.depth++;
+
+			// Collect information for checking.
+			int keyCount = 0;
+			int indexFirstBlankKey = BTree.this.maxRecords;
+			int indexLastNonBlankKey = 0;
+			for (int i= 0; i < BTree.this.maxRecords; i++) {
+				if (getRecord(BTree.this.db.getChunk(node), node, i) != 0) {
+					keyCount++;
+					indexLastNonBlankKey = i;
+				} else if (indexFirstBlankKey == BTree.this.maxRecords) {
+					indexFirstBlankKey = i;
+				}
+			}
+
+			int childCount = 0;
+			for (int i= 0; i < BTree.this.maxChildren; i++) {
+				if (getChild(BTree.this.db.getChunk(node), node, i) != 0) {
+					childCount++;
+				}
+			}
+
+			// Check that non-blank keys are contiguous and blank key terminated.
+			if (indexFirstBlankKey != indexLastNonBlankKey + 1) {
+				boolean full = indexFirstBlankKey == BTree.this.maxRecords && indexLastNonBlankKey == BTree.this.maxRecords - 1;
+				boolean empty = indexFirstBlankKey == 0 && indexLastNonBlankKey == 0;
+				if (!full && !empty) {
+					this.valid = false;
+					this.msg += MessageFormat.format("[{0} blanks inconsistent b={1} nb={2}]", //$NON-NLS-1$
+							new Object[] { new Long(node), new Integer(indexFirstBlankKey),
+									new Integer(indexLastNonBlankKey) });
+				}
+			}
+
+			// Check: Key number constrains child numbers
+			if (childCount != 0 && childCount != keyCount + 1) {
+				this.valid = false;
+				this.msg += MessageFormat.format("[{0} wrong number of children with respect to key count]", //$NON-NLS-1$
+						new Object[] { new Long(node) });
+			}
+
+			// The root node is excused from the remaining node constraints.
+			if (node == BTree.this.db.getRecPtr(BTree.this.rootPointer)) {
+				return;
+			}
+
+			// Check: Non-root nodes must have a keyCount within a certain range
+			if (keyCount < BTree.this.minRecords || keyCount > BTree.this.maxRecords) {
+				this.valid = false;
+				this.msg += MessageFormat.format("[{0} key count out of range]", new Object[] { new Long(node) }); //$NON-NLS-1$
+			}
+
+			// Check: All leaf nodes are at the same depth
+			if (childCount == 0) {
+				if (this.leafDepth == null) {
+					this.leafDepth = new Integer(this.depth);
+				}
+				if (this.depth != this.leafDepth.intValue()) {
+					this.valid = false;
+					this.msg += "Leaf nodes at differing depths"; //$NON-NLS-1$
+				}
+			}
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Chunk.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Chunk.java
new file mode 100644
index 0000000..4219483
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Chunk.java
@@ -0,0 +1,324 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2016 QNX Software Systems 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:
+ *     QNX - Initial API and implementation
+ *     Markus Schorn (Wind River Systems)
+ *     IBM Corporation
+ *     Sergey Prigogin (Google)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Caches the content of a piece of the database.
+ */
+final class Chunk {
+	final private byte[] fBuffer= new byte[Database.CHUNK_SIZE];
+
+	final Database fDatabase;
+	final int fSequenceNumber;
+
+	boolean fCacheHitFlag;
+	boolean fDirty;
+	boolean fLocked;	// locked chunks must not be released from cache.
+	int fCacheIndex= -1;
+
+	Chunk(Database db, int sequenceNumber) {
+		this.fDatabase= db;
+		this.fSequenceNumber= sequenceNumber;
+	}
+
+	void read() throws IndexException {
+		try {
+			final ByteBuffer buf= ByteBuffer.wrap(this.fBuffer);
+			this.fDatabase.read(buf, (long) this.fSequenceNumber * Database.CHUNK_SIZE);
+		} catch (IOException e) {
+			throw new IndexException(new DBStatus(e));
+		}
+	}
+
+	/**
+	 * Uninterruptable. Returns true iff an attempt was made to interrupt the flush with
+	 * {@link Thread#interrupt()}.
+	 */
+	boolean flush() throws IndexException {
+		boolean wasCanceled = false;
+		try {
+			final ByteBuffer buf= ByteBuffer.wrap(this.fBuffer);
+			wasCanceled = this.fDatabase.write(buf, (long) this.fSequenceNumber * Database.CHUNK_SIZE);
+		} catch (IOException e) {
+			throw new IndexException(new DBStatus(e));
+		}
+		this.fDirty= false;
+		return wasCanceled;
+	}
+
+	private static int recPtrToIndex(final long offset) {
+		return (int) (offset & Database.OFFSET_IN_CHUNK_MASK);
+	}
+
+	public void putByte(final long offset, final byte value) {
+		assert this.fLocked;
+		this.fDirty= true;
+		this.fBuffer[recPtrToIndex(offset)]= value;
+	}
+
+	public byte getByte(final long offset) {
+		return this.fBuffer[recPtrToIndex(offset)];
+	}
+
+	public byte[] getBytes(final long offset, final int length) {
+		final byte[] bytes = new byte[length];
+		System.arraycopy(this.fBuffer, recPtrToIndex(offset), bytes, 0, length);
+		return bytes;
+	}
+
+	public void putBytes(final long offset, final byte[] bytes) {
+		assert this.fLocked;
+		this.fDirty= true;
+		System.arraycopy(bytes, 0, this.fBuffer, recPtrToIndex(offset), bytes.length);
+	}
+
+	public void putInt(final long offset, final int value) {
+		assert this.fLocked;
+		this.fDirty= true;
+		int idx= recPtrToIndex(offset);
+		putInt(value, this.fBuffer, idx);
+	}
+
+	static final void putInt(final int value, final byte[] buffer, int idx) {
+		buffer[idx]=   (byte) (value >> 24);
+		buffer[++idx]= (byte) (value >> 16);
+		buffer[++idx]= (byte) (value >> 8);
+		buffer[++idx]= (byte) (value);
+	}
+
+	public int getInt(final long offset) {
+		return getInt(this.fBuffer, recPtrToIndex(offset));
+	}
+
+	static final int getInt(final byte[] buffer, int idx) {
+		return ((buffer[idx] & 0xff) << 24) |
+				((buffer[++idx] & 0xff) << 16) |
+				((buffer[++idx] & 0xff) <<  8) |
+				((buffer[++idx] & 0xff) <<  0);
+	}
+
+	/**
+	 * A free Record Pointer is a pointer to a raw block, i.e. the
+	 * pointer is not moved past the BLOCK_HEADER_SIZE.
+	 */
+	static int compressFreeRecPtr(final long value) {
+		// This assert verifies the alignment. We expect the low bits to be clear.
+		assert (value & (Database.BLOCK_SIZE_DELTA - 1)) == 0;
+		final int dense = (int) (value >> Database.BLOCK_SIZE_DELTA_BITS);
+		return dense;
+	}
+
+	/**
+	 * A free Record Pointer is a pointer to a raw block,
+	 * i.e. the pointer is not moved past the BLOCK_HEADER_SIZE.
+	 */
+	static long expandToFreeRecPtr(int value) {
+		/*
+		 * We need to properly manage the integer that was read. The value will be sign-extended
+		 * so if the most significant bit is set, the resulting long will look negative. By
+		 * masking it with ((long)1 << 32) - 1 we remove all the sign-extended bits and just
+		 * have an unsigned 32-bit value as a long. This gives us one more useful bit in the
+		 * stored record pointers.
+		 */
+		long address = value & 0xFFFFFFFFL;
+		return address << Database.BLOCK_SIZE_DELTA_BITS;
+	}
+
+	/**
+	 * A Record Pointer is a pointer as returned by Database.malloc().
+	 * This is a pointer to a block + BLOCK_HEADER_SIZE.
+	 */
+	public void putRecPtr(final long offset, final long value) {
+		assert this.fLocked;
+		this.fDirty = true;
+		int idx = recPtrToIndex(offset);
+		Database.putRecPtr(value, this.fBuffer, idx);
+	}
+
+	/**
+	 * A free Record Pointer is a pointer to a raw block,
+	 * i.e. the pointer is not moved past the BLOCK_HEADER_SIZE.
+	 */
+	public void putFreeRecPtr(final long offset, final long value) {
+		assert this.fLocked;
+		this.fDirty = true;
+		int idx = recPtrToIndex(offset);
+		putInt(compressFreeRecPtr(value), this.fBuffer, idx);
+	}
+
+	public long getRecPtr(final long offset) {
+		final int idx = recPtrToIndex(offset);
+		return Database.getRecPtr(this.fBuffer, idx);
+	}
+
+	public long getFreeRecPtr(final long offset) {
+		final int idx = recPtrToIndex(offset);
+		int value = getInt(this.fBuffer, idx);
+		return expandToFreeRecPtr(value);
+	}
+
+	public void put3ByteUnsignedInt(final long offset, final int value) {
+		assert this.fLocked;
+		this.fDirty= true;
+		int idx= recPtrToIndex(offset);
+		this.fBuffer[idx]= (byte) (value >> 16);
+		this.fBuffer[++idx]= (byte) (value >> 8);
+		this.fBuffer[++idx]= (byte) (value);
+	}
+
+	public int get3ByteUnsignedInt(final long offset) {
+		int idx= recPtrToIndex(offset);
+		return ((this.fBuffer[idx] & 0xff) << 16) |
+				((this.fBuffer[++idx] & 0xff) <<  8) |
+				((this.fBuffer[++idx] & 0xff) <<  0);
+	}
+
+	public void putShort(final long offset, final short value) {
+		assert this.fLocked;
+		this.fDirty= true;
+		int idx= recPtrToIndex(offset);
+		this.fBuffer[idx]= (byte) (value >> 8);
+		this.fBuffer[++idx]= (byte) (value);
+	}
+
+	public short getShort(final long offset) {
+		int idx= recPtrToIndex(offset);
+		return (short) (((this.fBuffer[idx] << 8) | (this.fBuffer[++idx] & 0xff)));
+	}
+
+	public long getLong(final long offset) {
+		int idx= recPtrToIndex(offset);
+		return ((((long) this.fBuffer[idx] & 0xff) << 56) |
+				(((long) this.fBuffer[++idx] & 0xff) << 48) |
+				(((long) this.fBuffer[++idx] & 0xff) << 40) |
+				(((long) this.fBuffer[++idx] & 0xff) << 32) |
+				(((long) this.fBuffer[++idx] & 0xff) << 24) |
+				(((long) this.fBuffer[++idx] & 0xff) << 16) |
+				(((long) this.fBuffer[++idx] & 0xff) <<  8) |
+				(((long) this.fBuffer[++idx] & 0xff) <<  0));
+	}
+
+	public double getDouble(long offset) {
+		return Double.longBitsToDouble(getLong(offset));
+	}
+
+	public float getFloat(long offset) {
+		return Float.intBitsToFloat(getInt(offset));
+	}
+
+	public void putLong(final long offset, final long value) {
+		assert this.fLocked;
+		this.fDirty= true;
+		int idx= recPtrToIndex(offset);
+
+		this.fBuffer[idx]=   (byte) (value >> 56);
+		this.fBuffer[++idx]= (byte) (value >> 48);
+		this.fBuffer[++idx]= (byte) (value >> 40);
+		this.fBuffer[++idx]= (byte) (value >> 32);
+		this.fBuffer[++idx]= (byte) (value >> 24);
+		this.fBuffer[++idx]= (byte) (value >> 16);
+		this.fBuffer[++idx]= (byte) (value >> 8);
+		this.fBuffer[++idx]= (byte) (value);
+	}
+
+	public void putChar(final long offset, final char value) {
+		assert this.fLocked;
+		this.fDirty= true;
+		int idx= recPtrToIndex(offset);
+		this.fBuffer[idx]= (byte) (value >> 8);
+		this.fBuffer[++idx]= (byte) (value);
+	}
+
+	public void putChars(final long offset, char[] chars, int start, int len) {
+		assert this.fLocked;
+		this.fDirty= true;
+		int idx= recPtrToIndex(offset)-1;
+		final int end= start + len;
+		for (int i = start; i < end; i++) {
+			char value= chars[i];
+			this.fBuffer[++idx]= (byte) (value >> 8);
+			this.fBuffer[++idx]= (byte) (value);
+		}
+	}
+
+	public void putCharsAsBytes(final long offset, char[] chars, int start, int len) {
+		assert this.fLocked;
+		this.fDirty= true;
+		int idx= recPtrToIndex(offset)-1;
+		final int end= start + len;
+		for (int i = start; i < end; i++) {
+			char value= chars[i];
+			this.fBuffer[++idx]= (byte) (value);
+		}
+	}
+
+	public void putDouble(final long offset, double value) {
+		putLong(offset, Double.doubleToLongBits(value));
+	}
+
+	public void putFloat(final long offset, float value) {
+		putInt(offset, Float.floatToIntBits(value));
+	}
+
+	public char getChar(final long offset) {
+		int idx= recPtrToIndex(offset);
+		return (char) (((this.fBuffer[idx] << 8) | (this.fBuffer[++idx] & 0xff)));
+	}
+
+	public void getChars(final long offset, final char[] result, int start, int len) {
+		final ByteBuffer buf= ByteBuffer.wrap(this.fBuffer);
+		buf.position(recPtrToIndex(offset));
+		buf.asCharBuffer().get(result, start, len);
+	}
+
+	public void getCharsFromBytes(final long offset, final char[] result, int start, int len) {
+		final int pos = recPtrToIndex(offset);
+		for (int i = 0; i < len; i++) {
+			result[start + i] =  (char) (this.fBuffer[pos + i] & 0xff);
+		}
+	}
+
+	void clear(final long offset, final int length) {
+		assert this.fLocked;
+		this.fDirty= true;
+		int idx = recPtrToIndex(offset);
+		final int end = idx + length;
+		for (; idx < end; idx++) {
+			this.fBuffer[idx] = 0;
+		}
+	}
+
+	void put(final long offset, final byte[] data, final int len) {
+		put(offset, data, 0, len);
+	}
+
+	void put(final long offset, final byte[] data, int dataPos, final int len) {
+		assert this.fLocked;
+		this.fDirty = true;
+		int idx = recPtrToIndex(offset);
+		System.arraycopy(data, dataPos, this.fBuffer, idx, len);
+	}
+
+	public void get(final long offset, byte[] data) {
+		get(offset, data, 0, data.length);
+	}
+
+	public void get(final long offset, byte[] data, int dataPos, int len) {
+		int idx = recPtrToIndex(offset);
+		System.arraycopy(this.fBuffer, idx, data, dataPos, len);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ChunkCache.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ChunkCache.java
new file mode 100644
index 0000000..1cd3736
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ChunkCache.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2016 Wind River Systems, 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:
+ *     Markus Schorn - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+public final class ChunkCache {
+	private static ChunkCache sSharedInstance= new ChunkCache();
+
+	private Chunk[] fPageTable;
+	private boolean fTableIsFull;
+	private int fPointer;
+
+	public static ChunkCache getSharedInstance() {
+		return sSharedInstance;
+	}
+
+	public ChunkCache() {
+		this(5 * 1024 * 1024);
+	}
+
+	public ChunkCache(long maxSize) {
+		this.fPageTable= new Chunk[computeLength(maxSize)];
+	}
+
+	public synchronized void add(Chunk chunk, boolean locked) {
+		if (locked) {
+			chunk.fLocked= true;
+		}
+		if (chunk.fCacheIndex >= 0) {
+			chunk.fCacheHitFlag= true;
+			return;
+		}
+		if (this.fTableIsFull) {
+			evictChunk();
+			chunk.fCacheIndex= this.fPointer;
+			this.fPageTable[this.fPointer]= chunk;
+		} else {
+			chunk.fCacheIndex= this.fPointer;
+			this.fPageTable[this.fPointer]= chunk;
+
+			this.fPointer++;
+			if (this.fPointer == this.fPageTable.length) {
+				this.fPointer= 0;
+				this.fTableIsFull= true;
+			}
+		}
+	}
+
+	/**
+	 * Evicts a chunk from the page table and the chunk table.
+	 * After this method returns, {@link #fPointer}  will contain
+	 * the index of the evicted chunk within the page table.
+	 */
+	private void evictChunk() {
+		/*
+		 * Use the CLOCK algorithm to determine which chunk to evict.
+		 * i.e., if the chunk in the current slot of the page table has been
+		 * recently referenced (i.e. the reference flag is set), unset the
+		 * reference flag and move to the next slot.  Otherwise, evict the
+		 * chunk in the current slot.
+		 */
+		while (true) {
+			Chunk chunk = this.fPageTable[this.fPointer];
+			if (chunk.fCacheHitFlag) {
+				chunk.fCacheHitFlag= false;
+				this.fPointer= (this.fPointer + 1) % this.fPageTable.length;
+			} else {
+				chunk.fDatabase.releaseChunk(chunk);
+				chunk.fCacheIndex= -1;
+				this.fPageTable[this.fPointer] = null;
+				return;
+			}
+		}
+	}
+
+	public synchronized void remove(Chunk chunk) {
+		final int idx= chunk.fCacheIndex;
+		if (idx >= 0) {
+			if (this.fTableIsFull) {
+				this.fPointer= this.fPageTable.length-1;
+				this.fTableIsFull= false;
+			} else {
+				this.fPointer--;
+			}
+			chunk.fCacheIndex= -1;
+			final Chunk move= this.fPageTable[this.fPointer];
+			this.fPageTable[idx]= move;
+			move.fCacheIndex= idx;
+			this.fPageTable[this.fPointer]= null;
+		}
+	}
+
+	/**
+	 * Returns the maximum size of the chunk cache in bytes.
+	 */
+	public synchronized long getMaxSize() {
+		return (long) this.fPageTable.length * Database.CHUNK_SIZE;
+	}
+
+	/**
+	 * Clears the page table and changes it to hold chunks with
+	 * maximum total memory of <code>maxSize</code>.
+	 * @param maxSize the total size of the chunks in bytes.
+	 */
+	public synchronized void setMaxSize(long maxSize) {
+		final int newLength= computeLength(maxSize);
+		final int oldLength= this.fTableIsFull ? this.fPageTable.length : this.fPointer;
+		if (newLength > oldLength) {
+			Chunk[] newTable= new Chunk[newLength];
+			System.arraycopy(this.fPageTable, 0, newTable, 0, oldLength);
+			this.fTableIsFull= false;
+			this.fPointer= oldLength;
+			this.fPageTable= newTable;
+		} else {
+			for (int i= newLength; i < oldLength; i++) {
+				final Chunk chunk= this.fPageTable[i];
+				chunk.fDatabase.releaseChunk(chunk);
+				chunk.fCacheIndex= -1;
+			}
+			Chunk[] newTable= new Chunk[newLength];
+			System.arraycopy(this.fPageTable, 0, newTable, 0, newLength);
+			this.fTableIsFull= true;
+			this.fPointer= 0;
+			this.fPageTable= newTable;
+		}
+	}
+
+	private int computeLength(long maxSize) {
+		long maxLength= Math.min(maxSize / Database.CHUNK_SIZE, Integer.MAX_VALUE);
+		return Math.max(1, (int) maxLength);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/DBProperties.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/DBProperties.java
new file mode 100644
index 0000000..cfa6050
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/DBProperties.java
@@ -0,0 +1,261 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2016 Symbian Software Systems 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:
+ *     Andrew Ferguson (Symbian) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+/**
+ * DBProperties is a bare-bones implementation of a String->String mapping. It is neither
+ * a Map or a Properties subclass, because of their more general applications.
+ */
+public class DBProperties {
+	static final int PROP_INDEX = 0;
+	static final int RECORD_SIZE = 4;
+
+	protected BTree index;
+	protected Database db;
+	protected long record;
+
+	/**
+	 * Allocate storage for a new DBProperties record in the specified database
+	 */
+	public DBProperties(Nd nd) throws IndexException {
+		Database database = nd.getDB();
+		this.record= database.malloc(RECORD_SIZE, Database.POOL_DB_PROPERTIES);
+		this.index= new BTree(nd, this.record + PROP_INDEX, DBProperty.getComparator());
+		this.db= database;
+	}
+
+	/**
+	 * Creates an object for accessing an existing DBProperties record at the specified location
+	 * of the specified database.
+	 */
+	public DBProperties(Nd nd, long record) throws IndexException {
+		Database database = nd.getDB();
+		this.record= record;
+		this.index= new BTree(nd, record + PROP_INDEX, DBProperty.getComparator());
+		this.db= database;
+	}
+
+	/**
+	 * Reads the named property from this properties storage.
+	 * @param key a case-sensitive identifier for a property, or null
+	 * @return the value associated with the key, or null if either no such property is set,
+	 *     or the specified key was null
+	 * @throws IndexException
+	 */
+	public String getProperty(String key) throws IndexException {
+		if (key != null) {
+			DBProperty existing= DBProperty.search(this.db, this.index, key);
+			if (existing != null) {
+				return existing.getValue().getString();
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Reads the named property from this properties storage, returning the default value if there
+	 * is no such property.
+	 * @param key a case-sensitive identifier for a property, or null
+	 * @param defaultValue a value to return in case the specified key was null
+	 * @return the value associated with the key, or the specified default value if either no such
+	 *     property is set, or the specified key was null
+	 * @throws IndexException
+	 */
+	public String getProperty(String key, String defaultValue) throws IndexException {
+		String val= getProperty(key);
+		return (val == null) ? defaultValue : val;
+	}
+
+	/**
+	 * Returns the Set of property names stored in this object
+	 * @return the Set of property names stored in this object
+	 * @throws IndexException
+	 */
+	public Set<String> getKeySet() throws IndexException {
+		return DBProperty.getKeySet(this.db, this.index);
+	}
+
+	/**
+	 * Writes the key, value mapping to the properties. If a mapping for the
+	 * same key already exists, it is overwritten.
+	 * @param key a non-null property name
+	 * @param value a value to associate with the key. may not be null.
+	 * @throws IndexException
+	 * @throws NullPointerException if key is null
+	 */
+	public void setProperty(String key, String value) throws IndexException {
+		removeProperty(key);
+		DBProperty newProperty= new DBProperty(this.db, key, value);
+		this.index.insert(newProperty.getRecord());
+	}
+
+	/**
+	 * Deletes a property from this DBProperties object.
+	 * @param key
+	 * @return whether a property with matching key existed and was removed, or false if the key
+	 *     was null
+	 * @throws IndexException
+	 */
+	public boolean removeProperty(String key) throws IndexException {
+		if (key != null) {
+			DBProperty existing= DBProperty.search(this.db, this.index, key);
+			if (existing != null) {
+				this.index.delete(existing.getRecord());
+				existing.delete();
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Deletes all properties, does not delete the record associated with the object itself
+	 * - that is it can be re-populated.
+	 * @throws IndexException
+	 */
+	public void clear() throws IndexException {
+		this.index.accept(new IBTreeVisitor(){
+			@Override
+			public int compare(long address) throws IndexException {
+				return 0;
+			}
+			@Override
+			public boolean visit(long address) throws IndexException {
+				new DBProperty(DBProperties.this.db, address).delete();
+				return false; // there should never be duplicates
+			}
+		});
+	}
+
+	/**
+	 * Deletes all properties stored in this object and the record associated with this object
+	 * itself.
+	 * <br><br>
+	 * <b>The behaviour of objects of this class after calling this method is undefined</b>
+	 * @throws IndexException
+	 */
+	public void delete() throws IndexException {
+		clear();
+		this.db.free(this.record, Database.POOL_DB_PROPERTIES);
+	}
+
+	public long getRecord() {
+		return this.record;
+	}
+
+	private static class DBProperty {
+		static final int KEY = 0;
+		static final int VALUE = 4;
+		@SuppressWarnings("hiding")
+		static final int RECORD_SIZE = 8;
+
+		Database db;
+		long record;
+
+		public long getRecord() {
+			return this.record;
+		}
+
+		/**
+		 * Allocates and initializes a record in the specified database for a DBProperty record
+		 * @param db
+		 * @param key a non-null property name
+		 * @param value a non-null property value
+		 * @throws IndexException
+		 */
+		DBProperty(Database db, String key, String value) throws IndexException {
+			assert key != null;
+			assert value != null;
+			IString dbkey= db.newString(key);
+			IString dbvalue= db.newString(value);
+			this.record= db.malloc(RECORD_SIZE, Database.POOL_DB_PROPERTIES);
+			db.putRecPtr(this.record + KEY, dbkey.getRecord());
+			db.putRecPtr(this.record + VALUE, dbvalue.getRecord());
+			this.db= db;
+		}
+
+		/**
+		 * Returns an object for accessing an existing DBProperty record at the specified location
+		 * in the specified database.
+		 * @param db
+		 * @param record
+		 */
+		DBProperty(Database db, long record) {
+			this.record= record;
+			this.db= db;
+		}
+
+		public IString getKey() throws IndexException {
+			return this.db.getString(this.db.getRecPtr(this.record + KEY));
+		}
+
+		public IString getValue() throws IndexException {
+			return this.db.getString(this.db.getRecPtr(this.record + VALUE));
+		}
+
+		public static IBTreeComparator getComparator() {
+			return new IBTreeComparator() {
+				@Override
+				public int compare(Nd nd, long record1, long record2) throws IndexException {
+					Database db = nd.getDB();
+					IString left= db.getString(db.getRecPtr(record1 + KEY));
+					IString right= db.getString(db.getRecPtr(record2 + KEY));
+					return left.compare(right, true);
+				}
+			};
+		}
+
+		public static DBProperty search(final Database db, final BTree index, final String key) throws IndexException {
+			final DBProperty[] result= new DBProperty[1];
+			index.accept(new IBTreeVisitor(){
+				@Override
+				public int compare(long record) throws IndexException {
+					return db.getString(db.getRecPtr(record + KEY)).compare(key, true);
+				}
+
+				@Override
+				public boolean visit(long record) throws IndexException {
+					result[0] = new DBProperty(db, record);
+					return false; // There should never be duplicates.
+				}
+			});
+			return result[0];
+		}
+
+		public static Set<String> getKeySet(final Database db, final BTree index) throws IndexException {
+			final Set<String> result= new HashSet<String>();
+			index.accept(new IBTreeVisitor(){
+				@Override
+				public int compare(long record) throws IndexException {
+					return 0;
+				}
+
+				@Override
+				public boolean visit(long record) throws IndexException {
+					result.add(new DBProperty(db, record).getKey().getString());
+					return true; // There should never be duplicates.
+				}
+			});
+			return result;
+		}
+
+		public void delete() throws IndexException {
+			this.db.getString(this.db.getRecPtr(this.record + KEY)).delete();
+			this.db.getString(this.db.getRecPtr(this.record + VALUE)).delete();
+			this.db.free(this.record, Database.POOL_DB_PROPERTIES);
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/DBStatus.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/DBStatus.java
new file mode 100644
index 0000000..d881ff2
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/DBStatus.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2016 QNX Software Systems 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:
+ *     QNX - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import java.io.IOException;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+public class DBStatus extends Status {
+	/**
+	 * @param exception
+	 */
+	public DBStatus(IOException exception) {
+		super(IStatus.ERROR, Package.PLUGIN_ID, 0, "IOException", exception); //$NON-NLS-1$
+	}
+
+	public DBStatus(String msg) {
+		super(IStatus.ERROR, Package.PLUGIN_ID, 0, "Error", null); //$NON-NLS-1$
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Database.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Database.java
new file mode 100644
index 0000000..95b4b8d
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Database.java
@@ -0,0 +1,959 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2016 QNX Software Systems 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:
+ *     QNX - Initial API and implementation
+ *     Symbian - Add some non-javadoc implementation notes
+ *     Markus Schorn (Wind River Systems)
+ *     IBM Corporation
+ *     Sergey Prigogin (Google)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedByInterruptException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.osgi.util.NLS;
+
+import com.ibm.icu.text.MessageFormat;
+
+/**
+ * Database encapsulates access to a flat binary format file with a memory-manager-like API for
+ * obtaining and releasing areas of storage (memory).
+ */
+/*
+ * The file encapsulated is divided into Chunks of size CHUNK_SIZE, and a table of contents
+ * mapping chunk index to chunk address is maintained. Chunk structure exists only conceptually -
+ * it is not a structure that appears in the file.
+ *
+ * ===== The first chunk is used by Database itself for house-keeping purposes and has structure
+ *
+ * offset                content
+ * 	                     _____________________________
+ * 0                    | version number
+ * INT_SIZE             | pointer to head of linked list of blocks of size MIN_BLOCK_DELTAS*BLOCK_SIZE_DELTA
+ * ..                   | ...
+ * INT_SIZE * (M + 1)   | pointer to head of linked list of blocks of size (M + MIN_BLOCK_DELTAS) * BLOCK_SIZE_DELTA
+ * WRITE_NUMBER_OFFSET  | long integer which is incremented on every write
+ * MALLOC_STATS_OFFSET  | memory usage statistics 
+ * DATA_AREA            | The database singletons are stored here and use the remainder of chunk 0
+ *
+ * M = CHUNK_SIZE / BLOCK_SIZE_DELTA - MIN_BLOCK_DELTAS
+ *
+ * ===== block structure (for free/unused blocks)
+ *
+ * offset            content
+ * 	                 _____________________________
+ * 0                | size of block (positive indicates an unused block) (2 bytes)
+ * PREV_OFFSET      | pointer to previous block (of same size) (only in free blocks)
+ * NEXT_OFFSET      | pointer to next block (of same size) (only in free blocks)
+ * ...              | unused space
+ *
+ *====== block structure (for allocated blocks)
+ *
+ * offset            content
+ * 	                 _____________________________
+ * 0                | size of block (negative indicates the block is in use) (2 bytes)
+ * 2                | content of the struct 
+ *
+ */
+public class Database {
+	public static final int CHAR_SIZE = 2;
+	public static final int BYTE_SIZE = 1;
+	public static final int SHORT_SIZE = 2;
+	public static final int INT_SIZE = 4;
+	public static final int LONG_SIZE = 8;
+	public static final int CHUNK_SIZE = 1024 * 4;
+	public static final int OFFSET_IN_CHUNK_MASK= CHUNK_SIZE - 1;
+	public static final int BLOCK_HEADER_SIZE = SHORT_SIZE;
+
+	public static final int BLOCK_SIZE_DELTA_BITS = 3;
+	public static final int BLOCK_SIZE_DELTA= 1 << BLOCK_SIZE_DELTA_BITS;
+	
+	// Fields that are only used by free blocks
+	private static final int BLOCK_PREV_OFFSET = BLOCK_HEADER_SIZE;
+	private static final int BLOCK_NEXT_OFFSET = BLOCK_HEADER_SIZE + INT_SIZE;
+	private static final int FREE_BLOCK_HEADER_SIZE = BLOCK_NEXT_OFFSET + INT_SIZE;
+	
+	public static final int MIN_BLOCK_DELTAS = (FREE_BLOCK_HEADER_SIZE + BLOCK_SIZE_DELTA - 1) /
+			BLOCK_SIZE_DELTA; // Must be enough multiples of BLOCK_SIZE_DELTA in order to fit the free block header
+	public static final int MAX_BLOCK_DELTAS = CHUNK_SIZE / BLOCK_SIZE_DELTA;
+	public static final int MAX_MALLOC_SIZE = MAX_BLOCK_DELTAS * BLOCK_SIZE_DELTA - BLOCK_HEADER_SIZE;
+	public static final int PTR_SIZE = 4;  // size of a pointer in the database in bytes
+	public static final int STRING_SIZE = PTR_SIZE;
+	public static final int FLOAT_SIZE = INT_SIZE;
+	public static final int DOUBLE_SIZE = LONG_SIZE;
+	public static final long MAX_DB_SIZE= ((long) 1 << (Integer.SIZE + BLOCK_SIZE_DELTA_BITS));
+
+	public static final int VERSION_OFFSET = 0;
+	private static final int MALLOC_TABLE_OFFSET = VERSION_OFFSET + INT_SIZE;
+	public static final int WRITE_NUMBER_OFFSET = MALLOC_TABLE_OFFSET
+			+ (CHUNK_SIZE / BLOCK_SIZE_DELTA - MIN_BLOCK_DELTAS + 1) * INT_SIZE;
+	public static final int MALLOC_STATS_OFFSET = WRITE_NUMBER_OFFSET + LONG_SIZE;
+	public static final int DATA_AREA_OFFSET = MALLOC_STATS_OFFSET + MemoryStats.SIZE;
+
+	// Malloc pool IDs (used for classifying memory allocations and recording statistics about them)
+	/** Misc pool -- may be used for any purpose that doesn't fit the IDs below. */
+	public static final short POOL_MISC 			= 0x0000;
+	public static final short POOL_BTREE 			= 0x0001;
+	public static final short POOL_DB_PROPERTIES 	= 0x0002;
+	public static final short POOL_STRING_LONG 		= 0x0003;
+	public static final short POOL_STRING_SHORT		= 0x0004;
+	public static final short POOL_LINKED_LIST		= 0x0005;
+	public static final short POOL_STRING_SET 		= 0x0006;
+	public static final short POOL_GROWABLE_ARRAY	= 0x0007;
+	/** Id for the first node type. All node types will record their stats in a pool whose ID is POOL_FIRST_NODE_TYPE + node_id*/
+	public static final short POOL_FIRST_NODE_TYPE	= 0x0100;
+
+	private final File fLocation;
+	private final boolean fReadOnly;
+	private RandomAccessFile fFile;
+	private boolean fExclusiveLock;	 // Necessary for any write operation.
+	private boolean fLocked;		 // Necessary for any operation.
+	private boolean fIsMarkedIncomplete;
+
+	private int fVersion;
+	private final Chunk fHeaderChunk;
+	private Chunk[] fChunks;
+	private int fChunksUsed;
+	private int fChunksAllocated;
+	private ChunkCache fCache;
+
+	private long malloced;
+	private long freed;
+	private long cacheHits;
+	private long cacheMisses;
+
+	private MemoryStats memoryUsage;
+
+	/**
+	 * Construct a new Database object, creating a backing file if necessary.
+	 * @param location the local file path for the database
+	 * @param cache the cache to be used optimization
+	 * @param version the version number to store in the database (only applicable for new databases)
+	 * @param openReadOnly whether this Database object will ever need writing to
+	 * @throws IndexException
+	 */
+	public Database(File location, ChunkCache cache, int version, boolean openReadOnly) throws IndexException {
+		try {
+			this.fLocation = location;
+			this.fReadOnly= openReadOnly;
+			this.fCache= cache;
+			openFile();
+
+			int nChunksOnDisk = (int) (this.fFile.length() / CHUNK_SIZE);
+			this.fHeaderChunk= new Chunk(this, 0);
+			this.fHeaderChunk.fLocked= true;		// Never makes it into the cache, needed to satisfy assertions.
+			if (nChunksOnDisk <= 0) {
+				this.fVersion= version;
+				this.fChunks= new Chunk[1];
+				this.fChunksUsed = this.fChunksAllocated = this.fChunks.length;
+			} else {
+				this.fHeaderChunk.read();
+				this.fVersion= this.fHeaderChunk.getInt(VERSION_OFFSET);
+				this.fChunks = new Chunk[nChunksOnDisk];	// chunk[0] is unused.
+				this.fChunksUsed = this.fChunksAllocated = nChunksOnDisk;
+			}
+		} catch (IOException e) {
+			throw new IndexException(new DBStatus(e));
+		}
+		this.memoryUsage = new MemoryStats(this.fHeaderChunk, MALLOC_STATS_OFFSET);
+	}
+
+	private static int divideRoundingUp(int num, int den) {
+		return (num + den - 1) / den;
+	}
+
+	private void openFile() throws FileNotFoundException {
+		this.fFile = new RandomAccessFile(this.fLocation, this.fReadOnly ? "r" : "rw"); //$NON-NLS-1$ //$NON-NLS-2$
+	}
+
+	void read(ByteBuffer buf, long position) throws IOException {
+		int retries= 0;
+		do {
+			try {
+				this.fFile.getChannel().read(buf, position);
+				return;
+			} catch (ClosedChannelException e) {
+				// Always reopen the file if possible or subsequent reads will fail.
+				openFile();
+
+				// This is the most common type of interruption. If another thread called Thread.interrupt,
+				// throw an OperationCanceledException.
+				if (e instanceof ClosedByInterruptException) {
+					throw new OperationCanceledException();
+				}
+
+				// If we've retried too many times, just rethrow the exception.
+				if (++retries >= 20) {
+					throw e;
+				}
+
+				// Otherwise, retry
+			}
+		} while (true);
+	}
+
+	/**
+	 * Attempts to write to the given position in the file. Will retry if interrupted by Thread.interrupt() until,
+	 * the write succeeds. It will return true if any call to Thread.interrupt() was detected.
+	 *
+	 * @return true iff a call to Thread.interrupt() was detected at any point during the operation.
+	 * @throws IOException
+	 */
+	boolean write(ByteBuffer buf, long position) throws IOException {
+		return performUninterruptableWrite(() -> {this.fFile.getChannel().write(buf, position);});
+	}
+
+	private static interface IORunnable {
+		void run() throws IOException;
+	}
+
+	/**
+	 * Attempts to perform an uninterruptable write operation on the database. Returns true if an attempt was made
+	 * to interrupt it. 
+	 * 
+	 * @throws IOException
+	 */
+	private boolean performUninterruptableWrite(IORunnable runnable) throws IOException {
+		boolean interrupted = false;
+		int retries= 0;
+		while (true) {
+			try {
+				runnable.run();
+				return interrupted;
+			} catch (ClosedChannelException e) {
+				openFile();
+
+				if (e instanceof ClosedByInterruptException) {
+					// Retry forever if necessary as long as another thread is calling Thread.interrupt
+					interrupted = true;
+				} else {
+					if (++retries > 20) {
+						throw e;
+					}
+				}
+			}
+		}
+	}
+	
+	public void transferTo(FileChannel target) throws IOException {
+		assert this.fLocked;
+        final FileChannel from= this.fFile.getChannel();
+        long nRead = 0;
+        long position = 0;
+        long size = from.size();
+        while (position < size) {
+        	nRead = from.transferTo(position, 4096 * 16, target);
+        	if (nRead == 0) {
+        		break;		// Should not happen.
+        	} else {
+        		position+= nRead;
+        	}
+        }
+	}
+
+	public int getVersion() {
+		return this.fVersion;
+	}
+
+	public void setVersion(int version) throws IndexException {
+		assert this.fExclusiveLock;
+		this.fHeaderChunk.putInt(VERSION_OFFSET, version);
+		this.fVersion= version;
+	}
+
+	/**
+	 * Empty the contents of the Database, make it ready to start again. Interrupting the thread with
+	 * {@link Thread#interrupt()} won't interrupt the write. Returns true iff the thread was interrupted
+	 * with {@link Thread#interrupt()}.
+	 * 
+	 * @throws IndexException
+	 */
+	public boolean clear(int version) throws IndexException {
+		assert this.fExclusiveLock;
+		boolean wasCanceled = false;
+		removeChunksFromCache();
+
+		this.fVersion= version;
+		// Clear the first chunk.
+		this.fHeaderChunk.clear(0, CHUNK_SIZE);
+		// Chunks have been removed from the cache, so we may just reset the array of chunks.
+		this.fChunks = new Chunk[] {null};
+		this.fChunksUsed = this.fChunksAllocated = this.fChunks.length;
+		try {
+			wasCanceled = this.fHeaderChunk.flush() || wasCanceled; // Zero out header chunk.
+			wasCanceled = performUninterruptableWrite(() -> {
+				this.fFile.getChannel().truncate(CHUNK_SIZE);
+			}) || wasCanceled;
+		} catch (IOException e) {
+			Package.log(e);
+		}
+		this.malloced = this.freed = 0;
+		/*
+		 * This is for debugging purposes in order to simulate having a very large Nd database.
+		 * This will set aside the specified number of chunks.
+		 * Nothing uses these chunks so subsequent allocations come after these fillers.
+		 * The special function createNewChunks allocates all of these chunks at once.
+		 * 524288 for a file starting at 2G
+		 * 8388608 for a file starting at 32G
+		 *
+		 */
+		long setasideChunks = Long.getLong("org.eclipse.jdt.core.parser.nd.chunks", 0); //$NON-NLS-1$
+		if (setasideChunks != 0) {
+			setVersion(getVersion());
+			createNewChunks((int) setasideChunks);
+			wasCanceled = flush() || wasCanceled;
+		}
+		this.memoryUsage.refresh();
+		return wasCanceled;
+	}
+
+	private void removeChunksFromCache() {
+		synchronized (this.fCache) {
+			for (int i= 1; i < this.fChunks.length; i++) {
+				Chunk chunk= this.fChunks[i];
+				if (chunk != null) {
+					this.fCache.remove(chunk);
+					this.fChunks[i]= null;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Return the Chunk that contains the given offset.
+	 * @throws IndexException
+	 */
+	public Chunk getChunk(long offset) throws IndexException {
+		assertLocked();
+		if (offset < CHUNK_SIZE) {
+			return this.fHeaderChunk;
+		}
+		long long_index = offset / CHUNK_SIZE;
+		assert long_index < Integer.MAX_VALUE;
+
+		synchronized (this.fCache) {
+			assert this.fLocked;
+			final int index = (int) long_index;
+			if (index < 0 || index >= this.fChunks.length) {
+				databaseCorruptionDetected();
+			}
+			Chunk chunk= this.fChunks[index];
+			if (chunk == null) {
+				this.cacheMisses++;
+				chunk = new Chunk(this, index);
+				chunk.read();
+				this.fChunks[index] = chunk;
+			} else {
+				this.cacheHits++;
+			}
+			this.fCache.add(chunk, this.fExclusiveLock);
+			return chunk;
+		}
+	}
+
+	public void assertLocked() {
+		if (!this.fLocked) {
+			throw new IllegalStateException("Database not locked!"); //$NON-NLS-1$
+		}
+	}
+
+	private void databaseCorruptionDetected() throws IndexException {
+		String msg = MessageFormat.format("Corrupted database: {0}", //$NON-NLS-1$
+				new Object[] { this.fLocation.getName() });
+		throw new IndexException(new DBStatus(msg));
+	}
+
+	/**
+	 * Copies numBytes from source to destination
+	 */
+	public void memcpy(long dest, long source, int numBytes) {
+		assert numBytes >= 0;
+		assert numBytes <= MAX_MALLOC_SIZE;
+		// TODO: make use of lower-level System.arrayCopy
+		for (int count = 0; count < numBytes; count++) {
+			putByte(dest + count, getByte(source + count));
+		}
+	}
+
+	/**
+	 * Allocate a block out of the database.
+	 */
+	public long malloc(final int datasize, final short poolId) throws IndexException {
+		assert this.fExclusiveLock;
+		assert datasize >= 0;
+		assert datasize <= MAX_MALLOC_SIZE;
+
+		int needDeltas= divideRoundingUp(datasize + BLOCK_HEADER_SIZE, BLOCK_SIZE_DELTA);
+		if (needDeltas < MIN_BLOCK_DELTAS) {
+			needDeltas= MIN_BLOCK_DELTAS;
+		}
+
+		// Which block size.
+		long freeblock = 0;
+		int useDeltas;
+		for (useDeltas= needDeltas; useDeltas <= MAX_BLOCK_DELTAS; useDeltas++) {
+			freeblock = getFirstBlock(useDeltas * BLOCK_SIZE_DELTA);
+			if (freeblock != 0)
+				break;
+		}
+
+		// Get the block.
+		Chunk chunk;
+		if (freeblock == 0) {
+			// Allocate a new chunk.
+			freeblock= createNewChunk();
+			useDeltas = MAX_BLOCK_DELTAS;
+			chunk = getChunk(freeblock);
+		} else {
+			chunk = getChunk(freeblock);
+			removeBlock(chunk, useDeltas * BLOCK_SIZE_DELTA, freeblock);
+		}
+
+		final int unusedDeltas = useDeltas - needDeltas;
+		if (unusedDeltas >= MIN_BLOCK_DELTAS) {
+			// Add in the unused part of our block.
+			addBlock(chunk, unusedDeltas * BLOCK_SIZE_DELTA, freeblock + needDeltas * BLOCK_SIZE_DELTA);
+			useDeltas= needDeltas;
+		}
+
+		// Make our size negative to show in use.
+		final int usedSize= useDeltas * BLOCK_SIZE_DELTA;
+		chunk.putShort(freeblock, (short) -usedSize);
+
+		// Clear out the block, lots of people are expecting this.
+		chunk.clear(freeblock + BLOCK_HEADER_SIZE, usedSize - BLOCK_HEADER_SIZE);
+
+		this.malloced += usedSize;
+		long result = freeblock + BLOCK_HEADER_SIZE;
+		this.memoryUsage.recordMalloc(poolId, usedSize);
+		return result;
+	}
+
+	private long createNewChunk() throws IndexException {
+		assert this.fExclusiveLock;
+		synchronized (this.fCache) {
+			final int newChunkIndex = this.fChunksUsed; // fChunks.length;
+
+			final Chunk chunk = new Chunk(this, newChunkIndex);
+			chunk.fDirty = true;
+
+			if (newChunkIndex >= this.fChunksAllocated) {
+				int increment = Math.max(1024, this.fChunksAllocated / 20);
+				Chunk[] newchunks = new Chunk[this.fChunksAllocated + increment];
+				System.arraycopy(this.fChunks, 0, newchunks, 0, this.fChunksAllocated);
+
+				this.fChunks = newchunks;
+				this.fChunksAllocated += increment;
+			}
+			this.fChunksUsed += 1;
+			this.fChunks[newChunkIndex] = chunk;
+
+			this.fCache.add(chunk, true);
+			long address = (long) newChunkIndex * CHUNK_SIZE;
+
+			/*
+			 * Non-dense pointers are at most 31 bits dense pointers are at most 35 bits Check the sizes here and throw
+			 * an exception if the address is too large. By throwing the IndexException with the special status, the
+			 * indexing operation should be stopped. This is desired since generally, once the max size is exceeded,
+			 * there are lots of errors.
+			 */
+			if (address >= MAX_DB_SIZE) {
+				Object bindings[] = { this.getLocation().getAbsolutePath(), MAX_DB_SIZE };
+				throw new IndexException(new Status(IStatus.ERROR, Package.PLUGIN_ID, Package.STATUS_DATABASE_TOO_LARGE,
+						NLS.bind("Database too large! Address = " + address + ", max size = " + MAX_DB_SIZE, bindings), //$NON-NLS-1$ //$NON-NLS-2$
+						null));
+			}
+			return address;
+		}
+	}
+
+	/**
+	 * For testing purposes, only.
+	 */
+	private long createNewChunks(int numChunks) throws IndexException {
+		assert this.fExclusiveLock;
+		synchronized (this.fCache) {
+			final int oldLen= this.fChunks.length;
+			Chunk[] newchunks = new Chunk[oldLen + numChunks];
+			System.arraycopy(this.fChunks, 0, newchunks, 0, oldLen);
+			final Chunk chunk= new Chunk(this, oldLen + numChunks - 1);
+			chunk.fDirty= true;
+			newchunks[ oldLen + numChunks - 1 ] = chunk;
+			this.fChunks= newchunks;
+			this.fCache.add(chunk, true);
+			this.fChunksAllocated=oldLen + numChunks;
+			this.fChunksUsed=oldLen + numChunks;
+			return (long) (oldLen + numChunks - 1) * CHUNK_SIZE;
+		}
+	}
+
+	/**
+	 * @param blocksize (must be a multiple of BLOCK_SIZE_DELTA)
+	 */
+	private long getFirstBlock(int blocksize) throws IndexException {
+		assert this.fLocked;
+		return this.fHeaderChunk.getFreeRecPtr(MALLOC_TABLE_OFFSET + (blocksize / BLOCK_SIZE_DELTA - MIN_BLOCK_DELTAS) * INT_SIZE);
+	}
+
+	private void setFirstBlock(int blocksize, long block) throws IndexException {
+		assert this.fExclusiveLock;
+		this.fHeaderChunk.putFreeRecPtr(MALLOC_TABLE_OFFSET + (blocksize / BLOCK_SIZE_DELTA - MIN_BLOCK_DELTAS) * INT_SIZE, block);
+	}
+
+	private void removeBlock(Chunk chunk, int blocksize, long block) throws IndexException {
+		assert this.fExclusiveLock;
+		long prevblock = chunk.getFreeRecPtr(block + BLOCK_PREV_OFFSET);
+		long nextblock = chunk.getFreeRecPtr(block + BLOCK_NEXT_OFFSET);
+		if (prevblock != 0) {
+			putFreeRecPtr(prevblock + BLOCK_NEXT_OFFSET, nextblock);
+		} else { // We were the head.
+			setFirstBlock(blocksize, nextblock);
+		}
+
+		if (nextblock != 0)
+			putFreeRecPtr(nextblock + BLOCK_PREV_OFFSET, prevblock);
+	}
+
+	private void addBlock(Chunk chunk, int blocksize, long block) throws IndexException {
+		assert this.fExclusiveLock;
+		// Mark our size
+		chunk.putShort(block, (short) blocksize);
+
+		// Add us to the head of the list.
+		long prevfirst = getFirstBlock(blocksize);
+		chunk.putFreeRecPtr(block + BLOCK_PREV_OFFSET, 0);
+		chunk.putFreeRecPtr(block + BLOCK_NEXT_OFFSET, prevfirst);
+		if (prevfirst != 0)
+			putFreeRecPtr(prevfirst + BLOCK_PREV_OFFSET, block);
+		setFirstBlock(blocksize, block);
+	}
+
+	/**
+	 * Free an allocated block.
+	 *
+	 * @param address memory address to be freed
+	 * @param poolId the same ID that was previously passed into malloc when allocating this memory address
+	 */
+	public void free(long address, short poolId) throws IndexException {
+		assert this.fExclusiveLock;
+		if (address == 0) {
+			return;
+		}
+		// TODO Look for opportunities to merge blocks
+		long block = address - BLOCK_HEADER_SIZE;
+		Chunk chunk = getChunk(block);
+		int blocksize = - chunk.getShort(block);
+		if (blocksize < 0) {
+			// Already freed.
+			throw new IndexException(new Status(IStatus.ERROR, Package.PLUGIN_ID, 0,
+					"Already freed record " + address, new Exception())); //$NON-NLS-1$
+		}
+		addBlock(chunk, blocksize, block);
+		this.freed += blocksize;
+		this.memoryUsage.recordFree(poolId, blocksize);
+	}
+
+	public void putByte(long offset, byte value) throws IndexException {
+		getChunk(offset).putByte(offset, value);
+	}
+
+	public byte getByte(long offset) throws IndexException {
+		return getChunk(offset).getByte(offset);
+	}
+
+	public void putInt(long offset, int value) throws IndexException {
+		getChunk(offset).putInt(offset, value);
+	}
+
+	public int getInt(long offset) throws IndexException {
+		return getChunk(offset).getInt(offset);
+	}
+
+	public void putRecPtr(long offset, long value) throws IndexException {
+		getChunk(offset).putRecPtr(offset, value);
+	}
+
+	public long getRecPtr(long offset) throws IndexException {
+		return getChunk(offset).getRecPtr(offset);
+	}
+
+	private void putFreeRecPtr(long offset, long value) throws IndexException {
+		getChunk(offset).putFreeRecPtr(offset, value);
+	}
+
+	private long getFreeRecPtr(long offset) throws IndexException {
+		return getChunk(offset).getFreeRecPtr(offset);
+	}
+
+	public void put3ByteUnsignedInt(long offset, int value) throws IndexException {
+		getChunk(offset).put3ByteUnsignedInt(offset, value);
+	}
+
+	public int get3ByteUnsignedInt(long offset) throws IndexException {
+		return getChunk(offset).get3ByteUnsignedInt(offset);
+	}
+
+	public void putShort(long offset, short value) throws IndexException {
+		getChunk(offset).putShort(offset, value);
+	}
+
+	public short getShort(long offset) throws IndexException {
+		return getChunk(offset).getShort(offset);
+	}
+
+	public void putLong(long offset, long value) throws IndexException {
+		getChunk(offset).putLong(offset, value);
+	}
+
+	public void putDouble(long offset, double value) throws IndexException {
+		getChunk(offset).putDouble(offset, value);
+	}
+
+	public void putFloat(long offset, float value) throws IndexException {
+		getChunk(offset).putFloat(offset, value);
+	}
+
+	public long getLong(long offset) throws IndexException {
+		return getChunk(offset).getLong(offset);
+	}
+
+	public double getDouble(long offset) throws IndexException {
+		return getChunk(offset).getDouble(offset);
+	}
+
+	public float getFloat(long offset) throws IndexException {
+		return getChunk(offset).getFloat(offset);
+	}
+
+	public void putChar(long offset, char value) throws IndexException {
+		getChunk(offset).putChar(offset, value);
+	}
+
+	public char getChar(long offset) throws IndexException {
+		return getChunk(offset).getChar(offset);
+	}
+
+	public void clearBytes(long offset, int byteCount) throws IndexException {
+		getChunk(offset).clear(offset, byteCount);
+	}
+
+	public void putBytes(long offset, byte[] data, int len) throws IndexException {
+		getChunk(offset).put(offset, data, len);
+	}
+
+	public void putBytes(long offset, byte[] data, int dataPos, int len) throws IndexException {
+		getChunk(offset).put(offset, data, dataPos, len);
+	}
+
+	public void getBytes(long offset, byte[] data) throws IndexException {
+		getChunk(offset).get(offset, data);
+	}
+
+	public void getBytes(long offset, byte[] data, int dataPos, int len) throws IndexException {
+		getChunk(offset).get(offset, data, dataPos, len);
+	}
+
+	public IString newString(String string) throws IndexException {
+		return newString(string.toCharArray());
+	}
+
+	public IString newString(char[] chars) throws IndexException {
+		int len= chars.length;
+		int bytelen;
+		final boolean useBytes = useBytes(chars);
+		if (useBytes) {
+			bytelen= len;
+		} else {
+			bytelen= 2 * len;
+		}
+
+		if (bytelen > ShortString.MAX_BYTE_LENGTH) {
+			return new LongString(this, chars, useBytes);
+		} else {
+			return new ShortString(this, chars, useBytes);
+		}
+	}
+
+	private boolean useBytes(char[] chars) {
+		for (char c : chars) {
+			if ((c & 0xff00) != 0)
+				return false;
+		}
+		return true;
+	}
+
+	public IString getString(long offset) throws IndexException {
+		final int l = getInt(offset);
+		int bytelen= l < 0 ? -l : 2 * l;
+		if (bytelen > ShortString.MAX_BYTE_LENGTH) {
+			return new LongString(this, offset);
+		}
+		return new ShortString(this, offset);
+	}
+
+	public long getDatabaseSize() {
+		return this.fChunksUsed * CHUNK_SIZE;
+	}
+
+	/**
+	 * For debugging purposes, only.
+	 */
+	public void reportFreeBlocks() throws IndexException {
+		System.out.println("Allocated size: " + getDatabaseSize() + " bytes"); //$NON-NLS-1$ //$NON-NLS-2$
+		System.out.println("malloc'ed: " + this.malloced); //$NON-NLS-1$
+		System.out.println("free'd: " + this.freed); //$NON-NLS-1$
+		System.out.println("wasted: " + (getDatabaseSize() - (this.malloced - this.freed))); //$NON-NLS-1$
+		System.out.println("Free blocks"); //$NON-NLS-1$
+		for (int bs = MIN_BLOCK_DELTAS*BLOCK_SIZE_DELTA; bs <= CHUNK_SIZE; bs += BLOCK_SIZE_DELTA) {
+			int count = 0;
+			long block = getFirstBlock(bs);
+			while (block != 0) {
+				++count;
+				block = getFreeRecPtr(block + BLOCK_NEXT_OFFSET);
+			}
+			if (count != 0)
+				System.out.println("Block size: " + bs + "=" + count); //$NON-NLS-1$ //$NON-NLS-2$
+		}
+	}
+
+	/**
+	 * Closes the database.
+	 * <p>
+	 * The behavior of any further calls to the Database is undefined
+	 * @throws IndexException
+	 */
+	public void close() throws IndexException {
+		assert this.fExclusiveLock;
+		flush();
+		removeChunksFromCache();
+
+		// Chunks have been removed from the cache, so we are fine.
+		this.fHeaderChunk.clear(0, CHUNK_SIZE);
+		this.memoryUsage.refresh();
+		this.fHeaderChunk.fDirty= false;
+		this.fChunks= new Chunk[] { null };
+		this.fChunksUsed = this.fChunksAllocated = this.fChunks.length;
+		try {
+			this.fFile.close();
+		} catch (IOException e) {
+			throw new IndexException(new DBStatus(e));
+		}
+	}
+
+	/**
+     * This method is public for testing purposes only.
+     */
+	public File getLocation() {
+		return this.fLocation;
+	}
+
+	/**
+	 * Called from any thread via the cache, protected by {@link #fCache}.
+	 */
+	void releaseChunk(final Chunk chunk) {
+		if (!chunk.fLocked) {
+			this.fChunks[chunk.fSequenceNumber]= null;
+		}
+	}
+
+	/**
+	 * Returns the cache used for this database.
+	 * @since 4.0
+	 */
+	public ChunkCache getChunkCache() {
+		return this.fCache;
+	}
+
+	/**
+	 * Asserts that database is used by one thread exclusively. This is necessary when doing
+	 * write operations.
+	 */
+	public void setExclusiveLock() {
+		this.fExclusiveLock= true;
+		this.fLocked= true;
+	}
+
+	public void setLocked(boolean val) {
+		this.fLocked= val;
+	}
+
+	public boolean giveUpExclusiveLock(final boolean flush) throws IndexException {
+		boolean wasInterrupted = false;
+		if (this.fExclusiveLock) {
+			try {
+				ArrayList<Chunk> dirtyChunks= new ArrayList<>();
+				synchronized (this.fCache) {
+					for (int i= 1; i < this.fChunksUsed; i++) {
+						Chunk chunk= this.fChunks[i];
+						if (chunk != null) {
+							if (chunk.fCacheIndex < 0) {
+								// Locked chunk that has been removed from cache.
+								if (chunk.fDirty) {
+									dirtyChunks.add(chunk); // Keep in fChunks until it is flushed.
+								} else {
+									chunk.fLocked= false;
+									this.fChunks[i]= null;
+								}
+							} else if (chunk.fLocked) {
+								// Locked chunk, still in cache.
+								if (chunk.fDirty) {
+									if (flush) {
+										dirtyChunks.add(chunk);
+									}
+								} else {
+									chunk.fLocked= false;
+								}
+							} else {
+								assert !chunk.fDirty; // Dirty chunks must be locked.
+							}
+						}
+					}
+				}
+				// Also handles header chunk.
+				wasInterrupted = flushAndUnlockChunks(dirtyChunks, flush) || wasInterrupted;
+			} finally {
+				this.fExclusiveLock= false;
+			}
+		}
+		return wasInterrupted;
+	}
+
+	public boolean flush() throws IndexException {
+		boolean wasInterrupted = false;
+		assert this.fLocked;
+		if (this.fExclusiveLock) {
+			try {
+				wasInterrupted = giveUpExclusiveLock(true) || wasInterrupted;
+			} finally {
+				setExclusiveLock();
+			}
+			return wasInterrupted;
+		}
+
+		// Be careful as other readers may access chunks concurrently.
+		ArrayList<Chunk> dirtyChunks= new ArrayList<>();
+		synchronized (this.fCache) {
+			for (int i= 1; i < this.fChunksUsed ; i++) {
+				Chunk chunk= this.fChunks[i];
+				if (chunk != null && chunk.fDirty) {
+					dirtyChunks.add(chunk);
+				}
+			}
+		}
+
+		// Also handles header chunk.
+		return flushAndUnlockChunks(dirtyChunks, true) || wasInterrupted;
+	}
+
+	/**
+	 * Interrupting the thread with {@link Thread#interrupt()} won't interrupt the write. Returns true iff an attempt
+	 * was made to interrupt the thread with {@link Thread#interrupt()}.
+	 * 
+	 * @throws IndexException
+	 */
+	private boolean flushAndUnlockChunks(final ArrayList<Chunk> dirtyChunks, boolean isComplete) throws IndexException {
+		boolean wasInterrupted = false;
+		assert !Thread.holdsLock(this.fCache);
+		synchronized (this.fHeaderChunk) {
+			final boolean haveDirtyChunks = !dirtyChunks.isEmpty();
+			if (haveDirtyChunks || this.fHeaderChunk.fDirty) {
+				wasInterrupted = markFileIncomplete() || wasInterrupted;
+			}
+			if (haveDirtyChunks) {
+				for (Chunk chunk : dirtyChunks) {
+					if (chunk.fDirty) {
+						wasInterrupted = chunk.flush() || wasInterrupted;
+					}
+				}
+
+				// Only after the chunks are flushed we may unlock and release them.
+				synchronized (this.fCache) {
+					for (Chunk chunk : dirtyChunks) {
+						chunk.fLocked= false;
+						if (chunk.fCacheIndex < 0) {
+							this.fChunks[chunk.fSequenceNumber]= null;
+						}
+					}
+				}
+			}
+
+			if (isComplete) {
+				if (this.fHeaderChunk.fDirty || this.fIsMarkedIncomplete) {
+					this.fHeaderChunk.putInt(VERSION_OFFSET, this.fVersion);
+					wasInterrupted = this.fHeaderChunk.flush() || wasInterrupted;
+					this.fIsMarkedIncomplete= false;
+				}
+			}
+		}
+		return wasInterrupted;
+	}
+
+	private boolean markFileIncomplete() throws IndexException {
+		boolean wasInterrupted = false;
+		if (!this.fIsMarkedIncomplete) {
+			this.fIsMarkedIncomplete= true;
+			try {
+				final ByteBuffer buf= ByteBuffer.wrap(new byte[4]);
+				wasInterrupted = performUninterruptableWrite(() -> this.fFile.getChannel().write(buf, 0));
+			} catch (IOException e) {
+				throw new IndexException(new DBStatus(e));
+			}
+		}
+		return wasInterrupted;
+	}
+
+	public void resetCacheCounters() {
+		this.cacheHits= this.cacheMisses= 0;
+	}
+
+	public long getCacheHits() {
+		return this.cacheHits;
+	}
+
+	public long getCacheMisses() {
+		return this.cacheMisses;
+	}
+
+	public long getSizeBytes() throws IOException {
+		return this.fFile.length();
+	}
+
+	/**
+	 * A Record Pointer is a pointer as returned by Database.malloc().
+	 * This is a pointer to a block + BLOCK_HEADER_SIZE.
+	 */
+	public static void putRecPtr(final long value, byte[] buffer, int idx) {
+		final int denseValue = value == 0 ? 0 : Chunk.compressFreeRecPtr(value - BLOCK_HEADER_SIZE);
+		Chunk.putInt(denseValue, buffer, idx);
+	}
+
+	/**
+	 * A Record Pointer is a pointer as returned by Database.malloc().
+	 * This is a pointer to a block + BLOCK_HEADER_SIZE.
+	 */
+	public static long getRecPtr(byte[] buffer, final int idx) {
+		int value = Chunk.getInt(buffer, idx);
+		long address = Chunk.expandToFreeRecPtr(value);
+		return address != 0 ? (address + BLOCK_HEADER_SIZE) : address;
+	}
+
+	public MemoryStats getMemoryStats() {
+		return this.memoryUsage;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/EmptyString.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/EmptyString.java
new file mode 100644
index 0000000..f32fdb5
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/EmptyString.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * 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.db;
+
+/**
+ * Represents an empty string.
+ */
+public class EmptyString implements IString {
+
+	private int compareResult;
+	private static EmptyString theEmptyString = new EmptyString();
+
+	private EmptyString() {
+		this.compareResult = "".compareTo("a");  //$NON-NLS-1$//$NON-NLS-2$
+	}
+
+	public static EmptyString create() {
+		return theEmptyString;
+	}
+
+	@Override
+	public long getRecord() {
+		return 0;
+	}
+
+	@Override
+	public int compare(IString string, boolean caseSensitive) {
+		if (string.length() == 0) {
+			return 0;
+		}
+		return this.compareResult;
+	}
+
+	@Override
+	public int compare(String string, boolean caseSensitive) {
+		if (string.length() == 0) {
+			return 0;
+		}
+		return this.compareResult;
+	}
+
+	@Override
+	public int compare(char[] chars, boolean caseSensitive) {
+		if (chars.length == 0) {
+			return 0;
+		}
+		return this.compareResult;
+	}
+
+	@Override
+	public int compareCompatibleWithIgnoreCase(IString string) {
+		if (string.length() == 0) {
+			return 0;
+		}
+		return this.compareResult;
+	}
+
+	@Override
+	public int compareCompatibleWithIgnoreCase(char[] chars) {
+		if (chars.length == 0) {
+			return 0;
+		}
+		return this.compareResult;
+	}
+
+	@Override
+	public int comparePrefix(char[] name, boolean caseSensitive) {
+		if (name.length == 0) {
+			return 0;
+		}
+		return this.compareResult;
+	}
+
+	@Override
+	public char[] getChars() {
+		return new char[0];
+	}
+
+	@Override
+	public String getString() {
+		return ""; //$NON-NLS-1$
+	}
+
+	@Override
+	public void delete() {
+		// Can't be deleted
+	}
+
+	@Override
+	public int length() {
+		return 0;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IBTreeComparator.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IBTreeComparator.java
new file mode 100644
index 0000000..b2ab084
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IBTreeComparator.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * 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.db;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+public interface IBTreeComparator {
+	/**
+	 * Compare two records. Used for insert.
+	 */
+	public abstract int compare(Nd nd, long record1, long record2);
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IBTreeVisitor.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IBTreeVisitor.java
new file mode 100644
index 0000000..e0a2823
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IBTreeVisitor.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2016 QNX Software Systems 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:
+ *     QNX - Initial API and implementation
+ *     Markus Schorn (Wind River Systems)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+/** 
+ * The visitor visits all records where compare returns 0.
+ */
+public interface IBTreeVisitor {
+	/**
+	 * Compare the record against an internally held key. The comparison must be
+	 * compatible with the one used for the btree.
+	 * Used for visiting.
+	 * 
+	 * @param record
+	 * @return -1 if record < key, 0 if record == key, 1 if record > key
+	 * @throws IndexException
+	 */
+	public abstract int compare(long record) throws IndexException;
+
+	/**
+	 * Visit a given record and return whether to continue or not.
+
+	 * @return <code>true</code> to continue the visit, <code>false</code> to abort it.
+	 * @throws IndexException
+	 */
+	public abstract boolean visit(long record) throws IndexException;	
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IString.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IString.java
new file mode 100644
index 0000000..8407979
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IString.java
@@ -0,0 +1,128 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2016 QNX Software Systems 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:
+ *     QNX - Initial API and implementation
+ *     Andrew Ferguson (Symbian)
+ *     Markus Schorn (Wind River Systems)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+/**
+ * Interface for strings stored in the database. There is more than one string
+ * format. This interface hides that fact. 
+ * 
+ * @author Doug Schaefer
+ */
+public interface IString {
+	/**
+	 * Get the offset of this IString record in the Nd
+	 */
+	public long getRecord();
+	
+	// strcmp equivalents
+	/**
+	 * Compare this IString record and the specified IString record
+	 * @param string
+	 * @param caseSensitive whether to compare in a case-sensitive way
+	 * @return <ul><li> -1 if this &lt; string
+	 * <li> 0 if this == string
+	 * <li> 1 if this &gt; string
+	 * </ul>
+	 * @throws IndexException
+	 */
+	public int compare(IString string, boolean caseSensitive) throws IndexException;
+	
+	/**
+	 * Compare this IString record and the specified String object
+	 * @param string
+	 * @param caseSensitive whether to compare in a case-sensitive way
+	 * @return <ul><li> -1 if this &lt; string
+	 * <li> 0 if this == string
+	 * <li> 1 if this &gt; string
+	 * </ul>
+	 * @throws IndexException
+	 */
+	public int compare(String string, boolean caseSensitive) throws IndexException;
+	
+	/**
+	 * Compare this IString record and the specified character array
+	 * @param chars
+	 * @param caseSensitive whether to compare in a case-sensitive way
+	 * @return <ul><li> -1 if this &lt; chars
+	 * <li> 0 if this == chars
+	 * <li> 1 if this &gt; chars
+	 * </ul>
+	 * @throws IndexException
+	 */
+	public int compare(char[] chars, boolean caseSensitive) throws IndexException;
+
+	/**
+	 * Compare this IString record and the specified IString record in a case sensitive manner
+	 * such that it is compatible with case insensitive comparison.
+	 * @param string
+	 * @return <ul><li> -1 if this &lt; string
+	 * <li> 0 if this == string
+	 * <li> 1 if this &gt; string
+	 * </ul>
+	 * @throws IndexException
+	 */
+	public int compareCompatibleWithIgnoreCase(IString string) throws IndexException;
+
+	/**
+	 * Compare this IString record and the specified char array in a case sensitive manner
+	 * such that it is compatible with case insensitive comparison.
+	 * @param chars
+	 * @return <ul><li> -1 if this &lt; string
+	 * <li> 0 if this == string
+	 * <li> 1 if this &gt; string
+	 * </ul>
+	 * @throws IndexException
+	 */
+	public int compareCompatibleWithIgnoreCase(char[] chars) throws IndexException;
+	
+	/**
+	 * Compare this IString record and the specified character array
+	 * @param name the name to compare to
+	 * @param caseSensitive whether to compare in a case-sensitive way
+	 * @return <ul><li> -1 if this &lt; chars
+	 * <li> 0 if this has a prefix chars
+	 * <li> 1 if this &gt; chars and does not have the prefix
+	 * </ul>
+	 * @throws IndexException
+	 */
+	public int comparePrefix(char[] name, boolean caseSensitive) throws IndexException;
+
+	/**
+	 * Get an equivalent character array to this IString record<p>
+	 * <b>N.B. This method can be expensive: compare and equals can be used for
+	 * efficient comparisons</b>
+	 * @return an equivalent character array to this IString record
+	 * @throws IndexException
+	 */
+	public char[] getChars() throws IndexException;
+	
+	/**
+	 * Get an equivalent String object to this IString record<p>
+	 * <b>N.B. This method can be expensive: compare and equals can be used for
+	 * efficient comparisons</b>
+	 * @return an equivalent String object to this IString record
+	 * @throws IndexException
+	 */
+	public String getString() throws IndexException;
+	
+	/**
+	 * Free the associated record in the Nd
+	 * @throws IndexException
+	 */
+	public void delete() throws IndexException;
+
+	/**
+	 * @return the length of the string
+	 */
+	public int length();
+}
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
new file mode 100644
index 0000000..f6ecf9a
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IndexException.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.db;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * This exception indicates corruption in the JDT index database.
+ */
+public class IndexException extends RuntimeException {
+
+	private IStatus status;
+
+	public IndexException(IStatus status) {
+		super(status.getMessage());
+		this.status = status;
+	}
+
+	public IndexException(String message) {
+		this(new Status(IStatus.ERROR, "org.eclipse.jdt.core", message)); //$NON-NLS-1$
+	}
+
+	@Override
+	public synchronized Throwable getCause() {
+		return this.status.getException();
+	}
+
+	/**
+	 * @return the status
+	 */
+	public IStatus getStatus() {
+		return this.status;
+	}
+
+	private static final long serialVersionUID = -6561893929558916225L;
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/LongString.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/LongString.java
new file mode 100644
index 0000000..c78b7f9
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/LongString.java
@@ -0,0 +1,245 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2016 QNX Software Systems 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:
+ *     QNX - Initial API and implementation
+ *     Andrew Ferguson (Symbian)
+ *     Markus Schorn (Wind River Systems)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+/**
+ * This is for strings that take up more than on chunk.
+ * The string will need to be broken up into sections and then
+ * reassembled when necessary.
+ *
+ * @author Doug Schaefer
+ */
+public class LongString implements IString {
+	private final Database db;
+	private final long record;
+	private int hash;
+
+	// Additional fields of first record.
+	private static final int LENGTH = 0; // Must be first to match ShortString.
+	private static final int NEXT1 = 4;
+	private static final int CHARS1 = 8;
+
+	private static final int NUM_CHARS1 = (Database.MAX_MALLOC_SIZE - CHARS1) / 2;
+
+	// Additional fields of subsequent records.
+	private static final int NEXTN = 0;
+	private static final int CHARSN = 4;
+
+	private static final int NUM_CHARSN = (Database.MAX_MALLOC_SIZE - CHARSN) / 2;
+
+	public LongString(Database db, long record) {
+		this.db = db;
+		this.record = record;
+	}
+
+	public LongString(Database db, final char[] chars, boolean useBytes) throws IndexException {
+		final int numChars1 = useBytes ? NUM_CHARS1 * 2 : NUM_CHARS1;
+		final int numCharsn = useBytes ? NUM_CHARSN * 2 : NUM_CHARSN;
+
+		this.db = db;
+		this.record = db.malloc(Database.MAX_MALLOC_SIZE, Database.POOL_STRING_LONG);
+
+		// Write the first record.
+		final int length = chars.length;
+		db.putInt(this.record, useBytes ? -length : length);
+		Chunk chunk= db.getChunk(this.record);
+
+		if (useBytes) {
+			chunk.putCharsAsBytes(this.record + CHARS1, chars, 0, numChars1);
+		} else {
+			chunk.putChars(this.record + CHARS1, chars, 0, numChars1);
+		}
+
+		// Write the subsequent records.
+		long lastNext = this.record + NEXT1;
+		int start = numChars1;
+		while (length - start > numCharsn) {
+			long nextRecord = db.malloc(Database.MAX_MALLOC_SIZE, Database.POOL_STRING_LONG);
+			db.putRecPtr(lastNext, nextRecord);
+			chunk= db.getChunk(nextRecord);
+			if (useBytes) {
+				chunk.putCharsAsBytes(nextRecord + CHARSN, chars, start, numCharsn);
+			} else {
+				chunk.putChars(nextRecord + CHARSN, chars, start, numCharsn);
+			}
+			start += numCharsn;
+			lastNext = nextRecord + NEXTN;
+		}
+
+		// Write the last record.
+		int remaining= length - start;
+		long nextRecord = db.malloc(CHARSN + (useBytes ? remaining : remaining * 2), Database.POOL_STRING_LONG);
+		db.putRecPtr(lastNext, nextRecord);
+		chunk= db.getChunk(nextRecord);
+		if (useBytes) {
+			chunk.putCharsAsBytes(nextRecord + CHARSN, chars, start, remaining);
+		} else {
+			chunk.putChars(nextRecord + CHARSN, chars, start, remaining);
+		}
+	}
+
+	@Override
+	public long getRecord() {
+		return this.record;
+	}
+
+	@Override
+	public char[] getChars() throws IndexException {
+		int length = this.db.getInt(this.record + LENGTH);
+		final boolean useBytes = length < 0;
+		int numChars1 = NUM_CHARS1;
+		int numCharsn = NUM_CHARSN;
+		if (useBytes) {
+			length= -length;
+			numChars1 *= 2;
+			numCharsn *= 2;
+		}
+
+		final char[] chars = new char[length];
+
+		// First record
+		long p = this.record;
+		Chunk chunk= this.db.getChunk(p);
+		if (useBytes) {
+			chunk.getCharsFromBytes(p + CHARS1, chars, 0, numChars1);
+		} else {
+			chunk.getChars(p + CHARS1, chars, 0, numChars1);
+		}
+
+		int start= numChars1;
+		p= this.record + NEXT1;
+
+		// Other records
+		while (start < length) {
+			p = this.db.getRecPtr(p);
+			int partLen= Math.min(length - start, numCharsn);
+			chunk= this.db.getChunk(p);
+			if (useBytes) {
+				chunk.getCharsFromBytes(p + CHARSN, chars, start, partLen);
+			} else {
+				chunk.getChars(p + CHARSN, chars, start, partLen);
+			}
+			start += partLen;
+			p= p + NEXTN;
+		}
+		return chars;
+	}
+
+	@Override
+	public void delete() throws IndexException {
+		int length = this.db.getInt(this.record + LENGTH);
+		final boolean useBytes = length < 0;
+		int numChars1 = NUM_CHARS1;
+		int numCharsn = NUM_CHARSN;
+		if (useBytes) {
+			length= -length;
+			numChars1 *= 2;
+			numCharsn *= 2;
+		}
+		long nextRecord = this.db.getRecPtr(this.record + NEXT1);
+		this.db.free(this.record, Database.POOL_STRING_LONG);
+		length -= numChars1;
+
+		// Middle records.
+		while (length > numCharsn) {
+			length -= numCharsn;
+			long nextnext = this.db.getRecPtr(nextRecord + NEXTN);
+			this.db.free(nextRecord, Database.POOL_STRING_LONG);
+			nextRecord = nextnext;
+		}
+
+		// Last record.
+		this.db.free(nextRecord, Database.POOL_STRING_LONG);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (obj == this)
+			return true;
+		try {
+			if (obj instanceof LongString) {
+				LongString lstr = (LongString)obj;
+				if (this.db == lstr.db && this.record == lstr.record)
+					return true;
+				return compare(lstr, true) == 0;
+			}
+			if (obj instanceof char[]) {
+				return compare((char[]) obj, true) == 0;
+			}
+			if (obj instanceof String) {
+				return compare((String) obj, true) == 0;
+			}
+		} catch (IndexException e) {
+			Package.log(e);
+		}
+		return false;
+	}
+
+	/**
+	 * Compatible with {@link String#hashCode()}
+	 */
+	@Override
+	public int hashCode() {
+		int h = this.hash;
+		if (h == 0) {
+			char chars[];
+			chars = getChars();
+			final int len = chars.length;
+			for (int i = 0; i < len; i++) {
+				h = 31 * h + chars[i];
+			}
+			this.hash = h;
+		}
+		return h;
+	}
+
+	@Override
+	public int compare(IString string, boolean caseSensitive) throws IndexException {
+		return ShortString.compare(getChars(), string.getChars(), caseSensitive);
+	}
+
+	@Override
+	public int compare(String other, boolean caseSensitive) throws IndexException {
+		return ShortString.compare(getChars(), other.toCharArray(), caseSensitive);
+	}
+
+	@Override
+	public int compare(char[] other, boolean caseSensitive) throws IndexException {
+		return ShortString.compare(getChars(), other, caseSensitive);
+	}
+
+	@Override
+	public int compareCompatibleWithIgnoreCase(IString string) throws IndexException {
+		return ShortString.compareCompatibleWithIgnoreCase(getChars(), string.getChars());
+	}
+
+	@Override
+	public int comparePrefix(char[] other, boolean caseSensitive) throws IndexException {
+		return ShortString.comparePrefix(getChars(), other, caseSensitive);
+	}
+
+	@Override
+	public String getString() throws IndexException {
+		return new String(getChars());
+	}
+
+	@Override
+	public int compareCompatibleWithIgnoreCase(char[] other) throws IndexException {
+		return ShortString.compareCompatibleWithIgnoreCase(getChars(), other);
+	}
+
+	@Override
+	public int length() {
+		return this.db.getInt(this.record + LENGTH);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/MemoryStats.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/MemoryStats.java
new file mode 100644
index 0000000..8de5777
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/MemoryStats.java
@@ -0,0 +1,232 @@
+/*******************************************************************************
+ * 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.db;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.internal.core.nd.ITypeFactory;
+import org.eclipse.jdt.internal.core.nd.NdNodeTypeRegistry;
+
+public class MemoryStats {
+	public static final int TOTAL_MALLOC_POOLS = 64;
+	/** The size of the statistics for a single malloc pool */
+	public static final int SIZE = TOTAL_MALLOC_POOLS * PoolStats.RECORD_SIZE;
+
+	private Map<Integer, PoolStats> stats = new HashMap<>();
+
+	public final long address;
+	private Chunk db;
+
+	public static final class PoolStats {
+		public static int POOL_ID_OFFSET = 0;
+		public static int NUM_ALLOCATIONS_OFFSET = POOL_ID_OFFSET + Database.SHORT_SIZE;
+		public static int TOTAL_SIZE_OFFSET = NUM_ALLOCATIONS_OFFSET + Database.LONG_SIZE;
+
+		public static final int RECORD_SIZE = TOTAL_SIZE_OFFSET + Database.LONG_SIZE;
+
+		short poolId;
+		long numAllocations;
+		long totalSize;
+		long address;
+
+		public PoolStats(Chunk db, long address) {
+			this.address = address;
+			this.poolId = db.getShort(POOL_ID_OFFSET + address);
+			this.numAllocations = db.getLong(NUM_ALLOCATIONS_OFFSET + address);
+			this.totalSize = db.getLong(TOTAL_SIZE_OFFSET + address);
+		}
+
+		public void setAllocations(Chunk db, long numAllocations) {
+			this.numAllocations = numAllocations;
+			db.putLong(this.address + NUM_ALLOCATIONS_OFFSET, numAllocations);
+		}
+
+		public void setTotalSize(Chunk db, long totalSize) {
+			this.totalSize = totalSize;
+			db.putLong(this.address + TOTAL_SIZE_OFFSET, totalSize);
+		}
+
+		public void setPoolId(Chunk db, short poolId) {
+			this.poolId = poolId;
+			db.putShort(this.address + POOL_ID_OFFSET, poolId);
+		}
+
+		public long getNumAllocations() {
+			return this.numAllocations;
+		}
+
+		public short getPoolId() {
+			return this.poolId;
+		}
+
+		public long getTotalSize() {
+			return this.totalSize;
+		}
+	}
+
+	public MemoryStats(Chunk db, long address) {
+		this.db = db;
+		this.address = address;
+	}
+
+	public void printMemoryStats(NdNodeTypeRegistry<?> nodeRegistry) {
+		StringBuilder builder = new StringBuilder();
+		for (PoolStats next : getSortedPools()) {
+			builder.append(getPoolName(nodeRegistry, next.poolId));
+			builder.append(" "); //$NON-NLS-1$
+			builder.append(next.numAllocations);
+			builder.append(" allocations, "); //$NON-NLS-1$
+			builder.append(next.totalSize);
+			builder.append(" bytes\n"); //$NON-NLS-1$
+		}
+		System.out.println(builder.toString());
+	}
+
+	private String getPoolName(NdNodeTypeRegistry<?> registry, int poolId) {
+		switch (poolId) {
+			case Database.POOL_MISC: return "Miscellaneous"; //$NON-NLS-1$
+			case Database.POOL_BTREE: return "B-Trees"; //$NON-NLS-1$
+			case Database.POOL_DB_PROPERTIES: return "DB Properties"; //$NON-NLS-1$
+			case Database.POOL_STRING_LONG: return "Long Strings"; //$NON-NLS-1$
+			case Database.POOL_STRING_SHORT: return "Short Strings"; //$NON-NLS-1$
+			case Database.POOL_LINKED_LIST: return "Linked Lists"; //$NON-NLS-1$
+			case Database.POOL_STRING_SET: return "String Sets"; //$NON-NLS-1$
+			case Database.POOL_GROWABLE_ARRAY: return "Growable Arrays"; //$NON-NLS-1$
+			default:
+				if (poolId >= Database.POOL_FIRST_NODE_TYPE) {
+					ITypeFactory<?> type = registry.getClassForType((short)(poolId - Database.POOL_FIRST_NODE_TYPE));
+
+					if (type != null) {
+						return type.getElementClass().getSimpleName();
+					}
+				}
+				return "Unknown memory pool " + poolId; //$NON-NLS-1$
+		}
+	}
+
+	public Collection<PoolStats> getPools() {
+		return this.stats.values();
+	}
+
+	public List<PoolStats> getSortedPools() {
+		List<PoolStats> unsorted = new ArrayList<>();
+		unsorted.addAll(getPools());
+		Collections.sort(unsorted, new Comparator<PoolStats>() {
+			@Override
+			public int compare(PoolStats o1, PoolStats o2) {
+				return Long.signum(o2.totalSize - o1.totalSize);
+			}
+		});
+		return unsorted;
+	}
+
+	public void recordMalloc(short poolId, long size) {
+		PoolStats toRecord = getPoolStats(poolId);
+		toRecord.setAllocations(this.db, toRecord.numAllocations + 1);
+		toRecord.setTotalSize(this.db, toRecord.totalSize + size);
+	}
+
+	private PoolStats getPoolStats(short poolId) {
+		if (this.stats.isEmpty()) {
+			refresh();
+		}
+		PoolStats result = this.stats.get((int)poolId);
+		if (result == null) {
+			if (this.stats.size() >= TOTAL_MALLOC_POOLS) {
+				throw new IndexException("Too many malloc pools. Please increase the size of TOTAL_MALLOC_POOLS."); //$NON-NLS-1$
+			}
+			// Find the insertion position
+			int idx = 0;
+			for (;;idx++) {
+				PoolStats nextPool = readPool(idx);
+				if (idx > 0 && nextPool.poolId == 0) {
+					break;
+				}
+				if (nextPool.poolId == poolId) {
+					throw new IllegalStateException("The stats were out of sync with the database."); //$NON-NLS-1$
+				}
+				if (nextPool.poolId > poolId) {
+					break;
+				}
+			}
+
+			// Find the last pool position
+			int lastIdx = idx;
+			for (;;lastIdx++) {
+				PoolStats nextPool = readPool(lastIdx);
+				if (lastIdx > 0 && nextPool.poolId == 0) {
+					break;
+				}
+			}
+
+			// Shift all the pools to make room
+			for (int shiftIdx = lastIdx; shiftIdx > idx; shiftIdx--) {
+				PoolStats writeTo = readPool(shiftIdx);
+				PoolStats readFrom = readPool(shiftIdx - 1);
+
+				writeTo.setAllocations(this.db, readFrom.numAllocations);
+				writeTo.setTotalSize(this.db, readFrom.totalSize);
+				writeTo.setPoolId(this.db, readFrom.poolId);
+			}
+
+			result = readPool(idx);
+			result.setAllocations(this.db, 0);
+			result.setTotalSize(this.db, 0);
+			result.setPoolId(this.db, poolId);
+
+			refresh();
+
+			result = this.stats.get((int)poolId);
+		}
+		return result;
+	}
+
+	private List<PoolStats> loadStats() {
+		List<PoolStats> result = new ArrayList<>();
+		for (int idx = 0; idx < TOTAL_MALLOC_POOLS; idx++) {
+			PoolStats next = readPool(idx);
+
+			if (idx > 0 && next.poolId == 0) {
+				break;
+			}
+			
+			result.add(next);
+		}
+		return result;
+	}
+
+	public void refresh() {
+		this.stats.clear();
+
+		for (PoolStats next : loadStats()) {
+			this.stats.put((int)next.poolId, next);
+		}
+	}
+
+	public PoolStats readPool(int idx) {
+		return new PoolStats(this.db, this.address + idx * PoolStats.RECORD_SIZE);
+	}
+
+	public void recordFree(short poolId, long size) {
+		PoolStats toRecord = getPoolStats(poolId);
+		if (toRecord.numAllocations <= 0 || toRecord.totalSize < size) {
+			throw new IndexException("Attempted to free more memory from pool " + poolId + " than was ever allocated");  //$NON-NLS-1$//$NON-NLS-2$
+		}
+		toRecord.setAllocations(this.db, toRecord.numAllocations - 1);
+		toRecord.setTotalSize(this.db, toRecord.totalSize - size);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/NdStringSet.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/NdStringSet.java
new file mode 100644
index 0000000..37bb613
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/NdStringSet.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2013, 2016 QNX Software Systems 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:
+ *     Andrew Eidsness - Initial implementation
+ */
+
+package org.eclipse.jdt.internal.core.nd.db;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * A container for storing a set of strings in the Database. The container allows only one instance of each
+ * string to be stored.
+ * <p>
+ * This implementation should only be used when the set is expected to be small. It uses a singly linked list
+ * for storing strings in Database. Which means that a linear lookup is needed to find strings in the list. An
+ * in-memory, lazily-loaded, cache is provided so the list will only be fully retrieved once in the lifetime
+ * of this instance. A BTree will be more efficient for larger sets.
+ */
+public class NdStringSet {
+	private final Database db;
+
+	private long ptr;
+	private long head;
+	private long loaded;
+
+	// A lazily initialized, in-memory cache that maps a persisted string to its storage record.
+	private Map<String, Long> lazyCache;
+
+	public NdStringSet(Database db, long ptr) throws CoreException {
+		this.db = db;
+		this.ptr = ptr;
+
+		this.head = 0;
+		this.loaded = 0;
+	}
+
+	public void clearCaches() {
+		this.head = 0;
+		this.loaded = 0;
+
+		if (this.lazyCache != null)
+			this.lazyCache = null;
+	}
+
+	private long getHead() throws CoreException {
+		if (this.head == 0)
+			this.head = this.db.getRecPtr(this.ptr);
+		return this.head;
+	}
+
+	// A simple enum describing the type of the information that is stored in the Database. Each
+	// enumerator represents a single field in the persistent structure and is able to answer its
+	// offset in that structure.
+	private static enum NodeType {
+		Next, Item, _last;
+
+		// NOTE: All fields are pointers, if that changes then these initializations will need
+		// to be updated.
+		public final long offset = ordinal() * Database.PTR_SIZE;
+		public static final int sizeof = (int) _last.offset;
+
+		/** Return the value of the pointer stored in this field in the given instance. */
+		public long get(Database db, long instance) throws CoreException {
+			return db.getRecPtr(instance + this.offset);
+		}
+
+		/** Store the given pointer into this field in the given instance. */
+		public void put(Database db, long instance, long value) throws CoreException {
+			db.putRecPtr(instance + this.offset, value);
+		}
+	}
+
+	/**
+	 * Adds the given string to the receiving set. May cause the entire list to be loaded from the Database
+	 * while testing for uniqueness. Returns the record of the string that was inserted into the list.
+	 */
+	public long add(String str) throws CoreException {
+		long record = find(str);
+		if (record != 0)
+			return record;
+
+		IString string = this.db.newString(str);
+		record = string.getRecord();
+
+		long new_node = this.db.malloc(NodeType.sizeof, Database.POOL_STRING_SET);
+		NodeType.Next.put(this.db, new_node, getHead());
+		NodeType.Item.put(this.db, new_node, record);
+
+		if (this.lazyCache == null)
+			this.lazyCache = new HashMap<String, Long>();
+		this.lazyCache.put(str, record);
+
+		// If the Database has already been partially searched, then the loaded pointer will be after the
+		// head. Since we've already put this new record into the lazy cache, there is no reason to try to
+		// load it again. We put the new node at the start of the list so that it will be before the loaded
+		// pointer.
+		this.head = new_node;
+		if (this.loaded == 0)
+			this.loaded = new_node;
+		this.db.putRecPtr(this.ptr, new_node);
+		return record;
+	}
+
+	/**
+	 * Search for the given string in the receiver. This could cause the entire list to be loaded from the
+	 * Database. The results are cached, so the list will only be loaded one time during the lifetime of this
+	 * instance. Returns the record of the String.
+	 */
+	public long find(String str) throws CoreException {
+		if (this.lazyCache != null) {
+			Long l = this.lazyCache.get(str);
+			if (l != null)
+				return l.longValue();
+		}
+
+		// if there is nothing in the Database, then there is nothing to load
+		if (getHead() == 0)
+			return 0;
+
+		// otherwise prepare the cache for the data that is about to be loaded
+		if (this.lazyCache == null)
+			this.lazyCache = new HashMap<String, Long>();
+
+		// if nothing has been loaded, then start loading with the head node, otherwise continue
+		// loading from whatever is after the last loaded node
+		long curr = this.loaded == 0 ? getHead() : NodeType.Next.get(this.db, this.loaded);
+		while (curr != 0) {
+			long next = NodeType.Next.get(this.db, curr);
+			long item = NodeType.Item.get(this.db, curr);
+
+			IString string = this.db.getString(item);
+
+			// put the value into the cache
+			this.lazyCache.put(string.getString(), Long.valueOf(item));
+
+			// return immediately if this is the target
+			if (string.compare(str, true) == 0)
+				return item;
+
+			// otherwise keep looking
+			this.loaded = curr;
+			curr = next;
+		}
+
+		return 0;
+	}
+
+	/**
+	 * Return a pointer to the record of the String that was removed.
+	 */
+	public long remove(String str) throws CoreException {
+		if (this.lazyCache != null)
+			this.lazyCache.remove(str);
+
+		long prev = 0;
+		long curr = getHead();
+		while (curr != 0) {
+			long next = NodeType.Next.get(this.db, curr);
+			long item = NodeType.Item.get(this.db, curr);
+
+			IString string = this.db.getString(item);
+
+			if (string.compare(str, true) == 0) {
+				if (this.head != curr)
+					NodeType.Next.put(this.db, prev, next);
+				else {
+					this.db.putRecPtr(this.ptr, next);
+					this.head = next;
+				}
+
+				this.db.free(curr, Database.POOL_STRING_SET);
+				return item;
+			}
+
+			prev = curr;
+			curr = next;
+		}
+
+		return 0;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Package.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Package.java
new file mode 100644
index 0000000..b68df3c
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Package.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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.db;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.JavaCore;
+
+/**
+ * This class is not intended to be referenced by clients
+ */
+/* package */ class Package {
+	public static String PLUGIN_ID = JavaCore.PLUGIN_ID;
+
+	/**
+	 * Status code for core exception that is thrown if a database grew larger than the supported limit.
+	 */
+	public static final int STATUS_DATABASE_TOO_LARGE = 4;
+
+	public static void log(Throwable e) {
+		String msg= e.getMessage();
+		if (msg == null) {
+			log("Error", e); //$NON-NLS-1$
+		} else {
+			log("Error: " + msg, e); //$NON-NLS-1$
+		}
+	}
+
+	public static void log(String message, Throwable e) {
+		log(createStatus(message, e));
+	}
+
+	public static IStatus createStatus(String msg, Throwable e) {
+		return new Status(IStatus.ERROR, PLUGIN_ID, msg, e);
+	}
+
+	public static void log(IStatus status) {
+		JavaCore.getPlugin().getLog().log(status);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ShortString.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ShortString.java
new file mode 100644
index 0000000..09992a5
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ShortString.java
@@ -0,0 +1,296 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2016 QNX Software Systems 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:
+ *     QNX - Initial API and implementation
+ *     Andrew Ferguson (Symbian)
+ *     Markus Schorn (Wind River Systems)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+
+/**
+ * This is for strings that fit inside a single chunk.
+ */
+public class ShortString implements IString {
+	private final Database db;
+	private final long record;
+	private int hash;
+
+	private static final int LENGTH = 0;
+	private static final int CHARS = 4;
+
+	public static final int MAX_BYTE_LENGTH = Database.MAX_MALLOC_SIZE - CHARS;
+
+	public ShortString(Database db, long offset) {
+		this.db = db;
+		this.record = offset;
+	}
+
+	public ShortString(Database db, char[] chars, boolean useBytes) throws IndexException {
+		final int n = chars.length;
+		this.db = db;
+
+		this.record = db.malloc(CHARS + (useBytes ? n : 2 * n), Database.POOL_STRING_SHORT);
+		Chunk chunk = db.getChunk(this.record);
+		chunk.putInt(this.record + LENGTH, useBytes ? -n : n);
+		long p = this.record + CHARS;
+		if (useBytes) {
+			chunk.putCharsAsBytes(p, chars, 0, n);
+		} else {
+			chunk.putChars(p, chars, 0, n);
+		}
+	}
+
+	@Override
+	public long getRecord() {
+		return this.record;
+	}
+
+	@Override
+	public void delete() throws IndexException {
+		this.db.free(this.record, Database.POOL_STRING_SHORT);
+	}
+
+	@Override
+	public char[] getChars() throws IndexException {
+		final Chunk chunk = this.db.getChunk(this.record);
+		final int l = chunk.getInt(this.record + LENGTH);
+		final int length = Math.abs(l);
+		final char[] chars = new char[length];
+		if (l < 0) {
+			chunk.getCharsFromBytes(this.record + CHARS, chars, 0, length);
+		} else {
+			chunk.getChars(this.record + CHARS, chars, 0, length);
+		}
+		return chars;
+	}
+
+	@Override
+	public String getString() throws IndexException {
+		return new String(getChars());
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (obj == this)
+			return true;
+
+		try {
+			if (obj instanceof ShortString) {
+				ShortString string = (ShortString)obj;
+				if (this.db == string.db && this.record == string.record)
+					return true;
+
+				Chunk chunk1 = this.db.getChunk(this.record);
+				Chunk chunk2 = string.db.getChunk(string.record);
+
+				int n1 = chunk1.getInt(this.record);
+				int n2 = chunk2.getInt(string.record);
+				if (n1 != n2)
+					return false;
+
+				return CharArrayUtils.equals(getChars(), string.getChars());
+			}
+			if (obj instanceof char[]) {
+				char[] chars = (char[])obj;
+
+				// Make sure size is the same
+				if (length() != chars.length)
+					return false;
+
+				return CharArrayUtils.equals(getChars(), chars);
+			} else if (obj instanceof String) {
+				String string = (String)obj;
+				if (length() != string.length())
+					return false;
+
+				return CharArrayUtils.equals(getChars(), string.toCharArray());
+			}
+		} catch (IndexException e) {
+			Package.log(e);
+		}
+		return false;
+	}
+
+	/**
+	 * Compatible with {@link String#hashCode()}
+	 */
+	@Override
+	public int hashCode() {
+		int h = this.hash;
+		if (h == 0) {
+			char chars[];
+			chars = getChars();
+			final int len = chars.length;
+			for (int i = 0; i < len; i++) {
+				h = 31 * h + chars[i];
+			}
+			this.hash = h;
+		}
+		return h;
+	}
+
+	public static int compare(final char[] chars, char[] other, boolean caseSensitive) {
+		final int n = Math.min(chars.length, other.length);
+		for (int i = 0; i < n; i++) {
+			int cmp= compareChars(chars[i], other[i], caseSensitive);
+			if (cmp != 0)
+				return cmp;
+		}
+		return chars.length - other.length;
+	}
+
+	@Override
+	public int compare(char[] other, boolean caseSensitive) throws IndexException {
+		return compare(getChars(), other, caseSensitive);
+	}
+
+	@Override
+	public int compare(IString string, boolean caseSensitive) throws IndexException {
+		return compare(getChars(), string.getChars(), caseSensitive);
+	}
+
+	@Override
+	public int compare(String other, boolean caseSensitive) throws IndexException {
+		return compare(getChars(), other.toCharArray(), caseSensitive);
+	}
+
+	@Override
+	public int compareCompatibleWithIgnoreCase(IString string) throws IndexException {
+		return compareCompatibleWithIgnoreCase(string.getChars());
+	}
+
+	@Override
+	public int compareCompatibleWithIgnoreCase(char[] other) throws IndexException {
+		return compareCompatibleWithIgnoreCase(getChars(), other);
+	}
+
+	public static int compareCompatibleWithIgnoreCase(final char[] chars, char[] other) {
+		final int n = Math.min(chars.length, other.length);
+		int sensitiveCmp= 0;
+
+		for (int i = 0; i < n; i++) {
+			final char c1= chars[i];
+			final char c2= other[i];
+			if (c1 != c2) {
+				int cmp= compareChars(c1, c2, false); // insensitive
+				if (cmp != 0)
+					return cmp;
+
+				if (sensitiveCmp == 0) {
+					if (c1 < c2) {
+						sensitiveCmp= -1;
+					} else {
+						sensitiveCmp= 1;
+					}
+				}
+			}
+		}
+		int cmp= chars.length - other.length;
+		if (cmp != 0)
+			return cmp;
+
+		return sensitiveCmp;
+	}
+
+	@Override
+	public int comparePrefix(char[] other, boolean caseSensitive) throws IndexException {
+		return comparePrefix(getChars(), other, caseSensitive);
+	}
+
+	public static int comparePrefix(final char[] chars, char[] other, boolean caseSensitive) {
+		final int n = Math.min(chars.length, other.length);
+
+		for (int i = 0; i < n; i++) {
+			int cmp= compareChars(chars[i], other[i], caseSensitive);
+			if (cmp != 0)
+				return cmp;
+		}
+		if (chars.length < other.length)
+			return -1;
+
+		return 0;
+	}
+
+	/**
+	 * Compare characters case-sensitively, or case-insensitively.
+	 *
+	 * <b>Limitation</b> This only maps the range a-z,A-Z onto each other
+	 * @param a a character
+	 * @param b a character
+	 * @param caseSensitive whether to compare case-sensitively
+	 * @return
+	 * <ul>
+	 * <li>-1 if a < b
+	 * <li>0 if a == b
+	 * <li>1 if a > b
+	 * </ul>
+	 */
+	public static int compareChars(char a, char b, boolean caseSensitive) {
+		if (caseSensitive) {
+			if (a < b)
+				return -1;
+			if (a > b)
+				return 1;
+		} else {
+			if (a != b) {
+				a= a >= 'a' && a <='z' ? (char) (a - 32) : a;
+				b= b >= 'a' && b <='z' ? (char) (b - 32) : b;
+				if (a < b)
+					return -1;
+				if (a > b)
+					return 1;
+			}
+		}
+		return 0;
+	}
+
+/* TODO - this is more correct than the above implementation, but we need to
+ * benchmark first.
+ *
+ * public static int compareChars(char a, char b, boolean caseSensitive) {
+		if (caseSensitive) {
+			if (a < b)
+				return -1;
+			if (a > b)
+				return 1;
+		} else {
+			if (a != b) {
+				a = Character.toUpperCase(a);
+				b = Character.toUpperCase(b);
+				if (a != b) {
+					a = Character.toLowerCase(a);
+					b = Character.toLowerCase(b);
+					if (a != b) {
+						if (a < b)
+							return -1;
+						if (a > b)
+							return 1;
+					}
+				}
+			}
+		}
+		return 0;
+	}
+*/
+
+	@Override
+	public String toString() {
+		try {
+			return getString();
+		} catch (IndexException e) {
+			return super.toString();
+		}
+	}
+
+	@Override
+	public int length() {
+		return Math.abs(this.db.getInt(this.record + LENGTH));
+	}
+}
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
new file mode 100644
index 0000000..bab45d4
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/Field.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * 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.field;
+
+import org.eclipse.jdt.internal.core.nd.ITypeFactory;
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+/**
+ * Used to represent a single field of an object stored in the database. Objects 
+ * which store themselves in the database should store a set of static final
+ * FieldDefinitions at the top of their class definition to indicate their memory map.
+ * This serves as a standard way to document the memory map for such structs, provides
+ * access to the field offsets, and provides a convenience getter.
+ * <p>
+ * There are two ways to use this. Callers can either use the "get" method to access
+ * the value of the field, or can use the public "offset" attribute to perform the reads
+ * manually. The get function is more convenient but allocates objects and so should
+ * probably not be used for frequently-accessed fields or primitive types that would
+ * end up being autoboxed unnecessarily.
+ * 
+ * @param <T>
+ */
+public final class Field<T> implements IField, IDestructableField {
+	private int offset;
+	public final ITypeFactory<T> factory;
+
+	public Field(ITypeFactory<T> objectFactory) {
+		this.factory = objectFactory;
+	}
+
+	public T get(Nd nd, long address) {
+		return this.factory.create(nd, address + this.offset);
+	}
+
+	public boolean hasDestructor() {
+		return this.factory.hasDestructor();
+	}
+
+	@Override
+	public void destruct(Nd nd, long address) {
+		this.factory.destruct(nd, address + this.offset);
+	}
+
+	@Override
+	public void setOffset(int offset) {
+		this.offset = offset;
+	}
+
+	@Override
+	public int getRecordSize() {
+		return this.factory.getRecordSize();
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldByte.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldByte.java
new file mode 100644
index 0000000..c7a9ef5
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldByte.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Declares a Nd field of type byte. Can be used in place of {@link Field}&lt{@link Byte}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldByte implements IField {
+	private int offset;
+
+	public FieldByte() {
+	}
+
+	public byte get(Nd nd, long address) {
+		Database db = nd.getDB();
+		return db.getByte(address + this.offset);
+	}
+
+	public void put(Nd nd, long address, byte newValue) {
+		nd.getDB().putByte(address + this.offset, newValue);
+	}
+
+	@Override
+	public void setOffset(int offset) {
+		this.offset = offset; 
+	}
+
+	@Override
+	public int getRecordSize() {
+		return Database.BYTE_SIZE;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldChar.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldChar.java
new file mode 100644
index 0000000..e4b0e17
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldChar.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Declares a Nd field of type char. Can be used in place of  {@link Field}&lt{@link Character}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldChar implements IField {
+	private int offset;
+
+	public FieldChar() {
+	}
+
+	public char get(Nd nd, long address) {
+		Database db = nd.getDB();
+		return db.getChar(address + this.offset);
+	}
+
+	public void put(Nd nd, long address, char newValue) {
+		nd.getDB().putChar(address + this.offset, newValue);
+	}
+
+	@Override
+	public void setOffset(int offset) {
+		this.offset = offset; 
+	}
+
+	@Override
+	public int getRecordSize() {
+		return Database.CHAR_SIZE;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldDouble.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldDouble.java
new file mode 100644
index 0000000..f0932e2
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldDouble.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Declares a Nd field of type double. Can be used in place of  {@link Field}&lt{@link Double}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldDouble implements IField {
+	private int offset;
+
+	public FieldDouble() {
+	}
+
+	public double get(Nd nd, long address) {
+		Database db = nd.getDB();
+		return db.getDouble(address + this.offset);
+	}
+
+	public void put(Nd nd, long address, double newValue) {
+		nd.getDB().putDouble(address + this.offset, newValue);
+	}
+
+	@Override
+	public void setOffset(int offset) {
+		this.offset = offset;
+	}
+
+	@Override
+	public int getRecordSize() {
+		return Database.DOUBLE_SIZE;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldFloat.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldFloat.java
new file mode 100644
index 0000000..4ddd093
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldFloat.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Declares a Nd field of type float. Can be used in place of  {@link Field}&lt{@link Float}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldFloat implements IField {
+	private int offset;
+
+	public FieldFloat() {
+	}
+
+	public float get(Nd nd, long address) {
+		Database db = nd.getDB();
+		return db.getFloat(address + this.offset);
+	}
+
+	public void put(Nd nd, long address, float newValue) {
+		nd.getDB().putFloat(address + this.offset, newValue);
+	}
+
+	@Override
+	public void setOffset(int offset) {
+		this.offset = offset;
+	}
+
+	@Override
+	public int getRecordSize() {
+		return Database.FLOAT_SIZE;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldInt.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldInt.java
new file mode 100644
index 0000000..06e9b8a
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldInt.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Declares a Nd field of type int. Can be used in place of  {@link Field}&lt{@link Integer}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldInt implements IField {
+	private int offset;
+
+	public FieldInt() {
+	}
+
+	public int get(Nd nd, long address) {
+		Database db = nd.getDB();
+		return db.getInt(address + this.offset);
+	}
+
+	public void put(Nd nd, long address, int newValue) {
+		nd.getDB().putInt(address + this.offset, newValue);
+	}
+
+	@Override
+	public void setOffset(int offset) {
+		this.offset = offset; 
+	}
+
+	@Override
+	public int getRecordSize() {
+		return Database.INT_SIZE;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldLong.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldLong.java
new file mode 100644
index 0000000..6a66ac2
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldLong.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Declares a Nd field of type long. Can be used in place of  {@link Field}&lt{@link Long}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldLong implements IField {
+	private int offset;
+
+	public FieldLong() {
+	}
+
+	public long get(Nd nd, long address) {
+		Database db = nd.getDB();
+		return db.getLong(address + this.offset);
+	}
+
+	public void put(Nd nd, long address, long newValue) {
+		nd.getDB().putLong(address + this.offset, newValue);
+	}
+
+	@Override
+	public void setOffset(int offset) {
+		this.offset = offset;
+	}
+
+	@Override
+	public int getRecordSize() {
+		return Database.LONG_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
new file mode 100644
index 0000000..ed64495
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldManyToOne.java
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * 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.field;
+
+import org.eclipse.jdt.internal.core.nd.ITypeFactory;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+
+/**
+ * Holds the n side of a n..1 relationship. Declares a Nd field which is a pointer of a NdNode of the specified
+ * type. {@link FieldManyToOne} forms a one-to-many relationship with {@link FieldOneToMany}. Whenever a
+ * {@link FieldManyToOne} points to an object, the inverse pointer is automatically inserted into the matching back
+ * pointer list.
+ */
+public class FieldManyToOne<T extends NdNode> implements IDestructableField, IField, IRefCountedField {
+	public final static FieldPointer TARGET;
+	public final static FieldInt BACKPOINTER_INDEX;
+
+	private int offset;
+	Class<T> targetType;
+	final Class<? extends NdNode> localType;
+	FieldOneToMany<?> backPointer;
+	@SuppressWarnings("rawtypes")
+	private final static StructDef<FieldManyToOne> type;
+	/**
+	 * True iff the other end of this pointer should delete this object when its end of the pointer is cleared.
+	 */
+	public final boolean pointsToOwner;
+
+	static {
+		type = StructDef.createAbstract(FieldManyToOne.class);
+		TARGET = type.addPointer();
+		BACKPOINTER_INDEX = type.addInt();
+		type.done();
+	}
+
+	@SuppressWarnings({ "unchecked", "rawtypes" })
+	private FieldManyToOne(Class<? extends NdNode> localType, FieldOneToMany<?> backPointer, boolean pointsToOwner) {
+		this.localType = localType;
+		this.pointsToOwner = pointsToOwner;
+
+		if (backPointer != null) {
+			if (backPointer.forwardPointer != null && backPointer.forwardPointer != this) {
+				throw new IllegalArgumentException(
+						"Attempted to construct a FieldNodePointer referring to a backpointer list that is already in use" //$NON-NLS-1$
+								+ " by another field"); //$NON-NLS-1$
+			}
+			backPointer.targetType = (Class) localType;
+			this.targetType = (Class) backPointer.localType;
+			backPointer.forwardPointer = this;
+		}
+		this.backPointer = backPointer;
+	}
+
+	public static <T extends NdNode, B extends NdNode> FieldManyToOne<T> create(StructDef<B> builder,
+			FieldOneToMany<B> forwardPointer) {
+		FieldManyToOne<T> result = new FieldManyToOne<T>(builder.getStructClass(), forwardPointer, false);
+		builder.add(result);
+		builder.addDestructableField(result);
+		return result;
+	}
+
+	/**
+	 * Creates a many-to-one pointer which points to this object's owner. If the pointer is non-null when the owner is
+	 * deleted, this object will be deleted too.
+	 * 
+	 * @param builder the struct to which the field will be added
+	 * @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,
+			FieldOneToMany<B> forwardPointer) {
+
+		FieldManyToOne<T> result = new FieldManyToOne<T>(builder.getStructClass(), forwardPointer, true);
+		builder.add(result);
+		builder.addDestructableField(result);
+		builder.addOwnerField(result);
+		return result;
+	}
+
+	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);
+	}
+
+	/**
+	 * Directs this pointer to the given target. Also removes this pointer from the old backpointer list (if any) and
+	 * inserts it into the new backpointer list (if any)
+	 */
+	public void put(Nd nd, long address, T value) {
+		if (value != null) {
+			put(nd, address, value.address);
+		} else {
+			put(nd, address, 0);
+		}
+	}
+
+	public void put(Nd nd, long address, long newTargetAddress) {
+		long fieldStart = address + this.offset;
+		if (this.backPointer == null) {
+			throw new IllegalStateException("FieldNodePointer must be associated with a FieldBackPointer"); //$NON-NLS-1$
+		}
+		
+		long oldTargetAddress = TARGET.get(nd, fieldStart);
+		if (oldTargetAddress == newTargetAddress) {
+			return;
+		}
+
+		detachFromOldTarget(nd, address, oldTargetAddress);
+
+		TARGET.put(nd, fieldStart, newTargetAddress);
+		if (newTargetAddress != 0) {
+			// Note that newValue is the address of the backpointer list and record (the address of the struct
+			// containing the forward pointer) is the value being inserted into the list.
+			BACKPOINTER_INDEX.put(nd, fieldStart, this.backPointer.add(nd, newTargetAddress, address));
+		} else {
+			if (this.pointsToOwner) {
+				nd.scheduleDeletion(address);
+			}
+		}
+	}
+
+	protected void detachFromOldTarget(Nd nd, long address, long oldTargetAddress) {
+		long fieldStart = address + this.offset;
+		if (oldTargetAddress != 0) {
+			int oldIndex = BACKPOINTER_INDEX.get(nd, fieldStart);
+
+			this.backPointer.remove(nd, oldTargetAddress, oldIndex);
+
+			short targetTypeId = NdNode.NODE_TYPE.get(nd, oldTargetAddress);
+
+			ITypeFactory<T> typeFactory = nd.getTypeFactory(targetTypeId);
+
+			if (typeFactory.getDeletionSemantics() == StructDef.DeletionSemantics.REFCOUNTED 
+					&& typeFactory.isReadyForDeletion(nd, oldTargetAddress)) {
+				nd.scheduleDeletion(oldTargetAddress);
+			}
+		}
+	}
+
+	/**
+	 * Called when the index of this forward pointer has moved in the backpointer list. Adjusts the index.
+	 * <p>
+	 * Not intended to be called by clients. This is invoked by {@link FieldOneToMany} whenever it reorders elements in
+	 * the array.
+	 */
+	void adjustIndex(Nd nd, long address, int index) {
+		BACKPOINTER_INDEX.put(nd, address + this.offset, index);
+	}
+
+	@Override
+	public void destruct(Nd nd, long address) {
+		long fieldStart = address + this.offset;
+		long oldTargetAddress = TARGET.get(nd, fieldStart);
+		detachFromOldTarget(nd, address, oldTargetAddress);
+		TARGET.put(nd, fieldStart, 0);
+	}
+
+	void clearedByBackPointer(Nd nd, long address) {
+		long fieldStart = this.offset + address;
+		FieldManyToOne.TARGET.put(nd, fieldStart, 0);
+		FieldManyToOne.BACKPOINTER_INDEX.put(nd, fieldStart, 0);
+	}
+
+	@Override
+	public void setOffset(int offset) {
+		this.offset = offset;
+	}
+
+	@Override
+	public int getRecordSize() {
+		return type.size();
+	}
+
+	@Override
+	public boolean hasReferences(Nd nd, long address) {
+		long fieldStart = this.offset + address;
+		long target = TARGET.get(nd, fieldStart);
+		return target != 0;
+	}
+}
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
new file mode 100644
index 0000000..8f95c68
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToMany.java
@@ -0,0 +1,188 @@
+/*******************************************************************************
+ * 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.field;
+
+import java.util.ArrayList;
+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.RawGrowableArray;
+
+/**
+ * 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> implements IDestructableField, IRefCountedField, IField {
+	private int offset;
+	public Class<T> targetType;
+	public final Class<? extends NdNode> localType;
+	private final RawGrowableArray backPointerArray;
+	FieldManyToOne<?> forwardPointer;
+
+	public interface Visitor<T> {
+		public void visit(int index, T toVisit);
+	}
+
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	private FieldOneToMany(Class<? extends NdNode> localType, FieldManyToOne<? extends NdNode> forwardPointer,
+			int inlineElements) {
+		this.localType = localType;
+
+		if (forwardPointer != null) {
+			if (forwardPointer.backPointer != null && forwardPointer.backPointer != this) {
+				throw new IllegalArgumentException(
+					"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 = (Class)localType;
+			this.targetType = (Class)forwardPointer.localType;
+			forwardPointer.backPointer = this;
+		}
+		this.forwardPointer = forwardPointer;
+		this.backPointerArray = new RawGrowableArray(inlineElements);
+	}
+
+	/**
+	 * Creates a {@link FieldOneToMany} using the given builder. It will hold the many side of a one-to-many
+	 * relationship with nodeType. 
+	 * 
+	 * @param builder builder that is being used to construct the struct containing this field
+	 * @param forwardPointer field of the model object which holds the one side of this one-to-many relationship
+	 * @param inlineElementCount number of inline elements. If this is nonzero, space for this number elements is
+	 * preallocated and reserved in the header. The first few elements inserted will be stored here. For relationships
+	 * which will usually have more than a certain number of participants, using a small number of inline elements will
+	 * 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, 
+			FieldManyToOne<B> forwardPointer, int inlineElementCount) {
+		FieldOneToMany<T> result = new FieldOneToMany<T>(builder.getStructClass(), forwardPointer,
+				inlineElementCount);
+		builder.add(result);
+		builder.addDestructableField(result);
+		builder.addRefCountedField(result);
+		return result;
+	}
+
+	public static <T extends NdNode, B extends NdNode> 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);
+
+		for (int idx = 0; idx < size; idx++) {
+			visitor.visit(idx, get(nd, address, idx));
+		}
+	}
+
+	public List<T> asList(Nd nd, long address) {
+		final List<T> result = new ArrayList<>(size(nd, address));
+
+		accept(nd, address, new Visitor<T>() {
+			@Override
+			public void visit(int index, T toVisit) {
+				result.add(toVisit);
+			}
+		});
+
+		return result;
+	}
+
+	public boolean isEmpty(Nd nd, long address) {
+		return this.backPointerArray.isEmpty(nd, address + this.offset);
+	}
+	
+	public int size(Nd nd, long address) {
+		return this.backPointerArray.size(nd, address + this.offset);
+	}
+
+	public T get(Nd nd, long address, int index) {
+		long nextPointer = this.backPointerArray.get(nd, address + this.offset, index);
+
+		return NdNode.load(nd, nextPointer, this.targetType);
+	}
+
+	/**
+	 * Removes the given index from the list. If another element is swapped into the removed element's
+	 * location, that element's index will be updated. The removed element itself will not be modified. The
+	 * caller is responsible for nulling out the pointer and updating its index if necessary.
+	 * <p>
+	 * Not intended to be called by clients. The normal way to remove something from a backpointer list is
+	 * by calling {@link FieldManyToOne#put}, which performs the appropriate removals automatically.
+	 */
+	void remove(Nd nd, long address, int index) {
+		long swappedElement = this.backPointerArray.remove(nd, address + this.offset, index);
+
+		if (swappedElement != 0) {
+			this.forwardPointer.adjustIndex(nd, swappedElement, index);
+		}
+	}
+
+	/**
+	 * Addss the given forward pointer to the list and returns the insertion index. This should not be invoked
+	 * directly by clients. The normal way to insert into a backpointer list is to assign a forward pointer.
+	 */
+	int add(Nd nd, long address, long value) {
+		return this.backPointerArray.add(nd, address + this.offset, value);
+	}
+
+	/**
+	 * Returns the record size of the back pointer list 
+	 */
+	public int getRecordSize() {
+		return this.backPointerArray.getRecordSize();
+	}
+
+	public void ensureCapacity(Nd nd, long address, int capacity) {
+		long arrayAddress = address + this.offset;
+		this.backPointerArray.ensureCapacity(nd, arrayAddress, capacity);
+	}
+
+	@Override
+	public void destruct(Nd nd, long address) {
+		long arrayAddress = address + this.offset;
+		int size = size(nd, address);
+
+		boolean isOwner = this.forwardPointer.pointsToOwner;
+		for (int idx = 0; idx < size; idx++) {
+			long target = this.backPointerArray.get(nd, arrayAddress, idx);
+
+			this.forwardPointer.clearedByBackPointer(nd, target);
+
+			if (isOwner) {
+				nd.scheduleDeletion(target);
+			}
+		}
+
+		this.backPointerArray.destruct(nd, arrayAddress);
+	}
+
+	public int getCapacity(Nd nd, long address) {
+		return this.backPointerArray.getCapacity(nd, address + this.offset);
+	}
+
+	@Override
+	public boolean hasReferences(Nd nd, long address) {
+		// If this field owns the objects it points to, don't treat the incoming pointers as ref counts
+		if (this.forwardPointer.pointsToOwner) {
+			return false;
+		}
+		return !isEmpty(nd, address);
+	}
+
+	@Override
+	public void setOffset(int offset) {
+		this.offset = offset;
+	}
+}
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
new file mode 100644
index 0000000..c1dd228
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToOne.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * 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.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Represents a 1-to-0..1 relationship in a Nd database.
+ */
+public class FieldOneToOne<T extends NdNode> implements IField, IDestructableField, IRefCountedField {
+	private int offset;
+	public final Class<T> nodeType; 
+	FieldOneToOne<?> backPointer;
+	private boolean pointsToOwner;
+
+	/**
+	 * @param nodeType
+	 * @param backPointer
+	 */
+	private FieldOneToOne(Class<T> nodeType, FieldOneToOne<?> backPointer, boolean pointsToOwner) {
+		this.nodeType = nodeType;
+
+		if (backPointer != null) {
+			if (backPointer.backPointer != null && backPointer.backPointer != this) {
+				throw new IllegalArgumentException(
+					"Attempted to construct a FieldOneToOne referring to a backpointer list that is already in use" //$NON-NLS-1$
+						+ " by another field"); //$NON-NLS-1$
+			}
+			backPointer.backPointer = this;
+		}
+		this.backPointer = backPointer;
+		this.pointsToOwner = pointsToOwner;
+	}
+
+	public static <T extends NdNode, B extends NdNode> FieldOneToOne<T> create(StructDef<B> builder,
+			Class<T> nodeType, FieldOneToOne<B> forwardPointer) {
+
+		FieldOneToOne<T> result = new FieldOneToOne<T>(nodeType, forwardPointer, false);
+		builder.add(result);
+		builder.addDestructableField(result);
+		return result;
+	}
+
+	public static <T extends NdNode, B extends NdNode> FieldOneToOne<T> createOwner(StructDef<B> builder,
+			Class<T> nodeType, FieldOneToOne<B> forwardPointer) {
+
+		FieldOneToOne<T> result = new FieldOneToOne<T>(nodeType, forwardPointer, true);
+		builder.add(result);
+		builder.addDestructableField(result);
+		builder.addOwnerField(result);
+		return result;
+	}
+
+	public T get(Nd nd, long address) {
+		long ptr = nd.getDB().getRecPtr(address + this.offset);
+		return NdNode.load(nd, ptr, this.nodeType);
+	}
+
+	public void put(Nd nd, long address, T target) {
+		cleanup(nd, address);
+		nd.getDB().putRecPtr(address + this.offset, target == null ? 0 : target.address);
+		if (target == null && this.pointsToOwner) {
+			nd.scheduleDeletion(address);
+		}
+	}
+
+	@Override
+	public void destruct(Nd nd, long address) {
+		cleanup(nd, address);
+	}
+
+	private void cleanup(Nd nd, long address) {
+		Database db = nd.getDB();
+		long ptr = db.getRecPtr(address + this.offset);
+		if (ptr != 0) {
+			db.putRecPtr(ptr + this.backPointer.offset, 0);
+			// If we own our target, delete it
+			if (this.backPointer.pointsToOwner) {
+				nd.scheduleDeletion(ptr);
+			}
+		}
+	}
+
+	@Override
+	public void setOffset(int offset) {
+		this.offset = offset;
+	}
+
+	@Override
+	public int getRecordSize() {
+		return Database.PTR_SIZE;
+	}
+
+	@Override
+	public boolean hasReferences(Nd nd, long address) {
+		if (this.pointsToOwner) {
+			long ptr = nd.getDB().getRecPtr(address + this.offset);
+			return ptr != 0;
+		}
+		return false;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldPointer.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldPointer.java
new file mode 100644
index 0000000..fef3176
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldPointer.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+public class FieldPointer implements IField {
+	private int offset;
+
+	public FieldPointer() {
+	}
+
+	public long get(Nd nd, long address) {
+		Database db = nd.getDB();
+		return db.getRecPtr(address + this.offset);
+	}
+
+	public void put(Nd nd, long address, long newValue) {
+		nd.getDB().putRecPtr(address + this.offset, newValue);
+	}
+
+	@Override
+	public void setOffset(int offset) {
+		this.offset = offset;
+	}
+
+	@Override
+	public int getRecordSize() {
+		return Database.PTR_SIZE;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldSearchIndex.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldSearchIndex.java
new file mode 100644
index 0000000..f265fcf
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldSearchIndex.java
@@ -0,0 +1,298 @@
+/*******************************************************************************
+ * 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.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.NdNode;
+import org.eclipse.jdt.internal.core.nd.db.BTree;
+import org.eclipse.jdt.internal.core.nd.db.IBTreeComparator;
+import org.eclipse.jdt.internal.core.nd.db.IBTreeVisitor;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+
+/**
+ * Declares a field representing a case-insensitive search tree over elements which are a subtype of NdNode.
+ */
+public class FieldSearchIndex<T extends NdNode> implements IField, IDestructableField {
+	private int offset;
+	private final ITypeFactory<BTree> btreeFactory;
+	FieldSearchKey<?> searchKey;
+	private static IResultRank anything = new IResultRank() {
+		@Override
+		public long getRank(Nd nd, long address) {
+			return 1;
+		}
+	};
+ 
+	public static final class SearchCriteria {
+		private boolean matchCase = true;
+		private boolean isPrefix = false;
+		private char[] searchString;
+		private short requiredNodeType = -1;
+		private boolean matchingParentNodeAddress = false;
+
+		private SearchCriteria(char[] searchString) {
+			this.searchString = searchString;
+		}
+
+		public static SearchCriteria create(String searchString) {
+			return create(searchString.toCharArray());
+		}
+
+		public static SearchCriteria create(char[] searchString) {
+			return new SearchCriteria(searchString);
+		}
+
+		public SearchCriteria requireNodeType(short type) {
+			this.requiredNodeType = type;
+			return this;
+		}
+
+		public SearchCriteria allowAnyNodeType() {
+			this.requiredNodeType = -1;
+			return this;
+		}
+
+		public SearchCriteria matchCase(boolean match) {
+			this.matchCase = match;
+			return this;
+		}
+
+		public SearchCriteria prefix(boolean isPrefixSearch) {
+			this.isPrefix = isPrefixSearch;
+			return this;
+		}
+//
+//		public SearchCriteria requireParentNode(long parentNameAddress) {
+//			this.requiredParentNodeAddress = parentNameAddress;
+//			return this;
+//		}
+
+		public boolean isMatchingParentNodeAddress() {
+			return this.matchingParentNodeAddress;
+		}
+
+		public boolean isMatchingCase() {
+			return this.matchCase;
+		}
+
+		public boolean isPrefixSearch() {
+			return this.isPrefix;
+		}
+
+		public char[] getSearchString() {
+			return this.searchString;
+		}
+//
+//		public long getRequiredParentAddress() {
+//			return this.requiredParentNodeAddress;
+//		}
+
+		public boolean acceptsNodeType(short nodeType) {
+			return this.requiredNodeType == -1 || this.requiredNodeType == nodeType;
+		}
+
+		public boolean requiresSpecificNodeType() {
+			return this.requiredNodeType != -1;
+		}
+	}
+
+	public static interface IResultRank {
+		public long getRank(Nd nd, long address);
+	}
+
+	private abstract class SearchCriteriaToBtreeVisitorAdapter implements IBTreeVisitor {
+		private final SearchCriteria searchCriteria;
+		private final Nd nd;
+
+		public SearchCriteriaToBtreeVisitorAdapter(SearchCriteria searchCriteria, Nd nd) {
+			this.searchCriteria = searchCriteria;
+			this.nd = nd;
+		}
+
+		@Override
+		public int compare(long address) throws IndexException {
+			IString key = FieldSearchIndex.this.searchKey.get(this.nd, address);
+
+			if (this.searchCriteria.isPrefixSearch()) {
+				return key.comparePrefix(this.searchCriteria.getSearchString(), false);
+			} else {
+				return key.compareCompatibleWithIgnoreCase(this.searchCriteria.getSearchString());
+			}
+		}
+
+		@Override
+		public boolean visit(long address) throws IndexException {
+			if (this.searchCriteria.requiresSpecificNodeType()) {
+				short nodeType = NdNode.NODE_TYPE.get(this.nd, address);
+
+				if (!this.searchCriteria.acceptsNodeType(nodeType)) {
+					return true;
+				}
+			}
+
+			IString key = FieldSearchIndex.this.searchKey.get(this.nd, address);
+
+			if (this.searchCriteria.isMatchingCase()) {
+				if (this.searchCriteria.isPrefixSearch()) {
+					if (key.comparePrefix(this.searchCriteria.getSearchString(), true) != 0) {
+						return true;
+					}
+				} else {
+					if (key.compare(this.searchCriteria.getSearchString(), true) != 0) {
+						return true;
+					}
+				}
+			}
+
+			return acceptResult(address);
+		}
+
+		protected abstract boolean acceptResult(long address);
+	}
+
+	private FieldSearchIndex(FieldSearchKey<?> searchKey) {
+		this.btreeFactory = BTree.getFactory(new IBTreeComparator() {
+			@Override
+			public int compare(Nd nd, long record1, long record2) {
+				IString key1 = FieldSearchIndex.this.searchKey.get(nd, record1);
+				IString key2 = FieldSearchIndex.this.searchKey.get(nd, record2);
+
+				int cmp = key1.compareCompatibleWithIgnoreCase(key2);
+				if (cmp == 0) {
+					cmp = Long.signum(record1 - record2);
+				}
+
+				return cmp;
+			}
+		});
+
+		if (searchKey != null) {
+			if (searchKey.searchIndex != null && searchKey.searchIndex != this) {
+				throw new IllegalArgumentException(
+					"Attempted to construct a FieldSearchIndex referring to a search key that " //$NON-NLS-1$
+					+ "is already in use by a different index"); //$NON-NLS-1$
+			}
+			searchKey.searchIndex = this;
+		}
+		this.searchKey = searchKey;
+	}
+
+	public static <T extends NdNode, B> FieldSearchIndex<T> create(StructDef<B> builder,
+			final FieldSearchKey<B> searchKey) {
+
+		FieldSearchIndex<T> result = new FieldSearchIndex<T>(searchKey);
+
+		builder.add(result);
+		builder.addDestructableField(result);
+
+		return result;
+	}
+
+	public BTree get(Nd nd, long address) {
+		return this.btreeFactory.create(nd, address + this.offset);
+	}
+
+	@Override
+	public void destruct(Nd nd, long address) {
+		this.btreeFactory.destruct(nd, address);
+	}
+
+	@Override
+	public void setOffset(int offset) {
+		this.offset = offset;
+	}
+
+	@Override
+	public int getRecordSize() {
+		return this.btreeFactory.getRecordSize();
+	}
+
+	public T findFirst(final Nd nd, long address, final SearchCriteria searchCriteria) {
+		return findBest(nd, address, searchCriteria, anything);
+	}
+
+	@SuppressWarnings("unchecked")
+	public T findBest(final Nd nd, long address, final SearchCriteria searchCriteria, final IResultRank rankFunction) {
+		final long[] resultRank = new long[1];
+		final long[] result = new long[1];
+		get(nd, address).accept(new SearchCriteriaToBtreeVisitorAdapter(searchCriteria, nd) {
+			@Override
+			protected boolean acceptResult(long resultAddress) {
+				long rank = rankFunction.getRank(nd, resultAddress);
+				if (rank >= resultRank[0]) {
+					resultRank[0] = rank;
+					result[0] = resultAddress;
+				}
+				return true;
+			}
+		});
+
+		if (result[0] == 0) {
+			return null;
+		}
+		return (T)NdNode.load(nd, result[0]);
+	}
+
+	public interface Visitor<T> {
+		boolean visit(T toVisit);
+	}
+
+	public boolean visitAll(final Nd nd, long address, final SearchCriteria searchCriteria, final Visitor<T> visitor) {
+		return get(nd, address).accept(new SearchCriteriaToBtreeVisitorAdapter(searchCriteria, nd) {
+			@SuppressWarnings("unchecked")
+			@Override
+			protected boolean acceptResult(long resultAddress) {
+				return visitor.visit((T)NdNode.load(nd, resultAddress));
+			}
+		});
+	}
+
+	public List<T> findAll(final Nd nd, long address, final SearchCriteria searchCriteria) {
+		final List<T> result = new ArrayList<T>();
+		get(nd, address).accept(new SearchCriteriaToBtreeVisitorAdapter(searchCriteria, nd) {
+			@SuppressWarnings("unchecked")
+			@Override
+			protected boolean acceptResult(long resultAddress) {
+				result.add((T)NdNode.load(nd, resultAddress));
+				return true;
+			}
+		});
+
+		return result;
+	}
+
+	/**
+	 * Returns the entire contents of the index as a single list.
+	 */
+	public List<T> asList(final Nd nd, long address) {
+		final List<T> result = new ArrayList<T>();
+		get(nd, address).accept(new IBTreeVisitor() {
+			@Override
+			public int compare(long record) {
+				return 0;
+			}
+
+			@SuppressWarnings("unchecked")
+			@Override
+			public boolean visit(long resultAddress) {
+				result.add((T)NdNode.load(nd, resultAddress));
+				return true;
+			}
+		});
+
+		return result;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldSearchKey.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldSearchKey.java
new file mode 100644
index 0000000..1b585cb
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldSearchKey.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * 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.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.db.BTree;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.db.EmptyString;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+
+/**
+ * Represents a search key into a global search index.
+ */
+public class FieldSearchKey<T> implements IField, IDestructableField {
+	private int offset;
+	FieldSearchIndex<?> searchIndex;
+
+	private FieldSearchKey(FieldSearchIndex<?> searchIndex) {
+		if (searchIndex != null) {
+			if (searchIndex.searchKey != null && searchIndex.searchKey != this) {
+				throw new IllegalArgumentException(
+					"Attempted to construct a FieldSearchKey referring to a search index that is " //$NON-NLS-1$
+					+ "already in use by a different key"); //$NON-NLS-1$
+			}
+			searchIndex.searchKey = this;
+		}
+		this.searchIndex = searchIndex;
+	}
+
+	/**
+	 * Creates a search key attribute in the given struct which stores an entry in the given global search index
+	 */
+	public static <T, B extends NdNode> FieldSearchKey<T> create(StructDef<B> builder,
+			FieldSearchIndex<B> searchIndex) {
+		FieldSearchKey<T> result = new FieldSearchKey<T>(searchIndex);
+
+		builder.add(result);
+		builder.addDestructableField(result);
+
+		return result;
+	}
+
+	public void put(Nd nd, long address, String newString) {
+		put(nd, address, newString.toCharArray());
+	}
+
+	/**
+	 * Sets the value of the key and inserts it into the index if it is not already present
+	 */
+	public void put(Nd nd, long address, char[] newString) {
+		cleanup(nd, address);
+
+		Database db = nd.getDB();
+		BTree btree = this.searchIndex.get(nd, Database.DATA_AREA_OFFSET);
+		db.putRecPtr(address + this.offset, db.newString(newString).getRecord());
+		btree.insert(address);
+	}
+
+	public IString get(Nd nd, long address) {
+		Database db = nd.getDB();
+		long namerec = db.getRecPtr(address + this.offset);
+
+		if (namerec == 0) {
+			return EmptyString.create();
+		}
+		return db.getString(namerec);
+	}
+
+	@Override
+	public void destruct(Nd nd, long address) {
+		cleanup(nd, address);
+	}
+
+	private void cleanup(Nd nd, long address) {
+		boolean isInIndex = isInIndex(nd, address);
+
+		if (isInIndex) {
+			// Remove this entry from the search index
+			this.searchIndex.get(nd, Database.DATA_AREA_OFFSET).delete(address);
+
+			get(nd, address).delete();
+			nd.getDB().putRecPtr(address + this.offset, 0);
+		}
+	}
+
+	/**
+	 * Clears this key and removes it from the search index
+	 */
+	public void removeFromIndex(Nd nd, long address) {
+		cleanup(nd, address);
+	}
+
+	/**
+	 * Returns true iff this key is currently in the index
+	 */
+	public boolean isInIndex(Nd nd, long address) {
+		long fieldAddress = address + this.offset;
+		Database db = nd.getDB();
+		long namerec = db.getRecPtr(fieldAddress);
+
+		boolean isInIndex = namerec != 0;
+		return isInIndex;
+	}
+
+	@Override
+	public void setOffset(int offset) {
+		this.offset = offset;
+	}
+
+	@Override
+	public int getRecordSize() {
+		return FieldString.RECORD_SIZE;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldShort.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldShort.java
new file mode 100644
index 0000000..fe2a56b
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldShort.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Declares a Nd field of type short. Can be used in place of  {@link Field}&lt{@link Short}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldShort implements IField {
+	private int offset;
+
+	public FieldShort() {
+	}
+
+	public short get(Nd nd, long address) {
+		Database db = nd.getDB();
+		return db.getShort(address + this.offset);
+	}
+
+	public void put(Nd nd, long address, short newValue) {
+		nd.getDB().putShort(address + this.offset, newValue);
+	}
+
+	@Override
+	public void setOffset(int offset) {
+		this.offset = offset;
+	}
+
+	@Override
+	public int getRecordSize() {
+		return Database.SHORT_SIZE;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldString.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldString.java
new file mode 100644
index 0000000..ddd4493
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldString.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * 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.field;
+
+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.EmptyString;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+
+/**
+ * Declares a Nd field of type string. Can be used in place of  {@link Field}&lt{@link String}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldString implements IDestructableField, IField {
+	public static final int RECORD_SIZE = Database.STRING_SIZE;
+	private static final char[] EMPTY_CHAR_ARRAY = new char[0];
+	private int offset;
+
+	public FieldString() {
+	}
+
+	public IString get(Nd nd, long address) {
+		Database db = nd.getDB();
+		long namerec = db.getRecPtr(address + this.offset);
+
+		if (namerec == 0) {
+			return EmptyString.create();
+		}
+		return db.getString(namerec);
+	}
+
+	public void put(Nd nd, long address, char[] newString) {
+		if (newString == null) {
+			newString = EMPTY_CHAR_ARRAY;
+		}
+		final Database db= nd.getDB();
+		IString name= get(nd, address);
+		if (name.compare(newString, true) != 0) {
+			name.delete();
+			if (newString != null && newString.length > 0) {
+				db.putRecPtr(address + this.offset, db.newString(newString).getRecord());
+			} else {
+				db.putRecPtr(address + this.offset, 0);
+			}
+		}
+	}
+
+	public void put(Nd nd, long address, String newString) {
+		put(nd, address, newString.toCharArray());
+	}
+
+	public void destruct(Nd nd, long address) {
+		get(nd, address).delete();
+		nd.getDB().putRecPtr(address + this.offset, 0);
+	}
+
+	@Override
+	public void setOffset(int offset) {
+		this.offset = offset;
+	}
+
+	@Override
+	public int getRecordSize() {
+		return RECORD_SIZE;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IDestructableField.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IDestructableField.java
new file mode 100644
index 0000000..12d216c
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IDestructableField.java
@@ -0,0 +1,17 @@
+/*******************************************************************************
+ * 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.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+public interface IDestructableField {
+	public void destruct(Nd nd, long address);
+}
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
new file mode 100644
index 0000000..979a0eb
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IField.java
@@ -0,0 +1,16 @@
+/*******************************************************************************
+ * 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.field;
+
+public interface IField {
+	void setOffset(int offset);
+	int getRecordSize();
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IRefCountedField.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IRefCountedField.java
new file mode 100644
index 0000000..89c2783
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IRefCountedField.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * 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.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+public interface IRefCountedField {
+	/**
+	 * Returns true if this field knows of any remaining incoming references to this object. This is
+	 * used by the implementation of {@link FieldManyToOne} to determine whether or not
+	 * a refcounted object should be deleted after a reference is removed.
+	 * <p>
+	 * Implementations should return false if the refcount is 0 or true if the refcount
+	 * is nonzero.
+	 */	
+	public boolean hasReferences(Nd nd, long address);
+}
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
new file mode 100644
index 0000000..261e853
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/StructDef.java
@@ -0,0 +1,398 @@
+/*******************************************************************************
+ * 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.field;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.IDestructable;
+import org.eclipse.jdt.internal.core.nd.ITypeFactory;
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+/**
+ * Defines a data structure that will appear in the database.
+ * <p>
+ * There are three mechanisms for deleting a struct from the database:
+ * <ul>
+ * <li>Explicit deletion. This happens synchronously via manual calls to Nd.delete. Structs intended for manual
+ *     deletion have refCounted=false and an empty ownerFields.
+ * <li>Owner pointers. Such structs have one or more outbound pointers to an "owner" object. They are deleted
+ *     asynchronously when the last owner pointer is deleted. The structs have refCounted=false and a nonempty
+ *     ownerFields.
+ * <li>Refcounting. Such structs are deleted asynchronously when all elements are removed from all of their ManyToOne
+ *     relationships which are not marked as incoming owner pointers. Owner relationships need to be excluded from
+ *     refcounting since they would always create cycles. These structs have refCounted=true.
+ * </ul>
+ * <p>
+ * Structs deleted by refcounting and owner pointers are not intended to inherit from one another, but anything may
+ * inherit from a struct that uses manual deletion and anything may inherit from a struct that uses the same deletion
+ * mechanism.
+ */
+public final class StructDef<T> {
+	Class<T> clazz;
+	private StructDef<? super T> superClass;
+	private List<IField> fields = new ArrayList<>();
+	private boolean doneCalled;
+	private boolean offsetsComputed;
+	private List<StructDef<? extends T>> subClasses = new ArrayList<>();
+	private int size;
+	List<IDestructableField> destructableFields = new ArrayList<>();
+	boolean refCounted;
+	private List<IRefCountedField> refCountedFields = new ArrayList<>();
+	private List<IRefCountedField> ownerFields = new ArrayList<>();
+	boolean isAbstract;
+	private ITypeFactory<T> factory;
+	protected boolean hasUserDestructor;
+	private DeletionSemantics deletionSemantics;
+
+	public static enum DeletionSemantics {
+		EXPLICIT, OWNED, REFCOUNTED
+	}
+
+	private StructDef(Class<T> clazz) {
+		this(clazz, null);
+	}
+
+	private StructDef(Class<T> clazz, StructDef<? super T> superClass) {
+		this(clazz, superClass, Modifier.isAbstract(clazz.getModifiers()));
+	}
+
+	private StructDef(Class<T> clazz, StructDef<? super T> superClass, boolean isAbstract) {
+		this.clazz = clazz;
+		this.superClass = superClass;
+		if (this.superClass != null) {
+			this.superClass.subClasses.add(this);
+		}
+		this.isAbstract = isAbstract;
+		final String fullyQualifiedClassName = clazz.getName();
+
+		final Constructor<T> constructor;
+		if (!this.isAbstract) {
+			try {
+				constructor = clazz.getConstructor(new Class<?>[] { Nd.class, long.class });
+			} catch (NoSuchMethodException | SecurityException e) {
+				throw new IllegalArgumentException("The node class " + fullyQualifiedClassName //$NON-NLS-1$
+						+ " does not have an appropriate constructor for it to be used with Nd"); //$NON-NLS-1$
+			}
+		} else {
+			constructor = null;
+		}
+
+		this.hasUserDestructor = IDestructable.class.isAssignableFrom(clazz);
+
+		this.factory = new ITypeFactory<T>() {
+			public T create(Nd dom, long address) {
+				if (StructDef.this.isAbstract) {
+					throw new UnsupportedOperationException(
+							"Attempting to instantiate abstract class" + fullyQualifiedClassName); //$NON-NLS-1$
+				}
+
+				try {
+					return constructor.newInstance(dom, address);
+				} catch (InvocationTargetException e) {
+					Throwable target = e.getCause();
+
+					if (target instanceof RuntimeException) {
+						throw (RuntimeException) target;
+					}
+
+					throw new RuntimeException("Error in AutoTypeFactory", e); //$NON-NLS-1$
+				} catch (InstantiationException | IllegalAccessException e) {
+					throw new RuntimeException("Error in AutoTypeFactory", e); //$NON-NLS-1$
+				}
+			}
+
+			public int getRecordSize() {
+				return StructDef.this.size();
+			}
+
+			public boolean hasDestructor() {
+				return StructDef.this.hasUserDestructor || hasDestructableFields(); 
+			}
+
+			public Class<?> getElementClass() {
+				return StructDef.this.clazz;
+			}
+
+			public void destruct(Nd nd, long address) {
+				checkNotMutable();
+				if (StructDef.this.hasUserDestructor) {
+					IDestructable destructable = (IDestructable)create(nd, address);
+					destructable.destruct();
+				}
+				destructFields(nd, address);
+			}
+
+			public void destructFields(Nd dom, long address) {
+				StructDef.this.destructFields(dom, address);
+			}
+
+			@Override
+			public boolean isReadyForDeletion(Nd dom, long address) {
+				return StructDef.this.isReadyForDeletion(dom, address);
+			}
+			
+			@Override
+			public DeletionSemantics getDeletionSemantics() {
+				return StructDef.this.getDeletionSemantics();
+			}
+		};
+	}
+
+	public Class<T> getStructClass() {
+		return this.clazz;
+	}
+
+	@Override
+	public String toString() {
+		return this.clazz.getName();
+	}
+
+	public static <T> StructDef<T> createAbstract(Class<T> clazz) {
+		return new StructDef<T>(clazz, null, true);
+	}
+
+	public static <T> StructDef<T> createAbstract(Class<T> clazz, StructDef<? super T> superClass) {
+		return new StructDef<T>(clazz, superClass, true);
+	}
+
+	public static <T> StructDef<T> create(Class<T> clazz) {
+		return new StructDef<T>(clazz);
+	}
+
+	public static <T> StructDef<T> create(Class<T> clazz, StructDef<? super T> superClass) {
+		return new StructDef<T>(clazz, superClass);
+	}
+
+	protected boolean isReadyForDeletion(Nd dom, long address) {
+		List<IRefCountedField> toIterate = Collections.EMPTY_LIST;
+		switch (this.deletionSemantics) {
+			case EXPLICIT: return false;
+			case OWNED: toIterate = this.ownerFields; break;
+			case REFCOUNTED: toIterate = this.refCountedFields; break;
+		}
+
+		for (IRefCountedField next : toIterate) {
+			if (next.hasReferences(dom, address)) {
+				return false;
+			}
+		}
+
+		final StructDef<? super T> localSuperClass = StructDef.this.superClass;
+		if (localSuperClass != null && localSuperClass.deletionSemantics != DeletionSemantics.EXPLICIT) {
+			return localSuperClass.isReadyForDeletion(dom, address);
+		}
+		return true;
+	}
+
+	protected boolean hasDestructableFields() {
+		return (!StructDef.this.destructableFields.isEmpty() || 
+				(StructDef.this.superClass != null && StructDef.this.superClass.hasDestructableFields()));
+	}
+
+	public DeletionSemantics getDeletionSemantics() {
+		return this.deletionSemantics;
+	}
+
+	/**
+	 * Call this once all the fields have been added to the struct definition and it is
+	 * ready to use.
+	 */
+	public void done() {
+		if (this.doneCalled) {
+			throw new IllegalStateException("May not call done() more than once"); //$NON-NLS-1$
+		}
+		this.doneCalled = true;
+
+		if (this.superClass == null || this.superClass.areOffsetsComputed()) {
+			computeOffsets();
+		}
+	}
+
+	public void add(IField toAdd) {
+		checkMutable();
+
+		this.fields.add(toAdd);
+	}
+
+	public void addDestructableField(IDestructableField field) {
+		checkMutable();
+
+		this.destructableFields.add(field);
+	}
+
+	public StructDef<T> useStandardRefCounting() {
+		checkMutable();
+
+		this.refCounted = true;
+		return this;
+	}
+
+	public void addRefCountedField(IRefCountedField result) {
+		checkMutable();
+
+		this.refCountedFields.add(result);
+	}
+
+	public void addOwnerField(IRefCountedField result) {
+		checkMutable();
+
+		this.ownerFields.add(result);
+	}
+
+	public boolean areOffsetsComputed() {
+		return this.offsetsComputed;
+	}
+
+	public int size() {
+		checkNotMutable();
+		return this.size;
+	}
+
+	void checkNotMutable() {
+		if (!this.offsetsComputed) {
+			throw new IllegalStateException("Must call done() before using the struct"); //$NON-NLS-1$
+		}
+	}
+
+	private void checkMutable() {
+		if (this.doneCalled) {
+			throw new IllegalStateException("May not modify a StructDef after done() has been called"); //$NON-NLS-1$
+		}
+	}
+
+	/**
+	 * Invoked on all StructDef after both {@link #done()} has been called on the struct and
+	 * {@link #computeOffsets()} has been called on their base class.
+	 */
+	private void computeOffsets() {
+		int offset = this.superClass == null ? 0 : this.superClass.size();
+
+		for (IField next : this.fields) {
+			next.setOffset(offset);
+			offset += next.getRecordSize();
+		}
+
+		this.size = offset;
+		if (this.refCounted) {
+			this.deletionSemantics = DeletionSemantics.REFCOUNTED;
+		} else {
+			if (!this.ownerFields.isEmpty()) {
+				this.deletionSemantics = DeletionSemantics.OWNED;
+			} else if (this.superClass != null) {
+				this.deletionSemantics = this.superClass.deletionSemantics;
+			} else {
+				this.deletionSemantics = DeletionSemantics.EXPLICIT;
+			}
+		}
+		// Now verify that the deletion semantics of this struct are compatible with the deletion
+		// semantics of its superclass
+		if (this.superClass != null && this.deletionSemantics != this.superClass.deletionSemantics) {
+			if (this.superClass.deletionSemantics != DeletionSemantics.EXPLICIT) {
+				throw new IllegalStateException("A class (" + this.clazz.getName() + ") that uses "  //$NON-NLS-1$//$NON-NLS-2$
+					+ this.deletionSemantics.toString() + " deletion semantics may not inherit from a class " //$NON-NLS-1$
+					+ "that uses " + this.superClass.deletionSemantics.toString() + " semantics");  //$NON-NLS-1$//$NON-NLS-2$
+			}
+		}
+		
+		this.offsetsComputed = true;
+
+		for (StructDef<? extends T> next : this.subClasses) {
+			if (next.doneCalled) {
+				next.computeOffsets();
+			}
+		}
+	}
+
+	public FieldPointer addPointer() {
+		FieldPointer result = new FieldPointer();
+		add(result);
+		return result;
+	}
+
+	public FieldShort addShort() {
+		FieldShort result = new FieldShort();
+		add(result);
+		return result;
+	}
+
+	public FieldInt addInt() {
+		FieldInt result = new FieldInt();
+		add(result);
+		return result;
+	}
+
+	public FieldLong addLong() {
+		FieldLong result = new FieldLong();
+		add(result);
+		return result;
+	}
+
+	public FieldString addString() {
+		FieldString result = new FieldString();
+		add(result);
+		addDestructableField(result);
+		return result;
+	}
+
+	public FieldDouble addDouble() {
+		FieldDouble result = new FieldDouble();
+		add(result);
+		return result;
+	}
+
+	public FieldFloat addFloat() {
+		FieldFloat result = new FieldFloat();
+		add(result);
+		return result;
+	}
+
+	public FieldByte addByte() {
+		FieldByte result = new FieldByte();
+		add(result);
+		return result;
+	}
+
+	public FieldChar addChar() {
+		FieldChar result = new FieldChar();
+		add(result);
+		return result;
+	}
+
+	public <F> Field<F> add(ITypeFactory<F> factory1) {
+		Field<F> result = new Field<>(factory1);
+		add(result);
+		if (result.factory.hasDestructor()) {
+			this.destructableFields.add(result);
+		}
+		return result;
+	}
+
+	public ITypeFactory<T> getFactory() {
+		return this.factory;
+	}
+
+	void destructFields(Nd dom, long address) {
+		for (IDestructableField next : StructDef.this.destructableFields) {
+			next.destruct(dom, address);
+		}
+
+		if (this.superClass != null) {
+			this.superClass.destructFields(dom, address);
+		}
+	}
+
+	
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/BindingToIndexConverter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/BindingToIndexConverter.java
new file mode 100644
index 0000000..a8788eb
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/BindingToIndexConverter.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * 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.indexer;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.dom.IAnnotationBinding;
+import org.eclipse.jdt.core.dom.IBinding;
+import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
+import org.eclipse.jdt.core.dom.IMethodBinding;
+import org.eclipse.jdt.core.dom.IPackageBinding;
+import org.eclipse.jdt.core.dom.ITypeBinding;
+import org.eclipse.jdt.core.dom.IVariableBinding;
+import org.eclipse.jdt.internal.core.nd.Nd;
+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.NdResourceFile;
+import org.eclipse.jdt.internal.core.nd.java.NdTreeNode;
+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.NdTypeInterface;
+
+public class BindingToIndexConverter {
+	private static final boolean ENABLE_LOGGING = false;
+	private JavaIndex index;
+	private NdResourceFile resource;
+
+	public BindingToIndexConverter(NdResourceFile resource) {
+		this.resource = resource;
+		this.index = JavaIndex.getIndex(resource.getNd());
+	}
+
+	public void addBinding(NdTreeNode parent, IBinding binding, IProgressMonitor monitor) {
+		switch (binding.getKind()) {
+			case IBinding.TYPE:
+				addType((ITypeBinding) binding, monitor);
+				break;
+			case IBinding.ANNOTATION:
+				addAnnotation(parent, (IAnnotationBinding) binding, monitor);
+				break;
+			case IBinding.METHOD:
+				addMethod(parent, (IMethodBinding) binding, monitor);
+				break;
+			case IBinding.VARIABLE:
+				addVariable(parent, (IVariableBinding) binding, monitor);
+				break;
+			case IBinding.PACKAGE:
+				addPackage(parent, (IPackageBinding) binding, monitor);
+				break;
+			case IBinding.MEMBER_VALUE_PAIR:
+				addMemberValuePair(parent, (IMemberValuePairBinding) binding, monitor);
+				break;
+			default:
+				Package.log("Encountered unknown binding type: " + binding.getKind(), null); //$NON-NLS-1$
+		}
+	}
+
+	public void addMemberValuePair(NdTreeNode parent, IMemberValuePairBinding binding, IProgressMonitor monitor) {
+		logInfo("Adding member value pair: " + binding.getName()); //$NON-NLS-1$
+	}
+
+	public void addPackage(NdTreeNode parent, IPackageBinding binding, IProgressMonitor monitor) {
+		logInfo("Adding package: " + binding.getName()); //$NON-NLS-1$
+	}
+
+	public void addVariable(NdTreeNode parent, IVariableBinding binding, IProgressMonitor monitor) {
+		logInfo("Adding variable: " + binding.getName()); //$NON-NLS-1$
+	}
+
+	public void addMethod(NdTreeNode parent, IMethodBinding binding, IProgressMonitor monitor) {
+		logInfo("Adding method: " + binding.getName()); //$NON-NLS-1$
+	}
+
+	public void addAnnotation(NdTreeNode parent, IAnnotationBinding binding, IProgressMonitor monitor) {
+		logInfo("Adding annotation: " + binding.getName()); //$NON-NLS-1$
+	}
+
+	public NdType addType(ITypeBinding binding, IProgressMonitor monitor) {
+		logInfo("Adding type: " + binding.getBinaryName()); //$NON-NLS-1$
+
+		NdTypeId name = makeTypeId(binding);
+		NdType type = name.findTypeByResourceAddress(this.resource.address);
+
+		if (type == null) {
+			type = new NdType(getNd(), this.resource);
+		}
+
+		type.setTypeId(name);
+
+		ITypeBinding superclass = binding.getSuperclass();
+
+		if (superclass != null) {
+			type.setSuperclass(makeTypeId(superclass));
+		}
+
+		for (ITypeBinding next : binding.getInterfaces()) {
+			new NdTypeInterface(getNd(), type, makeTypeId(next));
+		}
+
+		return type;
+	}
+
+	private void logInfo(String string) {
+		if (ENABLE_LOGGING) {
+			Package.logInfo(string);
+		}
+	}
+
+	private NdTypeId makeTypeId(ITypeBinding forBinding) {
+		return this.index.createTypeId(JavaNames.binaryNameToFieldDescriptor(forBinding.getBinaryName().toCharArray()));
+	}
+
+	private Nd getNd() {
+		return this.resource.getNd();
+	}
+}
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
new file mode 100644
index 0000000..afd740e
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/ClassFileToIndexConverter.java
@@ -0,0 +1,930 @@
+/*******************************************************************************
+ * 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.indexer;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.IClassFile;
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.codegen.AnnotationTargetTypeConstants;
+import org.eclipse.jdt.internal.compiler.env.ClassSignature;
+import org.eclipse.jdt.internal.compiler.env.EnumConstantSignature;
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryElementValuePair;
+import org.eclipse.jdt.internal.compiler.env.IBinaryField;
+import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.lookup.SignatureWrapper;
+import org.eclipse.jdt.internal.core.JarPackageFragmentRoot;
+import org.eclipse.jdt.internal.core.JavaModelManager;
+import org.eclipse.jdt.internal.core.Openable;
+import org.eclipse.jdt.internal.core.PackageFragment;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+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;
+import org.eclipse.jdt.internal.core.nd.java.NdConstant;
+import org.eclipse.jdt.internal.core.nd.java.NdConstantAnnotation;
+import org.eclipse.jdt.internal.core.nd.java.NdConstantArray;
+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;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeSignature;
+import org.eclipse.jdt.internal.core.nd.java.NdVariable;
+import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeDescriptor;
+import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeFactory;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+import org.eclipse.jdt.internal.core.util.Util;
+
+public final class ClassFileToIndexConverter {
+	private static final char[] JAVA_LANG_OBJECT_FIELD_DESCRIPTOR = "Ljava/lang/Object;".toCharArray(); //$NON-NLS-1$
+	private static final char[] INNER_TYPE_SEPARATOR = new char[] { '$' };
+	private static final char[] FIELD_DESCRIPTOR_SUFFIX = new char[] { ';' };
+	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];
+	private static final char[] PATH_SEPARATOR = new char[]{'/'};
+	private static final char[] ARRAY_FIELD_DESCRIPTOR_PREFIX = new char[] { '[' };
+	private NdResourceFile resource;
+	private JavaIndex index;
+
+	public ClassFileToIndexConverter(NdResourceFile resourceFile) {
+		this.resource = resourceFile;
+		this.index = JavaIndex.getIndex(resourceFile.getNd());
+	}
+
+	private Nd getNd() {
+		return this.resource.getNd();
+	}
+
+	public static IBinaryType getTypeFromClassFile(IClassFile iClassFile, IProgressMonitor monitor)
+			throws CoreException, ClassFormatException {
+		BinaryTypeDescriptor descriptor = BinaryTypeFactory.createDescriptor(iClassFile);
+		return BinaryTypeFactory.rawReadType(descriptor, true);
+	}
+
+	/**
+	 * Create a type info from the given class file in a jar and adds it to the given list of infos.
+	 *
+	 * @throws CoreException
+	 */
+	protected static IBinaryType createInfoFromClassFileInJar(Openable classFile) throws CoreException {
+		PackageFragment pkg = (PackageFragment) classFile.getParent();
+		String classFilePath = Util.concatWith(pkg.names, classFile.getElementName(), '/');
+		IBinaryType info = null;
+		java.util.zip.ZipFile zipFile = null;
+		try {
+			zipFile = ((JarPackageFragmentRoot) pkg.getParent()).getJar();
+			info = org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader.read(zipFile, classFilePath);
+		} catch (Exception e) {
+			throw new CoreException(Package.createStatus("Unable to parse JAR file", e)); //$NON-NLS-1$
+		} finally {
+			JavaModelManager.getJavaModelManager().closeZipFile(zipFile);
+		}
+		return info;
+	}
+
+	/**
+	 * Adds a type to the index, given an input class file and a binary name. Note that the given binary name is 
+	 * 
+	 * @param binaryType an object used for parsing the .class file itself
+	 * @param fieldDescriptor the name that is used to locate the class, computed from the .class file's name and location.
+	 * In the event that the .class file has been moved, this may differ from the binary name stored in the .class file
+	 * itself, which is why this is received as an argument rather than extracted from the .class file.
+	 * @throws CoreException
+	 */
+	public NdType addType(IBinaryType binaryType, char[] fieldDescriptor, IProgressMonitor monitor) throws CoreException {
+		char[] fieldDescriptorFromClass = JavaNames.binaryNameToFieldDescriptor(binaryType.getName());
+		logInfo("adding binary type " + new String(fieldDescriptor)); //$NON-NLS-1$
+
+		NdTypeId name = createTypeIdFromFieldDescriptor(fieldDescriptor);
+		NdType type = name.findTypeByResourceAddress(this.resource.address);
+
+		if (type == null) {
+			type = new NdType(getNd(), this.resource);
+		}
+
+		IBinaryTypeAnnotation[] typeAnnotations = binaryType.getTypeAnnotations();
+		if (typeAnnotations != null) {
+			for (IBinaryTypeAnnotation typeAnnotation : typeAnnotations) {
+				NdTypeAnnotationInType annotation = new NdTypeAnnotationInType(getNd(), type);
+
+				initTypeAnnotation(annotation, typeAnnotation);
+			}
+		}
+
+		type.setTypeId(name);
+
+		if (!CharArrayUtils.equals(fieldDescriptorFromClass, fieldDescriptor)) {
+			type.setFieldDescriptorFromClass(fieldDescriptorFromClass);
+		}
+
+		char[][] interfaces = binaryType.getInterfaceNames();
+		if (interfaces == null) {
+			interfaces = EMPTY_CHAR_ARRAY_ARRAY;
+		}
+
+		if (binaryType.getGenericSignature() != null) {
+			type.setFlag(NdType.FLG_GENERIC_SIGNATURE_PRESENT, true);
+		}
+
+		// Create the default generic signature if the .class file didn't supply one
+		SignatureWrapper signatureWrapper = GenericSignatures.getGenericSignature(binaryType);
+
+		type.setModifiers(binaryType.getModifiers());
+		type.setDeclaringType(createTypeIdFromBinaryName(binaryType.getEnclosingTypeName()));
+
+		readTypeParameters(type, signatureWrapper);
+
+		char[] superclassFieldDescriptor;
+		char[] superclassBinaryName = binaryType.getSuperclassName();
+		if (superclassBinaryName == null) {
+			superclassFieldDescriptor = JAVA_LANG_OBJECT_FIELD_DESCRIPTOR;
+		} else {
+			superclassFieldDescriptor = JavaNames.binaryNameToFieldDescriptor(superclassBinaryName);
+		}
+		type.setSuperclass(createTypeSignature(signatureWrapper, superclassFieldDescriptor));
+
+		short interfaceIdx = 0;
+		while (signatureWrapper.start < signatureWrapper.signature.length) {
+			// Note that there may be more interfaces listed in the generic signature than in the interfaces list.
+			// Although the VM spec doesn't discuss this case specifically, there are .class files in the wild with
+			// this characteristic. In such cases, we take what's in the generic signature and discard what's in the
+			// interfaces list.
+			char[] interfaceSpec = interfaceIdx < interfaces.length ? interfaces[interfaceIdx] : EMPTY_CHAR_ARRAY;
+			new NdTypeInterface(getNd(), type,
+					createTypeSignature(signatureWrapper, JavaNames.binaryNameToFieldDescriptor(interfaceSpec)));
+			interfaceIdx++;
+		}
+
+		IBinaryAnnotation[] annotations = binaryType.getAnnotations();
+		attachAnnotations(type, annotations);
+
+		type.setDeclaringMethod(createMethodId(binaryType.getEnclosingTypeName(), binaryType.getEnclosingMethod()));
+
+		IBinaryField[] fields = binaryType.getFields();
+
+		if (fields != null) {
+			for (IBinaryField nextField : fields) {
+				addField(type, nextField);
+			}
+		}
+
+		IBinaryMethod[] methods = binaryType.getMethods();
+
+		if (methods != null) {
+			for (IBinaryMethod next : methods) {
+				addMethod(type, next, binaryType);
+			}
+		}
+
+		char[][][] missingTypeNames = binaryType.getMissingTypeNames();
+		char[] missingTypeString = getMissingTypeString(missingTypeNames);
+
+		type.setMissingTypeNames(missingTypeString);
+		type.setSourceFileName(binaryType.sourceFileName());
+		type.setAnonymous(binaryType.isAnonymous());
+		type.setIsLocal(binaryType.isLocal());
+		type.setIsMember(binaryType.isMember());
+		type.setTagBits(binaryType.getTagBits());
+		type.setSourceNameOverride(binaryType.getSourceName());
+
+		return type;
+	}
+
+	private static char[] getMissingTypeString(char[][][] missingTypeNames) {
+		char[] missingTypeString = null;
+		if (missingTypeNames != null) {
+			CharArrayBuffer builder = new CharArrayBuffer();
+			for (int typeIdx = 0; typeIdx < missingTypeNames.length; typeIdx++) {
+				char[][] next = missingTypeNames[typeIdx];
+				if (typeIdx != 0) {
+					builder.append(COMMA);
+				}
+				if (next == null) {
+					continue;
+				}
+				for (int segmentIdx = 0; segmentIdx < next.length; segmentIdx++) {
+					char[] segment = next[segmentIdx];
+					if (segment == null) {
+						continue;
+					}
+					if (segmentIdx != 0) {
+						builder.append(PATH_SEPARATOR);
+					}
+					builder.append(segment);
+				}
+			}
+			missingTypeString = builder.getContents();
+		}
+		return missingTypeString;
+	}
+
+	private void attachAnnotations(NdMethod method, IBinaryAnnotation[] annotations) {
+		if (annotations != null) {
+			for (IBinaryAnnotation next : annotations) {
+				NdAnnotationInMethod annotation = new NdAnnotationInMethod(getNd(), method);
+				initAnnotation(annotation, next);
+			}
+		}
+	}
+
+	private void attachAnnotations(NdType type, IBinaryAnnotation[] annotations) {
+		if (annotations != null) {
+			for (IBinaryAnnotation next : annotations) {
+				NdAnnotationInType annotation = new NdAnnotationInType(getNd(), type);
+				initAnnotation(annotation, next);
+			}
+		}
+	}
+
+	private void attachAnnotations(NdVariable variable, IBinaryAnnotation[] annotations) {
+		if (annotations != null) {
+			for (IBinaryAnnotation next : annotations) {
+				NdAnnotationInVariable annotation = new NdAnnotationInVariable(getNd(), variable);
+				initAnnotation(annotation, next);
+			}
+		}
+	}
+
+	private void attachAnnotations(NdMethodParameter variable, IBinaryAnnotation[] annotations) {
+		if (annotations != null) {
+			for (IBinaryAnnotation next : annotations) {
+				NdAnnotationInMethodParameter annotation = new NdAnnotationInMethodParameter(getNd(), variable);
+				initAnnotation(annotation, next);
+			}
+		}
+	}
+
+	/**
+	 * Adds the given method to the given type
+	 *
+	 * @throws CoreException
+	 */
+	private void addMethod(NdType type, IBinaryMethod next, IBinaryType binaryType)
+			throws CoreException {
+		int flags = 0;
+		NdMethod method = new NdMethod(type);
+
+		attachAnnotations(method, next.getAnnotations());
+
+		if (next.getGenericSignature() != null) {
+			flags |= NdMethod.FLG_GENERIC_SIGNATURE_PRESENT;
+		}
+		SignatureWrapper signature = GenericSignatures.getGenericSignature(next);
+		SignatureWrapper descriptor = new SignatureWrapper(next.getMethodDescriptor());
+		readTypeParameters(method, signature);
+
+		IBinaryTypeAnnotation[] typeAnnotations = next.getTypeAnnotations();
+		if (typeAnnotations != null) {
+			for (IBinaryTypeAnnotation typeAnnotation : typeAnnotations) {
+				NdTypeAnnotationInMethod annotation = new NdTypeAnnotationInMethod(getNd(), method);
+
+				initTypeAnnotation(annotation, typeAnnotation);
+			}
+		}
+
+		skipChar(signature, '(');
+		skipChar(descriptor, '(');
+
+		List<char[]> parameterFieldDescriptors = new ArrayList<>();
+		while (!descriptor.atEnd()) {
+			if (descriptor.charAtStart() == ')') {
+				skipChar(descriptor, ')');
+				break;
+			}
+			parameterFieldDescriptors.add(readNextFieldDescriptor(descriptor));
+		}
+
+		char[][] parameterNames = next.getArgumentNames();
+		int numArgumentsInGenericSignature = countMethodArguments(signature);
+		int numCompilerDefinedParameters = Math.max(0,
+				parameterFieldDescriptors.size() - numArgumentsInGenericSignature);
+		
+		boolean compilerDefinedParametersAreIncludedInSignature = (next.getGenericSignature() == null);
+
+		// If there is no generic signature, then fall back to heuristics based on what we know about
+		// where the java compiler likes to create compiler-defined arguments
+		if (compilerDefinedParametersAreIncludedInSignature) {
+			// Constructors for non-static member types get a compiler-defined first argument
+			if (binaryType.isMember() 
+					&& next.isConstructor() 
+					&& ((binaryType.getModifiers() & Modifier.STATIC) == 0)
+					&& parameterFieldDescriptors.size() > 0) {
+
+				numCompilerDefinedParameters = 1;
+			} else {
+				numCompilerDefinedParameters = 0;
+			}
+		}
+
+		int parameterNameIdx = 0;
+		int annotatedParametersCount = next.getAnnotatedParametersCount();
+
+		short descriptorParameterIdx = 0;
+		char[] binaryTypeName = binaryType.getName();
+		while (!signature.atEnd()) {
+			if (signature.charAtStart() == ')') {
+				skipChar(signature, ')');
+				break;
+			}
+			char[] nextFieldDescriptor = parameterFieldDescriptors.get(descriptorParameterIdx);
+			/**
+			 * True iff this a parameter which is part of the field descriptor but not the generic signature -- that is,
+			 * it is a compiler-defined parameter.
+			 */
+			boolean isCompilerDefined = descriptorParameterIdx < numCompilerDefinedParameters;
+			SignatureWrapper nextFieldSignature = signature;
+			if (isCompilerDefined && !compilerDefinedParametersAreIncludedInSignature) {
+				nextFieldSignature = new SignatureWrapper(nextFieldDescriptor);
+			}
+			NdMethodParameter parameter = new NdMethodParameter(method,
+					createTypeSignature(nextFieldSignature, nextFieldDescriptor));
+
+			parameter.setCompilerDefined(isCompilerDefined);
+
+			if (descriptorParameterIdx < annotatedParametersCount) {
+				IBinaryAnnotation[] parameterAnnotations = next.getParameterAnnotations(descriptorParameterIdx,
+						binaryTypeName);
+
+				attachAnnotations(parameter, parameterAnnotations);
+			}
+			if (!isCompilerDefined && parameterNames != null && parameterNames.length > parameterNameIdx) {
+				parameter.setName(parameterNames[parameterNameIdx++]);
+			}
+			descriptorParameterIdx++;
+		}
+
+		skipChar(descriptor, ')');
+		char[] nextFieldDescriptor = readNextFieldDescriptor(descriptor);
+		method.setReturnType(createTypeSignature(signature, nextFieldDescriptor));
+
+		boolean hasExceptionsInSignature = hasAnotherException(signature);
+		char[][] exceptionTypes = next.getExceptionTypeNames();
+		if (exceptionTypes == null) {
+			exceptionTypes = CharArrayUtils.EMPTY_ARRAY_OF_CHAR_ARRAYS;
+		}
+		int throwsIdx = 0;
+		if (hasExceptionsInSignature) {
+			while (hasAnotherException(signature)) {
+				signature.start++;
+				new NdMethodException(method, createTypeSignature(signature,
+						JavaNames.binaryNameToFieldDescriptor(exceptionTypes[throwsIdx])));
+				throwsIdx++;
+			}
+		} else if (exceptionTypes.length != 0) {
+			for (;throwsIdx < exceptionTypes.length; throwsIdx++) {
+				char[] fieldDescriptor = JavaNames.binaryNameToFieldDescriptor(exceptionTypes[throwsIdx]);
+				SignatureWrapper convertedWrapper = new SignatureWrapper(fieldDescriptor);
+				new NdMethodException(method, createTypeSignature(convertedWrapper,
+						JavaNames.binaryNameToFieldDescriptor(exceptionTypes[throwsIdx])));
+			}
+		}
+
+		if (hasExceptionsInSignature) {
+			flags |= NdMethod.FLG_THROWS_SIGNATURE_PRESENT;
+		}
+
+		Object defaultValue = next.getDefaultValue();
+		if (defaultValue != null) {
+			method.setDefaultValue(createConstantFromMixedType(defaultValue));
+		}
+
+		method.setMethodId(createMethodId(binaryType.getName(), next.getSelector(), next.getMethodDescriptor()));
+		method.setModifiers(next.getModifiers());
+		method.setTagBits(next.getTagBits());
+		method.setFlags(flags);
+	}
+
+	private boolean hasAnotherException(SignatureWrapper signature) {
+		return !signature.atEnd() && signature.charAtStart() == '^';
+	}
+
+	private void skipChar(SignatureWrapper signature, char toSkip) {
+		if (signature.start < signature.signature.length && signature.charAtStart() == toSkip) {
+			signature.start++;
+		}
+	}
+
+	/**
+	 * Adds the given field to the given type
+	 */
+	private void addField(NdType type, IBinaryField nextField) throws CoreException {
+		NdVariable variable = new NdVariable(type);
+
+		variable.setName(nextField.getName());
+
+		if (nextField.getGenericSignature() != null) {
+			variable.setVariableFlag(NdVariable.FLG_GENERIC_SIGNATURE_PRESENT);
+		}
+
+		attachAnnotations(variable, nextField.getAnnotations());
+
+		variable.setConstant(NdConstant.create(getNd(), nextField.getConstant()));
+		variable.setModifiers(nextField.getModifiers());
+		SignatureWrapper nextTypeSignature = GenericSignatures.getGenericSignatureFor(nextField);
+
+		IBinaryTypeAnnotation[] typeAnnotations = nextField.getTypeAnnotations();
+		if (typeAnnotations != null) {
+			for (IBinaryTypeAnnotation next : typeAnnotations) {
+				NdTypeAnnotationInVariable annotation = new NdTypeAnnotationInVariable(getNd(), variable);
+	
+				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);
+	}
+
+	/**
+	 * Reads and attaches any generic type parameters at the current start position in the given wrapper. Sets
+	 * wrapper.start to the character following the type parameters.
+	 *
+	 * @throws CoreException
+	 */
+	private void readTypeParameters(NdBinding type, SignatureWrapper wrapper)
+			throws CoreException {
+		char[] genericSignature = wrapper.signature;
+		if (genericSignature.length == 0 || wrapper.charAtStart() != '<') {
+			return;
+		}
+
+		int indexOfClosingBracket = wrapper.skipAngleContents(wrapper.start) - 1;
+		wrapper.start++;
+		NdTypeParameter 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);
+				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() != ':');
+			}
+
+			skipChar(wrapper, ':');
+
+			NdTypeSignature boundSignature = createTypeSignature(wrapper, JAVA_LANG_OBJECT_FIELD_DESCRIPTOR);
+
+			new NdTypeBound(parameter, boundSignature);
+		}
+
+		skipChar(wrapper, '>');
+	}
+
+	private char[] readNextFieldDescriptor(SignatureWrapper genericSignature) {
+		int endPosition = findEndOfFieldDescriptor(genericSignature);
+
+		char[] result = CharArrayUtils.subarray(genericSignature.signature, genericSignature.start, endPosition);
+		genericSignature.start = endPosition;
+		return result;
+	}
+
+	private int findEndOfFieldDescriptor(SignatureWrapper genericSignature) {
+		char[] signature = genericSignature.signature;
+
+		if (signature == null || signature.length == 0) {
+			return genericSignature.start;
+		}
+		int current = genericSignature.start;
+		while (current < signature.length) {
+			char firstChar = signature[current];
+			switch (firstChar) {
+				case 'L':
+				case 'T': {
+					return CharArrayUtils.indexOf(';', signature, current, signature.length) + 1;
+				}
+				case '[': {
+					current++;
+					break;
+				}
+				case 'V':
+				case 'B':
+				case 'C':
+				case 'D':
+				case 'F':
+				case 'I':
+				case 'J':
+				case 'S':
+				case 'Z':
+					return current + 1;
+				default:
+					throw new IndexException(Package.createStatus("Field descriptor starts with unknown character: " //$NON-NLS-1$
+							+ genericSignature.toString()));
+			}
+		}
+		return current;
+	}
+
+	/**
+	 * Given a generic signature which is positioned at the open brace for method arguments, this returns the number of
+	 * method arguments. The start position of the given signature is not modified.
+	 */
+	private int countMethodArguments(SignatureWrapper genericSignature) {
+		SignatureWrapper lookaheadSignature = new SignatureWrapper(genericSignature.signature);
+		lookaheadSignature.start = genericSignature.start;
+		skipChar(lookaheadSignature, '(');
+		int argumentCount = 0;
+		while (!lookaheadSignature.atEnd() && !(lookaheadSignature.charAtStart() == ')')) {
+			switch (lookaheadSignature.charAtStart()) {
+				case 'T': {
+					// Skip the 'T' prefix
+					lookaheadSignature.nextWord();
+					skipChar(lookaheadSignature, ';');
+					argumentCount++;
+					break;
+				}
+				case '[': {
+					// Skip the '[' prefix
+					lookaheadSignature.start++;
+					break;
+				}
+				case 'V':
+				case 'B':
+				case 'C':
+				case 'D':
+				case 'F':
+				case 'I':
+				case 'J':
+				case 'S':
+				case 'Z':
+					argumentCount++;
+					lookaheadSignature.start++;
+					break;
+				case 'L':
+					for (;;) {
+						lookaheadSignature.nextWord();
+						lookaheadSignature.start = lookaheadSignature.skipAngleContents(lookaheadSignature.start);
+						char nextChar = lookaheadSignature.charAtStart();
+						if (nextChar == ';') {
+							break;
+						}
+						if (nextChar != '.') {
+							throw new IllegalStateException(
+									"Unknown char in generic signature " + lookaheadSignature.toString()); //$NON-NLS-1$
+						}
+					}
+					skipChar(lookaheadSignature, ';');
+					argumentCount++;
+					break;
+				default:
+					throw new IllegalStateException("Generic signature starts with unknown character: " //$NON-NLS-1$
+							+ lookaheadSignature.toString());
+			}
+		}
+		return argumentCount;
+	}
+
+	/**
+	 * Reads a type signature from the given {@link SignatureWrapper}, starting at the character pointed to by
+	 * wrapper.start. On return, wrapper.start will point to the first character following the type signature. Returns
+	 * null if given an empty signature or the signature for the void type.
+	 *
+	 * @param genericSignature
+	 *            the generic signature to parse
+	 * @param fieldDescriptorIfVariable
+	 *            the field descriptor to use if the type is a type variable -- or null if unknown (the field descriptor
+	 *            for java.lang.Object will be used)
+	 * @throws CoreException
+	 */
+	private NdTypeSignature createTypeSignature(SignatureWrapper genericSignature, char[] fieldDescriptorIfVariable)
+			throws CoreException {
+		char[] signature = genericSignature.signature;
+
+		if (signature == null || signature.length == 0) {
+			return null;
+		}
+
+		char firstChar = genericSignature.charAtStart();
+		switch (firstChar) {
+			case 'T': {
+				// Skip the 'T' prefix
+				genericSignature.start++;
+				NdComplexTypeSignature typeSignature = new NdComplexTypeSignature(getNd());
+				char[] fieldDescriptor = fieldDescriptorIfVariable;
+				if (fieldDescriptor == null) {
+					fieldDescriptor = JAVA_LANG_OBJECT_FIELD_DESCRIPTOR;
+				}
+				typeSignature.setRawType(createTypeIdFromFieldDescriptor(fieldDescriptor));
+				typeSignature.setVariableIdentifier(genericSignature.nextWord());
+				// Skip the trailing semicolon
+				skipChar(genericSignature, ';');
+				return typeSignature;
+			}
+			case '[': {
+				// Skip the '[' prefix
+				genericSignature.start++;
+				char[] nestedFieldDescriptor = null;
+				if (fieldDescriptorIfVariable != null && fieldDescriptorIfVariable.length > 0
+						&& fieldDescriptorIfVariable[0] == '[') {
+					nestedFieldDescriptor = CharArrayUtils.subarray(fieldDescriptorIfVariable, 1);
+				}
+				// Determine the array argument type
+				NdTypeSignature elementType = createTypeSignature(genericSignature, nestedFieldDescriptor);
+				char[] computedFieldDescriptor = CharArrayUtils.concat(ARRAY_FIELD_DESCRIPTOR_PREFIX,
+						elementType.getRawType().getFieldDescriptor().getChars());
+				NdTypeId rawType = createTypeIdFromFieldDescriptor(computedFieldDescriptor);
+				// We encode signatures as though they were a one-argument generic type whose element
+				// type is the generic argument.
+				NdComplexTypeSignature typeSignature = new NdComplexTypeSignature(getNd());
+				typeSignature.setRawType(rawType);
+				NdTypeArgument typeArgument = new NdTypeArgument(getNd(), typeSignature);
+				typeArgument.setType(elementType);
+				return typeSignature;
+			}
+			case 'V':
+				genericSignature.start++;
+				return null;
+			case 'B':
+			case 'C':
+			case 'D':
+			case 'F':
+			case 'I':
+			case 'J':
+			case 'S':
+			case 'Z':
+				genericSignature.start++;
+				return createTypeIdFromFieldDescriptor(new char[] { firstChar });
+			case 'L':
+				return parseClassTypeSignature(null, genericSignature);
+			case '+':
+			case '-':
+			case '*':
+				throw new CoreException(Package.createStatus("Unexpected wildcard in top-level of generic signature: " //$NON-NLS-1$
+						+ genericSignature.toString()));
+			default:
+				throw new CoreException(Package.createStatus("Generic signature starts with unknown character: " //$NON-NLS-1$
+						+ genericSignature.toString()));
+		}
+	}
+
+	/**
+	 * Parses a ClassTypeSignature (as described in section 4.7.9.1 of the Java VM Specification Java SE 8 Edition). The
+	 * read pointer should be located just after the identifier. The caller is expected to have already read the field
+	 * descriptor for the type.
+	 */
+	private NdTypeSignature parseClassTypeSignature(NdComplexTypeSignature parentTypeOrNull, SignatureWrapper wrapper)
+			throws CoreException {
+		char[] identifier = wrapper.nextWord();
+		char[] fieldDescriptor;
+
+		if (parentTypeOrNull != null) {
+			fieldDescriptor = CharArrayUtils.concat(
+					parentTypeOrNull.getRawType().getFieldDescriptorWithoutTrailingSemicolon(),
+					INNER_TYPE_SEPARATOR, identifier, FIELD_DESCRIPTOR_SUFFIX);
+		} else {
+			fieldDescriptor = CharArrayUtils.concat(identifier, FIELD_DESCRIPTOR_SUFFIX);
+		}
+
+		char[] genericSignature = wrapper.signature;
+		boolean hasGenericArguments = (genericSignature.length > wrapper.start)
+				&& genericSignature[wrapper.start] == '<';
+		boolean isRawTypeWithNestedClass = genericSignature[wrapper.start] == '.';
+		NdTypeId rawType = createTypeIdFromFieldDescriptor(fieldDescriptor);
+		NdTypeSignature result = rawType;
+
+		boolean checkForSemicolon = true;
+		// Special optimization for signatures with no type annotations, no arrays, and no generic arguments that
+		// are not an inner type of a class that can't use this optimization. Basically, if there would be no attributes
+		// set on a NdComplexTypeSignature besides what it picks up from its raw type, we just use the raw type.
+		if (hasGenericArguments || parentTypeOrNull != null || isRawTypeWithNestedClass) {
+			NdComplexTypeSignature typeSignature = new NdComplexTypeSignature(getNd());
+			typeSignature.setRawType(rawType);
+
+			if (hasGenericArguments) {
+				wrapper.start++;
+				while (wrapper.start < genericSignature.length && (genericSignature[wrapper.start] != '>')) {
+					NdTypeArgument typeArgument = new NdTypeArgument(getNd(), typeSignature);
+
+					switch (genericSignature[wrapper.start]) {
+						case '+': {
+							typeArgument.setWildcard(NdTypeArgument.WILDCARD_SUPER);
+							wrapper.start++;
+							break;
+						}
+						case '-': {
+							typeArgument.setWildcard(NdTypeArgument.WILDCARD_EXTENDS);
+							wrapper.start++;
+							break;
+						}
+						case '*': {
+							typeArgument.setWildcard(NdTypeArgument.WILDCARD_QUESTION);
+							wrapper.start++;
+							continue;
+						}
+					}
+
+					NdTypeSignature nextSignature = createTypeSignature(wrapper, null);
+					typeArgument.setType(nextSignature);
+				}
+