Update jdt.core to I20161005-1430
diff --git a/org.eclipse.jdt.core.tests.compiler/.classpath b/org.eclipse.jdt.core.tests.compiler/.classpath
index b277a8a..01836c4 100644
--- a/org.eclipse.jdt.core.tests.compiler/.classpath
+++ b/org.eclipse.jdt.core.tests.compiler/.classpath
@@ -2,6 +2,6 @@
 <classpath>
 	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-	<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="output" path="bin"/>
 </classpath>
diff --git a/org.eclipse.jdt.core.tests.compiler/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jdt.core.tests.compiler/.settings/org.eclipse.jdt.core.prefs
index 1c33923..9426fc4 100644
--- a/org.eclipse.jdt.core.tests.compiler/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jdt.core.tests.compiler/.settings/org.eclipse.jdt.core.prefs
@@ -15,9 +15,9 @@
 org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
 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
@@ -120,6 +120,6 @@
 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.incompatibleJDKLevel=ignore
 org.eclipse.jdt.core.incompleteClasspath=error
diff --git a/org.eclipse.jdt.core.tests.compiler/META-INF/MANIFEST.MF b/org.eclipse.jdt.core.tests.compiler/META-INF/MANIFEST.MF
index dc40e8b..e99046e 100644
--- a/org.eclipse.jdt.core.tests.compiler/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.core.tests.compiler/META-INF/MANIFEST.MF
@@ -24,7 +24,7 @@
  org.eclipse.jdt.annotation;bundle-version="[2.0.0,3.0.0)";resolution:=optional,
  org.eclipse.objectteams.otdt
 Import-Package: org.eclipse.jdt.internal.compiler.apt.dispatch
-Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Eclipse-BundleShape: dir
 Bundle-Activator: org.eclipse.jdt.core.tests.compiler.Activator
 Bundle-ActivationPolicy: lazy
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/SelectionParserTest18.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/SelectionParserTest18.java
index 852c395..01472ff 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/SelectionParserTest18.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/SelectionParserTest18.java
@@ -17,6 +17,7 @@
 public class SelectionParserTest18 extends AbstractSelectionTest {
 static {
 //		TESTS_NUMBERS = new int[] { 53 };
+//		TESTS_NAMES = new String[] { "test495912" };
 }
 public static Test suite() {
 	return buildMinimalComplianceTestSuite(SelectionParserTest18.class, F_1_8);
@@ -249,4 +250,207 @@
 			expectedReplacedSource,
 			testName);
 }
+public void test495912() {
+	String string = 
+			"package xy;\n" +
+			"public class Test {\n" +
+			"	{\n" +
+			"		Runnable r = () -> {\n" +
+			"		      Integer i= 1;\n" +
+			"		      byte b= i.byteValue();\n" +
+			"		      if (true) {\n" +
+			"		          if (false) {\n" +
+			"		          }\n" +
+			"		      }\n" +
+			"		      String s= new String();\n" +
+			"		};\n" +
+			"	}\n" +
+			"    public void foo(Runnable r) {\n" +
+			"    }\n" +
+			"}";
+
+	String expectedCompletionNodeToString = "<SelectOnMessageSend:i.byteValue()>";
+
+	String completionIdentifier = "byteValue";
+	String expectedUnitDisplayString =
+					"package xy;\n" + 
+					"public class Test {\n" + 
+					"  {\n" + 
+					"    Runnable r = () ->     {\n" + 
+					"      Integer i;\n" + 
+					"      byte b = <SelectOnMessageSend:i.byteValue()>;\n" + 
+					"      if (true)\n" + 
+					"          {\n" + 
+					"            if (false)\n" + 
+					"                {\n" + 
+					"                }\n" + 
+					"          }\n" + 
+					"      String s;\n" + 
+					"    };\n" + 
+					"  }\n" + 
+					"  public Test() {\n" + 
+					"  }\n" + 
+					"  public void foo(Runnable r) {\n" + 
+					"  }\n" + 
+					"}\n";
+	String expectedReplacedSource = "i.byteValue()";
+	String testName = "<select>";
+
+	int selectionStart = string.indexOf("byteValue");
+	int selectionEnd = selectionStart + completionIdentifier.length() - 1;
+
+	this.checkMethodParse(
+			string.toCharArray(),
+			selectionStart,
+			selectionEnd,
+			expectedCompletionNodeToString,
+			expectedUnitDisplayString,
+			completionIdentifier,
+			expectedReplacedSource,
+			testName);
+}
+public void test495912a() {
+	String string = 
+			"package xy;\n" +
+			"public class Test {\n" +
+			"	{\n" +
+			"		Runnable r = () -> {\n" +
+			"		      Integer i= 1;\n" +
+			"		      byte b= i.byteValue();\n" +
+			"		      if (true) {\n" +
+			"		          if (false) {\n" +
+			"		          }\n" +
+			"		      }\n" +
+			"		      for (int i1 = 0; i1 < 42; i1++) {\n" +
+			"		      }\n" +
+			"		};\n" +
+			"	}\n" +
+			"    public void foo(Runnable r) {\n" +
+			"    }\n" +
+			"}";
+
+	String expectedCompletionNodeToString = "<SelectOnMessageSend:i.byteValue()>";
+
+	String completionIdentifier = "byteValue";
+	String expectedUnitDisplayString =
+					"package xy;\n" + 
+					"public class Test {\n" + 
+					"  {\n" + 
+					"    Runnable r = () ->     {\n" + 
+					"      Integer i;\n" + 
+					"      byte b = <SelectOnMessageSend:i.byteValue()>;\n" + 
+					"      if (true)\n" + 
+					"          {\n" + 
+					"            if (false)\n" + 
+					"                {\n" + 
+					"                }\n" + 
+					"          }\n" + 
+					"      for (int i1;; (i1 < 42); i1 ++) \n" + 
+					"        {\n" + 
+					"        }\n" + 
+					"    };\n" + 
+					"  }\n" + 
+					"  public Test() {\n" + 
+					"  }\n" + 
+					"  public void foo(Runnable r) {\n" + 
+					"  }\n" + 
+					"}\n";
+	String expectedReplacedSource = "i.byteValue()";
+	String testName = "<select>";
+
+	int selectionStart = string.indexOf("byteValue");
+	int selectionEnd = selectionStart + completionIdentifier.length() - 1;
+
+	this.checkMethodParse(
+			string.toCharArray(),
+			selectionStart,
+			selectionEnd,
+			expectedCompletionNodeToString,
+			expectedUnitDisplayString,
+			completionIdentifier,
+			expectedReplacedSource,
+			testName);
+}
+public void test495912b() {
+	String string = 
+			"package xy;\n" +
+			"import org.eclipse.e4.ui.internal.workbench.PartServiceImpl;\n" +
+			"import org.eclipse.e4.ui.model.application.ui.basic.MPart;\n" +
+			"import org.eclipse.swt.widgets.Table;\n" +
+			"import org.eclipse.ui.IWorkbenchWindow;\n" +
+			"public abstract class CycleViewHandler extends CycleBaseHandler {\n" +
+			"	@Override\n" +
+			"	protected void addItems(Table table, WorkbenchPage page) {\n" +
+			"		List<MPart> parts = null;\n" +
+			"		parts.stream().sorted((firstPart, secondPart) -> {\n" +
+			"			Long firstPartActivationTime = (Long) firstPart.getTransientData()\n" +
+			"					.getOrDefault(PartServiceImpl.PART_ACTIVATION_TIME, Long.MIN_VALUE);\n" +
+			"			Long secondPartActivationTime = (Long) secondPart.getTransientData()\n" +
+			"					.getOrDefault(PartServiceImpl.PART_ACTIVATION_TIME, Long.MIN_VALUE);\n" +
+			"			return 0;\n" +
+			"		}).forEach(part -> {\n" +
+			"			if (true) {\n" +
+			"				if (true) {\n" +
+			"				}\n" +
+			"			} \n" +
+			"			else {\n" +
+			"				IWorkbenchWindow iwbw = page.getWorkbenchWindow();\n" +
+			"				if (true){\n" +
+			"				}\n" +
+			"			}\n" +
+			"		});\n" +
+			"	}\n" +
+			"}";
+
+	String expectedSelectionNodeToString = "<SelectOnMessageSend:firstPart.getTransientData()>";
+
+	String selectionIdentifier = "getTransientData";
+	String expectedUnitDisplayString =
+			"package xy;\n" + 
+					"import org.eclipse.e4.ui.internal.workbench.PartServiceImpl;\n" + 
+					"import org.eclipse.e4.ui.model.application.ui.basic.MPart;\n" + 
+					"import org.eclipse.swt.widgets.Table;\n" + 
+					"import org.eclipse.ui.IWorkbenchWindow;\n" + 
+					"public abstract class CycleViewHandler extends CycleBaseHandler {\n" + 
+					"  public CycleViewHandler() {\n" + 
+					"  }\n" + 
+					"  protected @Override void addItems(Table table, WorkbenchPage page) {\n" + 
+					"    List<MPart> parts;\n" + // this is the missing part without the fix in RecoveredMethod#add(Block....)
+					"    parts.stream().sorted((<no type> firstPart, <no type> secondPart) -> {\n" + 
+					"  Long firstPartActivationTime = (Long) <SelectOnMessageSend:firstPart.getTransientData()>.getOrDefault(PartServiceImpl.PART_ACTIVATION_TIME, Long.MIN_VALUE);\n" + 
+					"  Long secondPartActivationTime;\n" + 
+					"  return 0;\n" + 
+					"}).forEach((<no type> part) -> {\n" + 
+					"  if (true)\n" + 
+					"      {\n" + 
+					"        if (true)\n" + 
+					"            {\n" + 
+					"            }\n" + 
+					"      }\n" + 
+					"  else\n" + 
+					"      {\n" + 
+					"        IWorkbenchWindow iwbw;\n" + 
+					"        if (true)\n" + 
+					"            {\n" + 
+					"            }\n" + 
+					"      }\n" + 
+					"});\n" + 
+					"  }\n" + 
+					"}\n";
+	String expectedReplacedSource = "firstPart.getTransientData()";
+	String testName = "<select>";
+
+	int selectionStart = string.indexOf("getTransientData");
+	int selectionEnd = selectionStart + selectionIdentifier.length() - 1;
+
+	this.checkMethodParse(
+			string.toCharArray(),
+			selectionStart,
+			selectionEnd,
+			expectedSelectionNodeToString,
+			expectedUnitDisplayString,
+			selectionIdentifier,
+			expectedReplacedSource,
+			testName);
+}
 }
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractRegressionTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractRegressionTest.java
index 28b58aa..fb9c9a2 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractRegressionTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractRegressionTest.java
@@ -395,7 +395,11 @@
 					}
 				} : null,
 			EclipseBug177715 = RUN_JAVAC ? // https://bugs.eclipse.org/bugs/show_bug.cgi?id=177715
-				new EclipseHasABug(MismatchType.JavacErrorsEclipseNone) : null,
+				new EclipseHasABug(MismatchType.JavacErrorsEclipseNone) {
+					Excuse excuseFor(JavacCompiler compiler) {
+						return compiler.compliance < ClassFileConstants.JDK1_8 ? this : null; // in 1.8 rejected by both compilers
+					}
+				} : null,
 			EclipseBug207935 = RUN_JAVAC ? // https://bugs.eclipse.org/bugs/show_bug.cgi?id=207935
 				new EclipseHasABug(MismatchType.EclipseErrorsJavacNone | MismatchType.EclipseWarningsJavacNone) : null,
 			EclipseBug216558 = RUN_JAVAC ? // https://bugs.eclipse.org/bugs/show_bug.cgi?id=216558
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AmbiguousMethodTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AmbiguousMethodTest.java
index 7b65b81..41f359c 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AmbiguousMethodTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AmbiguousMethodTest.java
@@ -4362,24 +4362,29 @@
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=321485
 public void test087() {
-	this.runNegativeTest(
-		new String[] {
-			"X.java",
-			"import java.util.Collection;\n" +
-			"import java.util.List;\n" +
-			"public class X {\n" +
-			"    public static <T> List<T> with(List<? extends T> p) { return null; } \n" +
-			"    public static <T> Collection<T> with(Collection<T> p) { return null; }\n" +
-			"    static { with(null); }\n" +
-			"} \n"
-		},
-		"----------\n" + 
-		"1. ERROR in X.java (at line 6)\n" + 
-		"	static { with(null); }\n" + 
-		"	         ^^^^\n" + 
-		"The method with(List<? extends Object>) is ambiguous for the type X\n" + 
-		"----------\n"
-	);
+	String source =
+		"import java.util.Collection;\n" +
+		"import java.util.List;\n" +
+		"public class X {\n" +
+		"    public static <T> List<T> with(List<? extends T> p) { return null; } \n" +
+		"    public static <T> Collection<T> with(Collection<T> p) { return null; }\n" +
+		"    static { with(null); }\n" +
+		"} \n";
+	if (this.complianceLevel < ClassFileConstants.JDK1_8) {
+		this.runNegativeTest( // FIXME: Eclipse has a bug
+			new String[] { "X.java", source },
+			"----------\n" +
+			"1. ERROR in X.java (at line 6)\n" +
+			"	static { with(null); }\n" +
+			"	         ^^^^\n" +
+			"The method with(List<? extends Object>) is ambiguous for the type X\n" + 
+			"----------\n"
+		);
+	} else {
+		this.runConformTest(
+			new String[] { "X.java", source }
+		);
+	}
 }
 //https://bugs.eclipse.org/bugs/show_bug.cgi?id=354579
 public void test088a() {
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericTypeTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericTypeTest.java
index 0d715ee..ceba32c 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericTypeTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericTypeTest.java
@@ -29841,6 +29841,9 @@
 
 //https://bugs.eclipse.org/bugs/show_bug.cgi?id=127583
 public void test0910() {
+	int[] capIds = this.complianceLevel < ClassFileConstants.JDK1_8
+			? new int[]{ 1, 3, 4, 6, 13}
+			: new int[]{ 1, 2, 3, 4, 8};
 	this.runNegativeTest(
 		new String[] {
 			"X.java",
@@ -29893,12 +29896,12 @@
 		"4. ERROR in X.java (at line 13)\n" +
 		"	lc1 = lc3; //2 ko\n" +
 		"	      ^^^\n" +
-		"Type mismatch: cannot convert from List<capture#1-of ? extends Collection<?>> to List<Collection>\n" +
+		"Type mismatch: cannot convert from List<capture#"+capIds[0]+"-of ? extends Collection<?>> to List<Collection>\n" +
 		"----------\n" +
 		"5. ERROR in X.java (at line 14)\n" +
 		"	lc1 = lc4; //3 ko\n" +
 		"	      ^^^\n" +
-		"Type mismatch: cannot convert from List<capture#3-of ? extends Collection> to List<Collection>\n" +
+		"Type mismatch: cannot convert from List<capture#"+capIds[1]+"-of ? extends Collection> to List<Collection>\n" +
 		"----------\n" +
 		"6. ERROR in X.java (at line 15)\n" +
 		"	lc2 = lc1; //4 ko\n" +
@@ -29908,12 +29911,12 @@
 		"7. ERROR in X.java (at line 16)\n" +
 		"	lc2 = lc3; //5 ko\n" +
 		"	      ^^^\n" +
-		"Type mismatch: cannot convert from List<capture#4-of ? extends Collection<?>> to List<Collection<?>>\n" +
+		"Type mismatch: cannot convert from List<capture#"+capIds[2]+"-of ? extends Collection<?>> to List<Collection<?>>\n" +
 		"----------\n" +
 		"8. ERROR in X.java (at line 17)\n" +
 		"	lc2 = lc4; //6 ko\n" +
 		"	      ^^^\n" +
-		"Type mismatch: cannot convert from List<capture#6-of ? extends Collection> to List<Collection<?>>\n" +
+		"Type mismatch: cannot convert from List<capture#"+capIds[3]+"-of ? extends Collection> to List<Collection<?>>\n" +
 		"----------\n" +
 		"9. ERROR in X.java (at line 18)\n" +
 		"	lc3 = lc1; //7 ko\n" +
@@ -29923,7 +29926,7 @@
 		"10. ERROR in X.java (at line 20)\n" +
 		"	lc3 = lc4; //9 ko\n" +
 		"	      ^^^\n" +
-		"Type mismatch: cannot convert from List<capture#13-of ? extends Collection> to List<? extends Collection<?>>\n" +
+		"Type mismatch: cannot convert from List<capture#"+capIds[4]+"-of ? extends Collection> to List<? extends Collection<?>>\n" +
 		"----------\n" +
 		"11. WARNING in X.java (at line 25)\n" +
 		"	private final List<Collection> aList = new ArrayList<Collection>();\n" +
@@ -38756,11 +38759,7 @@
 }
 //https://bugs.eclipse.org/bugs/show_bug.cgi?id=177715
 public void test1118() {
-	runConformTest(
-		// test directory preparation
-		new String[] { /* test files */
-			"X.java",
-			"import java.util.List;\n" +
+	String source = "import java.util.List;\n" +
 			"\n" +
 			"public class X {\n" +
 			"	X() {\n" +
@@ -38771,10 +38770,21 @@
 			"	<I, T extends List<I>> T foo(Class<T> pClass) {\n" +
 			"		return null;\n" +
 			"	}\n" +
-			"}\n", // =================
-		},
-		// javac options
-		JavacTestOptions.EclipseHasABug.EclipseBug177715 /* javac test options */);
+			"}\n";
+	if (this.complianceLevel < ClassFileConstants.JDK1_8) {
+		runConformTest(
+				new String[] { "X.java", source },
+				JavacTestOptions.EclipseHasABug.EclipseBug177715 /* javac test options */);
+	} else {
+		runNegativeTest(
+			new String[] { "X.java", source },
+			"----------\n" +
+			"1. ERROR in X.java (at line 6)\n" +
+			"	foo(cls);\n" +
+			"	^^^\n" +
+			"The method foo(Class<T>) in the type X is not applicable for the arguments (Class<capture#1-of ? extends List<?>>)\n" + 
+			"----------\n");
+	}
 }
 //https://bugs.eclipse.org/bugs/show_bug.cgi?id=169728
 public void test1119() {
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest.java
index 80a24af..8375b7b 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest.java
@@ -5921,5 +5921,97 @@
 			"}\n"
 		});
 }
+public void testBug499126() {
+	runConformTest(
+		new String[] {
+			"bug_ise_immutablelist/$Immutable.java",
+			"package bug_ise_immutablelist;\n" +
+			"\n" +
+			"public class $Immutable<T> {\n" +
+			"}\n" +
+			"",
+			"bug_ise_immutablelist/Test.java",
+			"package bug_ise_immutablelist;\n" +
+			"\n" +
+			"public class Test {\n" +
+			"	public static $Immutable<Object> f;\n" +
+			"}\n" +
+			"",
+		}
+	);
+	runConformTest(
+			false,
+			new String[] {
+				"Usage.java",
+				"public class Usage {\n" +
+				"	Object f() {return bug_ise_immutablelist.Test.f;}\n" +
+				"}\n" +
+				"",
+			}, 
+			null,
+			null,
+			null,
+			null
+	);
+}
+public void testBug441905() {
+	runConformTest(
+		new String[] {
+			"EclipseJava8Generics.java",
+			"import java.util.List;\n" + 
+			"\n" + 
+			"public class EclipseJava8Generics {\n" + 
+			"\n" + 
+			"  public interface Foo<V> {\n" + 
+			"  }\n" + 
+			"\n" + 
+			"  public static class FooBar<V, T extends Foo<V>> {\n" + 
+			"  }\n" + 
+			"\n" + 
+			"  protected void doFoos(List<FooBar<?, ? extends Foo<?>>> fooBars) {\n" + 
+			"    FooBar<?, ? extends Foo<?>> fooBar = fooBars.iterator().next();\n" + 
+			"    doFoo(fooBar);\n" + 
+			"  }\n" + 
+			"\n" + 
+			"  protected static <F> void doFoo(FooBar<F, ? extends Foo<F>> fooBar) {\n" + 
+			"  }\n" + 
+			"\n" + 
+			"}\n"
+		});
+}
+public void testBug469297() {
+	String source = "    import java.util.List;\n" + 
+			"    \n" + 
+			"    public class Test {\n" + 
+			"    \n" + 
+			"        static final void a(Class<? extends List<?>> type) {\n" + 
+			"            b(newList(type));\n" + 
+			"        }\n" + 
+			"    \n" + 
+			"        static final <T> List<T> b(List<T> list) {\n" + 
+			"            return list;\n" + 
+			"        }\n" + 
+			"    \n" + 
+			"        static final <L extends List<?>> L newList(Class<L> type) {\n" + 
+			"            try {\n" + 
+			"                return type.newInstance();\n" + 
+			"            }\n" + 
+			"            catch (Exception e) {\n" + 
+			"                throw new RuntimeException(e);\n" + 
+			"            }\n" + 
+			"        }\n" + 
+			"    }\n";
+	if (this.complianceLevel < ClassFileConstants.JDK1_8) {
+		runConformTest(new String[] { "Test.java", source });
+	} else {
+		runNegativeTest(new String[] { "Test.java", source },
+			"----------\n" + 
+			"1. ERROR in Test.java (at line 6)\n" + 
+			"	b(newList(type));\n" + 
+			"	^\n" + 
+			"The method b(List<T>) in the type Test is not applicable for the arguments (capture#1-of ? extends List<?>)\n" + 
+			"----------\n");
+	}
+}
 }
 
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_1_8.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_1_8.java
index f25e40f..e53c451 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_1_8.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_1_8.java
@@ -6886,4 +6886,73 @@
 		}
 	);
 }
+public void testBug472851() {
+	runNegativeTest(
+		new String[] {
+			"Test.java",
+			"import java.util.*;\n" + 
+			"\n" + 
+			"public class Test {\n" + 
+			"    public static void main(String... arg) {\n" + 
+			"    List<Integer> l1=Arrays.asList(0, 1, 2);\n" + 
+			"    List<String>  l2=Arrays.asList(\"0\", \"1\", \"2\");\n" + 
+			"    a(Arrays.asList(l1, l2));\n" + 
+			"}\n" + 
+			"static final void a(List<? extends List<?>> type) {\n" + 
+			"    test(type);\n" + 
+			"}\n" + 
+			"static final <Y,L extends List<Y>> void test(List<L> type) {\n" + 
+			"    L l1=type.get(0), l2=type.get(1);\n" + 
+			"    l2.set(0, l1.get(0));\n" + 
+			"}\n" + 
+			"}\n"
+		},
+		"----------\n" + 
+		"1. ERROR in Test.java (at line 10)\n" + 
+		"	test(type);\n" + 
+		"	^^^^\n" + 
+		"The method test(List<L>) in the type Test is not applicable for the arguments (List<capture#1-of ? extends List<?>>)\n" + 
+		"----------\n");
+}
+public void testBug502350() {
+	runNegativeTest(
+		new String[] {
+			"makeCompilerFreeze/EclipseJava8Bug.java",
+			"package makeCompilerFreeze;\n" +
+			"\n" +
+			"interface Comparable<E> {} \n" +
+			"\n" +
+			"interface Comparator<A> {\n" +
+			"  public static <B extends Comparable<B>> Comparator<B> naturalOrder() {\n" +
+			"    return null;\n" +
+			"  }\n" +
+			"}\n" +
+			"\n" +
+			"\n" +
+			"class Stuff {\n" +
+			"  public static <T, S extends T> Object func(Comparator<T> comparator) {\n" +
+			"    return null;\n" +
+			"  }\n" +
+			"}\n" +
+			"\n" +
+			"public class EclipseJava8Bug {\n" +
+			"  static final Object BORKED =\n" +
+			"      Stuff.func(Comparator.naturalOrder());\n" +
+			"}\n" +
+			"\n" +
+			"",
+		},
+		"----------\n" + 
+		"1. ERROR in makeCompilerFreeze\\EclipseJava8Bug.java (at line 20)\n" + 
+		"	Stuff.func(Comparator.naturalOrder());\n" + 
+		"	      ^^^^\n" + 
+		"The method func(Comparator<T>) in the type Stuff is not applicable for the arguments (Comparator<Comparable<Comparable<B>>>)\n" + 
+		"----------\n" + 
+		"2. ERROR in makeCompilerFreeze\\EclipseJava8Bug.java (at line 20)\n" + 
+		"	Stuff.func(Comparator.naturalOrder());\n" + 
+		"	           ^^^^^^^^^^^^^^^^^^^^^^^^^\n" + 
+		"Type mismatch: cannot convert from Comparator<Comparable<Comparable<B>>> to Comparator<T>\n" + 
+		"----------\n"
+	);
+}
 }
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LambdaExpressionsTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LambdaExpressionsTest.java
index 98d0c5f..74b2de1 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LambdaExpressionsTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LambdaExpressionsTest.java
@@ -6182,6 +6182,111 @@
 	"private static java.lang.reflect.Method Test.lambda$0(java.lang.Void)\n" +
 	"private java.lang.reflect.Method AnotherClass.lambda$0(java.lang.Void)");
 }
+public void testBug499258() {
+	runConformTest(
+		new String[] {
+			"bug499258/ShellTab.java",
+			"package bug499258;\n" +
+			"class Controller {\n" +
+			"	public void newTerminal(Object... path) {\n" +
+			"	}\n" +
+			"}\n" +
+			"\n" +
+			"interface EventHandler {\n" +
+			"	void handle();\n" +
+			"}\n" +
+			"\n" +
+			"public class ShellTab {\n" +
+			"	private final Controller controller;\n" +
+			"\n" +
+			"	public ShellTab(Controller controller) {\n" +
+			"		this.controller = controller;\n" +
+			"		EventHandler h = this.controller::newTerminal;\n" +
+			"	}\n" +
+			"}\n" +
+			"",
+		}
+	);
+}
+// https://bugs.eclipse.org/bugs/show_bug.cgi?id=500374 Using a method reference to a generic method in a Base class gives me NoSuchMethodError
+public void test500374() {
+	this.runConformTest(
+		new String[] {
+			"client/Client.java",
+			"package client;\n" + 
+			"import lib.Sub;\n" + 
+			"public class Client {\n" + 
+			"    public static void main(String[] args) throws Throwable {\n" + 
+			"        Sub s1 = new Sub();\n" + 
+			"        doSomething(() -> s1.m());\n" + 
+			"        doSomething(s1::m);\n" + 
+			"    }\n" + 
+			"    interface Aaa {\n" + 
+			"        Object f() throws Throwable;\n" + 
+			"    }\n" + 
+			"    public static void doSomething(Aaa a) throws Throwable {\n" + 
+			"        System.out.println(\"Done\");\n" + 
+			"    }\n" + 
+			"}\n",
+			"lib/Sub.java",
+			"package lib;\n" + 
+			"public class Sub extends Base<Sub> {}",
+			"lib/Base.java",
+			"package lib;\n" + 
+			"class Base<T> {\n" + 
+			"    public T m() {\n" + 
+			"        System.out.println(\"m\");\n" + 
+			"        return thisInstance();\n" + 
+			"    }\n" + 
+			"    @SuppressWarnings(\"unchecked\")\n" + 
+			"    T thisInstance() {\n" + 
+			"        return (T) this;\n" + 
+			"    }\n" + 
+			"}"
+	},
+	"Done\n" +
+	"Done");
+}
+// https://bugs.eclipse.org/bugs/show_bug.cgi?id=500374 Using a method reference to a generic method in a Base class gives me NoSuchMethodError
+public void test500374a() {
+	this.runConformTest(
+		new String[] {
+			"client/Client.java",
+			"package client;\n" + 
+			"import java.lang.invoke.MethodHandle;\n" + 
+			"import java.lang.invoke.MethodHandles;\n" + 
+			"import java.lang.invoke.MethodType;\n" + 
+			"import lib.Sub;\n" + 
+			"public class Client {\n" + 
+			"    public static void main(String[] args) throws Throwable {\n" + 
+			"        MethodHandle mh = MethodHandles.lookup().findVirtual(Sub.class, \"m\", MethodType.methodType(Object.class));\n" + 
+			"        doSomething(mh::invoke);\n" + 
+			"    }\n" + 
+			"    interface Aaa {\n" + 
+			"        Object f() throws Throwable;\n" + 
+			"    }\n" + 
+			"    public static void doSomething(Aaa a) throws Throwable {\n" + 
+			"        System.out.println(\"Done\");\n" + 
+			"    }\n" + 
+			"}\n",
+			"lib/Sub.java",
+			"package lib;\n" + 
+			"public class Sub extends Base<Sub> {}",
+			"lib/Base.java",
+			"package lib;\n" + 
+			"class Base<T> {\n" + 
+			"    public T m() {\n" + 
+			"        System.out.println(\"m\");\n" + 
+			"        return thisInstance();\n" + 
+			"    }\n" + 
+			"    @SuppressWarnings(\"unchecked\")\n" + 
+			"    T thisInstance() {\n" + 
+			"        return (T) this;\n" + 
+			"    }\n" + 
+			"}"
+	},
+	"Done");
+}
 public static Class testClass() {
 	return LambdaExpressionsTest.class;
 }
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/MethodVerifyTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/MethodVerifyTest.java
index 4f090c0..9e52dad 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/MethodVerifyTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/MethodVerifyTest.java
@@ -14300,4 +14300,31 @@
 		},
 		compilerOptions);
 }
+public void testBug500673() {
+	runNegativeTest(
+		new String[] {
+			"mfi.java",
+			"interface mfi {\n" + 
+			"    public transient void a(Throwable throwable);\n" + 
+			"}\n",
+			"mfa.java",
+			"final class mfa implements mfi {\n" + 
+			"}\n"
+		},
+		"----------\n" + 
+		"1. ERROR in mfi.java (at line 2)\n" + 
+		"	public transient void a(Throwable throwable);\n" + 
+		"	                      ^^^^^^^^^^^^^^^^^^^^^^\n" +
+		(this.complianceLevel < ClassFileConstants.JDK1_8
+		? "Illegal modifier for the interface method a; only public & abstract are permitted\n"
+		: "Illegal modifier for the interface method a; only public, abstract, default, static and strictfp are permitted\n"
+		) +
+		"----------\n" + 
+		"----------\n" + 
+		"1. ERROR in mfa.java (at line 1)\n" + 
+		"	final class mfa implements mfi {\n" + 
+		"	            ^^^\n" + 
+		"The type mfa must implement the inherited abstract method mfi.a(Throwable)\n" + 
+		"----------\n");
+}
 }
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullReferenceTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullReferenceTest.java
index 14d4d19..d2c8891 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullReferenceTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullReferenceTest.java
@@ -17974,4 +17974,181 @@
 		"----------\n"
 	);
 }
+public void testBug447695() {
+	runConformTest(
+		new String[] {
+		"test/Test447695.java",
+			"package test;\n" +
+			"\n" +
+			"public class Test447695 {\n" +
+			"	public static void f() {\n" +
+			"		int[] array = null;\n" +
+			"		(array = new int[1])[0] = 42;\n" +
+			"	}\n" +
+			"	public static int g() {\n" +
+			"		int[] array = null;\n" +
+			"		return (array = new int[1])[0];\n" +
+			"	}\n" +
+			"}\n"
+		} 
+	);
+}
+public void testBug447695b() {
+	if (this.complianceLevel < ClassFileConstants.JDK1_5) return; // uses foreach
+	runConformTest(
+		new String[] {
+			"X.java",
+			"import java.util.*;\n" +
+			"public class X {\n" +
+			"	void test(String[] ss) {\n" +
+			"		List<String> strings = null;\n" +
+			"		for (String s : (strings = Arrays.asList(ss)))\n" +
+			"			System.out.println(s);\n" +
+			"	}\n" +
+			"}\n"
+		});
+}
+public void testBug447695c() {
+	if (this.complianceLevel < ClassFileConstants.JDK1_5) return; // uses autoboxing
+	runConformTest(
+		new String[] {
+			"test/Test447695.java",
+			"package test;\n" +
+			"\n" +
+			"public class Test447695 {\n" +
+			"	void f() {\n" +
+			"		Integer l1 = null;\n" +
+			"		Integer l2 = null;\n" +
+			"		int b = (l1 = new Integer(2)) + (l2 = new Integer(1));\n" +
+			"	}\n" +
+			"}\n"
+		}
+	);
+}
+public void testBug447695d() {
+	if (this.complianceLevel < ClassFileConstants.JDK1_8) return; // uses reference expression
+	runConformTest(
+		new String[] {
+			"test/Test447695.java",
+			"package test;\n" +
+			"\n" +
+			"import java.util.function.Supplier;\n" +
+			"\n" +
+			"public class Test447695 {\n" +
+			"	void f() {\n" +
+			"		String s = null;\n" +
+			"		Supplier<String> l = (s = \"\")::toString;\n" +
+			"	}\n" +
+			"}\n"
+		} 
+	);
+}
+public void testBug447695e() {
+	if (this.complianceLevel < ClassFileConstants.JDK1_5) return; // uses autoboxing
+	runConformTest(
+		new String[] {
+			"test/Test447695.java",
+			"package test;\n" +
+			"\n" +
+			"public class Test447695 {\n" +
+			"	void f() {\n" +
+			"		Integer i = null;\n" +
+			"		int j = -(i = new Integer(1));\n" +
+			"		Boolean b1 = null;\n" +
+			"		boolean b = !(b1 = new Boolean(false));\n" +
+			"	}\n" +
+			"}\n"
+		}
+	);
+}
+public void testBug447695f() {
+	if (this.complianceLevel < ClassFileConstants.JDK1_5) return; // uses autoboxing
+	runConformTest(
+		new String[] {
+			"test/Test447695.java",
+			"package test;\n" +
+			"\n" +
+			"public class Test447695 {\n" +
+			"	void f() {\n" +
+			"		int i = 0;\n" +
+			"		Integer i1 = null;\n" +
+			"		Integer i2 = null;\n" +
+			"		Integer i3 = null;\n" +
+			"		int j = (i1 = new Integer(1)) \n" +
+			"				+ (i2 = new Integer(1)) \n" +
+			"				+ i + i + i + i + i + i + i + i + i + i + i + i + i + i + i + i + i + i + i \n" +
+			"				+ (i3 = new Integer(2)) + i;\n" +
+			"	}\n" +
+			"}\n"
+		}
+	);
+}
+public void testBug447695g() {
+	runNegativeTest(
+		new String[] {
+			"test/Test447695.java",
+			"package test;\n" +
+			"\n" +
+			"class X {\n" +
+			"	int i;\n" +
+			"}\n" +
+			"\n" +
+			"public class Test447695 {\n" +
+			"	void f() {\n" +
+			"		X x1 = null;\n" +
+			"		X x2 = null;\n" +
+			"		X x3 = null;\n" +
+			"		X x4 = null;\n" +
+			"		X x5 = null;\n" +
+			"		X x6 = null;\n" +
+			"		X x7 = null;\n" +
+			"		X x8 = null;\n" +
+			"		X x9 = null;\n" +
+			"		X x10 = null;\n" +
+			"		X x11 = null;\n" +
+			"		x1.i = 1; // error 1 expected\n" +
+			"		x2.i += 1; // error 2 expected\n" +
+			"		(x3).i = 1; // error 3 expected\n" +
+			"		(x4).i += 1; // error 4 expected\n" +
+			"		(x5 = new X()).i = (x6 = new X()).i;\n" +
+			"		(x7 = new X()).i += (x8 = new X()).i;\n" +
+			"		int i1 = x9.i; // error 5 expected\n" +
+			"		int i2 = (x10).i; // error 6 expected\n" +
+			"		int i3 = (x11 = new X()).i;\n" +
+			"	}\n" +
+			"}\n"
+		},
+		"----------\n" + 
+		"1. ERROR in test\\Test447695.java (at line 20)\n" + 
+		"	x1.i = 1; // error 1 expected\n" + 
+		"	^^\n" + 
+		"Null pointer access: The variable x1 can only be null at this location\n" + 
+		"----------\n" + 
+		"2. ERROR in test\\Test447695.java (at line 21)\n" + 
+		"	x2.i += 1; // error 2 expected\n" + 
+		"	^^\n" + 
+		"Null pointer access: The variable x2 can only be null at this location\n" + 
+		"----------\n" + 
+		"3. ERROR in test\\Test447695.java (at line 22)\n" + 
+		"	(x3).i = 1; // error 3 expected\n" + 
+		"	^^^^\n" + 
+		"Null pointer access: The variable x3 can only be null at this location\n" + 
+		"----------\n" + 
+		"4. ERROR in test\\Test447695.java (at line 23)\n" + 
+		"	(x4).i += 1; // error 4 expected\n" + 
+		"	^^^^\n" + 
+		"Null pointer access: The variable x4 can only be null at this location\n" + 
+		"----------\n" + 
+		"5. ERROR in test\\Test447695.java (at line 26)\n" + 
+		"	int i1 = x9.i; // error 5 expected\n" + 
+		"	         ^^\n" + 
+		"Null pointer access: The variable x9 can only be null at this location\n" + 
+		"----------\n" + 
+		"6. ERROR in test\\Test447695.java (at line 27)\n" + 
+		"	int i2 = (x10).i; // error 6 expected\n" + 
+		"	         ^^^^^\n" + 
+		"Null pointer access: The variable x10 can only be null at this location\n" + 
+		"----------\n"
+	);
+}
 }
\ No newline at end of file
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullTypeAnnotationTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullTypeAnnotationTest.java
index 5db5078..4cb5285 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullTypeAnnotationTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullTypeAnnotationTest.java
@@ -12226,6 +12226,101 @@
 		"----------\n"
 	);
 }
+public void testBug489674() {
+	Map options = new HashMap<>(getCompilerOptions());
+	options.put(JavaCore.COMPILER_NONNULL_ANNOTATION_SECONDARY_NAMES, "org.foo.NonNull");
+	options.put(JavaCore.COMPILER_NULLABLE_ANNOTATION_SECONDARY_NAMES, "org.foo.Nullable");
+	runConformTest(
+		new String[] {
+			"org/foo/Nullable.java",
+			"package org.foo;\n" +
+			"import java.lang.annotation.*;\n" +
+			"import static java.lang.annotation.ElementType.*;\n" +
+			"@Retention(RetentionPolicy.CLASS)\n" + 
+			"@Target({ FIELD, METHOD, PARAMETER, LOCAL_VARIABLE })\n" + 
+			"public @interface Nullable {}\n",
+			"org/foo/NonNull.java",
+			"package org.foo;\n" +
+			"import java.lang.annotation.*;\n" +
+			"import static java.lang.annotation.ElementType.*;\n" +
+			"@Retention(RetentionPolicy.CLASS)\n" + 
+			"@Target({ FIELD, METHOD, PARAMETER, LOCAL_VARIABLE })\n" + 
+			"public @interface NonNull {}\n",
+			"}"
+		},
+		options);
+	runConformTestWithLibs(
+			new String[] {
+				"with_other_nullable/P1.java",
+				"package with_other_nullable;\n" +
+				"\n" +
+				"import org.foo.Nullable;\n" +
+				"\n" +
+				"public class P1 {\n" +
+				"	public static @Nullable String f0() {\n" +
+				"		return null;\n" +
+				"	}\n" +
+				"\n" +
+				"	public static <T> T check(T t) {\n" +
+				"		return t;\n" +
+				"	}\n" +
+				"}\n" +
+				"",
+				"with_other_nullable/P2.java",
+				"package with_other_nullable;\n" +
+				"\n" +
+				"import org.foo.NonNull;\n" +
+				"\n" +
+				"public class P2 {\n" +
+				"	public static void f(@NonNull String s) {\n" +
+				"	}\n" +
+				"\n" +
+				"	public static <T> T check(T t) {\n" +
+				"		return t;\n" +
+				"	}\n" +
+				"}\n" +
+				"",
+			}, 
+			options,
+			""
+	);
+	runNegativeTestWithLibs(
+			new String[] {
+				"test/Test4.java",
+				"package test;\n" +
+				"\n" +
+				"import org.eclipse.jdt.annotation.NonNullByDefault;\n" +
+				"\n" +
+				"import with_other_nullable.P1;\n" +
+				"import with_other_nullable.P2;\n" +
+				"\n" +
+				"@NonNullByDefault\n" +
+				"public class Test4 {\n" +
+				"	void m1(String s) {\n" +
+				"		P1.f0().hashCode();\n" +
+				"		s = P1.check(s);\n" +
+				"	}\n" +
+				"	void m2(String s) {\n" +
+				"		P2.f(null);\n" +
+				"		s = P2.check(s);\n" +
+				"	}\n" +
+				"}\n" +
+				"",
+			}, 
+			options,
+			"----------\n" + 
+			"1. ERROR in test\\Test4.java (at line 11)\n" + 
+			"	P1.f0().hashCode();\n" + 
+			"	^^^^^^^\n" + 
+			"Potential null pointer access: The method f0() may return null\n" + 
+			"----------\n" + 
+			"2. ERROR in test\\Test4.java (at line 15)\n" + 
+			"	P2.f(null);\n" + 
+			"	     ^^^^\n" + 
+			"Null type mismatch: required \'String\' but the provided value is null\n" + 
+			"----------\n"
+		);
+}
 public void testBug492327() {
 	runConformTestWithLibs(
 		new String[] {
@@ -12651,4 +12746,166 @@
 		""
 	);
 }
+public void testBug499862a() {
+	runConformTestWithLibs(
+		new String[] {
+			"Test.java",
+			"import org.eclipse.jdt.annotation.*;\n" +
+			"import java.util.*;\n" +
+			"public class Test {\n" +
+			"	static void printChecked(Collection<? extends @Nullable String> collection) {\n" + 
+			"		for(String s : collection)\n" + 
+			"			if (s != null)\n" + 
+			"				System.out.println(s.toString());\n" + 
+			"			else\n" + 
+			"				System.out.println(\"NULL\");\n" + 
+			"	}\n" +
+			"}\n"
+		},
+		getCompilerOptions(),
+		"");
+}
+public void testBug499862b() {
+	runNegativeTestWithLibs(
+		new String[] {
+			"Test.java",
+			"import org.eclipse.jdt.annotation.*;\n" +
+			"import java.util.*;\n" +
+			"public class Test {\n" +
+			"	static void printChecked(Collection<? extends @Nullable String> collection) {\n" + 
+			"		for(String s : collection)\n" + 
+			"			System.out.println(s.toString());\n" + 
+			"	}\n" +
+			"}\n"
+		},
+		getCompilerOptions(),
+		"----------\n" + 
+		"1. ERROR in Test.java (at line 6)\n" + 
+		"	System.out.println(s.toString());\n" + 
+		"	                   ^\n" + 
+		"Potential null pointer access: The variable s may be null at this location\n" + 
+		"----------\n");
+}
+public void testBug499862c() {
+	runNegativeTestWithLibs(
+		new String[] {
+			"Test.java",
+			"import java.util.*;\n" +
+			"public class Test {\n" +
+			"	static <T> void printUnchecked(Collection<T> collection) {\n" + 
+			"		for(T t : collection)\n" + 
+			"			System.out.println(t.toString());\n" + 
+			"	}\n" + 
+			"}\n"
+		},
+		getCompilerOptions(),
+		"----------\n" + 
+		"1. ERROR in Test.java (at line 5)\n" + 
+		"	System.out.println(t.toString());\n" + 
+		"	                   ^\n" + 
+		"Potential null pointer access: this expression has type \'T\', a free type variable that may represent a \'@Nullable\' type\n" + 
+		"----------\n");
+}
+public void testBug499597simplified() {
+	runConformTestWithLibs(
+		new String[] {
+			"Foo2.java",
+			"import org.eclipse.jdt.annotation.NonNull;\n" +
+			"import org.eclipse.jdt.annotation.NonNullByDefault;\n" +
+			"\n" +
+			"@NonNullByDefault\n" +
+			"class Foo2 {\n" +
+			"	static <T> T of(T t) {\n" +
+			"		return t;\n" +
+			"	}\n" +
+			"\n" +
+			"	static String foo() {\n" +
+			"		return Foo2.<String>of(\"\"); // <-- warning here\n" +
+			"	}\n" +
+			"\n" +
+			"	static String bar() {\n" +
+			"		return Foo2.<@NonNull String>of(\"\"); // <-- no warning\n" +
+			"	}\n" +
+			"}\n" +
+			"",
+		}, 
+		getCompilerOptions(),
+		""
+	);
+}
+public void testBug499597original() {
+	runNegativeTestWithLibs(
+		new String[] {
+			"Foo.java",
+			"import static org.eclipse.jdt.annotation.DefaultLocation.*;\n" +
+			"import org.eclipse.jdt.annotation.*;\n" +
+			"\n" +
+			"import java.util.Collection;\n" +
+			"import java.util.Collections;\n" +
+			"\n" +
+			"class Foo {\n" +
+			"	static @NonNull String @NonNull [] X = { \"A\" };\n" +
+			"\n" +
+			"	@NonNullByDefault({ PARAMETER, RETURN_TYPE, FIELD, TYPE_PARAMETER, TYPE_BOUND, TYPE_ARGUMENT, ARRAY_CONTENTS })\n" +
+			"	@SafeVarargs\n" +
+			"	static <T> Collection<T> of(@NonNull T @NonNull... elements) {\n" +
+			"		return Collections.singleton(elements[0]);\n" +
+			"	}\n" +
+			"\n" +
+			"	@NonNullByDefault({ PARAMETER, RETURN_TYPE, FIELD, TYPE_PARAMETER, TYPE_BOUND, TYPE_ARGUMENT, ARRAY_CONTENTS })\n" +
+			"	static Collection<String[]> foo() {\n" +
+			"		return Foo.<String[]>of(X); // <-- warning here\n" +
+			"	}\n" +
+			"\n" +
+			"	@NonNullByDefault({ PARAMETER, RETURN_TYPE, FIELD, TYPE_PARAMETER, TYPE_BOUND, TYPE_ARGUMENT, ARRAY_CONTENTS })\n" +
+			"	static Collection<String[]> bar() {\n" +
+			"		return Foo.<String @NonNull []>of(X); // <-- no warning\n" +
+			"	}\n" +
+			"}\n" +
+			"",
+		}, 
+		getCompilerOptions(),
+		"----------\n" + 
+		"1. WARNING in Foo.java (at line 12)\n" + 
+		"	static <T> Collection<T> of(@NonNull T @NonNull... elements) {\n" + 
+		"	                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + 
+		"The nullness annotation is redundant with a default that applies to this location\n" + 
+		"----------\n" + 
+		"2. WARNING in Foo.java (at line 13)\n" + 
+		"	return Collections.singleton(elements[0]);\n" + 
+		"	       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + 
+		"Null type safety (type annotations): The expression of type \'Set<T>\' needs unchecked conversion to conform to \'@NonNull Collection<T>\', corresponding supertype is \'Collection<T>\'\n" + 
+		"----------\n"
+	);
+}
+public void testBug501449() {
+	runNegativeTestWithLibs(
+		new String[] {
+			"Test.java",
+			"import org.eclipse.jdt.annotation.Nullable;\n" +
+			"\n" +
+			"public class Test {\n" +
+			"	<T, S extends T> void f(T[] objects, @Nullable T nullableValue, T value, S subclassValue) {\n" +
+			"		objects[0] = null;\n" +
+			"		objects[1] = nullableValue;\n" +
+			"		objects[2] = value;\n" +
+			"		objects[3] = subclassValue;\n" +
+			"	}\n" +
+			"}\n" +
+			"",
+		}, 
+		getCompilerOptions(),
+		"----------\n" + 
+		"1. ERROR in Test.java (at line 5)\n" + 
+		"	objects[0] = null;\n" + 
+		"	^^^^^^^^^^\n" + 
+		"Null type mismatch (type annotations): \'null\' is not compatible to the free type variable \'T\'\n" + 
+		"----------\n" + 
+		"2. ERROR in Test.java (at line 6)\n" + 
+		"	objects[1] = nullableValue;\n" + 
+		"	^^^^^^^^^^\n" + 
+		"Null type mismatch (type annotations): required \'T\' but this expression has type \'@Nullable T\', where \'T\' is a free type variable\n" + 
+		"----------\n" 
+	);
+}
 }
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PackageBindingTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PackageBindingTest.java
index 827e53d..f848e19 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PackageBindingTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PackageBindingTest.java
@@ -29,8 +29,9 @@
 	/**
 	 * This test checks if it is searched for packages before searching for types.
 	 * The search for packages is much faster than searching for types, therefore it should get executed before searching for types.
+	 * Commented since reverted to original behaviour as per bug 495598
 	 */
-	public void test01() {
+	public void _test01() {
 		NameEnvironmentDummy nameEnv = new NameEnvironmentDummy(true);
 
 		PackageBinding packageBinding = new PackageBinding(new LookupEnvironment(null, new CompilerOptions(), null, nameEnv));
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SerializableLambdaTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SerializableLambdaTest.java
index 18e554d..30541d6 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SerializableLambdaTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SerializableLambdaTest.java
@@ -2158,6 +2158,47 @@
 		null,true,
 		new String[]{"-Ddummy"});
 	}
+	public void testbug503118() {
+		this.runConformTest(
+			new String[]{
+				"lambdabug/App.java",
+				"package lambdabug;\n" + 
+				"import java.io.ByteArrayInputStream;\n" + 
+				"import java.io.ByteArrayOutputStream;\n" + 
+				"import java.io.ObjectInputStream;\n" + 
+				"import java.io.ObjectOutputStream;\n" + 
+				"import java.io.Serializable;\n" + 
+				"import java.util.function.Function;\n" + 
+				"public class App {\n" + 
+				"	public static interface SerialFunction<T, R> extends Function<T, R>, Serializable {\n" + 
+				"	}\n" + 
+				"	public static interface TestInterface extends Serializable {\n" + 
+				"		public Integer method(Integer i);\n" + 
+				"	}\n" + 
+				"	public static class TestClass implements TestInterface {\n" + 
+				"		private static final long serialVersionUID = 1L;\n" + 
+				"		@Override\n" + 
+				"		public Integer method(Integer i) {\n" + 
+				"			return i;\n" + 
+				"		}\n" + 
+				"	}\n" + 
+				"	public static void main(String[] args) throws Exception {\n" + 
+				"		TestInterface testService = getService();\n" + 
+				"		SerialFunction<Integer, Integer> sf = testService::method;\n" + 
+				"		ByteArrayOutputStream bos = new ByteArrayOutputStream();\n" + 
+				"		new ObjectOutputStream(bos).writeObject(sf);\n" + 
+				"		Object o = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())).readObject();\n" + 
+				"		System.out.println(o.getClass().getInterfaces()[0]);\n" + 
+				"	}\n" + 
+				"	private static TestInterface getService() {\n" + 
+				"		return new TestClass();\n" + 
+				"	}\n" + 
+				"}\n"
+		},
+		"interface lambdabug.App$SerialFunction",
+		null,true,
+		new String[]{"-Ddummy"});
+	}
 	// ---
 	
 	private void checkExpected(String expected, String actual) {
diff --git a/org.eclipse.jdt.core.tests.model/.classpath b/org.eclipse.jdt.core.tests.model/.classpath
index f4a5c79..0035118 100644
--- a/org.eclipse.jdt.core.tests.model/.classpath
+++ b/org.eclipse.jdt.core.tests.model/.classpath
@@ -1,6 +1,6 @@
 <classpath>
 	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-	<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="output" path="bin"/>
 </classpath>
diff --git a/org.eclipse.jdt.core.tests.model/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jdt.core.tests.model/.settings/org.eclipse.jdt.core.prefs
index 69354e7..0f72841 100644
--- a/org.eclipse.jdt.core.tests.model/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jdt.core.tests.model/.settings/org.eclipse.jdt.core.prefs
@@ -15,9 +15,9 @@
 org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
 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
@@ -119,6 +119,6 @@
 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.incompatibleJDKLevel=ignore
 org.eclipse.jdt.core.incompleteClasspath=error
diff --git a/org.eclipse.jdt.core.tests.model/META-INF/MANIFEST.MF b/org.eclipse.jdt.core.tests.model/META-INF/MANIFEST.MF
index 1752649..7a26b38 100644
--- a/org.eclipse.jdt.core.tests.model/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.core.tests.model/META-INF/MANIFEST.MF
@@ -25,7 +25,7 @@
  org.eclipse.core.filesystem;bundle-version="[1.2.0,2.0.0)",
  org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)";resolution:=optional,
  org.eclipse.jdt.annotation;bundle-version="[2.0.0,3.0.0)";resolution:=optional
-Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Eclipse-BundleShape: dir
 Bundle-Activator: org.eclipse.jdt.core.tests.Activator
 Bundle-ActivationPolicy: lazy
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/Activator.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/Activator.java
index defb6d0..2d22199 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/Activator.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/Activator.java
@@ -16,21 +16,20 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 
-/**
- * Make the PackageAdmin service accessible to tests.
- * 
- * @deprecated uses deprecated class PackageAdmin.
- */
 @SuppressWarnings({"rawtypes", "unchecked"})
 public class Activator extends Plugin {
 
 	private static final String PLUGIN_ID = "org.eclipse.jdt.core.tests.model";
 
+	/**
+	 * @deprecated uses deprecated class PackageAdmin.
+	 */
 	static org.osgi.service.packageadmin.PackageAdmin packageAdmin = null;
 
 	static Plugin instance;
 
 
+	@SuppressWarnings("deprecation")
 	public void start(BundleContext context) throws Exception {
 		
 		ServiceReference ref= context.getServiceReference(org.osgi.service.packageadmin.PackageAdmin.class.getName());
@@ -46,6 +45,11 @@
 		// nothing
 	}
 
+	/**
+	 * Make the PackageAdmin service accessible to tests.
+	 * 
+	 * @deprecated uses deprecated class PackageAdmin.
+	 */
 	public static org.osgi.service.packageadmin.PackageAdmin getPackageAdmin() {
 		return packageAdmin;
 	}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/APIDocumentationTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/APIDocumentationTests.java
index d59474a..25368a3 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/APIDocumentationTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/APIDocumentationTests.java
@@ -141,7 +141,7 @@
 
 	// fetch JavaCore source file
 	// 1. attempt: workspace relative location in project org.eclipse.jdt.core:
-	@SuppressWarnings("deprecation")Bundle bundle = org.eclipse.jdt.core.tests.Activator.getInstance().getBundle();
+	Bundle bundle = org.eclipse.jdt.core.tests.Activator.getInstance().getBundle();
 	URL url = bundle.getEntry("/");
 	IPath path = new Path(FileLocator.toFileURL(url).getPath());
 	path = path.removeLastSegments(1).append(ORG_ECLIPSE_JDT_CORE);
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterBugsTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterBugsTests.java
index bf98fb7..c0b797c 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterBugsTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterBugsTests.java
@@ -12499,4 +12499,124 @@
 		"}";
 	formatSource(source);
 }
+/**
+ * https://bugs.eclipse.org/500443 - [formatter] NPE on block comment before 'force-wrap' element
+ */
+public void testBug500443() {
+	this.formatterPrefs.alignment_for_enum_constants = Alignment.M_ONE_PER_LINE_SPLIT + Alignment.M_FORCE;
+	this.formatterPrefs.alignment_for_superclass_in_type_declaration = Alignment.M_ONE_PER_LINE_SPLIT + Alignment.M_FORCE;
+	String source =
+		"public class SomeClass\n" + 
+		"		/* */ extends\n" + 
+		"		Object {\n" + 
+		"	enum MyEnum {\n" + 
+		"		/* 1 */ ONE\n" + 
+		"	}\n" + 
+		"}";
+	formatSource(source);
+}
+/**
+ * https://bugs.eclipse.org/500092 - [formatter] Blank lines at beginning of method body doesn't work in constructors
+ */
+public void testBug500092() {
+	this.formatterPrefs.blank_lines_at_beginning_of_method_body = 1;
+	String source =
+		"public class Test {\n" + 
+		"	public Test() { int a; }\n" + 
+		"}";
+	formatSource(source,
+		"public class Test {\n" + 
+		"	public Test() {\n" + 
+		"\n" + 
+		"		int a;\n" + 
+		"	}\n" + 
+		"}"
+	);
+}
+/**
+ * https://bugs.eclipse.org/500135 - [formatter] 'Parenthesis positions' ignores single member annotations
+ */
+public void testBug500135() {
+	this.formatterPrefs.parenthesis_positions_in_annotation = DefaultCodeFormatterConstants.SEPARATE_LINES;
+	String source =
+		"@SomeAnnotation(\n" + 
+		"	\"some value\"\n" + 
+		")\n" + 
+		"public class Test {\n" + 
+		"}";
+	formatSource(source);
+}
+/**
+ * https://bugs.eclipse.org/500096 - [formatter] Indent declarations within enum declaration doesn't affect enum constants
+ */
+public void testBug500096a() {
+	this.formatterPrefs.indent_body_declarations_compare_to_enum_declaration_header = false;
+	String source =
+		"public enum Test {\n" + 
+		"AAA, BBB;\n" + 
+		"Test() {\n" + 
+		"}\n" + 
+		"}";
+	formatSource(source);
+}
+/**
+ * https://bugs.eclipse.org/500096 - [formatter] Indent declarations within enum declaration doesn't affect enum constants
+ */
+public void testBug500096b() {
+	this.formatterPrefs.indent_body_declarations_compare_to_enum_declaration_header = false;
+	this.formatterPrefs.alignment_for_enum_constants = Alignment.M_COMPACT_SPLIT + Alignment.M_INDENT_BY_ONE;
+	String source =
+		"public enum Test {\n" + 
+		"	AAA, BBB;\n" + 
+		"Test() {\n" + 
+		"}\n" + 
+		"}";
+	formatSource(source);
+}
+/**
+ * https://bugs.eclipse.org/500093 - [formatter] AssertionError with 'Next line on wrap' for array initializers
+ */
+public void testBug500093() {
+	this.formatterPrefs.brace_position_for_array_initializer = DefaultCodeFormatterConstants.NEXT_LINE_ON_WRAP;
+	this.formatterPrefs.page_width = 60;
+	this.formatterPrefs.use_tabs_only_for_leading_indentations = true;
+	String source =
+		"public class SomeClass {\n" + 
+		"	void foo() {\n" + 
+		"		Arrays.asList(new String[] { \"ddd\", \"eee\", \"fff\" });\n" + 
+		"		Arrays.asList(new String[] { \"a\", \"b\", \"c\" },\n" + 
+		"		        new String[]\n" + 
+		"		        { \"a\", \"b\", \"c\", });\n" + 
+		"		Arrays.asList(//\n" + 
+		"		        new String[]\n" + 
+		"		        { \"ddd\", \"eee\", \"fff\" });\n" + 
+		"		Arrays.asList(\n" + 
+		"		        new String[]\n" + 
+		"		        { \"eedd\", \"eee\", \"fff\" });\n" + 
+		"		Arrays.asList(\n" + 
+		"		        new String[]\n" + 
+		"		        { \"aa\", \"bb\", \"cc\", \"dd\", \"ee\", \"ff\", \"gg\",\n" + 
+		"		                \"hh\", \"ii\" });\n" + 
+		"		String[][] test = { { \"aaaaaa\", \"bbbbb\", \"ccccc\" },\n" + 
+		"		        { \"aaaa\", \"bb\", \"ccc\" } };\n" + 
+		"		test[123456 //\n" + 
+		"		        * (234567 + 345678 + 456789 - 567890\n" + 
+		"		                - 678901)] = new String[]\n" + 
+		"		                { \"a\", \"b\", \"c\" };\n" + 
+		"	}\n" + 
+		"}";
+	formatSource(source);
+}
+/**
+ * https://bugs.eclipse.org/500853 - [Formatter] java code formatter doesn't honour new parentheses settings
+ */
+public void testBug500853() {
+	this.formatterPrefs.parenthesis_positions_in_method_declaration = new String(DefaultCodeFormatterConstants.PRESERVE_POSITIONS);
+	String source =
+		"public class SomeClass {\n" + 
+		"	void foo() {\n" + 
+		"	}\n" + 
+		"}";
+	formatSource(source);
+}
 }
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterCommentsTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterCommentsTests.java
index b67a696..9edcdc6 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterCommentsTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterCommentsTests.java
@@ -76,20 +76,6 @@
 	super.setUpSuite();
 }
 
-/**
- * Reset the jar placeholder and delete project.
- */
-public void tearDownSuite() throws Exception {
-	if (ALL_TEST_SUITES == null) {
-		deleteProject(JAVA_PROJECT); //$NON-NLS-1$
-	} else {
-		ALL_TEST_SUITES.remove(getClass());
-		if (ALL_TEST_SUITES.size() == 0) {
-			deleteProject(JAVA_PROJECT); //$NON-NLS-1$
-		}
-	}
-}
-
 void compareFormattedSource(ICompilationUnit compilationUnit) throws JavaModelException {
 	DefaultCodeFormatter codeFormatter = codeFormatter();
 	String source = compilationUnit.getSource();
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AbstractJavaModelTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AbstractJavaModelTests.java
index a04e279..2659d99 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AbstractJavaModelTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AbstractJavaModelTests.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
@@ -32,11 +32,13 @@
 import org.eclipse.jdt.internal.core.ClasspathEntry;
 import org.eclipse.jdt.internal.core.JavaCorePreferenceInitializer;
 import org.eclipse.jdt.internal.core.JavaElement;
+import org.eclipse.jdt.internal.core.JavaElementDelta;
 import org.eclipse.jdt.internal.core.JavaModelManager;
 import org.eclipse.jdt.internal.core.JavaProject;
 import org.eclipse.jdt.internal.core.NameLookup;
 import org.eclipse.jdt.internal.core.ResolvedSourceMethod;
 import org.eclipse.jdt.internal.core.ResolvedSourceType;
+import org.eclipse.jdt.internal.core.nd.indexer.Indexer;
 import org.eclipse.jdt.internal.core.search.BasicSearchEngine;
 import org.eclipse.jdt.internal.core.util.Util;
 
@@ -238,10 +240,17 @@
 		public synchronized String stackTraces() {
 			return this.stackTraces.toString();
 		}
+
 		public synchronized String toString() {
-			StringBuffer buffer = new StringBuffer();
-			for (int i=0, length= this.deltas.length; i<length; i++) {
+			StringBuilder buffer = new StringBuilder();
+			for (int i = 0, length= this.deltas.length; i < length; i++) {
 				IJavaElementDelta delta = this.deltas[i];
+				if (((JavaElementDelta) delta).ignoreFromTests) {
+					continue;
+				}
+				if (buffer.length() != 0) {
+					buffer.append("\n\n");
+				}
 				IJavaElementDelta[] children = delta.getAffectedChildren();
 				int childrenLength=children.length;
 				IResourceDelta[] resourceDeltas = delta.getResourceDeltas();
@@ -250,28 +259,23 @@
 					buffer.append(delta);
 				} else {
 					sortDeltas(children);
-					for (int j=0; j<childrenLength; j++) {
-						buffer.append(children[j]);
-						if (j != childrenLength-1) {
-							buffer.append("\n");
+					for (int j = 0; j < childrenLength; j++) {
+						if (buffer.length() != 0 && buffer.charAt(buffer.length() - 1) != '\n') {
+							buffer.append('\n');
 						}
+						buffer.append(children[j]);
 					}
-					for (int j=0; j<resourceDeltasLength; j++) {
-						if (j == 0 && buffer.length() != 0) {
-							buffer.append("\n");
+					for (int j = 0; j < resourceDeltasLength; j++) {
+						if (buffer.length() != 0 && buffer.charAt(buffer.length() - 1) != '\n') {
+							buffer.append('\n');
 						}
 						buffer.append(resourceDeltas[j]);
-						if (j != resourceDeltasLength-1) {
-							buffer.append("\n");
-						}
 					}
 				}
-				if (i != length-1) {
-					buffer.append("\n\n");
-				}
 			}
 			return buffer.toString();
 		}
+
 		public void waitForResourceDelta() {
 			long start = System.currentTimeMillis();
 			while (!this.gotResourceDelta) {
@@ -2422,6 +2426,7 @@
 	protected void refreshExternalArchives(IJavaProject p) throws JavaModelException {
 		waitForAutoBuild(); // ensure that the auto-build job doesn't interfere with external jar refreshing
 		getJavaModel().refreshExternalArchives(new IJavaElement[] {p}, null);
+		Indexer.getInstance().waitForIndex(null);
 	}
 
 	protected void removeJavaNature(String projectName) throws CoreException {
@@ -3139,6 +3144,7 @@
 		do {
 			try {
 				Job.getJobManager().join(ResourcesPlugin.FAMILY_AUTO_BUILD, null);
+				Indexer.getInstance().waitForIndex(null);
 				wasInterrupted = false;
 			} catch (OperationCanceledException e) {
 				e.printStackTrace();
@@ -3153,6 +3159,7 @@
 		do {
 			try {
 				Job.getJobManager().join(ResourcesPlugin.FAMILY_MANUAL_REFRESH, null);
+				Indexer.getInstance().waitForIndex(null);
 				wasInterrupted = false;
 			} catch (OperationCanceledException e) {
 				e.printStackTrace();
@@ -3167,6 +3174,7 @@
 		SearchEngine engine = new SearchEngine();
 		IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
 		try {
+			Indexer.getInstance().waitForIndex(null);
 			engine.searchAllTypeNames(
 				null,
 				SearchPattern.R_EXACT_MATCH,
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java
index 6c82e92..24aaeee 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.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
@@ -16,6 +16,7 @@
 import java.lang.reflect.Method;
 
 import org.eclipse.jdt.core.tests.junit.extension.TestCase;
+import org.eclipse.jdt.core.tests.nd.RunIndexTests;
 
 import junit.framework.Test;
 import junit.framework.TestSuite;
@@ -57,6 +58,9 @@
 		// Java search tests
 		RunJavaSearchTests.class,
 
+		// Testss for the new index
+		RunIndexTests.class,
+
 		// Working copy tests
 		WorkingCopyTests.class,
 		WorkingCopyNotInClasspathTests.class,
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AttachedJavadocTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AttachedJavadocTests.java
index 9376aa6..203b2bc 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AttachedJavadocTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AttachedJavadocTests.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
@@ -260,20 +260,7 @@
 		try {
 			IClasspathEntry[] entries = this.project.getRawClasspath();
 			savedEntries = entries.clone();
-			IResource resource = this.project.getProject().findMember("/doc.zip"); //$NON-NLS-1$
-			assertNotNull("doc folder cannot be null", resource); //$NON-NLS-1$
-			URI locationURI = resource.getLocationURI();
-			assertNotNull("doc folder cannot be null", locationURI); //$NON-NLS-1$
-			URL docUrl = null;
-			try {
-				docUrl = locationURI.toURL();
-			} catch (MalformedURLException e) {
-				assertTrue("Should not happen", false); //$NON-NLS-1$
-			} catch(IllegalArgumentException e) {
-				assertTrue("Should not happen", false); //$NON-NLS-1$
-			}
-			final String path = "jar:" + docUrl.toExternalForm() + "!/doc"; //$NON-NLS-1$ //$NON-NLS-2$
-			//final String path = "jar:" + "platform:/resource/AttachedJavadocProject/doc.zip" + "!/doc";
+			final String path = "jar:" + "platform:/resource/AttachedJavadocProject/doc.zip" + "!/doc";
 			IClasspathAttribute attribute = JavaCore.newClasspathAttribute(IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, path);
 			for (int i = 0, max = entries.length; i < max; i++) {
 				final IClasspathEntry entry = entries[i];
@@ -1315,5 +1302,27 @@
 			assertTrue("Should not happen", false);
 		}
 	}
+	public void testBug499196() throws JavaModelException {
+		try {
+			IClasspathAttribute attribute =
+					JavaCore.newClasspathAttribute(
+							IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME,
+							"jar:platform:/resource/AttachedJavadocProject/bug499196_doc.zip!/");
+			IClasspathEntry newEntry = JavaCore.newLibraryEntry(new Path("/AttachedJavadocProject/bug499196.jar"), null, null, null, new IClasspathAttribute[] {attribute}, true);
+			this.project.setRawClasspath(new IClasspathEntry[]{newEntry}, null);
+			this.project.getResolvedClasspath(false);
+
+			IPackageFragmentRoot jarRoot = this.project.getPackageFragmentRoot(getFile("/AttachedJavadocProject/bug499196.jar"));
+			final IType type = jarRoot.getPackageFragment("p1.p2").getClassFile("Bug499196.class").getType();
+			assertNotNull(type);
+			IMethod method = type.getMethod("Bug499196", new String[] {}); //$NON-NLS-1$
+			assertNotNull("Constructor should not be null", method);
+			assertTrue(method.exists());
+			String javadoc = method.getAttachedJavadoc(new NullProgressMonitor()); //$NON-NLS-1$
+			assertNotNull("Should have a javadoc", javadoc); //$NON-NLS-1$
+		} catch (IndexOutOfBoundsException e) {
+			assertTrue("Should not happen", false);
+		}
+	}
 }
 
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompilationUnitTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompilationUnitTests.java
index 2a52510..3c431dd 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompilationUnitTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompilationUnitTests.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2012 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
@@ -2580,6 +2580,34 @@
 			typeParameters[0].getBoundsSignatures());
 	
 }
-
-
+public void testBug495598_001() throws CoreException {
+	try {
+		createFolder("/P/src/a/b/C");
+		createFile("/P/src/a/b/C/readme.txt", "This is not a Java file");
+	
+		createFile(
+				"/P/src/a/b/C.java",
+				"package a.b;\n" +
+				"public class C{};\n"
+			);
+		
+		createFile("/P/src/X.java", 
+				"import a.b.C;\n" +
+				"public class X {}\n");
+		ICompilationUnit cuD = getCompilationUnit("/P/src/X.java");
+		
+		ASTParser parser = ASTParser.newParser(AST.JLS8);
+		parser.setProject(this.testProject);
+		parser.setSource(cuD);
+		parser.setResolveBindings(true);
+		org.eclipse.jdt.core.dom.CompilationUnit cuAST = (org.eclipse.jdt.core.dom.CompilationUnit) parser.createAST(null);
+		IProblem[] problems = cuAST.getProblems();
+		assertEquals("Should have 1 problem", 1, problems.length);
+		assertEquals("Should have only an unused warning", "The import a.b.C is never used", problems[0].getMessage());
+	} finally {
+		deleteFile("/P/src/X.java");
+		deleteFile("/P/src/a/b/C.java");
+		deleteFolder("/P/src/a");
+	}
+}
 }
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests.java
index 2be5e05..7a5ecda 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests.java
@@ -20676,11 +20676,11 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 
 	// In the absence of the fix, it would have been:
-	// "IBar[TYPE_REF]{IBar, test, Ltest.IBar;, null, 44}\n" +
-	// "IFoo[TYPE_REF]{IFoo, test, Ltest.IFoo;, null, 44}"
+	// "IBar[TYPE_REF]{IBar, test, Ltest.IBar;, null, " + (R_DEFAULT + 39) + "}\n" +
+	// "IFoo[TYPE_REF]{IFoo, test, Ltest.IFoo;, null, " + (R_DEFAULT + 39) + "}"
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"IBar[TYPE_REF]{IBar, test, Ltest.IBar;, null, 44}",
+			"IBar[TYPE_REF]{IBar, test, Ltest.IBar;, null, " + (R_DEFAULT + 39) + "}",
 			requestor.getResults());
 }
 
@@ -20712,8 +20712,8 @@
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
 			// In the absence of the fix, it would also complete to Interface1:
-			//"Interface1[TYPE_REF]{Interface1, test, Ltest.Interface1;, null, 44}\n"+
-			"Interface2[TYPE_REF]{Interface2, test, Ltest.Interface2;, null, 44}",
+			//"Interface1[TYPE_REF]{Interface1, test, Ltest.Interface1;, null, " + (R_DEFAULT + 39) + "}\n"+
+			"Interface2[TYPE_REF]{Interface2, test, Ltest.Interface2;, null, " + (R_DEFAULT + 39) + "}",
 			requestor.getResults());
 }
 
@@ -20743,7 +20743,7 @@
 	assertResults(
 			// In the absence of the fix, it would suggest both p.Enclosing.Interface2, and
 			// p.Enclosing.Interface1 while it should have suppressed the latter which is in use.
-			"Enclosing.Interface2[TYPE_REF]{p.Enclosing.Interface2, p, Lp.Enclosing$Interface2;, null, 44}",
+			"Enclosing.Interface2[TYPE_REF]{p.Enclosing.Interface2, p, Lp.Enclosing$Interface2;, null, " + (R_DEFAULT + 39) + "}",
 			requestor.getResults());
 }
 
@@ -20778,8 +20778,8 @@
 	assertResults(
 			// In the absence of the fix, it would suggest only p.Enclosing.Interface2, as it
 			// was wrongly suppressing p.Enclosing.Interface1 confusing it with p.Interface1.
-			"Enclosing.Interface1[TYPE_REF]{p.Enclosing.Interface1, p, Lp.Enclosing$Interface1;, null, 44}\n" +
-			"Enclosing.Interface2[TYPE_REF]{p.Enclosing.Interface2, p, Lp.Enclosing$Interface2;, null, 44}",
+			"Enclosing.Interface1[TYPE_REF]{p.Enclosing.Interface1, p, Lp.Enclosing$Interface1;, null, " + (R_DEFAULT + 39) + "}\n" +
+			"Enclosing.Interface2[TYPE_REF]{p.Enclosing.Interface2, p, Lp.Enclosing$Interface2;, null, " + (R_DEFAULT + 39) + "}",
 			requestor.getResults());
 }
 
@@ -21182,9 +21182,9 @@
 
 	assertResults(
 			// without the fix no proposals obtained.
-			"Tr[POTENTIAL_METHOD_DECLARATION]{Tr, Ltest.Try;, ()V, Tr, null, 14}\n" +
-			"transient[KEYWORD]{transient, null, null, transient, null, 14}\n" +
-			"Try[TYPE_REF]{Try, test, Ltest.Try;, null, null, 27}",
+			"Tr[POTENTIAL_METHOD_DECLARATION]{Tr, Ltest.Try;, ()V, Tr, null, " + (R_DEFAULT + 9) + "}\n" +
+			"transient[KEYWORD]{transient, null, null, transient, null, " + (R_DEFAULT + 9) + "}\n" +
+			"Try[TYPE_REF]{Try, test, Ltest.Try;, null, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 }
 
@@ -21220,10 +21220,10 @@
 
 	assertResults(
 			// without the fix no proposals obtained.
-			"Error[TYPE_REF]{Error, java.lang, Ljava.lang.Error;, null, null, 17}\n" +
-			"Exception[TYPE_REF]{Exception, java.lang, Ljava.lang.Exception;, null, null, 17}\n" +
-			"equals[METHOD_REF]{Try.this.equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), 24}\n" +
-			"equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), 27}",
+			"Error[TYPE_REF]{Error, java.lang, Ljava.lang.Error;, null, null, " + (R_DEFAULT + 12) + "}\n" +
+			"Exception[TYPE_REF]{Exception, java.lang, Ljava.lang.Exception;, null, null, " + (R_DEFAULT + 12) + "}\n" +
+			"equals[METHOD_REF]{Try.this.equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), " + (R_DEFAULT + 19) + "}\n" +
+			"equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 }
 
@@ -21322,7 +21322,7 @@
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
 	assertResults(
-			"AClass[TYPE_REF]{AClass, test, Ltest.AClass;, null, null, 27}",
+			"AClass[TYPE_REF]{AClass, test, Ltest.AClass;, null, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 }
 
@@ -21366,7 +21366,7 @@
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
 	assertResults(
-			"AClass[TYPE_REF]{AClass, test, Ltest.AClass;, null, null, 27}",
+			"AClass[TYPE_REF]{AClass, test, Ltest.AClass;, null, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 }
 
@@ -21396,10 +21396,10 @@
 
 	assertResults(
 			// without the fix no proposals obtained.
-			"Inn.Inn2[TYPE_REF]{test.Inn.Inn2, test, Ltest.Inn$Inn2;, null, null, 44}\n" +
-			"ABC.ABCInterface[TYPE_REF]{ABCInterface, test, Ltest.ABC$ABCInterface;, null, null, 47}\n" +
-			"In[TYPE_REF]{In, test, Ltest.In;, null, null, 47}\n" +
-			"Inn[TYPE_REF]{Inn, test, Ltest.Inn;, null, null, 47}",
+			"Inn.Inn2[TYPE_REF]{test.Inn.Inn2, test, Ltest.Inn$Inn2;, null, null, " + (R_DEFAULT + 39) + "}\n" +
+			"ABC.ABCInterface[TYPE_REF]{ABCInterface, test, Ltest.ABC$ABCInterface;, null, null, " + (R_DEFAULT + 42) + "}\n" +
+			"In[TYPE_REF]{In, test, Ltest.In;, null, null, " + (R_DEFAULT + 42) + "}\n" +
+			"Inn[TYPE_REF]{Inn, test, Ltest.Inn;, null, null, " + (R_DEFAULT + 42) + "}",
 			requestor.getResults());
 }
 
@@ -21430,11 +21430,11 @@
 
 	assertResults(
 			// without the fix no proposals obtained.
-			"Inn.Inn2[TYPE_REF]{test.Inn.Inn2, test, Ltest.Inn$Inn2;, null, null, 44}\n" +
-			"Inn.Inn2.Inn3[TYPE_REF]{test.Inn.Inn2.Inn3, test, Ltest.Inn$Inn2$Inn3;, null, null, 44}\n" +
-			"ABC[TYPE_REF]{ABC, test, Ltest.ABC;, null, null, 47}\n" +
-			"In[TYPE_REF]{In, test, Ltest.In;, null, null, 47}\n" +
-			"Inn[TYPE_REF]{Inn, test, Ltest.Inn;, null, null, 47}",
+			"Inn.Inn2[TYPE_REF]{test.Inn.Inn2, test, Ltest.Inn$Inn2;, null, null, " + (R_DEFAULT + 39) + "}\n" +
+			"Inn.Inn2.Inn3[TYPE_REF]{test.Inn.Inn2.Inn3, test, Ltest.Inn$Inn2$Inn3;, null, null, " + (R_DEFAULT + 39) + "}\n" +
+			"ABC[TYPE_REF]{ABC, test, Ltest.ABC;, null, null, " + (R_DEFAULT + 42) + "}\n" +
+			"In[TYPE_REF]{In, test, Ltest.In;, null, null, " + (R_DEFAULT + 42) + "}\n" +
+			"Inn[TYPE_REF]{Inn, test, Ltest.Inn;, null, null, " + (R_DEFAULT + 42) + "}",
 			requestor.getResults());
 }
 
@@ -21463,9 +21463,9 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
-	int relevance1 = R_RESOLVED + R_INTERESTING + R_CASE + R_NON_STATIC + R_EXACT_EXPECTED_TYPE;
-	int relevance2 = R_RESOLVED + R_INTERESTING + R_CASE + R_NON_STATIC;
-	int relevance3 = R_RESOLVED + R_CASE + R_NON_STATIC;
+	int relevance1 = R_DEFAULT + 52;
+	int relevance2 = R_DEFAULT + 22;
+	int relevance3 = R_DEFAULT + 17;
 	int start1 = str.lastIndexOf("/**/") + "".length();
 	int end1 = start1 + "/**/".length();
 	assertResults(
@@ -21620,7 +21620,7 @@
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
 	assertResults(
-			"myVar1[LOCAL_VARIABLE_REF]{myVar1, null, I, myVar1, null, 57}",
+			"myVar1[LOCAL_VARIABLE_REF]{myVar1, null, I, myVar1, null, " + (R_DEFAULT + 52) + "}",
 			requestor.getResults());
 }
 
@@ -21942,10 +21942,10 @@
     this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
     assertResults(
-            "MyClass[TYPE_REF]{mypackage.MyClass, mypackage, Lmypackage.MyClass;, null, null, " + (R_NON_STATIC + R_UNQUALIFIED) + "}\n" + 
-            "mypackage[PACKAGE_REF]{mypackage, mypackage, null, null, null, " + (R_NON_STATIC + R_UNQUALIFIED + R_CASE) + "}\n" + 
-            "myString[FIELD_REF]{myString, Ltest.X;, Ljava.lang.String;, myString, null, " + (R_NON_STATIC + R_UNQUALIFIED + R_CASE + R_NON_RESTRICTED) + "}\n" +
-            "myString2[FIELD_REF]{myString2, Ltest.X;, Ljava.lang.String;, myString2, null, " + (R_NON_STATIC + R_UNQUALIFIED + R_CASE + R_NON_RESTRICTED) + "}",
+            "MyClass[TYPE_REF]{mypackage.MyClass, mypackage, Lmypackage.MyClass;, null, null, " + (R_DEFAULT + 9) + "}\n" + 
+            "mypackage[PACKAGE_REF]{mypackage, mypackage, null, null, null, " + (R_DEFAULT + 19) + "}\n" + 
+            "myString[FIELD_REF]{myString, Ltest.X;, Ljava.lang.String;, myString, null, " + (R_DEFAULT + 22) + "}\n" +
+            "myString2[FIELD_REF]{myString2, Ltest.X;, Ljava.lang.String;, myString2, null, " + (R_DEFAULT + 22) + "}",
             requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=312603
@@ -21969,9 +21969,9 @@
 			this.wcOwner);
 
 	assertResults(
-			"MyClass[TYPE_REF]{mypackage.MyClass, mypackage, Lmypackage.MyClass;, null, null, 14}\n" + 
-			"mypackage[PACKAGE_REF]{mypackage, mypackage, null, null, null, 24}\n" + 
-			"myString[FIELD_REF]{myString, Ltest.X;, Ljava.lang.String;, myString, null, 57}",
+			"MyClass[TYPE_REF]{mypackage.MyClass, mypackage, Lmypackage.MyClass;, null, null, " + (R_DEFAULT + 9) + "}\n" + 
+			"mypackage[PACKAGE_REF]{mypackage, mypackage, null, null, null, " + (R_DEFAULT + 19) + "}\n" + 
+			"myString[FIELD_REF]{myString, Ltest.X;, Ljava.lang.String;, myString, null, " + (R_DEFAULT + 52) + "}",
 			requestor.getResults());
 }
 
@@ -22001,10 +22001,10 @@
 			this.wcOwner);
 
 	assertResults(
-			"MyClass[TYPE_REF]{mypackage.MyClass, mypackage, Lmypackage.MyClass;, null, null, " + (R_NON_STATIC + R_UNQUALIFIED) + "}\n" + 
-			"mypackage[PACKAGE_REF]{mypackage, mypackage, null, null, null, " + (R_NON_STATIC + R_UNQUALIFIED + R_CASE) + "}\n" + 
-			"myString[LOCAL_VARIABLE_REF]{myString, null, Ljava.lang.String;, myString, null, " + (R_NON_STATIC + R_UNQUALIFIED + R_CASE + R_NON_RESTRICTED + R_EXACT_EXPECTED_TYPE) + "}\n" +
-			"myString2[LOCAL_VARIABLE_REF]{myString2, null, Ljava.lang.String;, myString2, null, " + (R_NON_STATIC + R_UNQUALIFIED + R_CASE + R_NON_RESTRICTED + R_EXACT_EXPECTED_TYPE) + "}",
+			"MyClass[TYPE_REF]{mypackage.MyClass, mypackage, Lmypackage.MyClass;, null, null, " + (R_DEFAULT + 9) + "}\n" + 
+			"mypackage[PACKAGE_REF]{mypackage, mypackage, null, null, null, " + (R_DEFAULT + 19) + "}\n" + 
+			"myString[LOCAL_VARIABLE_REF]{myString, null, Ljava.lang.String;, myString, null, " + (R_DEFAULT + 52) + "}\n" +
+			"myString2[LOCAL_VARIABLE_REF]{myString2, null, Ljava.lang.String;, myString2, null, " + (R_DEFAULT + 52) + "}",
 			requestor.getResults());
 }
 
@@ -22034,10 +22034,10 @@
 			this.wcOwner);
 
 	assertResults(
-			"MyClass[TYPE_REF]{mypackage.MyClass, mypackage, Lmypackage.MyClass;, null, null, " + (R_NON_STATIC + R_UNQUALIFIED) + "}\n" + 
-			"mypackage[PACKAGE_REF]{mypackage, mypackage, null, null, null, " + (R_NON_STATIC + R_UNQUALIFIED + R_CASE) + "}\n" + 
-			"myString[LOCAL_VARIABLE_REF]{myString, null, Ljava.lang.String;, myString, null, " + (R_NON_STATIC + R_UNQUALIFIED + R_CASE + R_NON_RESTRICTED + R_EXACT_EXPECTED_TYPE) + "}\n" +
-			"myString1[LOCAL_VARIABLE_REF]{myString1, null, Ljava.lang.String;, myString1, null, " + (R_NON_STATIC + R_UNQUALIFIED + R_CASE + R_NON_RESTRICTED + R_EXACT_EXPECTED_TYPE) + "}",
+			"MyClass[TYPE_REF]{mypackage.MyClass, mypackage, Lmypackage.MyClass;, null, null, " + (R_DEFAULT + 9) + "}\n" + 
+			"mypackage[PACKAGE_REF]{mypackage, mypackage, null, null, null, " + (R_DEFAULT + 19) + "}\n" + 
+			"myString[LOCAL_VARIABLE_REF]{myString, null, Ljava.lang.String;, myString, null, " + (R_DEFAULT + 52) + "}\n" +
+			"myString1[LOCAL_VARIABLE_REF]{myString1, null, Ljava.lang.String;, myString1, null, " + (R_DEFAULT + 52) + "}",
 			requestor.getResults());
 }
 
@@ -22058,7 +22058,7 @@
 		int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 		
 		cu.codeComplete(cursorLocation, requestor);
-		int relevance = R_INTERFACE + R_UNQUALIFIED + R_NON_RESTRICTED;
+		int relevance = R_DEFAULT + 21;
 		assumeEquals(
 				"should have two completions",
 				"element:ADD_CUSTOM_ATTRIBUTES    completion:ADD_CUSTOM_ATTRIBUTES    relevance:" + relevance +"\n" + 
@@ -23979,7 +23979,7 @@
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 		
 		assertResults(
-			"def[POTENTIAL_METHOD_DECLARATION]{def, Ltest.P;, ()V, def, null, 14}\n" + "default[KEYWORD]{default, null, null, default, null, 24}" ,
+			"def[POTENTIAL_METHOD_DECLARATION]{def, Ltest.P;, ()V, def, null, " + (R_DEFAULT + 9) + "}\n" + "default[KEYWORD]{default, null, null, default, null, " + (R_DEFAULT + 19) + "}" ,
 			requestor.getResults());
 	} finally {
 		// Restore compliance settings.
@@ -24043,7 +24043,7 @@
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 		
 		assertResults(
-			"def[POTENTIAL_METHOD_DECLARATION]{def, Ltest.P;, ()V, def, null, 14}" ,
+			"def[POTENTIAL_METHOD_DECLARATION]{def, Ltest.P;, ()V, def, null, " + (R_DEFAULT + 9) + "}" ,
 			requestor.getResults());
 	} finally {
 		// Restore compliance settings.
@@ -24109,7 +24109,7 @@
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 		
 		assertResults(
-			"def[POTENTIAL_METHOD_DECLARATION]{def, Ltest.ZZ$I;, ()V, def, null, 14}\n" + "default[KEYWORD]{default, null, null, default, null, 24}" ,
+			"def[POTENTIAL_METHOD_DECLARATION]{def, Ltest.ZZ$I;, ()V, def, null, " + (R_DEFAULT + 9) + "}\n" + "default[KEYWORD]{default, null, null, default, null, " + (R_DEFAULT + 19) + "}" ,
 			requestor.getResults());
 	} finally {
 		// Restore compliance settings.
@@ -24573,10 +24573,10 @@
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner, monitor);
 		
 		assertResults(
-				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<TT;>;, (Ljava.lang.String;Ljava.lang.String;)V, TXYU, (s, s2), 30}\n" +
-				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, 30}\n" +
-				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<TT;>;, (TT;)V, TXYU, (t), 30}\n" +
-				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, 30}",
+				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<TT;>;, (Ljava.lang.String;Ljava.lang.String;)V, TXYU, (s, s2), " + (R_DEFAULT + 25) + "}\n" +
+				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, " + (R_DEFAULT + 25) + "}\n" +
+				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<TT;>;, (TT;)V, TXYU, (t), " + (R_DEFAULT + 25) + "}\n" +
+				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, " + (R_DEFAULT + 25) + "}",
 				requestor.getResults());
 		assertEquals(true,
 			requestor.canUseDiamond(0));
@@ -24616,10 +24616,10 @@
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner, monitor);
 		
 		assertResults(
-				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<TT;>;, (Ljava.lang.String;Ljava.lang.String;)V, TXYU, (s, s2), 30}\n" +
-				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, 30}\n" +
-				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<TT;>;, (TT;)V, TXYU, (t), 30}\n" +
-				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, 30}",
+				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<TT;>;, (Ljava.lang.String;Ljava.lang.String;)V, TXYU, (s, s2), " + (R_DEFAULT + 25) + "}\n" +
+				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, " + (R_DEFAULT + 25) + "}\n" +
+				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<TT;>;, (TT;)V, TXYU, (t), " + (R_DEFAULT + 25) + "}\n" +
+				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, " + (R_DEFAULT + 25) + "}",
 				requestor.getResults());
 		assertEquals(false,
 			requestor.canUseDiamond(1));
@@ -24662,10 +24662,10 @@
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner, monitor);
 		
 		assertResults(
-				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.Test<Ljava.lang.Object;>.TXYU;, (Ljava.lang.String;Ljava.lang.String;)V, TXYU, (s, s2), 30}\n" +
-				"   Test.TXYU[TYPE_REF]{TXYU, test, Ltest.Test$TXYU;, null, null, 30}\n" +
-				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.Test<Ljava.lang.Object;>.TXYU;, (TT;)V, TXYU, (t), 30}\n" +
-				"   Test.TXYU[TYPE_REF]{TXYU, test, Ltest.Test$TXYU;, null, null, 30}",
+				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.Test<Ljava.lang.Object;>.TXYU;, (Ljava.lang.String;Ljava.lang.String;)V, TXYU, (s, s2), " + (R_DEFAULT + 25) + "}\n" +
+				"   Test.TXYU[TYPE_REF]{TXYU, test, Ltest.Test$TXYU;, null, null, " + (R_DEFAULT + 25) + "}\n" +
+				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.Test<Ljava.lang.Object;>.TXYU;, (TT;)V, TXYU, (t), " + (R_DEFAULT + 25) + "}\n" +
+				"   Test.TXYU[TYPE_REF]{TXYU, test, Ltest.Test$TXYU;, null, null, " + (R_DEFAULT + 25) + "}",
 				requestor.getResults());
 		assertEquals(true,
 			requestor.canUseDiamond(0));
@@ -24708,10 +24708,10 @@
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner, monitor);
 		
 		assertResults(
-				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.Test<Ljava.lang.Object;>.TXYU;, (Ljava.lang.String;Ljava.lang.String;)V, TXYU, (s, s2), 30}\n" +
-				"   Test.TXYU[TYPE_REF]{TXYU, test, Ltest.Test$TXYU;, null, null, 30}\n" +
-				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.Test<Ljava.lang.Object;>.TXYU;, (TT;)V, TXYU, (t), 30}\n" +
-				"   Test.TXYU[TYPE_REF]{TXYU, test, Ltest.Test$TXYU;, null, null, 30}",
+				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.Test<Ljava.lang.Object;>.TXYU;, (Ljava.lang.String;Ljava.lang.String;)V, TXYU, (s, s2), " + (R_DEFAULT + 25) + "}\n" +
+				"   Test.TXYU[TYPE_REF]{TXYU, test, Ltest.Test$TXYU;, null, null, " + (R_DEFAULT + 25) + "}\n" +
+				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.Test<Ljava.lang.Object;>.TXYU;, (TT;)V, TXYU, (t), " + (R_DEFAULT + 25) + "}\n" +
+				"   Test.TXYU[TYPE_REF]{TXYU, test, Ltest.Test$TXYU;, null, null, " + (R_DEFAULT + 25) + "}",
 				requestor.getResults());
 		assertEquals(false,
 			requestor.canUseDiamond(1));
@@ -24757,10 +24757,10 @@
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner, monitor);
 		
 		assertResults(
-				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<LNumber;>;, (LNumber;)V, TXYU, (t), 60}\n" +
-				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, 60}\n" +
-				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<LNumber;>;, (Ljava.lang.String;Ljava.lang.String;)V, TXYU, (s, s2), 60}\n" +
-				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, 60}",
+				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<LNumber;>;, (LNumber;)V, TXYU, (t), " + (R_DEFAULT + 55) + "}\n" +
+				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, " + (R_DEFAULT + 55) + "}\n" +
+				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<LNumber;>;, (Ljava.lang.String;Ljava.lang.String;)V, TXYU, (s, s2), " + (R_DEFAULT + 55) + "}\n" +
+				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, " + (R_DEFAULT + 55) + "}",
 				requestor.getResults());
 		assertEquals(false,
 			requestor.canUseDiamond(0));
@@ -24806,10 +24806,10 @@
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner, monitor);
 		
 		assertResults(
-				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<LNumber;>;, (LNumber;)V, TXYU, (t), 60}\n" +
-				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, 60}\n" +
-				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<LNumber;>;, (Ljava.lang.String;Ljava.lang.String;)V, TXYU, (s, s2), 60}\n" +
-				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, 60}",
+				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<LNumber;>;, (LNumber;)V, TXYU, (t), " + (R_DEFAULT + 55) + "}\n" +
+				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, " + (R_DEFAULT + 55) + "}\n" +
+				"TXYU[CONSTRUCTOR_INVOCATION]{(), Ltest.TXYU<LNumber;>;, (Ljava.lang.String;Ljava.lang.String;)V, TXYU, (s, s2), " + (R_DEFAULT + 55) + "}\n" +
+				"   TXYU[TYPE_REF]{TXYU, test, Ltest.TXYU;, null, null, " + (R_DEFAULT + 55) + "}",
 				requestor.getResults());
 		assertEquals(true,
 			requestor.canUseDiamond(1));
@@ -24850,11 +24850,11 @@
 			"expectedTypesKeys={Ltest/Try~MyClass;}",
 			requestor.getContext());
 	assertResults(
-			"mypackage[PACKAGE_REF]{mypackage, mypackage, null, null, null, " + (R_NON_STATIC + R_UNQUALIFIED) + "}\n" +
-			"MyClass[TYPE_REF]{mypackage.MyClass, mypackage, Lmypackage.MyClass;, null, null, " + (R_NON_STATIC + R_UNQUALIFIED + R_CASE) + "}\n" +
-			"MyClass[TYPE_REF]{MyClass, test, Ltest.MyClass;, null, null, " + (R_NON_STATIC + R_UNQUALIFIED + R_CASE + R_NON_RESTRICTED + R_EXACT_EXPECTED_TYPE) + "}\n" +
-			"MyClassField[FIELD_REF]{MyClassField, Ltest.Try;, Ltest.MyClass;, MyClassField, null, " + (R_NON_STATIC + R_UNQUALIFIED + R_CASE + R_NON_RESTRICTED + R_EXACT_EXPECTED_TYPE) + "}\n" +
-			"MyClassMethod[METHOD_REF]{MyClassMethod(), Ltest.Try;, ()Ltest.MyClass;, MyClassMethod, null, " + (R_NON_STATIC + R_UNQUALIFIED + R_CASE + R_NON_RESTRICTED + R_EXACT_EXPECTED_TYPE) + "}",
+			"mypackage[PACKAGE_REF]{mypackage, mypackage, null, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"MyClass[TYPE_REF]{mypackage.MyClass, mypackage, Lmypackage.MyClass;, null, null, " + (R_DEFAULT + 19) + "}\n" +
+			"MyClass[TYPE_REF]{MyClass, test, Ltest.MyClass;, null, null, " + (R_DEFAULT + 52) + "}\n" +
+			"MyClassField[FIELD_REF]{MyClassField, Ltest.Try;, Ltest.MyClass;, MyClassField, null, " + (R_DEFAULT + 52) + "}\n" +
+			"MyClassMethod[METHOD_REF]{MyClassMethod(), Ltest.Try;, ()Ltest.MyClass;, MyClassMethod, null, " + (R_DEFAULT + 52) + "}",
 			requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=292087
@@ -24888,21 +24888,21 @@
 			"expectedTypesKeys={Ltest/Try~MyClass;}",
 			requestor.getContext());
 	assertResults(
-			"finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, finalize, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_VOID) + "}\n" +
-			"notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, notify, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_VOID) + "}\n" +
-			"notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, notifyAll, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_VOID) + "}\n" +
-			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, wait, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_VOID) + "}\n" +
-			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, wait, (millis), " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_VOID) + "}\n" +
-			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, wait, (millis, nanos), " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_VOID) + "}\n" +
-			"Try[TYPE_REF]{Try, test, Ltest.Try;, null, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS) + "}\n" +
-			"clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS) + "}\n" +
-			"equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS) + "}\n" +
-			"getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class;, getClass, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS) + "}\n" +
-			"hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, hashCode, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS) + "}\n" +
-			"toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS) + "}\n" +
-			"MyClass[TYPE_REF]{MyClass, test, Ltest.MyClass;, null, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_EXACT_EXPECTED_TYPE) + "}\n" +
-			"MyClassField[FIELD_REF]{MyClassField, Ltest.Try;, Ltest.MyClass;, MyClassField, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_EXACT_EXPECTED_TYPE) + "}\n" +
-			"MyClassMethod[METHOD_REF]{MyClassMethod(), Ltest.Try;, ()Ltest.MyClass;, MyClassMethod, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_EXACT_EXPECTED_TYPE) + "}",
+			"finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, finalize, null, " + (R_DEFAULT + 17) + "}\n" +
+			"notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, notify, null, " + (R_DEFAULT + 17) + "}\n" +
+			"notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, notifyAll, null, " + (R_DEFAULT + 17) + "}\n" +
+			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, wait, null, " + (R_DEFAULT + 17) + "}\n" +
+			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, wait, (millis), " + (R_DEFAULT + 17) + "}\n" +
+			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, wait, (millis, nanos), " + (R_DEFAULT + 17) + "}\n" +
+			"Try[TYPE_REF]{Try, test, Ltest.Try;, null, null, " + (R_DEFAULT + 22) + "}\n" +
+			"clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, " + (R_DEFAULT + 22) + "}\n" +
+			"equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), " + (R_DEFAULT + 22) + "}\n" +
+			"getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class;, getClass, null, " + (R_DEFAULT + 22) + "}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, hashCode, null, " + (R_DEFAULT + 22) + "}\n" +
+			"toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, " + (R_DEFAULT + 22) + "}\n" +
+			"MyClass[TYPE_REF]{MyClass, test, Ltest.MyClass;, null, null, " + (R_DEFAULT + 52) + "}\n" +
+			"MyClassField[FIELD_REF]{MyClassField, Ltest.Try;, Ltest.MyClass;, MyClassField, null, " + (R_DEFAULT + 52) + "}\n" +
+			"MyClassMethod[METHOD_REF]{MyClassMethod(), Ltest.Try;, ()Ltest.MyClass;, MyClassMethod, null, " + (R_DEFAULT + 52) + "}",
 			requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=292087
@@ -24938,21 +24938,21 @@
 			"expectedTypesKeys={Ltest/Try~MyClass;}",
 			requestor.getContext());
 	assertResults(
-			"finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, finalize, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_VOID) + "}\n" +
-			"notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, notify, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_VOID) + "}\n" +
-			"notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, notifyAll, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_VOID) + "}\n" +
-			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, wait, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_VOID) + "}\n" +
-			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, wait, (millis), " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_VOID) + "}\n" +
-			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, wait, (millis, nanos), " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_VOID) + "}\n" +
-			"Try[TYPE_REF]{Try, test, Ltest.Try;, null, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS) + "}\n" +
-			"clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS) + "}\n" +
-			"equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS) + "}\n" +
-			"getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class;, getClass, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS) + "}\n" +
-			"hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, hashCode, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS) + "}\n" +
-			"toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS) + "}\n" +
-			"MyClass[TYPE_REF]{MyClass, test, Ltest.MyClass;, null, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_EXACT_EXPECTED_TYPE) + "}\n" +
-			"MyClassField[FIELD_REF]{MyClassField, Ltest.Try;, Ltest.MyClass;, MyClassField, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_EXACT_EXPECTED_TYPE) + "}\n" +
-			"MyClassMethod[METHOD_REF]{MyClassMethod(), Ltest.Try;, ()Ltest.MyClass;, MyClassMethod, null, " + (R_RESOLVED + R_NON_STATIC + R_NAME_LESS_NEW_CHARACTERS + R_EXACT_EXPECTED_TYPE) + "}",
+			"finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, finalize, null, " + (R_DEFAULT + 17) + "}\n" +
+			"notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, notify, null, " + (R_DEFAULT + 17) + "}\n" +
+			"notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, notifyAll, null, " + (R_DEFAULT + 17) + "}\n" +
+			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, wait, null, " + (R_DEFAULT + 17) + "}\n" +
+			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, wait, (millis), " + (R_DEFAULT + 17) + "}\n" +
+			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, wait, (millis, nanos), " + (R_DEFAULT + 17) + "}\n" +
+			"Try[TYPE_REF]{Try, test, Ltest.Try;, null, null, " + (R_DEFAULT + 22) + "}\n" +
+			"clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, " + (R_DEFAULT + 22) + "}\n" +
+			"equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), " + (R_DEFAULT + 22) + "}\n" +
+			"getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class;, getClass, null, " + (R_DEFAULT + 22) + "}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, hashCode, null, " + (R_DEFAULT + 22) + "}\n" +
+			"toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, " + (R_DEFAULT + 22) + "}\n" +
+			"MyClass[TYPE_REF]{MyClass, test, Ltest.MyClass;, null, null, " + (R_DEFAULT + 52) + "}\n" +
+			"MyClassField[FIELD_REF]{MyClassField, Ltest.Try;, Ltest.MyClass;, MyClassField, null, " + (R_DEFAULT + 52) + "}\n" +
+			"MyClassMethod[METHOD_REF]{MyClassMethod(), Ltest.Try;, ()Ltest.MyClass;, MyClassMethod, null, " + (R_DEFAULT + 52) + "}",
 			requestor.getResults());
 }
 
@@ -25181,15 +25181,15 @@
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner, monitor);
 		
 		assertResults(
-				"IT_MAY_BE_DUE_TO_MIXING_PERHAPS[FIELD_REF]{IT_MAY_BE_DUE_TO_MIXING_PERHAPS, Ltest.ExampleEnumNoAutocomplete;, Ltest.ExampleEnumNoAutocomplete;, IT_MAY_BE_DUE_TO_MIXING_PERHAPS, null, 26}\n" +
-				"MORE_STUFF[FIELD_REF]{MORE_STUFF, Ltest.ExampleEnumNoAutocomplete;, Ltest.ExampleEnumNoAutocomplete;, MORE_STUFF, null, 26}\n" +
-				"OTHER[FIELD_REF]{OTHER, Ltest.ExampleEnumNoAutocomplete;, Ltest.ExampleEnumNoAutocomplete;, OTHER, null, 26}\n" +
-				"STILL_OTHER[FIELD_REF]{STILL_OTHER, Ltest.ExampleEnumNoAutocomplete;, Ltest.ExampleEnumNoAutocomplete;, STILL_OTHER, null, 26}\n" +
-				"STUFF[FIELD_REF]{STUFF, Ltest.ExampleEnumNoAutocomplete;, Ltest.ExampleEnumNoAutocomplete;, STUFF, null, 26}\n" +
-				"THINGS[FIELD_REF]{THINGS, Ltest.ExampleEnumNoAutocomplete;, Ltest.ExampleEnumNoAutocomplete;, THINGS, null, 26}\n" +
-				"class[FIELD_REF]{class, null, Ljava.lang.Class<Ltest.ExampleEnumNoAutocomplete;>;, class, null, 26}\n" +
-				"valueOf[METHOD_REF]{valueOf(), Ltest.ExampleEnumNoAutocomplete;, (Ljava.lang.String;)Ltest.ExampleEnumNoAutocomplete;, valueOf, (arg0), 26}\n" +
-				"values[METHOD_REF]{values(), Ltest.ExampleEnumNoAutocomplete;, ()[Ltest.ExampleEnumNoAutocomplete;, values, null, 26}",
+				"IT_MAY_BE_DUE_TO_MIXING_PERHAPS[FIELD_REF]{IT_MAY_BE_DUE_TO_MIXING_PERHAPS, Ltest.ExampleEnumNoAutocomplete;, Ltest.ExampleEnumNoAutocomplete;, IT_MAY_BE_DUE_TO_MIXING_PERHAPS, null, " + (R_DEFAULT + 21) + "}\n" +
+				"MORE_STUFF[FIELD_REF]{MORE_STUFF, Ltest.ExampleEnumNoAutocomplete;, Ltest.ExampleEnumNoAutocomplete;, MORE_STUFF, null, " + (R_DEFAULT + 21) + "}\n" +
+				"OTHER[FIELD_REF]{OTHER, Ltest.ExampleEnumNoAutocomplete;, Ltest.ExampleEnumNoAutocomplete;, OTHER, null, " + (R_DEFAULT + 21) + "}\n" +
+				"STILL_OTHER[FIELD_REF]{STILL_OTHER, Ltest.ExampleEnumNoAutocomplete;, Ltest.ExampleEnumNoAutocomplete;, STILL_OTHER, null, " + (R_DEFAULT + 21) + "}\n" +
+				"STUFF[FIELD_REF]{STUFF, Ltest.ExampleEnumNoAutocomplete;, Ltest.ExampleEnumNoAutocomplete;, STUFF, null, " + (R_DEFAULT + 21) + "}\n" +
+				"THINGS[FIELD_REF]{THINGS, Ltest.ExampleEnumNoAutocomplete;, Ltest.ExampleEnumNoAutocomplete;, THINGS, null, " + (R_DEFAULT + 21) + "}\n" +
+				"class[FIELD_REF]{class, null, Ljava.lang.Class<Ltest.ExampleEnumNoAutocomplete;>;, class, null, " + (R_DEFAULT + 21) + "}\n" +
+				"valueOf[METHOD_REF]{valueOf(), Ltest.ExampleEnumNoAutocomplete;, (Ljava.lang.String;)Ltest.ExampleEnumNoAutocomplete;, valueOf, (arg0), " + (R_DEFAULT + 21) + "}\n" +
+				"values[METHOD_REF]{values(), Ltest.ExampleEnumNoAutocomplete;, ()[Ltest.ExampleEnumNoAutocomplete;, values, null, " + (R_DEFAULT + 21) + "}",
 				requestor.getResults());
 		assertEquals(false,
 			requestor.canUseDiamond(0));
@@ -25242,7 +25242,7 @@
 	    this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner, monitor);
 	    
 	    assertResults(
-			"staticMethod[METHOD_REF]{staticMethod(), Ltest.Test;, ()V, staticMethod, null, 27}",
+			"staticMethod[METHOD_REF]{staticMethod(), Ltest.Test;, ()V, staticMethod, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 	} finally {
 		deleteProject("P");
@@ -25297,7 +25297,7 @@
 	    this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner, monitor);
 	    
 	    assertResults(
-			"staticMethod[METHOD_REF]{staticMethod(), Ltest.I;, ()V, staticMethod, null, 26}",
+			"staticMethod[METHOD_REF]{staticMethod(), Ltest.I;, ()V, staticMethod, null, " + (R_DEFAULT + 21) + "}",
 			requestor.getResults());
 	} finally {
 		deleteProject("P");
@@ -25353,7 +25353,7 @@
 	    this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner, monitor);
 	    
 	    assertResults(
-			"defaultMethod[METHOD_REF]{defaultMethod(), Ltest.I;, ()V, defaultMethod, null, 35}",
+			"defaultMethod[METHOD_REF]{defaultMethod(), Ltest.I;, ()V, defaultMethod, null, " + (R_DEFAULT + 30) + "}",
 			requestor.getResults());
 	} finally {
 		deleteProject("P");
@@ -25408,7 +25408,7 @@
 	    this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner, monitor);
 	    
 	    assertResults(
-			"defaultMethod[METHOD_REF]{defaultMethod(), Ltest.I;, ()V, defaultMethod, null, 27}",
+			"defaultMethod[METHOD_REF]{defaultMethod(), Ltest.I;, ()V, defaultMethod, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 	} finally {
 		deleteProject("P");
@@ -25452,7 +25452,7 @@
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner, monitor);
 		
 		assertResults(
-				"toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, 65}",
+				"toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, " + (R_DEFAULT + 60) + "}",
 				requestor.getResults());
 		assertEquals(false,
 			requestor.canUseDiamond(0));
@@ -25492,12 +25492,12 @@
 		int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 		assertResults(
-			"I[TYPE_REF]{I, test, Ltest.I;, null, null, 27}\n" +
-			"X[TYPE_REF]{X, test, Ltest.X;, null, null, 27}\n" +
-			"args[LOCAL_VARIABLE_REF]{args, null, [Ljava.lang.String;, args, null, 27}\n" +
-			"i[LOCAL_VARIABLE_REF]{i, null, Ltest.I;, i, null, 27}\n" +
-			"main[METHOD_REF]{main(), Ltest.X;, ([Ljava.lang.String;)V, main, (args), 27}\n" +
-			"x[LOCAL_VARIABLE_REF]{x, null, [[[Ltest.X;, x, null, 27}",
+			"I[TYPE_REF]{I, test, Ltest.I;, null, null, " + (R_DEFAULT + 22) + "}\n" +
+			"X[TYPE_REF]{X, test, Ltest.X;, null, null, " + (R_DEFAULT + 22) + "}\n" +
+			"args[LOCAL_VARIABLE_REF]{args, null, [Ljava.lang.String;, args, null, " + (R_DEFAULT + 22) + "}\n" +
+			"i[LOCAL_VARIABLE_REF]{i, null, Ltest.I;, i, null, " + (R_DEFAULT + 22) + "}\n" +
+			"main[METHOD_REF]{main(), Ltest.X;, ([Ljava.lang.String;)V, main, (args), " + (R_DEFAULT + 22) + "}\n" +
+			"x[LOCAL_VARIABLE_REF]{x, null, [[[Ltest.X;, x, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 	} finally {
 		// Restore compliance settings.
@@ -25534,13 +25534,13 @@
 		int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 		assertResults(
-			"I[TYPE_REF]{I, test, Ltest.I;, null, null, 27}\n" +
-			"S[TYPE_REF]{S, null, TS;, null, null, 27}\n" +
-			"X<S>[TYPE_REF]{X, test, Ltest.X<TS;>;, null, null, 27}\n" +
-			"args[LOCAL_VARIABLE_REF]{args, null, [Ljava.lang.String;, args, null, 27}\n" +
-			"i[LOCAL_VARIABLE_REF]{i, null, Ltest.I;, i, null, 27}\n" +
-			"main[METHOD_REF]{main(), Ltest.X<TS;>;, ([Ljava.lang.String;)V, main, (args), 27}\n" +
-			"x[LOCAL_VARIABLE_REF]{x, null, Ltest.X;, x, null, 27}",
+			"I[TYPE_REF]{I, test, Ltest.I;, null, null, " + (R_DEFAULT + 22) + "}\n" +
+			"S[TYPE_REF]{S, null, TS;, null, null, " + (R_DEFAULT + 22) + "}\n" +
+			"X<S>[TYPE_REF]{X, test, Ltest.X<TS;>;, null, null, " + (R_DEFAULT + 22) + "}\n" +
+			"args[LOCAL_VARIABLE_REF]{args, null, [Ljava.lang.String;, args, null, " + (R_DEFAULT + 22) + "}\n" +
+			"i[LOCAL_VARIABLE_REF]{i, null, Ltest.I;, i, null, " + (R_DEFAULT + 22) + "}\n" +
+			"main[METHOD_REF]{main(), Ltest.X<TS;>;, ([Ljava.lang.String;)V, main, (args), " + (R_DEFAULT + 22) + "}\n" +
+			"x[LOCAL_VARIABLE_REF]{x, null, Ltest.X;, x, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 	} finally {
 		// Restore compliance settings.
@@ -25694,7 +25694,7 @@
 		int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 		assertResults(
-			"pqrqwerty[LOCAL_VARIABLE_REF]{pqrqwerty, null, I, pqrqwerty, null, 27}",
+			"pqrqwerty[LOCAL_VARIABLE_REF]{pqrqwerty, null, I, pqrqwerty, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 	} finally {
 		// Restore compliance settings.
@@ -25739,7 +25739,7 @@
 		int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 		assertResults(
-			"pqrqwerty[LOCAL_VARIABLE_REF]{pqrqwerty, null, I, pqrqwerty, null, 27}",
+			"pqrqwerty[LOCAL_VARIABLE_REF]{pqrqwerty, null, I, pqrqwerty, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 	} finally {
 		// Restore compliance settings.
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests18.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests18.java
index 9002b44..7056054 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests18.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests18.java
@@ -26,7 +26,7 @@
 public class CompletionTests18 extends AbstractJavaModelCompletionTests {
 
 static {
-//	TESTS_NAMES = new String[] {"test001"};
+//		TESTS_NAMES = new String[] {"test001"};
 }
 
 public CompletionTests18(String name) {
@@ -63,7 +63,7 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"first[LOCAL_VARIABLE_REF]{first, null, I, first, null, 27}",
+			"first[LOCAL_VARIABLE_REF]{first, null, I, first, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 }
 public void test002() throws JavaModelException {
@@ -86,7 +86,7 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"first[LOCAL_VARIABLE_REF]{first, null, I, first, null, 27}",
+			"first[LOCAL_VARIABLE_REF]{first, null, I, first, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 }
 public void test003() throws JavaModelException {
@@ -118,22 +118,22 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"CASE_INSENSITIVE_ORDER[FIELD_REF]{CASE_INSENSITIVE_ORDER, Ljava.lang.String;, Ljava.util.Comparator<Ljava.lang.String;>;, CASE_INSENSITIVE_ORDER, null, 14}\n" +
-			"copyValueOf[METHOD_REF]{copyValueOf(), Ljava.lang.String;, ([C)Ljava.lang.String;, copyValueOf, (arg0), 24}\n" +
-			"copyValueOf[METHOD_REF]{copyValueOf(), Ljava.lang.String;, ([CII)Ljava.lang.String;, copyValueOf, (arg0, arg1, arg2), 24}\n" +
-			"charAt[METHOD_REF]{charAt(), Ljava.lang.String;, (I)C, charAt, (arg0), 35}\n" +
-			"chars[METHOD_REF]{chars(), Ljava.lang.CharSequence;, ()Ljava.util.stream.IntStream;, chars, null, 35}\n" +
-			"clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, 35}\n" +
-			"codePointAt[METHOD_REF]{codePointAt(), Ljava.lang.String;, (I)I, codePointAt, (arg0), 35}\n" +
-			"codePointBefore[METHOD_REF]{codePointBefore(), Ljava.lang.String;, (I)I, codePointBefore, (arg0), 35}\n" +
-			"codePointCount[METHOD_REF]{codePointCount(), Ljava.lang.String;, (II)I, codePointCount, (arg0, arg1), 35}\n" +
-			"codePoints[METHOD_REF]{codePoints(), Ljava.lang.CharSequence;, ()Ljava.util.stream.IntStream;, codePoints, null, 35}\n" +
-			"compareTo[METHOD_REF]{compareTo(), Ljava.lang.String;, (Ljava.lang.String;)I, compareTo, (arg0), 35}\n" +
-			"compareToIgnoreCase[METHOD_REF]{compareToIgnoreCase(), Ljava.lang.String;, (Ljava.lang.String;)I, compareToIgnoreCase, (arg0), 35}\n" +
-			"concat[METHOD_REF]{concat(), Ljava.lang.String;, (Ljava.lang.String;)Ljava.lang.String;, concat, (arg0), 35}\n" +
-			"contains[METHOD_REF]{contains(), Ljava.lang.String;, (Ljava.lang.CharSequence;)Z, contains, (arg0), 35}\n" +
-			"contentEquals[METHOD_REF]{contentEquals(), Ljava.lang.String;, (Ljava.lang.CharSequence;)Z, contentEquals, (arg0), 35}\n" +
-			"contentEquals[METHOD_REF]{contentEquals(), Ljava.lang.String;, (Ljava.lang.StringBuffer;)Z, contentEquals, (arg0), 35}",
+			"CASE_INSENSITIVE_ORDER[FIELD_REF]{CASE_INSENSITIVE_ORDER, Ljava.lang.String;, Ljava.util.Comparator<Ljava.lang.String;>;, CASE_INSENSITIVE_ORDER, null, " + (R_DEFAULT + 9) + "}\n" +
+			"copyValueOf[METHOD_REF]{copyValueOf(), Ljava.lang.String;, ([C)Ljava.lang.String;, copyValueOf, (arg0), " + (R_DEFAULT + 19) + "}\n" +
+			"copyValueOf[METHOD_REF]{copyValueOf(), Ljava.lang.String;, ([CII)Ljava.lang.String;, copyValueOf, (arg0, arg1, arg2), " + (R_DEFAULT + 19) + "}\n" +
+			"charAt[METHOD_REF]{charAt(), Ljava.lang.String;, (I)C, charAt, (arg0), " + (R_DEFAULT + 30) + "}\n" +
+			"chars[METHOD_REF]{chars(), Ljava.lang.CharSequence;, ()Ljava.util.stream.IntStream;, chars, null, " + (R_DEFAULT + 30) + "}\n" +
+			"clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, " + (R_DEFAULT + 30) + "}\n" +
+			"codePointAt[METHOD_REF]{codePointAt(), Ljava.lang.String;, (I)I, codePointAt, (arg0), " + (R_DEFAULT + 30) + "}\n" +
+			"codePointBefore[METHOD_REF]{codePointBefore(), Ljava.lang.String;, (I)I, codePointBefore, (arg0), " + (R_DEFAULT + 30) + "}\n" +
+			"codePointCount[METHOD_REF]{codePointCount(), Ljava.lang.String;, (II)I, codePointCount, (arg0, arg1), " + (R_DEFAULT + 30) + "}\n" +
+			"codePoints[METHOD_REF]{codePoints(), Ljava.lang.CharSequence;, ()Ljava.util.stream.IntStream;, codePoints, null, " + (R_DEFAULT + 30) + "}\n" +
+			"compareTo[METHOD_REF]{compareTo(), Ljava.lang.String;, (Ljava.lang.String;)I, compareTo, (arg0), " + (R_DEFAULT + 30) + "}\n" +
+			"compareToIgnoreCase[METHOD_REF]{compareToIgnoreCase(), Ljava.lang.String;, (Ljava.lang.String;)I, compareToIgnoreCase, (arg0), " + (R_DEFAULT + 30) + "}\n" +
+			"concat[METHOD_REF]{concat(), Ljava.lang.String;, (Ljava.lang.String;)Ljava.lang.String;, concat, (arg0), " + (R_DEFAULT + 30) + "}\n" +
+			"contains[METHOD_REF]{contains(), Ljava.lang.String;, (Ljava.lang.CharSequence;)Z, contains, (arg0), " + (R_DEFAULT + 30) + "}\n" +
+			"contentEquals[METHOD_REF]{contentEquals(), Ljava.lang.String;, (Ljava.lang.CharSequence;)Z, contentEquals, (arg0), " + (R_DEFAULT + 30) + "}\n" +
+			"contentEquals[METHOD_REF]{contentEquals(), Ljava.lang.String;, (Ljava.lang.StringBuffer;)Z, contentEquals, (arg0), " + (R_DEFAULT + 30) + "}",
 			requestor.getResults());
 }
 public void test004() throws JavaModelException {
@@ -154,8 +154,8 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"lpx5[LOCAL_VARIABLE_REF]{lpx5, null, I, lpx5, null, 27}\n" +
-			"lpx6[LOCAL_VARIABLE_REF]{lpx6, null, I, lpx6, null, 27}",
+			"lpx5[LOCAL_VARIABLE_REF]{lpx5, null, I, lpx5, null, " + (R_DEFAULT + 22) + "}\n" +
+			"lpx6[LOCAL_VARIABLE_REF]{lpx6, null, I, lpx6, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 }
 
@@ -183,7 +183,7 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"argument[LOCAL_VARIABLE_REF]{argument, null, I, argument, null, 57}",
+			"argument[LOCAL_VARIABLE_REF]{argument, null, I, argument, null, " + (R_DEFAULT + 52) + "}",
 			requestor.getResults());
 }
 public void test006() throws JavaModelException {
@@ -208,7 +208,7 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"argument[LOCAL_VARIABLE_REF]{argument, null, I, argument, null, 27}",
+			"argument[LOCAL_VARIABLE_REF]{argument, null, I, argument, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=405126, [1.8][code assist] Lambda parameters incorrectly recovered as fields. 
@@ -236,10 +236,10 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"class[FIELD_REF]{class, null, Ljava.lang.Class<LX;>;, class, null, 26}\n" +
-			"f[FIELD_REF]{f, LX;, LFoo;, f, null, 26}\n" +
-			"this[KEYWORD]{this, null, null, this, null, 26}\n" +
-			"x1[FIELD_REF]{x1, LX;, I, x1, null, 56}",
+			"class[FIELD_REF]{class, null, Ljava.lang.Class<LX;>;, class, null, " + (R_DEFAULT + 21) + "}\n" +
+			"f[FIELD_REF]{f, LX;, LFoo;, f, null, " + (R_DEFAULT + 21) + "}\n" +
+			"this[KEYWORD]{this, null, null, this, null, " + (R_DEFAULT + 21) + "}\n" +
+			"x1[FIELD_REF]{x1, LX;, I, x1, null, " + (R_DEFAULT + 51) + "}",
 			requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=422107, [1.8][code assist] Invoking code assist just before and after a variable initialized using lambda gives different result
@@ -265,35 +265,35 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"[POTENTIAL_METHOD_DECLARATION]{, LX;, ()V, , null, 14}\n" +
-			"abstract[KEYWORD]{abstract, null, null, abstract, null, 24}\n" +
+			"[POTENTIAL_METHOD_DECLARATION]{, LX;, ()V, , null, " + (R_DEFAULT + 9) + "}\n" +
+			"abstract[KEYWORD]{abstract, null, null, abstract, null, " + (R_DEFAULT + 19) + "}\n" +
 //{ObjectTeams:
-			"callin[KEYWORD]{callin, null, null, callin, null, 24}\n" +
+			"callin[KEYWORD]{callin, null, null, callin, null, " + (R_DEFAULT + 19) + "}\n" +
 // SH}
-			"class[KEYWORD]{class, null, null, class, null, 24}\n" +
-			"enum[KEYWORD]{enum, null, null, enum, null, 24}\n" +
-			"final[KEYWORD]{final, null, null, final, null, 24}\n" +
-			"interface[KEYWORD]{interface, null, null, interface, null, 24}\n" +
-			"native[KEYWORD]{native, null, null, native, null, 24}\n" +
-			"private[KEYWORD]{private, null, null, private, null, 24}\n" +
-			"protected[KEYWORD]{protected, null, null, protected, null, 24}\n" +
-			"public[KEYWORD]{public, null, null, public, null, 24}\n" +
-			"static[KEYWORD]{static, null, null, static, null, 24}\n" +
-			"strictfp[KEYWORD]{strictfp, null, null, strictfp, null, 24}\n" +
-			"synchronized[KEYWORD]{synchronized, null, null, synchronized, null, 24}\n" +
+			"class[KEYWORD]{class, null, null, class, null, " + (R_DEFAULT + 19) + "}\n" +
+			"enum[KEYWORD]{enum, null, null, enum, null, " + (R_DEFAULT + 19) + "}\n" +
+			"final[KEYWORD]{final, null, null, final, null, " + (R_DEFAULT + 19) + "}\n" +
+			"interface[KEYWORD]{interface, null, null, interface, null, " + (R_DEFAULT + 19) + "}\n" +
+			"native[KEYWORD]{native, null, null, native, null, " + (R_DEFAULT + 19) + "}\n" +
+			"private[KEYWORD]{private, null, null, private, null, " + (R_DEFAULT + 19) + "}\n" +
+			"protected[KEYWORD]{protected, null, null, protected, null, " + (R_DEFAULT + 19) + "}\n" +
+			"public[KEYWORD]{public, null, null, public, null, " + (R_DEFAULT + 19) + "}\n" +
+			"static[KEYWORD]{static, null, null, static, null, " + (R_DEFAULT + 19) + "}\n" +
+			"strictfp[KEYWORD]{strictfp, null, null, strictfp, null, " + (R_DEFAULT + 19) + "}\n" +
+			"synchronized[KEYWORD]{synchronized, null, null, synchronized, null, " + (R_DEFAULT + 19) + "}\n" +
 //{ObjectTeams:
-			"team[KEYWORD]{team, null, null, team, null, 24}\n" +
+			"team[KEYWORD]{team, null, null, team, null, " + (R_DEFAULT + 19) + "}\n" +
 // SH}
-			"transient[KEYWORD]{transient, null, null, transient, null, 24}\n" +
-			"volatile[KEYWORD]{volatile, null, null, volatile, null, 24}\n" +
-			"I[TYPE_REF]{I, , LI;, null, null, 27}\n" +
-			"J[TYPE_REF]{J, , LJ;, null, null, 27}\n" +
-			"X[TYPE_REF]{X, , LX;, null, null, 27}\n" +
-			"clone[METHOD_DECLARATION]{protected Object clone() throws CloneNotSupportedException, Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, 27}\n" +
-			"equals[METHOD_DECLARATION]{public boolean equals(Object obj), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), 27}\n" +
-			"finalize[METHOD_DECLARATION]{protected void finalize() throws Throwable, Ljava.lang.Object;, ()V, finalize, null, 27}\n" +
-			"hashCode[METHOD_DECLARATION]{public int hashCode(), Ljava.lang.Object;, ()I, hashCode, null, 27}\n" +
-			"toString[METHOD_DECLARATION]{public String toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, 27}",
+			"transient[KEYWORD]{transient, null, null, transient, null, " + (R_DEFAULT + 19) + "}\n" +
+			"volatile[KEYWORD]{volatile, null, null, volatile, null, " + (R_DEFAULT + 19) + "}\n" +
+			"I[TYPE_REF]{I, , LI;, null, null, " + (R_DEFAULT + 22) + "}\n" +
+			"J[TYPE_REF]{J, , LJ;, null, null, " + (R_DEFAULT + 22) + "}\n" +
+			"X[TYPE_REF]{X, , LX;, null, null, " + (R_DEFAULT + 22) + "}\n" +
+			"clone[METHOD_DECLARATION]{protected Object clone() throws CloneNotSupportedException, Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, " + (R_DEFAULT + 22) + "}\n" +
+			"equals[METHOD_DECLARATION]{public boolean equals(Object obj), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), " + (R_DEFAULT + 22) + "}\n" +
+			"finalize[METHOD_DECLARATION]{protected void finalize() throws Throwable, Ljava.lang.Object;, ()V, finalize, null, " + (R_DEFAULT + 22) + "}\n" +
+			"hashCode[METHOD_DECLARATION]{public int hashCode(), Ljava.lang.Object;, ()I, hashCode, null, " + (R_DEFAULT + 22) + "}\n" +
+			"toString[METHOD_DECLARATION]{public String toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=422107, [1.8][code assist] Invoking code assist just before and after a variable initialized using lambda gives different result
@@ -319,35 +319,35 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"[POTENTIAL_METHOD_DECLARATION]{, LX;, ()V, , null, 14}\n" +
-			"abstract[KEYWORD]{abstract, null, null, abstract, null, 24}\n" +
+			"[POTENTIAL_METHOD_DECLARATION]{, LX;, ()V, , null, " + (R_DEFAULT + 9) + "}\n" +
+			"abstract[KEYWORD]{abstract, null, null, abstract, null, " + (R_DEFAULT + 19) + "}\n" +
 //{ObjectTeams:
-			"callin[KEYWORD]{callin, null, null, callin, null, 24}\n" +
+			"callin[KEYWORD]{callin, null, null, callin, null, " + (R_DEFAULT + 9) + "}\n" +
 // SH}
-			"class[KEYWORD]{class, null, null, class, null, 24}\n" +
-			"enum[KEYWORD]{enum, null, null, enum, null, 24}\n" +
-			"final[KEYWORD]{final, null, null, final, null, 24}\n" +
-			"interface[KEYWORD]{interface, null, null, interface, null, 24}\n" +
-			"native[KEYWORD]{native, null, null, native, null, 24}\n" +
-			"private[KEYWORD]{private, null, null, private, null, 24}\n" +
-			"protected[KEYWORD]{protected, null, null, protected, null, 24}\n" +
-			"public[KEYWORD]{public, null, null, public, null, 24}\n" +
-			"static[KEYWORD]{static, null, null, static, null, 24}\n" +
-			"strictfp[KEYWORD]{strictfp, null, null, strictfp, null, 24}\n" +
-			"synchronized[KEYWORD]{synchronized, null, null, synchronized, null, 24}\n" +
+			"class[KEYWORD]{class, null, null, class, null, " + (R_DEFAULT + 19) + "}\n" +
+			"enum[KEYWORD]{enum, null, null, enum, null, " + (R_DEFAULT + 19) + "}\n" +
+			"final[KEYWORD]{final, null, null, final, null, " + (R_DEFAULT + 19) + "}\n" +
+			"interface[KEYWORD]{interface, null, null, interface, null, " + (R_DEFAULT + 19) + "}\n" +
+			"native[KEYWORD]{native, null, null, native, null, " + (R_DEFAULT + 19) + "}\n" +
+			"private[KEYWORD]{private, null, null, private, null, " + (R_DEFAULT + 19) + "}\n" +
+			"protected[KEYWORD]{protected, null, null, protected, null, " + (R_DEFAULT + 19) + "}\n" +
+			"public[KEYWORD]{public, null, null, public, null, " + (R_DEFAULT + 19) + "}\n" +
+			"static[KEYWORD]{static, null, null, static, null, " + (R_DEFAULT + 19) + "}\n" +
+			"strictfp[KEYWORD]{strictfp, null, null, strictfp, null, " + (R_DEFAULT + 19) + "}\n" +
+			"synchronized[KEYWORD]{synchronized, null, null, synchronized, null, " + (R_DEFAULT + 19) + "}\n" +
 //{ObjectTeams:
-			"team[KEYWORD]{team, null, null, team, null, 24}\n" +
+			"team[KEYWORD]{team, null, null, team, null, " + (R_DEFAULT + 9) + "}\n" +
 // SH}
-			"transient[KEYWORD]{transient, null, null, transient, null, 24}\n" +
-			"volatile[KEYWORD]{volatile, null, null, volatile, null, 24}\n" +
-			"I[TYPE_REF]{I, , LI;, null, null, 27}\n" +
-			"J[TYPE_REF]{J, , LJ;, null, null, 27}\n" +
-			"X[TYPE_REF]{X, , LX;, null, null, 27}\n" +
-			"clone[METHOD_DECLARATION]{protected Object clone() throws CloneNotSupportedException, Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, 27}\n" +
-			"equals[METHOD_DECLARATION]{public boolean equals(Object obj), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), 27}\n" +
-			"finalize[METHOD_DECLARATION]{protected void finalize() throws Throwable, Ljava.lang.Object;, ()V, finalize, null, 27}\n" +
-			"hashCode[METHOD_DECLARATION]{public int hashCode(), Ljava.lang.Object;, ()I, hashCode, null, 27}\n" +
-			"toString[METHOD_DECLARATION]{public String toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, 27}",
+			"transient[KEYWORD]{transient, null, null, transient, null, " + (R_DEFAULT + 19) + "}\n" +
+			"volatile[KEYWORD]{volatile, null, null, volatile, null, " + (R_DEFAULT + 19) + "}\n" +
+			"I[TYPE_REF]{I, , LI;, null, null, " + (R_DEFAULT + 22) + "}\n" +
+			"J[TYPE_REF]{J, , LJ;, null, null, " + (R_DEFAULT + 22) + "}\n" +
+			"X[TYPE_REF]{X, , LX;, null, null, " + (R_DEFAULT + 22) + "}\n" +
+			"clone[METHOD_DECLARATION]{protected Object clone() throws CloneNotSupportedException, Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, " + (R_DEFAULT + 22) + "}\n" +
+			"equals[METHOD_DECLARATION]{public boolean equals(Object obj), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), " + (R_DEFAULT + 22) + "}\n" +
+			"finalize[METHOD_DECLARATION]{protected void finalize() throws Throwable, Ljava.lang.Object;, ()V, finalize, null, " + (R_DEFAULT + 22) + "}\n" +
+			"hashCode[METHOD_DECLARATION]{public int hashCode(), Ljava.lang.Object;, ()I, hashCode, null, " + (R_DEFAULT + 22) + "}\n" +
+			"toString[METHOD_DECLARATION]{public String toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 }
 public void test010() throws JavaModelException {
@@ -377,20 +377,20 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"goo[METHOD_REF]{goo(), LX;, (LI;)V, goo, (i), 24}\n" +
-			"goo[METHOD_REF]{goo(), LX;, (Ljava.lang.String;)V, goo, (s), 24}\n" +
-			"main[METHOD_REF]{main(), LX;, ([Ljava.lang.String;)V, main, (args), 24}\n" +
-			"clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, 35}\n" +
-			"equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), 35}\n" +
-			"finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, finalize, null, 35}\n" +
-			"getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, getClass, null, 35}\n" +
-			"hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, hashCode, null, 35}\n" +
-			"notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, notify, null, 35}\n" +
-			"notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, notifyAll, null, 35}\n" +
-			"toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, 35}\n" +
-			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, wait, null, 35}\n" +
-			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, wait, (millis), 35}\n" +
-			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, wait, (millis, nanos), 35}",
+			"goo[METHOD_REF]{goo(), LX;, (LI;)V, goo, (i), " + (R_DEFAULT + 19) + "}\n" +
+			"goo[METHOD_REF]{goo(), LX;, (Ljava.lang.String;)V, goo, (s), " + (R_DEFAULT + 19) + "}\n" +
+			"main[METHOD_REF]{main(), LX;, ([Ljava.lang.String;)V, main, (args), " + (R_DEFAULT + 19) + "}\n" +
+			"clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, clone, null, " + (R_DEFAULT + 30) + "}\n" +
+			"equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, equals, (obj), " + (R_DEFAULT + 30) + "}\n" +
+			"finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, finalize, null, " + (R_DEFAULT + 30) + "}\n" +
+			"getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, getClass, null, " + (R_DEFAULT + 30) + "}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, hashCode, null, " + (R_DEFAULT + 30) + "}\n" +
+			"notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, notify, null, " + (R_DEFAULT + 30) + "}\n" +
+			"notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, notifyAll, null, " + (R_DEFAULT + 30) + "}\n" +
+			"toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, " + (R_DEFAULT + 30) + "}\n" +
+			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, wait, null, " + (R_DEFAULT + 30) + "}\n" +
+			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, wait, (millis), " + (R_DEFAULT + 30) + "}\n" +
+			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, wait, (millis, nanos), " + (R_DEFAULT + 30) + "}",
 			requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=422901, [1.8][code assist] Code assistant sensitive to scope.referenceContext type identity.
@@ -509,8 +509,8 @@
 	String completeBehind = "arrayO";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("arrayOfStrings[LOCAL_VARIABLE_REF]{arrayOfStrings, null, [Ljava.lang.String;, null, null, arrayOfStrings, null, [168, 174], 27}\n" +
-					"arrayOfInts[LOCAL_VARIABLE_REF]{arrayOfInts, null, [I, null, null, arrayOfInts, null, [168, 174], 57}", requestor.getResults());
+	assertResults("arrayOfStrings[LOCAL_VARIABLE_REF]{arrayOfStrings, null, [Ljava.lang.String;, null, null, arrayOfStrings, null, [168, 174], " + (R_DEFAULT + 22) + "}\n" +
+					"arrayOfInts[LOCAL_VARIABLE_REF]{arrayOfInts, null, [I, null, null, arrayOfInts, null, [168, 174], " + (R_DEFAULT + 52) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=422901, [1.8][code assist] Code assistant sensitive to scope.referenceContext type identity.
 public void test015() throws JavaModelException { // ensure higher relevance for matching return type.
@@ -537,7 +537,7 @@
 	String completeBehind = "xyz";
 	int cursorLocation = str.indexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("xyzAfter[LOCAL_VARIABLE_REF]{xyzAfter, null, Ljava.lang.Object;, null, null, xyzAfter, null, [132, 135], 26}", requestor.getResults());
+	assertResults("xyzAfter[LOCAL_VARIABLE_REF]{xyzAfter, null, Ljava.lang.Object;, null, null, xyzAfter, null, [132, 135], " + (R_DEFAULT + 21) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=422901, [1.8][code assist] Code assistant sensitive to scope.referenceContext type identity.
 public void test016() throws JavaModelException { // ensure higher relevance for matching return type.
@@ -564,7 +564,7 @@
 	String completeBehind = "xyz";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("xyzBefore[LOCAL_VARIABLE_REF]{xyzBefore, null, Ljava.lang.Object;, null, null, xyzBefore, null, [163, 166], 26}", requestor.getResults());
+	assertResults("xyzBefore[LOCAL_VARIABLE_REF]{xyzBefore, null, Ljava.lang.Object;, null, null, xyzBefore, null, [163, 166], " + (R_DEFAULT + 21) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=422901, [1.8][code assist] Code assistant sensitive to scope.referenceContext type identity.
 public void test017() throws JavaModelException { // ensure higher relevance for matching return type.
@@ -732,20 +732,20 @@
 	String completeBehind = "Stri";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("StringBufferInputStream[TYPE_REF]{java.io.StringBufferInputStream, java.io, Ljava.io.StringBufferInputStream;, null, null, null, null, [155, 159], 24}\n" +
-			"StringCharBuffer[TYPE_REF]{java.nio.StringCharBuffer, java.nio, Ljava.nio.StringCharBuffer;, null, null, null, null, [155, 159], 24}\n" +
-			"StringCharacterIterator[TYPE_REF]{java.text.StringCharacterIterator, java.text, Ljava.text.StringCharacterIterator;, null, null, null, null, [155, 159], 24}\n" +
-			"StringJoiner[TYPE_REF]{java.util.StringJoiner, java.util, Ljava.util.StringJoiner;, null, null, null, null, [155, 159], 24}\n" +
-			"StringReader[TYPE_REF]{java.io.StringReader, java.io, Ljava.io.StringReader;, null, null, null, null, [155, 159], 24}\n" +
-			"StringTokenizer[TYPE_REF]{java.util.StringTokenizer, java.util, Ljava.util.StringTokenizer;, null, null, null, null, [155, 159], 24}\n" +
-			"StringWriter[TYPE_REF]{java.io.StringWriter, java.io, Ljava.io.StringWriter;, null, null, null, null, [155, 159], 24}\n" +
-			"StrictMath[TYPE_REF]{StrictMath, java.lang, Ljava.lang.StrictMath;, null, null, null, null, [155, 159], 27}\n" +
-			"String[TYPE_REF]{String, java.lang, Ljava.lang.String;, null, null, null, null, [155, 159], 27}\n" +
-			"StringBuffer[TYPE_REF]{StringBuffer, java.lang, Ljava.lang.StringBuffer;, null, null, null, null, [155, 159], 27}\n" +
-			"StringBuilder[TYPE_REF]{StringBuilder, java.lang, Ljava.lang.StringBuilder;, null, null, null, null, [155, 159], 27}\n" +
-			"StringCoding[TYPE_REF]{StringCoding, java.lang, Ljava.lang.StringCoding;, null, null, null, null, [155, 159], 27}\n" +
-			"StringIndexOutOfBoundsException[TYPE_REF]{StringIndexOutOfBoundsException, java.lang, Ljava.lang.StringIndexOutOfBoundsException;, null, null, null, null, [155, 159], 27}\n" +
-			"StringParameter[LOCAL_VARIABLE_REF]{StringParameter, null, LX;, null, null, StringParameter, null, [155, 159], 27}", requestor.getResults());
+	assertResults("StringBufferInputStream[TYPE_REF]{java.io.StringBufferInputStream, java.io, Ljava.io.StringBufferInputStream;, null, null, null, null, [155, 159], " + (R_DEFAULT + 19) + "}\n" +
+			"StringCharBuffer[TYPE_REF]{java.nio.StringCharBuffer, java.nio, Ljava.nio.StringCharBuffer;, null, null, null, null, [155, 159], " + (R_DEFAULT + 19) + "}\n" +
+			"StringCharacterIterator[TYPE_REF]{java.text.StringCharacterIterator, java.text, Ljava.text.StringCharacterIterator;, null, null, null, null, [155, 159], " + (R_DEFAULT + 19) + "}\n" +
+			"StringJoiner[TYPE_REF]{java.util.StringJoiner, java.util, Ljava.util.StringJoiner;, null, null, null, null, [155, 159], " + (R_DEFAULT + 19) + "}\n" +
+			"StringReader[TYPE_REF]{java.io.StringReader, java.io, Ljava.io.StringReader;, null, null, null, null, [155, 159], " + (R_DEFAULT + 19) + "}\n" +
+			"StringTokenizer[TYPE_REF]{java.util.StringTokenizer, java.util, Ljava.util.StringTokenizer;, null, null, null, null, [155, 159], " + (R_DEFAULT + 19) + "}\n" +
+			"StringWriter[TYPE_REF]{java.io.StringWriter, java.io, Ljava.io.StringWriter;, null, null, null, null, [155, 159], " + (R_DEFAULT + 19) + "}\n" +
+			"StrictMath[TYPE_REF]{StrictMath, java.lang, Ljava.lang.StrictMath;, null, null, null, null, [155, 159], " + (R_DEFAULT + 22) + "}\n" +
+			"String[TYPE_REF]{String, java.lang, Ljava.lang.String;, null, null, null, null, [155, 159], " + (R_DEFAULT + 22) + "}\n" +
+			"StringBuffer[TYPE_REF]{StringBuffer, java.lang, Ljava.lang.StringBuffer;, null, null, null, null, [155, 159], " + (R_DEFAULT + 22) + "}\n" +
+			"StringBuilder[TYPE_REF]{StringBuilder, java.lang, Ljava.lang.StringBuilder;, null, null, null, null, [155, 159], " + (R_DEFAULT + 22) + "}\n" +
+			"StringCoding[TYPE_REF]{StringCoding, java.lang, Ljava.lang.StringCoding;, null, null, null, null, [155, 159], " + (R_DEFAULT + 22) + "}\n" +
+			"StringIndexOutOfBoundsException[TYPE_REF]{StringIndexOutOfBoundsException, java.lang, Ljava.lang.StringIndexOutOfBoundsException;, null, null, null, null, [155, 159], " + (R_DEFAULT + 22) + "}\n" +
+			"StringParameter[LOCAL_VARIABLE_REF]{StringParameter, null, LX;, null, null, StringParameter, null, [155, 159], " + (R_DEFAULT + 22) + "}", requestor.getResults());
 }
 public void testBrokenMethodCall() throws JavaModelException { // ensure completion works when the containing call is not terminated properly.
 	this.workingCopies = new ICompilationUnit[1];
@@ -770,7 +770,7 @@
 	String completeBehind = "StringP";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("StringParameter[LOCAL_VARIABLE_REF]{StringParameter, null, LX;, null, null, StringParameter, null, [155, 162], 27}", requestor.getResults());
+	assertResults("StringParameter[LOCAL_VARIABLE_REF]{StringParameter, null, LX;, null, null, StringParameter, null, [155, 162], " + (R_DEFAULT + 22) + "}", requestor.getResults());
 }
 public void testExpressionBody() throws JavaModelException {
 	this.workingCopies = new ICompilationUnit[1];
@@ -795,21 +795,21 @@
 	String completeBehind = "xyz.";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("goo[METHOD_REF]{goo(), LX;, (LI;)V, null, null, goo, (i), [173, 173], 24}\n" +
-			"main[METHOD_REF]{main(), LX;, ([Ljava.lang.String;)V, null, null, main, (args), [173, 173], 24}\n" +
-			"clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, null, null, clone, null, [173, 173], 35}\n" +
-			"equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, null, null, equals, (obj), [173, 173], 35}\n" +
-			"field[FIELD_REF]{field, LX;, I, null, null, field, null, [173, 173], 35}\n" +
-			"finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, null, null, finalize, null, [173, 173], 35}\n" +
-			"foo[METHOD_REF]{foo(), LX;, ()V, null, null, foo, null, [173, 173], 35}\n" +
-			"getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [173, 173], 35}\n" +
-			"hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, null, null, hashCode, null, [173, 173], 35}\n" +
-			"notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, null, null, notify, null, [173, 173], 35}\n" +
-			"notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, null, null, notifyAll, null, [173, 173], 35}\n" +
-			"toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, null, null, toString, null, [173, 173], 35}\n" +
-			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, null, null, wait, null, [173, 173], 35}\n" +
-			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, null, null, wait, (millis), [173, 173], 35}\n" +
-			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, null, null, wait, (millis, nanos), [173, 173], 35}", requestor.getResults());
+	assertResults("goo[METHOD_REF]{goo(), LX;, (LI;)V, null, null, goo, (i), [173, 173], " + (R_DEFAULT + 19) + "}\n" +
+			"main[METHOD_REF]{main(), LX;, ([Ljava.lang.String;)V, null, null, main, (args), [173, 173], " + (R_DEFAULT + 19) + "}\n" +
+			"clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, null, null, clone, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, null, null, equals, (obj), [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"field[FIELD_REF]{field, LX;, I, null, null, field, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, null, null, finalize, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"foo[METHOD_REF]{foo(), LX;, ()V, null, null, foo, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, null, null, hashCode, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, null, null, notify, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, null, null, notifyAll, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, null, null, toString, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, null, null, wait, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, null, null, wait, (millis), [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, null, null, wait, (millis, nanos), [173, 173], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 }
 public void testExpressionBody2() throws JavaModelException {
 	this.workingCopies = new ICompilationUnit[1];
@@ -834,21 +834,21 @@
 	String completeBehind = "xyz.";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("goo[METHOD_REF]{goo(), LX;, (LI;)V, null, null, goo, (i), [173, 173], 24}\n" +
-			"main[METHOD_REF]{main(), LX;, ([Ljava.lang.String;)V, null, null, main, (args), [173, 173], 24}\n" +
-			"clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, null, null, clone, null, [173, 173], 35}\n" +
-			"equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, null, null, equals, (obj), [173, 173], 35}\n" +
-			"field[FIELD_REF]{field, LX;, I, null, null, field, null, [173, 173], 35}\n" +
-			"finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, null, null, finalize, null, [173, 173], 35}\n" +
-			"foo[METHOD_REF]{foo(), LX;, ()V, null, null, foo, null, [173, 173], 35}\n" +
-			"getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [173, 173], 35}\n" +
-			"hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, null, null, hashCode, null, [173, 173], 35}\n" +
-			"notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, null, null, notify, null, [173, 173], 35}\n" +
-			"notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, null, null, notifyAll, null, [173, 173], 35}\n" +
-			"toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, null, null, toString, null, [173, 173], 35}\n" +
-			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, null, null, wait, null, [173, 173], 35}\n" +
-			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, null, null, wait, (millis), [173, 173], 35}\n" +
-			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, null, null, wait, (millis, nanos), [173, 173], 35}", requestor.getResults());
+	assertResults("goo[METHOD_REF]{goo(), LX;, (LI;)V, null, null, goo, (i), [173, 173], " + (R_DEFAULT + 19) + "}\n" +
+			"main[METHOD_REF]{main(), LX;, ([Ljava.lang.String;)V, null, null, main, (args), [173, 173], " + (R_DEFAULT + 19) + "}\n" +
+			"clone[METHOD_REF]{clone(), Ljava.lang.Object;, ()Ljava.lang.Object;, null, null, clone, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"equals[METHOD_REF]{equals(), Ljava.lang.Object;, (Ljava.lang.Object;)Z, null, null, equals, (obj), [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"field[FIELD_REF]{field, LX;, I, null, null, field, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"finalize[METHOD_REF]{finalize(), Ljava.lang.Object;, ()V, null, null, finalize, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"foo[METHOD_REF]{foo(), LX;, ()V, null, null, foo, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.lang.Object;, ()I, null, null, hashCode, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"notify[METHOD_REF]{notify(), Ljava.lang.Object;, ()V, null, null, notify, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"notifyAll[METHOD_REF]{notifyAll(), Ljava.lang.Object;, ()V, null, null, notifyAll, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, null, null, toString, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, ()V, null, null, wait, null, [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (J)V, null, null, wait, (millis), [173, 173], " + (R_DEFAULT + 30) + "}\n" +
+			"wait[METHOD_REF]{wait(), Ljava.lang.Object;, (JI)V, null, null, wait, (millis, nanos), [173, 173], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 }
 // Bug 405125 - [1.8][code assist] static members of an interface appearing after the declaration of a static member lambda expression are not being suggested.
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=405125
@@ -877,7 +877,7 @@
 	    this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	    
 	    assertResults(
-	    	"bars[METHOD_REF]{bars(), LB;, ()I, bars, null, 27}",
+	    	"bars[METHOD_REF]{bars(), LB;, ()I, bars, null, " + (R_DEFAULT + 22) + "}",
 	    	requestor.getResults());
 }
 public void testBug405125b() throws JavaModelException {
@@ -905,7 +905,7 @@
 	    this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	    
 	    assertResults(
-	    	"another[FIELD_REF]{another, LB;, I, another, null, 27}",
+	    	"another[FIELD_REF]{another, LB;, I, another, null, " + (R_DEFAULT + 22) + "}",
 	    	requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=425084, [1.8][completion] Eclipse freeze while autocompleting try block in lambda.
@@ -932,8 +932,8 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"tryit[LOCAL_VARIABLE_REF]{tryit, null, I, null, null, tryit, null, [99, 102], 27}\n" +
-			"try[KEYWORD]{try, null, null, null, null, try, null, [99, 102], 28}", 
+			"tryit[LOCAL_VARIABLE_REF]{tryit, null, I, null, null, tryit, null, [99, 102], " + (R_DEFAULT + 22) + "}\n" +
+			"try[KEYWORD]{try, null, null, null, null, try, null, [99, 102], " + (R_DEFAULT + 23) + "}", 
 			requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=422901, [1.8][code assist] Code assistant sensitive to scope.referenceContext type identity.
@@ -1017,7 +1017,7 @@
 	String completeBehind = "Ty";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("TypeUse[TYPE_REF]{TypeUse, , LTypeUse;, null, null, null, null, [131, 133], 52}", requestor.getResults());
+	assertResults("TypeUse[TYPE_REF]{TypeUse, , LTypeUse;, null, null, null, null, [131, 133], " + (R_DEFAULT + 47) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=427532, [1.8][code assist] Completion engine does not like intersection casts
 public void test427532() throws JavaModelException {
@@ -1136,7 +1136,7 @@
 	String completeBehind = "@Ann";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("Annotation[TYPE_REF]{Annotation, , LAnnotation;, null, null, null, null, [138, 141], 47}", requestor.getResults());
+	assertResults("Annotation[TYPE_REF]{Annotation, , LAnnotation;, null, null, null, null, [138, 141], " + (R_DEFAULT + 42) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=428735,  [1.8][assist] Missing completion proposals inside lambda body expression - other than first token
 public void test428735() throws JavaModelException {
@@ -1159,8 +1159,8 @@
 	String completeBehind = "p.get";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [186, 189], 35}\n" +
-                  "getLastName[METHOD_REF]{getLastName(), LPerson;, ()Ljava.lang.String;, null, null, getLastName, null, [186, 189], 35}", requestor.getResults());
+	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [186, 189], " + (R_DEFAULT + 30) + "}\n" +
+                  "getLastName[METHOD_REF]{getLastName(), LPerson;, ()Ljava.lang.String;, null, null, getLastName, null, [186, 189], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=428735,  [1.8][assist] Missing completion proposals inside lambda body expression - other than first token
 public void test428735a() throws JavaModelException {
@@ -1186,8 +1186,8 @@
 	String completeBehind = "x.get";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [267, 270], 35}\n" +
-                  "getLastName[METHOD_REF]{getLastName(), LPerson;, ()Ljava.lang.String;, null, null, getLastName, null, [267, 270], 35}", requestor.getResults());
+	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [267, 270], " + (R_DEFAULT + 30) + "}\n" +
+                  "getLastName[METHOD_REF]{getLastName(), LPerson;, ()Ljava.lang.String;, null, null, getLastName, null, [267, 270], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=428735,  [1.8][assist] Missing completion proposals inside lambda body expression - other than first token
 public void test428735b() throws JavaModelException {
@@ -1213,8 +1213,8 @@
 	String completeBehind = "y.get";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [293, 296], 35}\n" +
-                  "getLastName[METHOD_REF]{getLastName(), LPerson;, ()Ljava.lang.String;, null, null, getLastName, null, [293, 296], 35}", requestor.getResults());
+	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [293, 296], " + (R_DEFAULT + 30) + "}\n" +
+                  "getLastName[METHOD_REF]{getLastName(), LPerson;, ()Ljava.lang.String;, null, null, getLastName, null, [293, 296], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=428735,  [1.8][assist] Missing completion proposals inside lambda body expression - other than first token
 public void test428735c() throws JavaModelException {
@@ -1240,8 +1240,8 @@
 	String completeBehind = "y.get";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [285, 288], 35}\n" +
-                  "getLastName[METHOD_REF]{getLastName(), LPerson;, ()Ljava.lang.String;, null, null, getLastName, null, [285, 288], 65}", requestor.getResults());
+	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [285, 288], " + (R_DEFAULT + 30) + "}\n" +
+                  "getLastName[METHOD_REF]{getLastName(), LPerson;, ()Ljava.lang.String;, null, null, getLastName, null, [285, 288], " + (R_DEFAULT + 60) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=428735,  [1.8][assist] Missing completion proposals inside lambda body expression - other than first token
 public void test428735d() throws JavaModelException {
@@ -1267,8 +1267,8 @@
 	String completeBehind = "x.get";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [272, 275], 35}\n" +
-                  "getLastName[METHOD_REF]{getLastName(), LPerson;, ()Ljava.lang.String;, null, null, getLastName, null, [272, 275], 65}", requestor.getResults());
+	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [272, 275], " + (R_DEFAULT + 30) + "}\n" +
+                  "getLastName[METHOD_REF]{getLastName(), LPerson;, ()Ljava.lang.String;, null, null, getLastName, null, [272, 275], " + (R_DEFAULT + 60) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=428735,  [1.8][assist] Missing completion proposals inside lambda body expression - other than first token
 public void test428735e() throws JavaModelException {
@@ -1293,8 +1293,8 @@
 	String completeBehind = "x.get";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [203, 206], 35}\n" +
-               "getLastName[METHOD_REF]{getLastName(), LPerson;, ()Ljava.lang.String;, null, null, getLastName, null, [203, 206], 65}", requestor.getResults());
+	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [203, 206], " + (R_DEFAULT + 30) + "}\n" +
+               "getLastName[METHOD_REF]{getLastName(), LPerson;, ()Ljava.lang.String;, null, null, getLastName, null, [203, 206], " + (R_DEFAULT + 60) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=402081, [1.8][code complete] No proposals while completing at method/constructor references
 public void test402081() throws JavaModelException {
@@ -1321,7 +1321,7 @@
 	String completeBehind = "long";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("longMethodName[METHOD_NAME_REFERENCE]{longMethodName, LX;, (Ljava.lang.String;)Ljava.lang.String;, null, null, longMethodName, (x), [183, 187], 35}", requestor.getResults());
+	assertResults("longMethodName[METHOD_NAME_REFERENCE]{longMethodName, LX;, (Ljava.lang.String;)Ljava.lang.String;, null, null, longMethodName, (x), [183, 187], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=402081, [1.8][code complete] No proposals while completing at method/constructor references
 public void test402081a() throws JavaModelException {
@@ -1351,7 +1351,7 @@
 		int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 		assertResults(
-			"longMethodName[METHOD_NAME_REFERENCE]{longMethodName, Ltest.X;, (Ljava.lang.String;)Ljava.lang.String;, longMethodName, (x), 35}",
+			"longMethodName[METHOD_NAME_REFERENCE]{longMethodName, Ltest.X;, (Ljava.lang.String;)Ljava.lang.String;, longMethodName, (x), " + (R_DEFAULT + 30) + "}",
 			requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=402081, [1.8][code complete] No proposals while completing at method/constructor references
@@ -1377,7 +1377,7 @@
 		int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 		assertResults(
-			"longMethodName[METHOD_NAME_REFERENCE]{longMethodName, Ltest.X<Ljava.lang.String;>;, (Ljava.lang.String;)Ljava.lang.String;, longMethodName, (x), 35}",
+			"longMethodName[METHOD_NAME_REFERENCE]{longMethodName, Ltest.X<Ljava.lang.String;>;, (Ljava.lang.String;)Ljava.lang.String;, longMethodName, (x), " + (R_DEFAULT + 30) + "}",
 			requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=402081, [1.8][code complete] No proposals while completing at method/constructor references
@@ -1406,7 +1406,7 @@
 		int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 		assertResults(
-			"longMethodName[METHOD_NAME_REFERENCE]{longMethodName, Ltest.Y;, (Ljava.lang.String;)Ljava.lang.String;, longMethodName, (x), 35}",
+			"longMethodName[METHOD_NAME_REFERENCE]{longMethodName, Ltest.Y;, (Ljava.lang.String;)Ljava.lang.String;, longMethodName, (x), " + (R_DEFAULT + 30) + "}",
 			requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=402081, [1.8][code complete] No proposals while completing at method/constructor references
@@ -1435,7 +1435,7 @@
 		int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 		assertResults(
-			"longMethodName[METHOD_NAME_REFERENCE]{longMethodName, Ltest.Y;, (Ljava.lang.String;)Ljava.lang.String;, longMethodName, (x), 35}",
+			"longMethodName[METHOD_NAME_REFERENCE]{longMethodName, Ltest.Y;, (Ljava.lang.String;)Ljava.lang.String;, longMethodName, (x), " + (R_DEFAULT + 30) + "}",
 			requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=431402, [assist] NPE in AssistParser.triggerRecoveryUponLambdaClosure:483 using Content Assist
@@ -1462,7 +1462,7 @@
 		int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 		assertResults(
-				"asList[LOCAL_VARIABLE_REF]{asList, null, Ljava.lang.Object;, asList, null, 47}",
+				"asList[LOCAL_VARIABLE_REF]{asList, null, Ljava.lang.Object;, asList, null, " + (R_DEFAULT + 42) + "}",
 			requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=432527, Content Assist crashes sometimes using JDK8 
@@ -1532,7 +1532,7 @@
 		int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 		assertResults(
-				"removeNodeFromGraph[METHOD_REF]{removeNodeFromGraph(), Ltest.X;, (Ltest.X$Node;)V, removeNodeFromGraph, (node), 27}",
+				"removeNodeFromGraph[METHOD_REF]{removeNodeFromGraph(), Ltest.X;, (Ltest.X$Node;)V, removeNodeFromGraph, (node), " + (R_DEFAULT + 22) + "}",
 			requestor.getResults());
 }
 //https://bugs.eclipse.org/bugs/show_bug.cgi?id=430441,  [compiler] NPE in ImplicitNullAnnotationVerifier.collectOverriddenMethods from Content Assist in a .jpage file
@@ -1579,8 +1579,8 @@
 		int cursorLocation = str.indexOf(completeBehind) + completeBehind.length();
 		this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 		assertResults(
-			"getClass[METHOD_NAME_REFERENCE]{getClass, Ljava.lang.Object;, ()Ljava.lang.Class<*>;, getClass, null, 35}\n" +
-			"getLastName[METHOD_NAME_REFERENCE]{getLastName, Ltest.Person;, ()Ljava.lang.String;, getLastName, null, 35}",
+			"getClass[METHOD_NAME_REFERENCE]{getClass, Ljava.lang.Object;, ()Ljava.lang.Class<*>;, getClass, null, " + (R_DEFAULT + 30) + "}\n" +
+			"getLastName[METHOD_NAME_REFERENCE]{getLastName, Ltest.Person;, ()Ljava.lang.String;, getLastName, null, " + (R_DEFAULT + 30) + "}",
 			requestor.getResults());
 }
 //https://bugs.eclipse.org/bugs/show_bug.cgi?id=433178
@@ -1656,7 +1656,7 @@
 	String completeBehind = "System.o";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("out[FIELD_REF]{out, Ljava.lang.System;, Ljava.io.PrintStream;, null, null, out, null, [83, 84], 26}", requestor.getResults());
+	assertResults("out[FIELD_REF]{out, Ljava.lang.System;, Ljava.io.PrintStream;, null, null, out, null, [83, 84], " + (R_DEFAULT + 21) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=435219, [1.8][content assist] No proposals for some closure cases 
 public void test435219a() throws JavaModelException {
@@ -1675,27 +1675,27 @@
 	String completeBehind = "System.out.p";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("print[METHOD_REF]{print(), Ljava.io.PrintStream;, (C)V, null, null, print, (arg0), [87, 88], 35}\n" +
-			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, (D)V, null, null, print, (arg0), [87, 88], 35}\n" +
-			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, (F)V, null, null, print, (arg0), [87, 88], 35}\n" +
-			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, (I)V, null, null, print, (arg0), [87, 88], 35}\n" +
-			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, (J)V, null, null, print, (arg0), [87, 88], 35}\n" +
-			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, (Ljava.lang.Object;)V, null, null, print, (arg0), [87, 88], 35}\n" +
-			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, (Ljava.lang.String;)V, null, null, print, (arg0), [87, 88], 35}\n" +
-			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, (Z)V, null, null, print, (arg0), [87, 88], 35}\n" +
-			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, ([C)V, null, null, print, (arg0), [87, 88], 35}\n" +
-			"printf[METHOD_REF]{printf(), Ljava.io.PrintStream;, (Ljava.lang.String;[Ljava.lang.Object;)Ljava.io.PrintStream;, null, null, printf, (arg0, arg1), [87, 88], 35}\n" +
-			"printf[METHOD_REF]{printf(), Ljava.io.PrintStream;, (Ljava.util.Locale;Ljava.lang.String;[Ljava.lang.Object;)Ljava.io.PrintStream;, null, null, printf, (arg0, arg1, arg2), [87, 88], 35}\n" +
-			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, ()V, null, null, println, null, [87, 88], 35}\n" +
-			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (C)V, null, null, println, (arg0), [87, 88], 35}\n" +
-			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (D)V, null, null, println, (arg0), [87, 88], 35}\n" +
-			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (F)V, null, null, println, (arg0), [87, 88], 35}\n" +
-			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (I)V, null, null, println, (arg0), [87, 88], 35}\n" +
-			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (J)V, null, null, println, (arg0), [87, 88], 35}\n" +
-			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (Ljava.lang.Object;)V, null, null, println, (arg0), [87, 88], 35}\n" +
-			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (Ljava.lang.String;)V, null, null, println, (arg0), [87, 88], 35}\n" +
-			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (Z)V, null, null, println, (arg0), [87, 88], 35}\n" +
-			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, ([C)V, null, null, println, (arg0), [87, 88], 35}", requestor.getResults());
+	assertResults("print[METHOD_REF]{print(), Ljava.io.PrintStream;, (C)V, null, null, print, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, (D)V, null, null, print, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, (F)V, null, null, print, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, (I)V, null, null, print, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, (J)V, null, null, print, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, (Ljava.lang.Object;)V, null, null, print, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, (Ljava.lang.String;)V, null, null, print, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, (Z)V, null, null, print, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"print[METHOD_REF]{print(), Ljava.io.PrintStream;, ([C)V, null, null, print, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"printf[METHOD_REF]{printf(), Ljava.io.PrintStream;, (Ljava.lang.String;[Ljava.lang.Object;)Ljava.io.PrintStream;, null, null, printf, (arg0, arg1), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"printf[METHOD_REF]{printf(), Ljava.io.PrintStream;, (Ljava.util.Locale;Ljava.lang.String;[Ljava.lang.Object;)Ljava.io.PrintStream;, null, null, printf, (arg0, arg1, arg2), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, ()V, null, null, println, null, [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (C)V, null, null, println, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (D)V, null, null, println, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (F)V, null, null, println, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (I)V, null, null, println, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (J)V, null, null, println, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (Ljava.lang.Object;)V, null, null, println, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (Ljava.lang.String;)V, null, null, println, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, (Z)V, null, null, println, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}\n" +
+			"println[METHOD_REF]{println(), Ljava.io.PrintStream;, ([C)V, null, null, println, (arg0), [87, 88], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=435219, [1.8][content assist] No proposals for some closure cases 
 public void test435219b() throws JavaModelException {
@@ -1714,9 +1714,9 @@
 	String completeBehind = "st";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("start[METHOD_REF]{start(), Ljava.lang.Thread;, ()V, null, null, start, null, [103, 105], 35}\n" +
-			"stop[METHOD_REF]{stop(), Ljava.lang.Thread;, ()V, null, null, stop, null, [103, 105], 35}\n" +
-			"stop[METHOD_REF]{stop(), Ljava.lang.Thread;, (Ljava.lang.Throwable;)V, null, null, stop, (arg0), [103, 105], 35}", requestor.getResults());
+	assertResults("start[METHOD_REF]{start(), Ljava.lang.Thread;, ()V, null, null, start, null, [103, 105], " + (R_DEFAULT + 30) + "}\n" +
+			"stop[METHOD_REF]{stop(), Ljava.lang.Thread;, ()V, null, null, stop, null, [103, 105], " + (R_DEFAULT + 30) + "}\n" +
+			"stop[METHOD_REF]{stop(), Ljava.lang.Thread;, (Ljava.lang.Throwable;)V, null, null, stop, (arg0), [103, 105], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=435219, [1.8][content assist] No proposals for some closure cases 
 public void test435219c() throws JavaModelException {
@@ -1738,9 +1738,9 @@
 	String completeBehind = "x.h";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("hashCode[METHOD_REF]{hashCode(), Ljava.lang.Integer;, (I)I, null, null, hashCode, (arg0), [187, 188], 54}\n" +
-			"highestOneBit[METHOD_REF]{highestOneBit(), Ljava.lang.Integer;, (I)I, null, null, highestOneBit, (arg0), [187, 188], 54}\n" +
-			"hashCode[METHOD_REF]{hashCode(), Ljava.lang.Integer;, ()I, null, null, hashCode, null, [187, 188], 65}", requestor.getResults());
+	assertResults("hashCode[METHOD_REF]{hashCode(), Ljava.lang.Integer;, (I)I, null, null, hashCode, (arg0), [187, 188], " + (R_DEFAULT + 49) + "}\n" +
+			"highestOneBit[METHOD_REF]{highestOneBit(), Ljava.lang.Integer;, (I)I, null, null, highestOneBit, (arg0), [187, 188], " + (R_DEFAULT + 49) + "}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.lang.Integer;, ()I, null, null, hashCode, null, [187, 188], " + (R_DEFAULT + 60) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=435219, [1.8][content assist] No proposals for some closure cases 
 public void test435219d() throws JavaModelException {
@@ -1762,27 +1762,27 @@
 	String completeBehind = "pri";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (C)V, null, null, print, null, [219, 222], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (D)V, null, null, print, null, [219, 222], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (F)V, null, null, print, null, [219, 222], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (I)V, null, null, print, null, [219, 222], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (J)V, null, null, print, null, [219, 222], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (Ljava.lang.Object;)V, null, null, print, null, [219, 222], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (Ljava.lang.String;)V, null, null, print, null, [219, 222], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (Z)V, null, null, print, null, [219, 222], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, ([C)V, null, null, print, null, [219, 222], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, ()V, null, null, println, null, [219, 222], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (C)V, null, null, println, null, [219, 222], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (D)V, null, null, println, null, [219, 222], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (F)V, null, null, println, null, [219, 222], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (I)V, null, null, println, null, [219, 222], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (J)V, null, null, println, null, [219, 222], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (Ljava.lang.Object;)V, null, null, println, null, [219, 222], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (Ljava.lang.String;)V, null, null, println, null, [219, 222], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (Z)V, null, null, println, null, [219, 222], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, ([C)V, null, null, println, null, [219, 222], 30}\n" +
-			"printf[METHOD_NAME_REFERENCE]{printf, Ljava.io.PrintStream;, (Ljava.lang.String;[Ljava.lang.Object;)Ljava.io.PrintStream;, null, null, printf, null, [219, 222], 35}\n" +
-			"printf[METHOD_NAME_REFERENCE]{printf, Ljava.io.PrintStream;, (Ljava.util.Locale;Ljava.lang.String;[Ljava.lang.Object;)Ljava.io.PrintStream;, null, null, printf, null, [219, 222], 35}", requestor.getResults());
+	assertResults("print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (C)V, null, null, print, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (D)V, null, null, print, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (F)V, null, null, print, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (I)V, null, null, print, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (J)V, null, null, print, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (Ljava.lang.Object;)V, null, null, print, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (Ljava.lang.String;)V, null, null, print, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (Z)V, null, null, print, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, ([C)V, null, null, print, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, ()V, null, null, println, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (C)V, null, null, println, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (D)V, null, null, println, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (F)V, null, null, println, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (I)V, null, null, println, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (J)V, null, null, println, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (Ljava.lang.Object;)V, null, null, println, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (Ljava.lang.String;)V, null, null, println, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (Z)V, null, null, println, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, ([C)V, null, null, println, null, [219, 222], " + (R_DEFAULT + 25) + "}\n" +
+			"printf[METHOD_NAME_REFERENCE]{printf, Ljava.io.PrintStream;, (Ljava.lang.String;[Ljava.lang.Object;)Ljava.io.PrintStream;, null, null, printf, null, [219, 222], " + (R_DEFAULT + 30) + "}\n" +
+			"printf[METHOD_NAME_REFERENCE]{printf, Ljava.io.PrintStream;, (Ljava.util.Locale;Ljava.lang.String;[Ljava.lang.Object;)Ljava.io.PrintStream;, null, null, printf, null, [219, 222], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=435219, [1.8][content assist] No proposals for some closure cases 
 public void test435219e() throws JavaModelException {
@@ -1806,9 +1806,9 @@
 	String completeBehind = "dou";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("doubleToLongBits[METHOD_REF]{doubleToLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToLongBits, (arg0), [355, 358], 24}\n" +
-			"doubleToRawLongBits[METHOD_REF]{doubleToRawLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToRawLongBits, (arg0), [355, 358], 24}\n" +
-			"doubleValue[METHOD_REF]{doubleValue(), Ljava.lang.Double;, ()D, null, null, doubleValue, null, [355, 358], 35}", requestor.getResults());
+	assertResults("doubleToLongBits[METHOD_REF]{doubleToLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToLongBits, (arg0), [355, 358], " + (R_DEFAULT + 19) + "}\n" +
+			"doubleToRawLongBits[METHOD_REF]{doubleToRawLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToRawLongBits, (arg0), [355, 358], " + (R_DEFAULT + 19) + "}\n" +
+			"doubleValue[METHOD_REF]{doubleValue(), Ljava.lang.Double;, ()D, null, null, doubleValue, null, [355, 358], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=435219, [1.8][content assist] No proposals for some closure cases 
 public void test435219f() throws JavaModelException {
@@ -1832,8 +1832,8 @@
 	String completeBehind = "g";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [391, 392], 35}\n" +
-			"get[METHOD_REF]{get(), Ljava.util.Optional<Ljava.lang.Double;>;, ()Ljava.lang.Double;, null, null, get, null, [391, 392], 55}", requestor.getResults());
+	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [391, 392], " + (R_DEFAULT + 30) + "}\n" +
+			"get[METHOD_REF]{get(), Ljava.util.Optional<Ljava.lang.Double;>;, ()Ljava.lang.Double;, null, null, get, null, [391, 392], " + (R_DEFAULT + 50) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=435219, [1.8][content assist] No proposals for some closure cases 
 public void test435219g() throws JavaModelException {
@@ -1857,9 +1857,9 @@
 	String completeBehind = "dou";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("doubleToLongBits[METHOD_REF]{doubleToLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToLongBits, (arg0), [376, 379], 54}\n" +
-				  "doubleToRawLongBits[METHOD_REF]{doubleToRawLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToRawLongBits, (arg0), [376, 379], 54}\n" +
-				  "doubleValue[METHOD_REF]{doubleValue(), Ljava.lang.Double;, ()D, null, null, doubleValue, null, [376, 379], 65}", requestor.getResults());
+	assertResults("doubleToLongBits[METHOD_REF]{doubleToLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToLongBits, (arg0), [376, 379], " + (R_DEFAULT + 49) + "}\n" +
+				  "doubleToRawLongBits[METHOD_REF]{doubleToRawLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToRawLongBits, (arg0), [376, 379], " + (R_DEFAULT + 49) + "}\n" +
+				  "doubleValue[METHOD_REF]{doubleValue(), Ljava.lang.Double;, ()D, null, null, doubleValue, null, [376, 379], " + (R_DEFAULT + 60) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=435682, [1.8] content assist not working inside lambda expression 
 public void test435682() throws JavaModelException {
@@ -1881,7 +1881,7 @@
 	String completeBehind = "so.tr";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("trim[METHOD_REF]{trim(), Ljava.lang.String;, ()Ljava.lang.String;, null, null, trim, null, [237, 239], 35}", requestor.getResults());
+	assertResults("trim[METHOD_REF]{trim(), Ljava.lang.String;, ()Ljava.lang.String;, null, null, trim, null, [237, 239], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=435682, [1.8] content assist not working inside lambda expression 
 public void test435682a() throws JavaModelException {
@@ -1903,7 +1903,7 @@
 	String completeBehind = "so.tr";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("trim[METHOD_REF]{trim(), Ljava.lang.String;, ()Ljava.lang.String;, null, null, trim, null, [246, 248], 35}", requestor.getResults());
+	assertResults("trim[METHOD_REF]{trim(), Ljava.lang.String;, ()Ljava.lang.String;, null, null, trim, null, [246, 248], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=430667, [1.8][content assist] no proposals around lambda as a field 
 public void test430667() throws JavaModelException {
@@ -1929,8 +1929,8 @@
 	String completeBehind = "D_F";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("D_F[POTENTIAL_METHOD_DECLARATION]{D_F, LD_DemoRefactorings;, ()V, null, null, D_F, null, [195, 198], 14}\n" +
-				  "D_FI[TYPE_REF]{D_FI, , LD_FI;, null, null, null, null, [195, 198], 27}", requestor.getResults());
+	assertResults("D_F[POTENTIAL_METHOD_DECLARATION]{D_F, LD_DemoRefactorings;, ()V, null, null, D_F, null, [195, 198], " + (R_DEFAULT + 9) + "}\n" +
+				  "D_FI[TYPE_REF]{D_FI, , LD_FI;, null, null, null, null, [195, 198], " + (R_DEFAULT + 22) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=430667, [1.8][content assist] no proposals around lambda as a field 
 public void test430667a() throws JavaModelException {
@@ -1957,8 +1957,8 @@
 	String completeBehind = "/*HERE*/D_F";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("D_F[POTENTIAL_METHOD_DECLARATION]{D_F, LD_DemoRefactorings;, ()V, null, null, D_F, null, [150, 153], 14}\n" +
-			"D_FI[TYPE_REF]{D_FI, , LD_FI;, null, null, null, null, [150, 153], 27}", requestor.getResults());
+	assertResults("D_F[POTENTIAL_METHOD_DECLARATION]{D_F, LD_DemoRefactorings;, ()V, null, null, D_F, null, [150, 153], " + (R_DEFAULT + 9) + "}\n" +
+			"D_FI[TYPE_REF]{D_FI, , LD_FI;, null, null, null, null, [150, 153], " + (R_DEFAULT + 22) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=430667, [1.8][content assist] no proposals around lambda as a field 
 public void test430667b() throws JavaModelException {
@@ -1984,8 +1984,8 @@
 	String completeBehind = "/*HERE*/D_F";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("D_F[POTENTIAL_METHOD_DECLARATION]{D_F, LD_DemoRefactorings;, ()V, null, null, D_F, null, [36, 39], 14}\n" +
-			"D_FI[TYPE_REF]{D_FI, , LD_FI;, null, null, null, null, [36, 39], 27}", requestor.getResults());
+	assertResults("D_F[POTENTIAL_METHOD_DECLARATION]{D_F, LD_DemoRefactorings;, ()V, null, null, D_F, null, [36, 39], " + (R_DEFAULT + 9) + "}\n" +
+			"D_FI[TYPE_REF]{D_FI, , LD_FI;, null, null, null, null, [36, 39], " + (R_DEFAULT + 22) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=443932, [1.8][code complete] method reference proposals not applied when caret inside method name
 public void test443932() throws JavaModelException {
@@ -2003,15 +2003,15 @@
 	String completeBehind = "to";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("toBinaryString[METHOD_NAME_REFERENCE]{toBinaryString, Ljava.lang.Integer;, (I)Ljava.lang.String;, null, null, toBinaryString, null, [90, 98], 24}\n" +
-			"toHexString[METHOD_NAME_REFERENCE]{toHexString, Ljava.lang.Integer;, (I)Ljava.lang.String;, null, null, toHexString, null, [90, 98], 24}\n" +
-			"toOctalString[METHOD_NAME_REFERENCE]{toOctalString, Ljava.lang.Integer;, (I)Ljava.lang.String;, null, null, toOctalString, null, [90, 98], 24}\n" +
-			"toString[METHOD_NAME_REFERENCE]{toString, Ljava.lang.Integer;, (I)Ljava.lang.String;, null, null, toString, null, [90, 98], 24}\n" +
-			"toString[METHOD_NAME_REFERENCE]{toString, Ljava.lang.Integer;, (II)Ljava.lang.String;, null, null, toString, null, [90, 98], 24}\n" +
-			"toUnsignedLong[METHOD_NAME_REFERENCE]{toUnsignedLong, Ljava.lang.Integer;, (I)J, null, null, toUnsignedLong, null, [90, 98], 24}\n" +
-			"toUnsignedString[METHOD_NAME_REFERENCE]{toUnsignedString, Ljava.lang.Integer;, (I)Ljava.lang.String;, null, null, toUnsignedString, null, [90, 98], 24}\n" +
-			"toUnsignedString[METHOD_NAME_REFERENCE]{toUnsignedString, Ljava.lang.Integer;, (II)Ljava.lang.String;, null, null, toUnsignedString, null, [90, 98], 24}\n" +
-			"toString[METHOD_NAME_REFERENCE]{toString, Ljava.lang.Integer;, ()Ljava.lang.String;, null, null, toString, null, [90, 98], 35}", requestor.getResults());
+	assertResults("toBinaryString[METHOD_NAME_REFERENCE]{toBinaryString, Ljava.lang.Integer;, (I)Ljava.lang.String;, null, null, toBinaryString, null, [90, 98], " + (R_DEFAULT + 19) + "}\n" +
+			"toHexString[METHOD_NAME_REFERENCE]{toHexString, Ljava.lang.Integer;, (I)Ljava.lang.String;, null, null, toHexString, null, [90, 98], " + (R_DEFAULT + 19) + "}\n" +
+			"toOctalString[METHOD_NAME_REFERENCE]{toOctalString, Ljava.lang.Integer;, (I)Ljava.lang.String;, null, null, toOctalString, null, [90, 98], " + (R_DEFAULT + 19) + "}\n" +
+			"toString[METHOD_NAME_REFERENCE]{toString, Ljava.lang.Integer;, (I)Ljava.lang.String;, null, null, toString, null, [90, 98], " + (R_DEFAULT + 19) + "}\n" +
+			"toString[METHOD_NAME_REFERENCE]{toString, Ljava.lang.Integer;, (II)Ljava.lang.String;, null, null, toString, null, [90, 98], " + (R_DEFAULT + 19) + "}\n" +
+			"toUnsignedLong[METHOD_NAME_REFERENCE]{toUnsignedLong, Ljava.lang.Integer;, (I)J, null, null, toUnsignedLong, null, [90, 98], " + (R_DEFAULT + 19) + "}\n" +
+			"toUnsignedString[METHOD_NAME_REFERENCE]{toUnsignedString, Ljava.lang.Integer;, (I)Ljava.lang.String;, null, null, toUnsignedString, null, [90, 98], " + (R_DEFAULT + 19) + "}\n" +
+			"toUnsignedString[METHOD_NAME_REFERENCE]{toUnsignedString, Ljava.lang.Integer;, (II)Ljava.lang.String;, null, null, toUnsignedString, null, [90, 98], " + (R_DEFAULT + 19) + "}\n" +
+			"toString[METHOD_NAME_REFERENCE]{toString, Ljava.lang.Integer;, ()Ljava.lang.String;, null, null, toString, null, [90, 98], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 	assertTrue(str.substring(90, 98).equals("toString"));
 	
 }
@@ -2034,8 +2034,8 @@
 	String completeBehind = "so.ch";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("charAt[METHOD_REF]{charAt(), Ljava.lang.String;, (I)C, null, null, charAt, (arg0), [232, 234], 35}\n" +
-			"chars[METHOD_REF]{chars(), Ljava.lang.CharSequence;, ()Ljava.util.stream.IntStream;, null, null, chars, null, [232, 234], 35}", requestor.getResults());
+	assertResults("charAt[METHOD_REF]{charAt(), Ljava.lang.String;, (I)C, null, null, charAt, (arg0), [232, 234], " + (R_DEFAULT + 30) + "}\n" +
+			"chars[METHOD_REF]{chars(), Ljava.lang.CharSequence;, ()Ljava.util.stream.IntStream;, null, null, chars, null, [232, 234], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=435219, [1.8][content assist] No proposals for some closure cases 
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=444300, [1.8] content assist not working inside lambda expression in case of fields
@@ -2056,9 +2056,9 @@
 	String completeBehind = "x.h";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("hashCode[METHOD_REF]{hashCode(), Ljava.lang.Integer;, (I)I, null, null, hashCode, (arg0), [164, 165], 54}\n" +
-			"highestOneBit[METHOD_REF]{highestOneBit(), Ljava.lang.Integer;, (I)I, null, null, highestOneBit, (arg0), [164, 165], 54}\n" +
-			"hashCode[METHOD_REF]{hashCode(), Ljava.lang.Integer;, ()I, null, null, hashCode, null, [164, 165], 65}", requestor.getResults());
+	assertResults("hashCode[METHOD_REF]{hashCode(), Ljava.lang.Integer;, (I)I, null, null, hashCode, (arg0), [164, 165], " + (R_DEFAULT + 49) + "}\n" +
+			"highestOneBit[METHOD_REF]{highestOneBit(), Ljava.lang.Integer;, (I)I, null, null, highestOneBit, (arg0), [164, 165], " + (R_DEFAULT + 49) + "}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.lang.Integer;, ()I, null, null, hashCode, null, [164, 165], " + (R_DEFAULT + 60) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=435219, [1.8][content assist] No proposals for some closure cases 
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=444300, [1.8] content assist not working inside lambda expression in case of fields
@@ -2079,27 +2079,27 @@
 	String completeBehind = "pri";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (C)V, null, null, print, null, [188, 191], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (D)V, null, null, print, null, [188, 191], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (F)V, null, null, print, null, [188, 191], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (I)V, null, null, print, null, [188, 191], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (J)V, null, null, print, null, [188, 191], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (Ljava.lang.Object;)V, null, null, print, null, [188, 191], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (Ljava.lang.String;)V, null, null, print, null, [188, 191], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (Z)V, null, null, print, null, [188, 191], 30}\n" +
-			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, ([C)V, null, null, print, null, [188, 191], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, ()V, null, null, println, null, [188, 191], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (C)V, null, null, println, null, [188, 191], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (D)V, null, null, println, null, [188, 191], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (F)V, null, null, println, null, [188, 191], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (I)V, null, null, println, null, [188, 191], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (J)V, null, null, println, null, [188, 191], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (Ljava.lang.Object;)V, null, null, println, null, [188, 191], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (Ljava.lang.String;)V, null, null, println, null, [188, 191], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (Z)V, null, null, println, null, [188, 191], 30}\n" +
-			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, ([C)V, null, null, println, null, [188, 191], 30}\n" +
-			"printf[METHOD_NAME_REFERENCE]{printf, Ljava.io.PrintStream;, (Ljava.lang.String;[Ljava.lang.Object;)Ljava.io.PrintStream;, null, null, printf, null, [188, 191], 35}\n" +
-			"printf[METHOD_NAME_REFERENCE]{printf, Ljava.io.PrintStream;, (Ljava.util.Locale;Ljava.lang.String;[Ljava.lang.Object;)Ljava.io.PrintStream;, null, null, printf, null, [188, 191], 35}", requestor.getResults());
+	assertResults("print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (C)V, null, null, print, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (D)V, null, null, print, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (F)V, null, null, print, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (I)V, null, null, print, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (J)V, null, null, print, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (Ljava.lang.Object;)V, null, null, print, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (Ljava.lang.String;)V, null, null, print, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, (Z)V, null, null, print, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"print[METHOD_NAME_REFERENCE]{print, Ljava.io.PrintStream;, ([C)V, null, null, print, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, ()V, null, null, println, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (C)V, null, null, println, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (D)V, null, null, println, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (F)V, null, null, println, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (I)V, null, null, println, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (J)V, null, null, println, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (Ljava.lang.Object;)V, null, null, println, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (Ljava.lang.String;)V, null, null, println, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, (Z)V, null, null, println, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"println[METHOD_NAME_REFERENCE]{println, Ljava.io.PrintStream;, ([C)V, null, null, println, null, [188, 191], " + (R_DEFAULT + 25) + "}\n" +
+			"printf[METHOD_NAME_REFERENCE]{printf, Ljava.io.PrintStream;, (Ljava.lang.String;[Ljava.lang.Object;)Ljava.io.PrintStream;, null, null, printf, null, [188, 191], " + (R_DEFAULT + 30) + "}\n" +
+			"printf[METHOD_NAME_REFERENCE]{printf, Ljava.io.PrintStream;, (Ljava.util.Locale;Ljava.lang.String;[Ljava.lang.Object;)Ljava.io.PrintStream;, null, null, printf, null, [188, 191], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=435219, [1.8][content assist] No proposals for some closure cases 
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=444300, [1.8] content assist not working inside lambda expression in case of fields
@@ -2121,9 +2121,9 @@
 	String completeBehind = "dou";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("doubleToLongBits[METHOD_REF]{doubleToLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToLongBits, (arg0), [235, 238], 24}\n" +
-			"doubleToRawLongBits[METHOD_REF]{doubleToRawLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToRawLongBits, (arg0), [235, 238], 24}\n" +
-			"doubleValue[METHOD_REF]{doubleValue(), Ljava.lang.Double;, ()D, null, null, doubleValue, null, [235, 238], 35}", requestor.getResults());
+	assertResults("doubleToLongBits[METHOD_REF]{doubleToLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToLongBits, (arg0), [235, 238], " + (R_DEFAULT + 19) + "}\n" +
+			"doubleToRawLongBits[METHOD_REF]{doubleToRawLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToRawLongBits, (arg0), [235, 238], " + (R_DEFAULT + 19) + "}\n" +
+			"doubleValue[METHOD_REF]{doubleValue(), Ljava.lang.Double;, ()D, null, null, doubleValue, null, [235, 238], " + (R_DEFAULT + 30) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=435219, [1.8][content assist] No proposals for some closure cases 
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=444300, [1.8] content assist not working inside lambda expression in case of fields
@@ -2145,8 +2145,8 @@
 	String completeBehind = "g";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [271, 272], 35}\n" +
-			"get[METHOD_REF]{get(), Ljava.util.Optional<Ljava.lang.Double;>;, ()Ljava.lang.Double;, null, null, get, null, [271, 272], 55}", requestor.getResults());
+	assertResults("getClass[METHOD_REF]{getClass(), Ljava.lang.Object;, ()Ljava.lang.Class<*>;, null, null, getClass, null, [271, 272], " + (R_DEFAULT + 30) + "}\n" +
+			"get[METHOD_REF]{get(), Ljava.util.Optional<Ljava.lang.Double;>;, ()Ljava.lang.Double;, null, null, get, null, [271, 272], " + (R_DEFAULT + 50) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=435219, [1.8][content assist] No proposals for some closure cases 
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=444300, [1.8] content assist not working inside lambda expression in case of fields
@@ -2168,22 +2168,22 @@
 	String completeBehind = "dou";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("doubleToLongBits[METHOD_REF]{doubleToLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToLongBits, (arg0), [256, 259], 54}\n" +
-			"doubleToRawLongBits[METHOD_REF]{doubleToRawLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToRawLongBits, (arg0), [256, 259], 54}\n" +
-			"doubleValue[METHOD_REF]{doubleValue(), Ljava.lang.Double;, ()D, null, null, doubleValue, null, [256, 259], 65}", requestor.getResults());
+	assertResults("doubleToLongBits[METHOD_REF]{doubleToLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToLongBits, (arg0), [256, 259], " + (R_DEFAULT + 49) + "}\n" +
+			"doubleToRawLongBits[METHOD_REF]{doubleToRawLongBits(), Ljava.lang.Double;, (D)J, null, null, doubleToRawLongBits, (arg0), [256, 259], " + (R_DEFAULT + 49) + "}\n" +
+			"doubleValue[METHOD_REF]{doubleValue(), Ljava.lang.Double;, ()D, null, null, doubleValue, null, [256, 259], " + (R_DEFAULT + 60) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=435281, [1.8][code assist] No import or completion proposal for anonymous class inside lambda
 public void test435281() throws JavaModelException {
 	this.workingCopies = new ICompilationUnit[3];
 	this.workingCopies[0] = getWorkingCopy(
-			"/Completion/src/p4a/FI1.java",
+			"/Completion/src/FI1.java",
 			"package p4a;\n" +
 			"@FunctionalInterface\n" +
 			"public interface FI1<R> {\n" +
 			"    public R foo1();\n" +
 			"}\n");
 	this.workingCopies[1] = getWorkingCopy(
-			"/Completion/src/p4a/FI2.java",
+			"/Completion/src/FI2.java",
 			"package p4a;\n" +
 			"@FunctionalInterface\n" +
 			"public interface FI2 {\n" +
@@ -2210,7 +2210,7 @@
 	String completeBehind = "FI2";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[2].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("FI2[TYPE_REF]{p4a.FI2, p4a, Lp4a.FI2;, null, null, null, null, [104, 107], 28}", requestor.getResults());
+	assertResults("FI2[TYPE_REF]{p4a.FI2, p4a, Lp4a.FI2;, null, null, null, null, [104, 107], " + (R_DEFAULT + 23) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=431811, content assist should propose keyword 'super' after type name
 public void test431811() throws JavaModelException {
@@ -2238,7 +2238,7 @@
 	String completeBehind = "su";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("super[KEYWORD]{super, null, null, null, null, super, null, [192, 194], 26}", requestor.getResults());
+	assertResults("super[KEYWORD]{super, null, null, null, null, super, null, [192, 194], " + (R_DEFAULT + 21) + "}", requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=447774, Auto complete does not work when using lambdas with cast
 public void test447774() throws JavaModelException {
@@ -2300,8 +2300,8 @@
 	String completeBehind = "/*HERE*/localMeth";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("localmethod1[METHOD_REF]{localmethod1(), LLambdaBug;, ()V, null, null, localmethod1, null, [181, 190], 17}\n" +
-                  "localmethod2[METHOD_REF]{localmethod2(), LLambdaBug;, ()V, null, null, localmethod2, null, [181, 190], 17}", requestor.getResults());
+	assertResults("localmethod1[METHOD_REF]{localmethod1(), LLambdaBug;, ()V, null, null, localmethod1, null, [181, 190], " + (R_DEFAULT + 12) + "}\n" +
+                  "localmethod2[METHOD_REF]{localmethod2(), LLambdaBug;, ()V, null, null, localmethod2, null, [181, 190], " + (R_DEFAULT + 12) + "}", requestor.getResults());
 	
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=449358, Content assist inside lambda broken in all methods except last 
@@ -2333,8 +2333,8 @@
 	String completeBehind = "/*HERE*/localMeth";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
-	assertResults("localmethod1[METHOD_REF]{localmethod1(), LLambdaBug;, ()V, null, null, localmethod1, null, [282, 291], 17}\n" +
-				  "localmethod2[METHOD_REF]{localmethod2(), LLambdaBug;, ()V, null, null, localmethod2, null, [282, 291], 17}", requestor.getResults());
+	assertResults("localmethod1[METHOD_REF]{localmethod1(), LLambdaBug;, ()V, null, null, localmethod1, null, [282, 291], " + (R_DEFAULT + 12) + "}\n" +
+				  "localmethod2[METHOD_REF]{localmethod2(), LLambdaBug;, ()V, null, null, localmethod2, null, [282, 291], " + (R_DEFAULT + 12) + "}", requestor.getResults());
 	
 }
 public void testBug459189_001() throws JavaModelException {
@@ -2359,9 +2359,9 @@
 	int cursorLocation = str.indexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"Retention[TYPE_REF]{java.lang.annotation.Retention, java.lang.annotation, Ljava.lang.annotation.Retention;, null, null, 14}\n"+
-			"RetentionPolicy[TYPE_REF]{java.lang.annotation.RetentionPolicy, java.lang.annotation, Ljava.lang.annotation.RetentionPolicy;, null, null, 14}\n"+
-			"return[KEYWORD]{return, null, null, return, null, 24}",
+			"Retention[TYPE_REF]{java.lang.annotation.Retention, java.lang.annotation, Ljava.lang.annotation.Retention;, null, null, " + (R_DEFAULT + 9) + "}\n"+
+			"RetentionPolicy[TYPE_REF]{java.lang.annotation.RetentionPolicy, java.lang.annotation, Ljava.lang.annotation.RetentionPolicy;, null, null, " + (R_DEFAULT + 9) + "}\n"+
+			"return[KEYWORD]{return, null, null, return, null, " + (R_DEFAULT + 19) + "}",
 			requestor.getResults());
 }
 public void testBug459189_002() throws JavaModelException {
@@ -2386,9 +2386,9 @@
 	int cursorLocation = str.indexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"Retention[TYPE_REF]{java.lang.annotation.Retention, java.lang.annotation, Ljava.lang.annotation.Retention;, null, null, 14}\n"+
-			"RetentionPolicy[TYPE_REF]{java.lang.annotation.RetentionPolicy, java.lang.annotation, Ljava.lang.annotation.RetentionPolicy;, null, null, 14}\n" +
-			"return[KEYWORD]{return, null, null, return, null, 24}",
+			"Retention[TYPE_REF]{java.lang.annotation.Retention, java.lang.annotation, Ljava.lang.annotation.Retention;, null, null, " + (R_DEFAULT + 9) + "}\n"+
+			"RetentionPolicy[TYPE_REF]{java.lang.annotation.RetentionPolicy, java.lang.annotation, Ljava.lang.annotation.RetentionPolicy;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"return[KEYWORD]{return, null, null, return, null, " + (R_DEFAULT + 19) + "}",
 			requestor.getResults());
 }
 public void testBug459189_003() throws JavaModelException {
@@ -2413,8 +2413,8 @@
 	int cursorLocation = str.indexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"catch[KEYWORD]{catch, null, null, catch, null, 24}\n"+
-			"finally[KEYWORD]{finally, null, null, finally, null, 24}",
+			"catch[KEYWORD]{catch, null, null, catch, null, " + (R_DEFAULT + 19) + "}\n"+
+			"finally[KEYWORD]{finally, null, null, finally, null, " + (R_DEFAULT + 19) + "}",
 			requestor.getResults());
 }
 public void testBug459189_004() throws JavaModelException {
@@ -2439,7 +2439,7 @@
 	int cursorLocation = str.indexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"while[KEYWORD]{while, null, null, while, null, 24}",
+			"while[KEYWORD]{while, null, null, while, null, " + (R_DEFAULT + 19) + "}",
 			requestor.getResults());
 }
 public void testBug460410() throws JavaModelException {
@@ -2491,7 +2491,7 @@
 	int cursorLocation = str.indexOf(completeBehind) ;
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"someMethod[METHOD_REF]{someMethod(), LX;, ()V, someMethod, null, 27}", requestor.getResults());
+			"someMethod[METHOD_REF]{someMethod(), LX;, ()V, someMethod, null, " + (R_DEFAULT + 22) + "}", requestor.getResults());
 }
 //https://bugs.eclipse.org/bugs/show_bug.cgi?id=481215
 public void testBug481215a() throws JavaModelException {
@@ -2523,11 +2523,11 @@
 	int cursorLocation = str.indexOf(completeBehind) ;
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"ResourceBundle[TYPE_REF]{java.util.ResourceBundle, java.util, Ljava.util.ResourceBundle;, null, null, 14}\n" +
-			"ResponseCache[TYPE_REF]{java.net.ResponseCache, java.net, Ljava.net.ResponseCache;, null, null, 14}\n" +
-			"ResultSet[TYPE_REF]{java.sql.ResultSet, java.sql, Ljava.sql.ResultSet;, null, null, 14}\n" +
-			"ResultSetMetaData[TYPE_REF]{java.sql.ResultSetMetaData, java.sql, Ljava.sql.ResultSetMetaData;, null, null, 14}\n" +
-			"result[LOCAL_VARIABLE_REF]{result, null, Ljava.lang.String;, result, null, 27}", requestor.getResults());
+			"ResourceBundle[TYPE_REF]{java.util.ResourceBundle, java.util, Ljava.util.ResourceBundle;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"ResponseCache[TYPE_REF]{java.net.ResponseCache, java.net, Ljava.net.ResponseCache;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"ResultSet[TYPE_REF]{java.sql.ResultSet, java.sql, Ljava.sql.ResultSet;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"ResultSetMetaData[TYPE_REF]{java.sql.ResultSetMetaData, java.sql, Ljava.sql.ResultSetMetaData;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"result[LOCAL_VARIABLE_REF]{result, null, Ljava.lang.String;, result, null, " + (R_DEFAULT + 22) + "}", requestor.getResults());
 }
 public void testBug481215b() throws JavaModelException {
 	this.workingCopies = new ICompilationUnit[1];
@@ -2555,11 +2555,11 @@
 	int cursorLocation = str.indexOf(completeBehind) ;
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"ResourceBundle[TYPE_REF]{java.util.ResourceBundle, java.util, Ljava.util.ResourceBundle;, null, null, 14}\n" +
-			"ResponseCache[TYPE_REF]{java.net.ResponseCache, java.net, Ljava.net.ResponseCache;, null, null, 14}\n" +
-			"ResultSet[TYPE_REF]{java.sql.ResultSet, java.sql, Ljava.sql.ResultSet;, null, null, 14}\n" +
-			"ResultSetMetaData[TYPE_REF]{java.sql.ResultSetMetaData, java.sql, Ljava.sql.ResultSetMetaData;, null, null, 14}\n" +
-			"result[LOCAL_VARIABLE_REF]{result, null, Ljava.lang.String;, result, null, 27}", requestor.getResults());
+			"ResourceBundle[TYPE_REF]{java.util.ResourceBundle, java.util, Ljava.util.ResourceBundle;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"ResponseCache[TYPE_REF]{java.net.ResponseCache, java.net, Ljava.net.ResponseCache;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"ResultSet[TYPE_REF]{java.sql.ResultSet, java.sql, Ljava.sql.ResultSet;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"ResultSetMetaData[TYPE_REF]{java.sql.ResultSetMetaData, java.sql, Ljava.sql.ResultSetMetaData;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"result[LOCAL_VARIABLE_REF]{result, null, Ljava.lang.String;, result, null, " + (R_DEFAULT + 22) + "}", requestor.getResults());
 }
 public void testBug481215c() throws JavaModelException {
 	this.workingCopies = new ICompilationUnit[1];
@@ -2587,11 +2587,11 @@
 	int cursorLocation = str.indexOf(completeBehind) ;
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"ResourceBundle[TYPE_REF]{java.util.ResourceBundle, java.util, Ljava.util.ResourceBundle;, null, null, 14}\n" +
-			"ResponseCache[TYPE_REF]{java.net.ResponseCache, java.net, Ljava.net.ResponseCache;, null, null, 14}\n" +
-			"ResultSet[TYPE_REF]{java.sql.ResultSet, java.sql, Ljava.sql.ResultSet;, null, null, 14}\n" +
-			"ResultSetMetaData[TYPE_REF]{java.sql.ResultSetMetaData, java.sql, Ljava.sql.ResultSetMetaData;, null, null, 14}\n" +
-			"result[LOCAL_VARIABLE_REF]{result, null, Ljava.lang.String;, result, null, 27}", requestor.getResults());
+			"ResourceBundle[TYPE_REF]{java.util.ResourceBundle, java.util, Ljava.util.ResourceBundle;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"ResponseCache[TYPE_REF]{java.net.ResponseCache, java.net, Ljava.net.ResponseCache;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"ResultSet[TYPE_REF]{java.sql.ResultSet, java.sql, Ljava.sql.ResultSet;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"ResultSetMetaData[TYPE_REF]{java.sql.ResultSetMetaData, java.sql, Ljava.sql.ResultSetMetaData;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"result[LOCAL_VARIABLE_REF]{result, null, Ljava.lang.String;, result, null, " + (R_DEFAULT + 22) + "}", requestor.getResults());
 }
 public void testBug481215d() throws JavaModelException {
 	this.workingCopies = new ICompilationUnit[1];
@@ -2627,12 +2627,12 @@
 	int cursorLocation = str.indexOf(completeBehind) ;
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"ResourceBundle[TYPE_REF]{java.util.ResourceBundle, java.util, Ljava.util.ResourceBundle;, null, null, 14}\n" +
-			"ResponseCache[TYPE_REF]{java.net.ResponseCache, java.net, Ljava.net.ResponseCache;, null, null, 14}\n" +
-			"ResultSet[TYPE_REF]{java.sql.ResultSet, java.sql, Ljava.sql.ResultSet;, null, null, 14}\n" +
-			"ResultSetMetaData[TYPE_REF]{java.sql.ResultSetMetaData, java.sql, Ljava.sql.ResultSetMetaData;, null, null, 14}\n" +
-			"result[LOCAL_VARIABLE_REF]{result, null, Ljava.lang.String;, result, null, 27}\n" +
-			"result2[LOCAL_VARIABLE_REF]{result2, null, Ljava.lang.String;, result2, null, 27}", requestor.getResults());
+			"ResourceBundle[TYPE_REF]{java.util.ResourceBundle, java.util, Ljava.util.ResourceBundle;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"ResponseCache[TYPE_REF]{java.net.ResponseCache, java.net, Ljava.net.ResponseCache;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"ResultSet[TYPE_REF]{java.sql.ResultSet, java.sql, Ljava.sql.ResultSet;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"ResultSetMetaData[TYPE_REF]{java.sql.ResultSetMetaData, java.sql, Ljava.sql.ResultSetMetaData;, null, null, " + (R_DEFAULT + 9) + "}\n" +
+			"result[LOCAL_VARIABLE_REF]{result, null, Ljava.lang.String;, result, null, " + (R_DEFAULT + 22) + "}\n" +
+			"result2[LOCAL_VARIABLE_REF]{result2, null, Ljava.lang.String;, result2, null, " + (R_DEFAULT + 22) + "}", requestor.getResults());
 }
 //https://bugs.eclipse.org/bugs/show_bug.cgi?id=484479
 public void test484479() throws JavaModelException {
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests2.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests2.java
index 827681c..ea37cbc 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests2.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests2.java
@@ -5502,7 +5502,7 @@
 		int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 		cu.codeComplete(cursorLocation, requestor);
 		assertResults(
-				"Enum[TYPE_REF]{Enum, java.lang, Ljava.lang.Enum;, null, null, 17}",
+				"Enum[TYPE_REF]{Enum, java.lang, Ljava.lang.Enum;, null, null, " + (R_DEFAULT + 12) + "}",
 				requestor.getResults());
 				
 	} finally {
@@ -5535,7 +5535,7 @@
 		int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 		cu.codeComplete(cursorLocation, requestor);
 		assertResults(
-				"Enum[TYPE_REF]{org.apache.commons.lang.enum.Enum, org.apache.commons.lang.enum, Lorg.apache.commons.lang.enum.Enum;, null, null, 14}",
+				"Enum[TYPE_REF]{org.apache.commons.lang.enum.Enum, org.apache.commons.lang.enum, Lorg.apache.commons.lang.enum.Enum;, null, null, " + (R_DEFAULT + 9) + "}",
 				requestor.getResults());
 				
 	} finally {
@@ -5597,7 +5597,7 @@
 		int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 		cu.codeComplete(cursorLocation, requestor);
 		assertResults(
-				"org.apache.commons.lang.enum[PACKAGE_REF]{org.apache.commons.lang.enum.*;, org.apache.commons.lang.enum, null, null, null, 24}",
+				"org.apache.commons.lang.enum[PACKAGE_REF]{org.apache.commons.lang.enum.*;, org.apache.commons.lang.enum, null, null, null, " + (R_DEFAULT + 19) + "}",
 				requestor.getResults());
 				
 	} finally {
@@ -6148,7 +6148,7 @@
 		CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true, false, false, true, true);
 		cu.codeComplete(cursorLocation, requestor);
 		assertResults(
-				"Main[TYPE_REF]{Main, p, Lp.Main;, null, null, 27}",
+				"Main[TYPE_REF]{Main, p, Lp.Main;, null, null, " + (R_DEFAULT + 22) + "}",
 				requestor.getResults());
 	} finally {
 		deleteProject("P");
@@ -6206,7 +6206,7 @@
 		CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true, false, false, true, true);
 		cu.codeComplete(cursorLocation, requestor);
 		assertResults(
-				"Main[TYPE_REF]{Main, p, Lp.Main;, null, null, 27}",
+				"Main[TYPE_REF]{Main, p, Lp.Main;, null, null, " + (R_DEFAULT + 22) + "}",
 				requestor.getResults());
 	} finally {
 		deleteProject("P");
@@ -6262,7 +6262,7 @@
 		CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true, false, false, true, true);
 		cu.codeComplete(cursorLocation, requestor);
 		assertResults(
-				"Main[TYPE_REF]{Main, p, Lp.Main;, null, null, 27}",
+				"Main[TYPE_REF]{Main, p, Lp.Main;, null, null, " + (R_DEFAULT + 22) + "}",
 				requestor.getResults());
 	} finally {
 		deleteProjects(new String[] { "Lib", "P" });
@@ -6318,7 +6318,7 @@
 		CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true, false, false, true, true);
 		cu.codeComplete(cursorLocation, requestor);
 		assertResults(
-				"Main[TYPE_REF]{Main, p, Lp.Main;, null, null, 27}",
+				"Main[TYPE_REF]{Main, p, Lp.Main;, null, null, " + (R_DEFAULT + 22) + "}",
 				requestor.getResults());
 	} finally {
 		deleteProjects(new String[] { "Lib", "P" });
@@ -6409,7 +6409,7 @@
 	    this.workingCopies[2].codeComplete(cursorLocation, requestor, this.wcOwner, monitor);
 	    
 	    assertResults(
-			"Nested[TYPE_REF]{Nested, myannotations, Lmyannotations.Nested;, null, null, 47}",
+			"Nested[TYPE_REF]{Nested, myannotations, Lmyannotations.Nested;, null, null, " + (R_DEFAULT + 42) + "}",
 			requestor.getResults());
 	} finally {
 		deleteProject("P");
@@ -6483,7 +6483,7 @@
 		CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true, false, false, true, true);
 		cu.codeComplete(cursorLocation, requestor);
 		assertResults(
-				"toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, 35}",
+				"toString[METHOD_REF]{toString(), Ljava.lang.Object;, ()Ljava.lang.String;, toString, null, " + (R_DEFAULT + 30) + "}",
 				requestor.getResults());
 	} finally {
 		deleteProject("P");
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests_1_5.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests_1_5.java
index 2a685d5..ac30574 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests_1_5.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/CompletionTests_1_5.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
@@ -13503,8 +13503,8 @@
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
 	assertResults(
-			"ThisClassIsFinal[TYPE_REF]{ThisClassIsFinal, test, Ltest.ThisClassIsFinal;, null, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED + R_CLASS) + "}\n" +
-			"ThisClassIsNotFinal[TYPE_REF]{ThisClassIsNotFinal, test, Ltest.ThisClassIsNotFinal;, null, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED + R_CLASS) + "}",
+			"ThisClassIsFinal[TYPE_REF]{ThisClassIsFinal, test, Ltest.ThisClassIsFinal;, null, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_EXPECTED_TYPE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}\n" +
+			"ThisClassIsNotFinal[TYPE_REF]{ThisClassIsNotFinal, test, Ltest.ThisClassIsNotFinal;, null, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_EXPECTED_TYPE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}",
 			requestor.getResults());
 }
 
@@ -13846,9 +13846,9 @@
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
 	assertResults(
-			"Inn.Inn2[TYPE_REF]{label.Inn.Inn2, label, Llabel.Inn$Inn2;, null, null, 44}\n" +
-			"In[TYPE_REF]{In, label, Llabel.In;, null, null, 47}\n" +
-			"Inn[TYPE_REF]{Inn, label, Llabel.Inn;, null, null, 47}",
+			"Inn.Inn2[TYPE_REF]{label.Inn.Inn2, label, Llabel.Inn$Inn2;, null, null, " + (R_DEFAULT + 39) + "}\n" +
+			"In[TYPE_REF]{In, label, Llabel.In;, null, null, " + (R_DEFAULT + 42) + "}\n" +
+			"Inn[TYPE_REF]{Inn, label, Llabel.Inn;, null, null, " + (R_DEFAULT + 42) + "}",
 			requestor.getResults());
 }
 
@@ -13874,8 +13874,8 @@
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
 	assertResults(
-			"Inn.Inn2[TYPE_REF]{Inn2, label, Llabel.Inn$Inn2;, null, null, 44}\n" +
-			"Inn.Inn3[TYPE_REF]{Inn3, label, Llabel.Inn$Inn3;, null, null, 44}",
+			"Inn.Inn2[TYPE_REF]{Inn2, label, Llabel.Inn$Inn2;, null, null, " + (R_DEFAULT + 39) + "}\n" +
+			"Inn.Inn3[TYPE_REF]{Inn3, label, Llabel.Inn$Inn3;, null, null, " + (R_DEFAULT + 39) + "}",
 			requestor.getResults());
 }
 
@@ -13891,8 +13891,10 @@
 	cu.codeComplete(cursorLocation, requestor);
 
 	assertResults(
-		"name[ANNOTATION_ATTRIBUTE_REF]{name, Ltestxxx.YAAnnot;, Ljava.lang.String;, name, null, " + (R_NAME_FIRST_PREFIX + R_EXPECTED_TYPE + R_RESOLVED) + "}\n" +
-		"val[ANNOTATION_ATTRIBUTE_REF]{val, Ltestxxx.YAAnnot;, I, val, null, " + (R_NAME_FIRST_PREFIX + R_EXPECTED_TYPE + R_RESOLVED) + "}",
+		"name[ANNOTATION_ATTRIBUTE_REF]{name, Ltestxxx.YAAnnot;, Ljava.lang.String;, name, null, " + 
+						(R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}\n" +
+		"val[ANNOTATION_ATTRIBUTE_REF]{val, Ltestxxx.YAAnnot;, I, val, null, " + 
+						(R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}",
 		requestor.getResults());
 }
 
@@ -13913,8 +13915,10 @@
 			requestor.getContext());
 	
 	assertResults(
-		"xxyy[FIELD_REF]{xxyy, Ltestxxx.TestType2;, I, xxyy, null, " + (R_NAME_FIRST_PREFIX + R_EXPECTED_TYPE + R_RESOLVED) + "}\n" +
-		"xxyy1[FIELD_REF]{xxyy1, Ltestxxx.TestType2;, Ljava.lang.String;, xxyy1, null, " + (R_NAME_FIRST_PREFIX + R_EXPECTED_TYPE + R_RESOLVED + R_EXACT_EXPECTED_TYPE) + "}",
+		"xxyy[FIELD_REF]{xxyy, Ltestxxx.TestType2;, I, xxyy, null, " + 
+				(R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}\n" +
+		"xxyy1[FIELD_REF]{xxyy1, Ltestxxx.TestType2;, Ljava.lang.String;, xxyy1, null, " + 
+				(R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED + R_EXACT_EXPECTED_TYPE) + "}",
 		requestor.getResults());
 }
 public void testBug351426() throws JavaModelException {
@@ -13936,7 +13940,7 @@
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
 
-	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXPECTED_TYPE;
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXACT_NAME + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED;
 	
 	assertResults(
 			"expectedTypesSignatures={Ltest.X<Ljava.lang.String;>;}\n" +
@@ -13965,7 +13969,7 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
-	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXPECTED_TYPE;
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXACT_NAME + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED;
 	
 	assertResults(
 			"expectedTypesSignatures={Ltest.X<Ljava.lang.String;>;}\n" +
@@ -13996,7 +14000,7 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
-	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXPECTED_TYPE;
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXACT_NAME + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED;
 	
 	assertResults(
 			"expectedTypesSignatures={Ltest.X<Ljava.lang.String;>.X1<Ljava.lang.String;>;}\n" +
@@ -14029,7 +14033,7 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
-	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXPECTED_TYPE;
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXACT_NAME + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED;
 	
 	assertResults(
 			"expectedTypesSignatures={Ltest.X<Ljava.lang.String;>.X1<Ljava.lang.Object;>.X11<Ljava.lang.String;>;}\n" +
@@ -14061,7 +14065,7 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
-	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXPECTED_TYPE;
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXACT_NAME + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED;
 	
 	assertResults(
 			"expectedTypesSignatures={Ltest.X$X1<Ljava.lang.String;>;}\n" +
@@ -14091,7 +14095,7 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
-	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXPECTED_TYPE;
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXACT_NAME + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED;
 	
 	assertResults(
 			"expectedTypesSignatures={Ltest.X<Ljava.lang.String;>;}\n" +
@@ -14122,7 +14126,7 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
-	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXPECTED_TYPE;
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXACT_NAME + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED;
 	
 	assertResults(
 			"expectedTypesSignatures={Ltest.X<TT;>.X1<Ljava.lang.String;>;}\n" +
@@ -14153,7 +14157,7 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
-	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXPECTED_TYPE;
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXACT_NAME + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED;
 	
 	assertResults(
 			"expectedTypesSignatures={Ltest.X$X1<Ljava.lang.String;>;}\n" +
@@ -14181,7 +14185,7 @@
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
 
-	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXPECTED_TYPE;
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXACT_NAME + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED;
 	
 	assertResults(
 			"expectedTypesSignatures={Ltest.X<Ljava.lang.String;>;}\n" +
@@ -14213,7 +14217,7 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
-	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXPECTED_TYPE;
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXACT_NAME + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED;
 	
 	assertResults(
 			"expectedTypesSignatures={Ltest.X$X1<Ljava.lang.String;>;}\n" +
@@ -14243,7 +14247,7 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
-	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXPECTED_TYPE;
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXACT_NAME + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED;
 	
 	assertResults(
 			"expectedTypesSignatures={Ltest.X<Ljava.lang.String;Ljava.lang.String;>;}\n" +
@@ -14278,7 +14282,7 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
-	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXPECTED_TYPE;
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXACT_NAME + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED;
 	
 	assertResults(
 			"expectedTypesSignatures={Ltest.X1<Ljava.lang.String;>;}\n" +
@@ -14305,7 +14309,8 @@
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 	assertResults(
-			"X<T>[TYPE_REF]{, , LX<TT;>;, null, null, replace[116, 116], token[116, 116], 51}",
+			"X<T>[TYPE_REF]{, , LX<TT;>;, null, null, replace[116, 116], token[116, 116], " +
+								(R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXACT_NAME + R_CASE + R_EXPECTED_TYPE + R_UNQUALIFIED + R_NON_RESTRICTED)+ "}",
 			requestor.getResults());
 }
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=326610
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExclusionPatternsTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExclusionPatternsTests.java
index 41ec92c..bfede2d 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExclusionPatternsTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExclusionPatternsTests.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2009 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
@@ -289,12 +289,16 @@
 		root.getNonJavaResources());
 }
 /*
- * Ensure that crearing an excluded package doesn't make it appear as a child of its package fragment root but it is a non-java resource.
+ * Ensure that creating an excluded package doesn't make it appear as a child of its package fragment root but it is a non-java resource.
  * (regression test for bug 65637 [model] Excluded package still in Java model)
  */
 public void testCreateExcludedPackage2() throws CoreException {
 	setClasspath(new String[] {"/P/src", "org/*|org/eclipse/*"});
 
+	// Trigger population of cache to check if it is properly invalidated by the delta processor.
+	// See http://bugs.eclipse.org/500714
+	getPackageFragmentRoot("/P/src").getChildren();
+
 	clearDeltas();
 	createFolder("/P/src/org/eclipse/mypack");
 
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations18Test.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations18Test.java
index 680ae6b..bc2e744 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations18Test.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations18Test.java
@@ -10,6 +10,7 @@
  *******************************************************************************/
 package org.eclipse.jdt.core.tests.model;
 
+import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -350,6 +351,19 @@
 		return ((AbstractCompilerTest.getPossibleComplianceLevels() & AbstractCompilerTest.F_1_8) != 0);
 	}
 
+	String readFully(IFile file) throws IOException, CoreException {
+		try (BufferedInputStream bs = new BufferedInputStream(file.getContents())) {
+			int available = 0;
+			StringBuilder buf = new StringBuilder();
+			while ((available = bs.available()) > 0) {
+				byte[] contents = new byte[available];
+				bs.read(contents);
+				buf.append(new String(contents));
+			}
+			return buf.toString();
+		}
+	}
+
 	/** Perform full build. */
 	public void test1FullBuild() throws Exception {
 		setupJavaProject("Test1");
@@ -1012,11 +1026,104 @@
 				MergeStrategy.OVERWRITE_ANNOTATIONS, null);
 		assertTrue("file should exist", annotationFile.exists());
 
-		// check that the error is even worse now:
+		// check that the error is resolved now:
 		reconciled = cu.reconcile(AST.JLS8, true, null, new NullProgressMonitor());
 		assertNoProblems(reconciled.getProblems());
 	}
 	
+
+	public void testAnnotateConstructorParameter() throws Exception {
+		myCreateJavaProject("TestLibs");
+		String lib1Content =
+				"package libs;\n" + 
+				"\n" +
+				"public class Lib1<U> {\n" +
+				"	public Lib1(int ignore, U string) {}\n" +
+				"}\n";
+		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
+				"/UnannotatedLib/libs/Lib1.java",
+				lib1Content
+			}, null);
+
+		// type check sources:
+		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
+		ICompilationUnit cu = fragment.createCompilationUnit("Test1.java",
+				"package tests;\n" + 
+				"import org.eclipse.jdt.annotation.*;\n" + 
+				"import libs.Lib1;\n" + 
+				"\n" + 
+				"public class Test1 {\n" + 
+				"	Object test0() {\n" + 
+				"		Lib1<@NonNull String> lib = new Lib1<>(1, null);\n" +
+				"		return lib;\n" + 
+				"	}\n" +
+				"}\n",
+				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
+		CompilationUnit reconciled = cu.reconcile(AST.JLS8, true, null, new NullProgressMonitor());
+		assertProblems(reconciled.getProblems(), new String[] {
+				"Pb(910) Null type mismatch: required '@NonNull String' but the provided value is null",
+		}, new int[] { 7 });
+
+		// acquire library AST:
+		IType type = this.project.findType("libs.Lib1");
+		ICompilationUnit libWorkingCopy = type.getClassFile().getWorkingCopy(this.wcOwner, null);
+		ASTParser parser = ASTParser.newParser(AST.JLS8);
+		parser.setSource(libWorkingCopy);
+		parser.setResolveBindings(true);
+		parser.setStatementsRecovery(false);
+		parser.setBindingsRecovery(false);
+		CompilationUnit unit = (CompilationUnit) parser.createAST(null);
+		libWorkingCopy.discardWorkingCopy();
+		
+		// find type binding:
+		int start = lib1Content.indexOf("U string");
+		ASTNode name = NodeFinder.perform(unit, start, 0);
+		assertTrue("should be simple name", name.getNodeType() == ASTNode.SIMPLE_NAME);
+		ASTNode method = name.getParent().getParent().getParent();
+		IMethodBinding methodBinding = ((MethodDeclaration)method).resolveBinding();
+		
+		// find annotation file (not yet existing):
+		IFile annotationFile = ExternalAnnotationUtil.getAnnotationFile(this.project, methodBinding.getDeclaringClass(), null);
+		assertFalse("file should not exist", annotationFile.exists());
+		assertEquals("file path", "/TestLibs/annots/libs/Lib1.eea", annotationFile.getFullPath().toString());
+
+		// annotate:
+		String originalSignature = ExternalAnnotationUtil.extractGenericSignature(methodBinding);
+		ExternalAnnotationUtil.annotateMember("libs/Lib1", annotationFile,
+				"<init>", 
+				originalSignature, 
+				"(IT0U;)V", // <- @Nullable U
+				MergeStrategy.OVERWRITE_ANNOTATIONS, null);
+		assertTrue("file should exist", annotationFile.exists());
+
+		// check that the error is resolved now:
+		reconciled = cu.reconcile(AST.JLS8, true, null, new NullProgressMonitor());
+		assertNoProblems(reconciled.getProblems());
+		
+		// invert annotation:
+		ExternalAnnotationUtil.annotateMethodParameterType("libs/Lib1", annotationFile,
+				"<init>", 
+				originalSignature, 
+				"T1U;", // <- @NonNull U
+				1, // position 
+				MergeStrategy.OVERWRITE_ANNOTATIONS, null);
+		assertTrue("file should exist", annotationFile.exists());
+
+		// check that the error is back now:
+		reconciled = cu.reconcile(AST.JLS8, true, null, new NullProgressMonitor());
+		assertProblems(reconciled.getProblems(), new String[] {
+				"Pb(910) Null type mismatch: required '@NonNull String' but the provided value is null",
+		}, new int[] { 7 });
+
+		// check that the previous entry has been overwritten:
+		assertEquals(
+				"class libs/Lib1\n" + 
+				"<init>\n" + 
+				" (ITU;)V\n" + 
+				" (IT1U;)V\n",
+				readFully(annotationFile));
+	}
+
 	// ===== white box tests for ExternalAnnotationUtil =====
 
 	public void testBug470666a() throws CoreException, IOException {
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/FreezeMonitor.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/FreezeMonitor.java
new file mode 100644
index 0000000..19fd7a7
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/FreezeMonitor.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * 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.core.tests.model;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.ICoreRunnable;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.jobs.Job;
+
+public class FreezeMonitor {
+
+	private static /* @Nullable */ Job monitorJob;  
+
+	public static void expectCompletionIn(final long millis) {
+		done();
+		monitorJob = Job.create("", new ICoreRunnable() {
+			@Override
+			public void run(IProgressMonitor monitor) throws CoreException {
+				if (monitor.isCanceled()) {
+					throw new OperationCanceledException();
+				}
+				StringBuilder result = new StringBuilder();
+				result.append("Possible frozen test case\n");
+				ThreadMXBean threadStuff = ManagementFactory.getThreadMXBean();
+				ThreadInfo[] allThreads = threadStuff.getThreadInfo(threadStuff.getAllThreadIds(), 200);
+				for (ThreadInfo threadInfo : allThreads) {
+					result.append("\"");
+					result.append(threadInfo.getThreadName());
+					result.append("\": ");
+					result.append(threadInfo.getThreadState());
+					result.append("\n");
+					final StackTraceElement[] elements = threadInfo.getStackTrace();
+					for (StackTraceElement element : elements) {
+						result.append("    ");
+						result.append(element);
+						result.append("\n");
+					}
+					result.append("\n");
+				}
+				System.out.println(result.toString());
+			}
+		});
+		monitorJob.schedule(millis);
+	}
+
+	public static void done() {
+		if (monitorJob != null) {
+			monitorJob.cancel();
+			monitorJob = null;
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavaProjectTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavaProjectTests.java
index ef9d23c..9ea4398 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavaProjectTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavaProjectTests.java
@@ -2809,4 +2809,75 @@
 			deleteProject(project15);
 	}
 }
+
+public void testBug501220() throws CoreException {
+	IJavaProject jdkPrj = null, swtPrj = null, egitPrj = null;
+	try {
+		jdkPrj = createJavaProject("JDK8", new String[]{"src"}, new String[] {"JCL_LIB"}, null, null, "bin", new String[]{"bin"}, null, null, "1.8");
+		createFolder("/JDK8/src/jdk8");
+		createFile("/JDK8/src/jdk8/MyConsumer.java",
+				"package jdk8;\n" + 
+				"@FunctionalInterface\n" + 
+				"public interface MyConsumer<T> {\n" + 
+				"    void accept(T t);\n" + 
+				"}\n");
+	
+		swtPrj = createJavaProject("SWT", new String[]{"src"}, new String[] {"JCL_LIB"}, new String[]{"/JDK8"}, null, "bin", new String[]{"bin"}, null, null, "1.8");
+		createFolder("/SWT/src/swt");
+		createFile("/SWT/src/swt/EventObject.java",
+				"package swt;\n" + 
+				"\n" + 
+				"import jdk8.MyConsumer;\n" + 
+				"\n" + 
+				"public class EventObject {\n" + 
+				"}");
+		createFile("/SWT/src/swt/SelectionListener.java",
+				"package swt;\n" + 
+				"\n" + 
+				"import java.util.EventObject;\n" + 
+				"\n" + 
+				"import jdk8.MyConsumer;\n" + 
+				"\n" + 
+				"public interface SelectionListener {\n" + 
+				"	void widgetSelected(EventObject event);\n" + 
+				"\n" + 
+				"	static SelectionListener widgetSelected(MyConsumer<EventObject> c) {\n" + 
+				"		return new SelectionListener() {\n" + 
+				"			public void widgetSelected(EventObject e) {\n" + 
+				"				c.accept(e);\n" + 
+				"			}\n" + 
+				"		};\n" + 
+				"	}\n" + 
+				"}");
+		egitPrj = createJavaProject("EGit", new String[]{"src"}, new String[] {"JCL_LIB"}, new String[]{"/SWT"}, null, "bin", new String[]{"bin"}, null, null, "1.8");
+		egitPrj.setOption(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
+		createFolder("/EGit/src/egit");
+		createFile("/EGit/src/egit/UIUtils.java",
+				"package egit; // Error: The type jdk8.MyConsumer cannot be resolved. It is indirectly referenced from required .class files\n" + 
+				"\n" + 
+				"import swt.EventObject;\n" + 
+				"\n" + 
+				"import swt.SelectionListener;\n" + 
+				"\n" + 
+				"public class UIUtils {\n" + 
+				"	void foo() {\n" + 
+				"		SelectionListener listener = new SelectionListener() {\n" + 
+				"			public void widgetSelected(EventObject event) {\n" + 
+				"			}\n" + 
+				"		};\n" + 
+				"		listener.toString();\n" + 
+				"	}\n" + 
+				"}\n");
+		egitPrj.getProject().getWorkspace().build(IncrementalProjectBuilder.AUTO_BUILD, null);
+		IMarker[] markers = egitPrj.getProject().findMarkers(null, true, IResource.DEPTH_INFINITE);
+		assertMarkers("Unexpected markers", "",  markers);
+	} finally {
+		if (jdkPrj != null)
+			deleteProject(jdkPrj);
+		if (swtPrj != null)
+			deleteProject(swtPrj);
+		if (egitPrj != null)
+			deleteProject(egitPrj);
+	}
+}
 }
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavadocFieldCompletionModelTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavadocFieldCompletionModelTest.java
index 696669a..2587d80 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavadocFieldCompletionModelTest.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavadocFieldCompletionModelTest.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
@@ -513,7 +513,7 @@
 			"}";
 		completeInJavadoc("/Completion/src/javadoc/fields/tags/BasicTestFields.java", source, true, "oTT");
 		assertResults(
-			"oneTwoThree[FIELD_REF]{oneTwoThree, Ljavadoc.fields.tags.BasicTestFields;, Ljava.lang.Object;, oneTwoThree, null, "+this.positions+"30}"
+			"oneTwoThree[FIELD_REF]{oneTwoThree, Ljavadoc.fields.tags.BasicTestFields;, Ljava.lang.Object;, oneTwoThree, null, "+this.positions+(R_DEFAULT + 25) + "}"
 		);
 	} finally {
 		JavaCore.setOptions(this.oldOptions);
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavadocMethodCompletionModelTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavadocMethodCompletionModelTest.java
index af02169..eeae0e8 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavadocMethodCompletionModelTest.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavadocMethodCompletionModelTest.java
@@ -1768,8 +1768,8 @@
 		"}\n";
 	completeInJavadoc("/Completion/src/javadoc/methods/tags/BasicTestMethods.java", source, true, "@param ", 0); // empty token
 	assertSortedResults(
-		"xtm[JAVADOC_PARAM_REF]{xtm, null, null, xtm, null, "+this.positions+"19}\n" +
-		"xtc[JAVADOC_PARAM_REF]{xtc, null, null, xtc, null, "+this.positions+"18}\n" +
+		"xtm[JAVADOC_PARAM_REF]{xtm, null, null, xtm, null, "+this.positions+(R_DEFAULT + 14)+"}\n" +
+		"xtc[JAVADOC_PARAM_REF]{xtc, null, null, xtc, null, "+this.positions+(R_DEFAULT + 13)+"}\n" +
 		"TM[JAVADOC_PARAM_REF]{<TM>, null, null, TM, null, "+this.positions+JAVADOC_RELEVANCE+"}"
 	);
 }
@@ -1834,8 +1834,8 @@
 		"}\n";
 	completeInJavadoc("/Completion/src/javadoc/methods/tags/BasicTestMethods.java", source, true, "@param ", 0); // empty token
 	assertSortedResults(
-		"xtm[JAVADOC_PARAM_REF]{xtm, null, null, xtm, null, [105, 108], 19}\n" +
-		"xtc[JAVADOC_PARAM_REF]{xtc, null, null, xtc, null, [105, 108], 18}\n" +
+		"xtm[JAVADOC_PARAM_REF]{xtm, null, null, xtm, null, [105, 108], " + (R_DEFAULT + 14) +"}\n" +
+		"xtc[JAVADOC_PARAM_REF]{xtc, null, null, xtc, null, [105, 108], " + (R_DEFAULT + 13) +"}\n" +
 		"TM[JAVADOC_PARAM_REF]{<TM>, null, null, TM, null, [105, 108], "+JAVADOC_RELEVANCE+"}"
 	);
 }
@@ -1883,8 +1883,8 @@
 		"}\n";
 	completeInJavadoc("/Completion/src/javadoc/methods/tags/BasicTestMethods.java", source, true, "@param ", 0); // empty token
 	assertSortedResults(
-		"xtm[JAVADOC_PARAM_REF]{xtm, null, null, xtm, null, "+this.positions+"19}\n" +
-		"xtc[JAVADOC_PARAM_REF]{xtc, null, null, xtc, null, "+this.positions+"18}\n" +
+		"xtm[JAVADOC_PARAM_REF]{xtm, null, null, xtm, null, "+this.positions+(R_DEFAULT + 14)+"}\n" +
+		"xtc[JAVADOC_PARAM_REF]{xtc, null, null, xtc, null, "+this.positions+(R_DEFAULT + 13)+"}\n" +
 		"TM[JAVADOC_PARAM_REF]{<TM>, null, null, TM, null, "+this.positions+JAVADOC_RELEVANCE+"}"
 	);
 }
@@ -1902,8 +1902,8 @@
 		"}\n";
 	completeInJavadoc("/Completion/src/javadoc/methods/tags/BasicTestMethods.java", source, true, "@param ", 0); // empty token
 	assertSortedResults(
-		"xtm[JAVADOC_PARAM_REF]{xtm, null, null, xtm, null, "+this.positions+"19}\n" +
-		"xtc[JAVADOC_PARAM_REF]{xtc, null, null, xtc, null, "+this.positions+"18}\n" +
+		"xtm[JAVADOC_PARAM_REF]{xtm, null, null, xtm, null, "+this.positions+(R_DEFAULT + 14)+"}\n" +
+		"xtc[JAVADOC_PARAM_REF]{xtc, null, null, xtc, null, "+this.positions+(R_DEFAULT + 13)+"}\n" +
 		"TM[JAVADOC_PARAM_REF]{<TM>, null, null, TM, null, "+this.positions+JAVADOC_RELEVANCE+"}"
 	);
 }
@@ -1921,8 +1921,8 @@
 		"}\n";
 	completeInJavadoc("/Completion/src/javadoc/methods/tags/BasicTestMethods.java", source, true, "@param ", 0); // empty token
 	assertSortedResults(
-		"xtm[JAVADOC_PARAM_REF]{xtm, null, null, xtm, null, "+this.positions+"19}\n" +
-		"xtc[JAVADOC_PARAM_REF]{xtc, null, null, xtc, null, "+this.positions+"18}\n" +
+		"xtm[JAVADOC_PARAM_REF]{xtm, null, null, xtm, null, "+this.positions+(R_DEFAULT + 14)+"}\n" +
+		"xtc[JAVADOC_PARAM_REF]{xtc, null, null, xtc, null, "+this.positions+(R_DEFAULT + 13)+"}\n" +
 		"TM[JAVADOC_PARAM_REF]{<TM>, null, null, TM, null, "+this.positions+JAVADOC_RELEVANCE+"}"
 	);
 }
@@ -2536,7 +2536,7 @@
 			"}\n";
 		completeInJavadoc("/Completion/src/javadoc/methods/tags/BasicTestMethods.java", source, true, "oTT");
 		assertResults(
-			"oneTwoThree[METHOD_REF]{oneTwoThree(int), Ljavadoc.methods.tags.BasicTestMethods;, (I)V, oneTwoThree, (i), "+this.positions+"30}"
+			"oneTwoThree[METHOD_REF]{oneTwoThree(int), Ljavadoc.methods.tags.BasicTestMethods;, (I)V, oneTwoThree, (i), "+this.positions+(R_DEFAULT + 25)+"}"
 		);
 	} finally {
 		JavaCore.setOptions(this.oldOptions);
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavadocTypeCompletionModelTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavadocTypeCompletionModelTest.java
index a567e07..1452f4d 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavadocTypeCompletionModelTest.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavadocTypeCompletionModelTest.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
@@ -1036,7 +1036,7 @@
 			" */\n" +
 			"public class BasicTestTypes {}\n";
 		completeInJavadoc("/Completion/src/javadoc/types/tags/BasicTestTypes.java", source, true, "BTT");
-		assertResults("BasicTestTypes[TYPE_REF]{BasicTestTypes, javadoc.types.tags, Ljavadoc.types.tags.BasicTestTypes;, null, null, "+this.positions+"22}");
+		assertResults("BasicTestTypes[TYPE_REF]{BasicTestTypes, javadoc.types.tags, Ljavadoc.types.tags.BasicTestTypes;, null, null, "+this.positions+ (R_DEFAULT + 17) +"}");
 	} finally {
 		JavaCore.setOptions(this.oldOptions);
 	}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ModifyingResourceTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ModifyingResourceTests.java
index 4a50363..3f44c37 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ModifyingResourceTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ModifyingResourceTests.java
@@ -19,6 +19,7 @@
 import org.eclipse.jdt.core.compiler.batch.BatchCompiler;
 import org.eclipse.jdt.core.tests.util.Util;
 import org.eclipse.jdt.internal.core.JavaElement;
+import org.eclipse.jdt.internal.core.nd.indexer.Indexer;
 
 public class ModifyingResourceTests extends AbstractJavaModelTests {
 
@@ -102,6 +103,7 @@
 	} catch (IOException e) {
 		e.printStackTrace();
 	}
+	Indexer.getInstance().waitForIndex(null);
 	return file;
 }
 
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/NameLookupTests2.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/NameLookupTests2.java
index a529c77..1dd9354 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/NameLookupTests2.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/NameLookupTests2.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
@@ -14,9 +14,6 @@
 import java.io.IOException;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.attribute.FileTime;
-import java.util.Arrays;
 
 import org.eclipse.core.resources.IWorkspaceRunnable;
 import org.eclipse.core.runtime.CoreException;
@@ -361,15 +358,7 @@
  * JavaProjectElementInfo cache without restarting Eclipse or closing and reopening the project.
  */
 public void testTransitionFromInvalidToValidJar() throws CoreException, IOException {
-	/*
-	 * Since it is difficult to test intermittent IO errors, simulate it
-	 * by creating two jars of equal size, one of which has an invalid format.
-	 * Set up the classpath with the invalid jar, and then swap in the valid jar
-	 * and reset its timestamp to be the same as the original file.
-	 */
-	String goodJar = getExternalPath() + "goodJar.jar";
 	String transitioningJar = getExternalPath() + "transitioningJar.jar";
-	java.nio.file.Path goodJarPath = FileSystems.getDefault().getPath(goodJar);
 	java.nio.file.Path transitioningJarPath = FileSystems.getDefault().getPath(transitioningJar);
 	IPath transitioningIPath = Path.fromOSString(transitioningJar);
 
@@ -385,33 +374,29 @@
 					"META-INF/MANIFEST.MF",
 					"Manifest-Version: 1.0\n"
 				},
-				goodJar,
+				transitioningJar,
 				JavaCore.VERSION_1_4);
-		char[] invalidContents = new char[(int) goodJarPath.toFile().length()];
-		Arrays.fill(invalidContents, ' ');
-		Util.createFile(transitioningJar, String.copyValueOf(invalidContents));
 
 		// Set up the project with the invalid jar and allow all of the classpath validation
 		// and delta processing to complete.
+		JavaModelManager.throwIoExceptionsInGetZipFile = true;
 		JavaProject proj = (JavaProject) createJavaProject("P", new String[] {}, new String[] {transitioningJar}, "bin");
 		JavaModelManager.getJavaModelManager().getJavaModel().refreshExternalArchives(null, null);
 		waitForAutoBuild();
 
 		assertTrue("The invalid archive cache should report that the jar is invalid",
-				JavaModelManager.getJavaModelManager().isInvalidArchive(transitioningIPath));
+				!JavaModelManager.getJavaModelManager().getArchiveValidity(transitioningIPath).isValid());
 		IType type = getNameLookup(proj).findType("test1.IResource", false, NameLookup.ACCEPT_CLASSES);
 		assertEquals("Name lookup should fail when the jar is invalid", null, type);
 
-		// Substitute the good jar, maintaining the timestamp.
-		FileTime fileTime = Files.getLastModifiedTime(transitioningJarPath);
-		Files.move(goodJarPath, transitioningJarPath, StandardCopyOption.REPLACE_EXISTING);
-		Files.setLastModifiedTime(transitioningJarPath, fileTime);
+		// Cause IO exceptions to be thrown on all file operations
+		JavaModelManager.throwIoExceptionsInGetZipFile = false;
 
 		// Since the timestamp hasn't changed, an external archive refresh isn't going
 		// to update the caches or cause name lookups to work.
 		JavaModelManager.getJavaModelManager().getJavaModel().refreshExternalArchives(null, null);
 		assertTrue("External archive refresh sees no changes, so the invalid archive cache should be unchanged",
-				JavaModelManager.getJavaModelManager().isInvalidArchive(transitioningIPath));
+				!JavaModelManager.getJavaModelManager().getArchiveValidity(transitioningIPath).isValid());
 		type = getNameLookup(proj).findType("test1.IResource", false, NameLookup.ACCEPT_CLASSES);
 		assertEquals("External archive refresh sees no changes, so the project cache should be unchanged",
 				null, type);
@@ -423,11 +408,10 @@
 		ClasspathEntry.validateClasspathEntry(proj, transitioningEntry, false, false);
 
 		assertFalse("The invalid archive cache should no longer report the jar as invalid",
-				JavaModelManager.getJavaModelManager().isInvalidArchive(transitioningIPath));
+				!JavaModelManager.getJavaModelManager().getArchiveValidity(transitioningIPath).isValid());
 		type = getNameLookup(proj).findType("test1.IResource", false, NameLookup.ACCEPT_CLASSES);
 		assertFalse("Name lookup should be able to find types in the valid jar", type == null);
 	} finally {
-		Files.deleteIfExists(goodJarPath);
 		Files.deleteIfExists(transitioningJarPath);
 		deleteProject("P");
 	}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/SubstringCompletionTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/SubstringCompletionTests.java
index 81e976d..4fea3b3 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/SubstringCompletionTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/SubstringCompletionTests.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2015 Gábor Kövesdán and others.
+ * Copyright (c) 2015, 2016 Gábor Kövesdán 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,59 +17,20 @@
 import org.eclipse.jdt.core.JavaModelException;
 
 import junit.framework.Test;
-import junit.framework.TestSuite;
 
 public class SubstringCompletionTests extends AbstractJavaModelCompletionTests {
 
 public static Test suite() {
-	if (TESTS_PREFIX != null || TESTS_NAMES != null || TESTS_NUMBERS != null || TESTS_RANGE != null) {
-		return buildModelTestSuite(SubstringCompletionTests.class);
-	}
-	TestSuite suite = new Suite(SubstringCompletionTests.class.getName());
-	suite.addTest(new SubstringCompletionTests("testQualifiedNonStaticMethod"));
-	suite.addTest(new SubstringCompletionTests("testQualifiedStaticMethod"));
-	suite.addTest(new SubstringCompletionTests("testUnqualifiedNonStaticMethod"));
-	suite.addTest(new SubstringCompletionTests("testUnqualifiedStaticMethod"));
-	suite.addTest(new SubstringCompletionTests("testQualifiedNonStaticField"));
-	suite.addTest(new SubstringCompletionTests("testQualifiedStaticField"));
-	suite.addTest(new SubstringCompletionTests("testUnqualifiedNonStaticField"));
-	suite.addTest(new SubstringCompletionTests("testUnqualifiedStaticField"));
-	suite.addTest(new SubstringCompletionTests("testLocalVariable"));
-	suite.addTest(new SubstringCompletionTests("testMethodParamVariable"));
-	suite.addTest(new SubstringCompletionTests("testClassTypeInstantiation"));
-	suite.addTest(new SubstringCompletionTests("testClassTypeFieldDeclaration"));
-	suite.addTest(new SubstringCompletionTests("testClassTypeParamDeclaration"));
-	suite.addTest(new SubstringCompletionTests("testClassTypeLocalVarDeclaration"));
-	suite.addTest(new SubstringCompletionTests("testClassTypeThrowsDeclaration"));
-	suite.addTest(new SubstringCompletionTests("testClassTypeExtends"));
-	suite.addTest(new SubstringCompletionTests("testClassTypeImplements"));
-	suite.addTest(new SubstringCompletionTests("testInnerClassTypeInstantiation"));
-	suite.addTest(new SubstringCompletionTests("testInnerClassTypeFieldDeclaration"));
-	suite.addTest(new SubstringCompletionTests("testInnerClassTypeParamDeclaration"));
-	suite.addTest(new SubstringCompletionTests("testInnerClassTypeLocalVarDeclaration"));
-	suite.addTest(new SubstringCompletionTests("testInnerClassTypeThrowsDeclaration"));
-	suite.addTest(new SubstringCompletionTests("testInnerClassTypeExtends"));
-	suite.addTest(new SubstringCompletionTests("testInnerClassTypeImplements"));
-	suite.addTest(new SubstringCompletionTests("testStaticNestedClassTypeInstantiation"));
-	suite.addTest(new SubstringCompletionTests("testStaticNestedClassTypeFieldDeclaration"));
-	suite.addTest(new SubstringCompletionTests("testStaticNestedClassTypeParamDeclaration"));
-	suite.addTest(new SubstringCompletionTests("testStaticNestedClassTypeLocalVarDeclaration"));
-	suite.addTest(new SubstringCompletionTests("testStaticNestedClassTypeThrowsDeclaration"));
-	suite.addTest(new SubstringCompletionTests("testStaticNestedClassTypeExtends"));
-	suite.addTest(new SubstringCompletionTests("testStaticNestedClassTypeImplements"));
-	suite.addTest(new SubstringCompletionTests("testLocalClassTypeInstantiation"));
-	suite.addTest(new SubstringCompletionTests("testLocalClassTypeLocalVarDeclaration"));
-	suite.addTest(new SubstringCompletionTests("testLocalClassTypeExtends"));
-	return suite;
+	return buildModelTestSuite(SubstringCompletionTests.class, BYTECODE_DECLARATION_ORDER);
 }
 public SubstringCompletionTests(String name) {
 	super(name);
 }
 public void setUpSuite() throws Exception {
 	if (COMPLETION_PROJECT == null)  {
-		COMPLETION_PROJECT = setUpJavaProject("Completion");
+		COMPLETION_PROJECT = setUpJavaProject("Completion", "1.8", true);
 	} else {
-		setUpProjectCompliance(COMPLETION_PROJECT, "1.8");
+		setUpProjectCompliance(COMPLETION_PROJECT, "1.8", true);
 	}
 	super.setUpSuite();
 	Hashtable<String, String> options = new Hashtable<>(this.oldOptions);
@@ -202,24 +163,24 @@
 		"/Completion/src/test/Test.java",
 		"package test;"+
 		"public class Test {\n" +
-		"  int element;\n" +
-		"  int otherElement;\n" +
-		"  long elementCount;\n" +
+		"  int items;\n" +
+		"  int otherItems;\n" +
+		"  long itemsCount;\n" +
 		"  void foo() {\n" +
-		"    this.elem\n" +
+		"    this.item\n" +
 		"  }\n" +
 		"}\n");
 
 	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
 	String str = this.workingCopies[0].getSource();
-	String completeBehind = "this.elem";
+	String completeBehind = "this.item";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
 	assertResults(
-			"otherElement[FIELD_REF]{otherElement, Ltest.Test;, I, otherElement, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_STATIC + R_NON_RESTRICTED + R_SUBSTRING) + "}\n" +
-			"element[FIELD_REF]{element, Ltest.Test;, I, element, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + + R_CASE + R_NON_STATIC + R_NON_RESTRICTED) + "}\n" +
-			"elementCount[FIELD_REF]{elementCount, Ltest.Test;, J, elementCount, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_NON_STATIC + R_NON_RESTRICTED) + "}",
+			"otherItems[FIELD_REF]{otherItems, Ltest.Test;, I, otherItems, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_STATIC + R_NON_RESTRICTED + R_SUBSTRING) + "}\n" +
+			"items[FIELD_REF]{items, Ltest.Test;, I, items, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + + R_CASE + R_NON_STATIC + R_NON_RESTRICTED) + "}\n" +
+			"itemsCount[FIELD_REF]{itemsCount, Ltest.Test;, J, itemsCount, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_NON_STATIC + R_NON_RESTRICTED) + "}",
 			requestor.getResults());
 }
 
@@ -229,25 +190,24 @@
 			"/Completion/src/test/Test.java",
 			"package test;"+
 			"public class Test {\n" +
-			"  int element;\n" +
-			"  int otherElement;\n" +
-			"  long elementCount;\n" +
+			"  int items;\n" +
+			"  int otherItems;\n" +
+			"  long itemsCount;\n" +
 			"  void foo() {\n" +
-			"    elem\n" +
+			"    item\n" +
 			"  }\n" +
 			"}\n");
 
 	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
 	String str = this.workingCopies[0].getSource();
-	String completeBehind = "elem";
+	String completeBehind = "item";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
 	assertResults(
-			"ElementType[TYPE_REF]{java.lang.annotation.ElementType, java.lang.annotation, Ljava.lang.annotation.ElementType;, null, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED) + "}\n" +
-			"otherElement[FIELD_REF]{otherElement, Ltest.Test;, I, otherElement, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_UNQUALIFIED + R_NON_RESTRICTED + R_SUBSTRING) + "}\n" +
-			"element[FIELD_REF]{element, Ltest.Test;, I, element, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}\n" +
-			"elementCount[FIELD_REF]{elementCount, Ltest.Test;, J, elementCount, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}",
+			"otherItems[FIELD_REF]{otherItems, Ltest.Test;, I, otherItems, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_UNQUALIFIED + R_NON_RESTRICTED + R_SUBSTRING) + "}\n" +
+			"items[FIELD_REF]{items, Ltest.Test;, I, items, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}\n" +
+			"itemsCount[FIELD_REF]{itemsCount, Ltest.Test;, J, itemsCount, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}",
 			requestor.getResults());
 }
 public void testQualifiedStaticField() throws JavaModelException {
@@ -256,23 +216,23 @@
 			"/Completion/src/test/Test.java",
 			"package test;"+
 			"public class Test {\n" +
-			"  static int element;\n" +
-			"  int otherElement;\n" +
-			"  static long elementCount;\n" +
+			"  static int items;\n" +
+			"  int otherItems;\n" +
+			"  static long itemsCount;\n" +
 			"  void foo() {\n" +
-			"    Test.elem\n" +
+			"    Test.item\n" +
 			"  }\n" +
 			"}\n");
 
 	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
 	String str = this.workingCopies[0].getSource();
-	String completeBehind = "Test.elem";
+	String completeBehind = "Test.item";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
 	assertResults(
-			"element[FIELD_REF]{element, Ltest.Test;, I, element, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_NON_RESTRICTED + R_NON_INHERITED) + "}\n" +
-			"elementCount[FIELD_REF]{elementCount, Ltest.Test;, J, elementCount, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_NON_RESTRICTED + R_NON_INHERITED) + "}",
+			"items[FIELD_REF]{items, Ltest.Test;, I, items, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_NON_RESTRICTED + R_NON_INHERITED) + "}\n" +
+			"itemsCount[FIELD_REF]{itemsCount, Ltest.Test;, J, itemsCount, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_NON_RESTRICTED + R_NON_INHERITED) + "}",
 			requestor.getResults());
 }
 public void testUnqualifiedStaticField() throws JavaModelException {
@@ -281,25 +241,24 @@
 			"/Completion/src/test/Test.java",
 			"package test;"+
 			"public class Test {\n" +
-			"  static int element;\n" +
-			"  int otherElement;\n" +
-			"  static long elementCount;\n" +
+			"  static int items;\n" +
+			"  int otherItems;\n" +
+			"  static long itemsCount;\n" +
 			"  void foo() {\n" +
-			"    elem\n" +
+			"    item\n" +
 			"  }\n" +
 			"}\n");
 
 	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
 	String str = this.workingCopies[0].getSource();
-	String completeBehind = "elem";
+	String completeBehind = "item";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
 	assertResults(
-			"ElementType[TYPE_REF]{java.lang.annotation.ElementType, java.lang.annotation, Ljava.lang.annotation.ElementType;, null, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED) + "}\n" +
-			"otherElement[FIELD_REF]{otherElement, Ltest.Test;, I, otherElement, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_UNQUALIFIED + R_NON_RESTRICTED + R_SUBSTRING) + "}\n" +
-			"element[FIELD_REF]{element, Ltest.Test;, I, element, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}\n" +
-			"elementCount[FIELD_REF]{elementCount, Ltest.Test;, J, elementCount, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}",
+			"otherItems[FIELD_REF]{otherItems, Ltest.Test;, I, otherItems, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_UNQUALIFIED + R_NON_RESTRICTED + R_SUBSTRING) + "}\n" +
+			"items[FIELD_REF]{items, Ltest.Test;, I, items, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}\n" +
+			"itemsCount[FIELD_REF]{itemsCount, Ltest.Test;, J, itemsCount, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}",
 			requestor.getResults());
 }
 public void testLocalVariable() throws JavaModelException {
@@ -308,27 +267,26 @@
 			"/Completion/src/test/Test.java",
 			"package test;"+
 			"public class Test {\n" +
-			"  static int element;\n" +
-			"  int otherElement;\n" +
-			"  static long elementCount;\n" +
+			"  static int items;\n" +
+			"  int otherItems;\n" +
+			"  static long itemsCount;\n" +
 			"  void foo() {\n" +
-			"    int temporaryElement = 0;\n" +
-			"    elem\n" +
+			"    int temporaryItem = 0;\n" +
+			"    item\n" +
 			"  }\n" +
 			"}\n");
 
 	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
 	String str = this.workingCopies[0].getSource();
-	String completeBehind = "elem";
+	String completeBehind = "item";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
 	assertResults(
-			"ElementType[TYPE_REF]{java.lang.annotation.ElementType, java.lang.annotation, Ljava.lang.annotation.ElementType;, null, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED) + "}\n" +
-			"otherElement[FIELD_REF]{otherElement, Ltest.Test;, I, otherElement, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_UNQUALIFIED + R_NON_RESTRICTED + R_SUBSTRING) + "}\n" +
-			"temporaryElement[LOCAL_VARIABLE_REF]{temporaryElement, null, I, temporaryElement, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_UNQUALIFIED + R_NON_RESTRICTED + R_SUBSTRING) + "}\n" +
-			"element[FIELD_REF]{element, Ltest.Test;, I, element, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}\n" +
-			"elementCount[FIELD_REF]{elementCount, Ltest.Test;, J, elementCount, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}",
+			"otherItems[FIELD_REF]{otherItems, Ltest.Test;, I, otherItems, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_UNQUALIFIED + R_NON_RESTRICTED + R_SUBSTRING) + "}\n" +
+			"temporaryItem[LOCAL_VARIABLE_REF]{temporaryItem, null, I, temporaryItem, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_UNQUALIFIED + R_NON_RESTRICTED + R_SUBSTRING) + "}\n" +
+			"items[FIELD_REF]{items, Ltest.Test;, I, items, null, " + (R_DEFAULT + R_RESOLVED + R_CASE + R_INTERESTING + R_UNQUALIFIED + R_NON_RESTRICTED) + "}\n" +
+			"itemsCount[FIELD_REF]{itemsCount, Ltest.Test;, J, itemsCount, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}",
 			requestor.getResults());
 }
 public void testMethodParamVariable() throws JavaModelException {
@@ -337,26 +295,25 @@
 			"/Completion/src/test/Test.java",
 			"package test;"+
 			"public class Test {\n" +
-			"  static int element;\n" +
-			"  int otherElement;\n" +
-			"  static long elementCount;\n" +
-			"  void foo(int initElement) {\n" +
-			"    elem\n" +
+			"  static int items;\n" +
+			"  int otherItems;\n" +
+			"  static long itemsCount;\n" +
+			"  void foo(int initItems) {\n" +
+			"    item\n" +
 			"  }\n" +
 			"}\n");
 
 	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
 	String str = this.workingCopies[0].getSource();
-	String completeBehind = "elem";
+	String completeBehind = "item";
 	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
 	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
 
 	assertResults(
-			"ElementType[TYPE_REF]{java.lang.annotation.ElementType, java.lang.annotation, Ljava.lang.annotation.ElementType;, null, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED) + "}\n" +
-			"initElement[LOCAL_VARIABLE_REF]{initElement, null, I, initElement, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_UNQUALIFIED + R_NON_RESTRICTED + R_SUBSTRING) + "}\n" +
-			"otherElement[FIELD_REF]{otherElement, Ltest.Test;, I, otherElement, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_UNQUALIFIED + R_NON_RESTRICTED + R_SUBSTRING) + "}\n" +
-			"element[FIELD_REF]{element, Ltest.Test;, I, element, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}\n" +
-			"elementCount[FIELD_REF]{elementCount, Ltest.Test;, J, elementCount, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}",
+			"initItems[LOCAL_VARIABLE_REF]{initItems, null, I, initItems, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_UNQUALIFIED + R_NON_RESTRICTED + R_SUBSTRING) + "}\n" +
+			"otherItems[FIELD_REF]{otherItems, Ltest.Test;, I, otherItems, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_UNQUALIFIED + R_NON_RESTRICTED + R_SUBSTRING) + "}\n" +
+			"items[FIELD_REF]{items, Ltest.Test;, I, items, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}\n" +
+			"itemsCount[FIELD_REF]{itemsCount, Ltest.Test;, J, itemsCount, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_CASE + R_UNQUALIFIED + R_NON_RESTRICTED) + "}",
 			requestor.getResults());
 }
 public void testClassTypeInstantiation() throws JavaModelException {
@@ -899,4 +856,257 @@
 			"FooBar[TYPE_REF]{FooBar, test, LFooBar;, null, null, " + (R_DEFAULT + R_RESOLVED + R_INTERESTING + R_EXPECTED_TYPE + R_NON_RESTRICTED + R_UNQUALIFIED + R_SUBSTRING) + "}",
 			requestor.getResults());
 }
-}
\ No newline at end of file
+public void testBug488441_1() throws JavaModelException {
+	this.workingCopies = new ICompilationUnit[1];
+	String content = "public class Try18 {\n" +
+						"	public void main(String[] args) {\n" +
+						"		\"s\".st\n" +
+						"	}\n" +
+						"}\n" +
+						"}\n";
+	this.workingCopies[0] = getWorkingCopy(
+			"/Completion/src/test/Test.java",
+			content);
+	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+	String str = this.workingCopies[0].getSource();
+	String completeBehind = ".st";
+	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_STATIC + R_NON_RESTRICTED;
+	assertResults(
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (I)I, lastIndexOf, (arg0), "+ (relevance + R_SUBSTRING) +"}\n" +
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (II)I, lastIndexOf, (arg0, arg1), "+ (relevance + R_SUBSTRING) +"}\n" +
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (Ljava.lang.String;)I, lastIndexOf, (arg0), "+ (relevance + R_SUBSTRING) +"}\n" +
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (Ljava.lang.String;I)I, lastIndexOf, (arg0, arg1), "+ (relevance + R_SUBSTRING) +"}\n" +
+			"replaceFirst[METHOD_REF]{replaceFirst(), Ljava.lang.String;, (Ljava.lang.String;Ljava.lang.String;)Ljava.lang.String;, replaceFirst, (arg0, arg1), "+ (relevance + R_SUBSTRING) +"}\n" +
+			"substring[METHOD_REF]{substring(), Ljava.lang.String;, (I)Ljava.lang.String;, substring, (arg0), "+ (relevance + R_SUBSTRING) +"}\n" +
+			"substring[METHOD_REF]{substring(), Ljava.lang.String;, (II)Ljava.lang.String;, substring, (arg0, arg1), "+ (relevance + R_SUBSTRING) +"}\n" +
+			"toString[METHOD_REF]{toString(), Ljava.lang.String;, ()Ljava.lang.String;, toString, null, "+ (relevance + R_SUBSTRING) +"}\n" +
+			"startsWith[METHOD_REF]{startsWith(), Ljava.lang.String;, (Ljava.lang.String;)Z, startsWith, (arg0), "+ (relevance + R_CASE) +"}\n" +
+			"startsWith[METHOD_REF]{startsWith(), Ljava.lang.String;, (Ljava.lang.String;I)Z, startsWith, (arg0, arg1), "+ (relevance + R_CASE) +"}",
+			requestor.getResults());
+}
+public void testBug488441_2() throws JavaModelException {
+	this.workingCopies = new ICompilationUnit[1];
+	String content = "public class Try18 {\n" +
+						"	public void main(String[] args) {\n" +
+						"		int i = \"s\".st\n" +
+						"	}\n" +
+						"}\n";
+	this.workingCopies[0] = getWorkingCopy(
+			"/Completion/src/test/Test.java",
+			content);
+
+	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+	String str = this.workingCopies[0].getSource();
+	String completeBehind = ".st";
+	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_STATIC + R_NON_RESTRICTED;
+	assertResults(
+			"replaceFirst[METHOD_REF]{replaceFirst(), Ljava.lang.String;, (Ljava.lang.String;Ljava.lang.String;)Ljava.lang.String;, replaceFirst, (arg0, arg1), "+ (relevance + R_SUBSTRING) +"}\n" +
+			"substring[METHOD_REF]{substring(), Ljava.lang.String;, (I)Ljava.lang.String;, substring, (arg0), "+ (relevance + R_SUBSTRING) +"}\n" +
+			"substring[METHOD_REF]{substring(), Ljava.lang.String;, (II)Ljava.lang.String;, substring, (arg0, arg1), "+ (relevance + R_SUBSTRING) +"}\n" +
+			"toString[METHOD_REF]{toString(), Ljava.lang.String;, ()Ljava.lang.String;, toString, null, "+ (relevance + R_SUBSTRING) +"}\n" +
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (I)I, lastIndexOf, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (II)I, lastIndexOf, (arg0, arg1), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (Ljava.lang.String;)I, lastIndexOf, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (Ljava.lang.String;I)I, lastIndexOf, (arg0, arg1), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"startsWith[METHOD_REF]{startsWith(), Ljava.lang.String;, (Ljava.lang.String;)Z, startsWith, (arg0), "+ (relevance + R_CASE) +"}\n" +
+			"startsWith[METHOD_REF]{startsWith(), Ljava.lang.String;, (Ljava.lang.String;I)Z, startsWith, (arg0, arg1), "+ (relevance + R_CASE) +"}",
+			requestor.getResults());
+}
+public void testBug488441_3() throws JavaModelException {
+	this.workingCopies = new ICompilationUnit[1];
+	String content = "public class Try18 {\n" +
+						"	public void main(String[] args) {\n" +
+						"		String s = \"s\".st\n" +
+						"	}\n" +
+						"}\n";
+	this.workingCopies[0] = getWorkingCopy(
+			"/Completion/src/test/Test.java",
+			content);
+
+	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+	String str = this.workingCopies[0].getSource();
+	String completeBehind = ".st";
+	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_STATIC + R_NON_RESTRICTED;
+	assertResults(
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (I)I, lastIndexOf, (arg0), "+ (relevance + R_SUBSTRING) +"}\n" +
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (II)I, lastIndexOf, (arg0, arg1), "+ (relevance + R_SUBSTRING) +"}\n" +
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (Ljava.lang.String;)I, lastIndexOf, (arg0), "+ (relevance + R_SUBSTRING) +"}\n" +
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (Ljava.lang.String;I)I, lastIndexOf, (arg0, arg1), "+ (relevance + R_SUBSTRING) +"}\n" +
+			"replaceFirst[METHOD_REF]{replaceFirst(), Ljava.lang.String;, (Ljava.lang.String;Ljava.lang.String;)Ljava.lang.String;, replaceFirst, (arg0, arg1), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"startsWith[METHOD_REF]{startsWith(), Ljava.lang.String;, (Ljava.lang.String;)Z, startsWith, (arg0), "+ (relevance + R_CASE) +"}\n" +
+			"startsWith[METHOD_REF]{startsWith(), Ljava.lang.String;, (Ljava.lang.String;I)Z, startsWith, (arg0, arg1), "+ (relevance + R_CASE) +"}\n" +
+			"substring[METHOD_REF]{substring(), Ljava.lang.String;, (I)Ljava.lang.String;, substring, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"substring[METHOD_REF]{substring(), Ljava.lang.String;, (II)Ljava.lang.String;, substring, (arg0, arg1), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"toString[METHOD_REF]{toString(), Ljava.lang.String;, ()Ljava.lang.String;, toString, null, "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}",
+			requestor.getResults());
+}
+public void testBug488441_4() throws JavaModelException {
+	this.workingCopies = new ICompilationUnit[1];
+	String content = "public class Try18 {\n" +
+						"	public void main(String[] args) {\n" +
+						"		boolean s = \"s\".st\n" +
+						"	}\n" +
+						"}\n";
+	this.workingCopies[0] = getWorkingCopy(
+			"/Completion/src/test/Test.java",
+			content);
+
+	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+	String str = this.workingCopies[0].getSource();
+	String completeBehind = ".st";
+	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_STATIC + R_NON_RESTRICTED;
+	assertResults(
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (I)I, lastIndexOf, (arg0), " + (relevance + R_SUBSTRING) + "}\n" +
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (II)I, lastIndexOf, (arg0, arg1), " + (relevance + R_SUBSTRING) + "}\n" +
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (Ljava.lang.String;)I, lastIndexOf, (arg0), " + (relevance + R_SUBSTRING) + "}\n" +
+			"lastIndexOf[METHOD_REF]{lastIndexOf(), Ljava.lang.String;, (Ljava.lang.String;I)I, lastIndexOf, (arg0, arg1), " + (relevance + R_SUBSTRING) + "}\n" +
+			"replaceFirst[METHOD_REF]{replaceFirst(), Ljava.lang.String;, (Ljava.lang.String;Ljava.lang.String;)Ljava.lang.String;, replaceFirst, (arg0, arg1), " + (relevance + R_SUBSTRING) + "}\n" +
+			"substring[METHOD_REF]{substring(), Ljava.lang.String;, (I)Ljava.lang.String;, substring, (arg0), " + (relevance + R_SUBSTRING) + "}\n" +
+			"substring[METHOD_REF]{substring(), Ljava.lang.String;, (II)Ljava.lang.String;, substring, (arg0, arg1), " + (relevance + R_SUBSTRING) + "}\n" +
+			"toString[METHOD_REF]{toString(), Ljava.lang.String;, ()Ljava.lang.String;, toString, null, " + (relevance + R_SUBSTRING) + "}\n" +
+			"startsWith[METHOD_REF]{startsWith(), Ljava.lang.String;, (Ljava.lang.String;)Z, startsWith, (arg0), " + (relevance + R_EXACT_EXPECTED_TYPE + R_CASE) + "}\n" +
+			"startsWith[METHOD_REF]{startsWith(), Ljava.lang.String;, (Ljava.lang.String;I)Z, startsWith, (arg0, arg1), " + (relevance + R_EXACT_EXPECTED_TYPE + R_CASE) + "}",
+			requestor.getResults());
+}
+public void testBug488441_5() throws JavaModelException {
+	this.workingCopies = new ICompilationUnit[1];
+	this.workingCopies[0] = getWorkingCopy(
+			"/Completion/src/test/Test.java",
+			"import java.util.Arrays;\n" +
+			"public class Try18 {\n" +
+			"	public void main(String[] args) {\n" +
+			"	String msg=\"\";\n" +
+			"	String[] parameters = {\"a\"};\n" +
+			"	System.out.println(msg + Arrays.as);\n" +
+			"	}\n" +
+			"}\n");
+
+	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+	String str = this.workingCopies[0].getSource();
+	String completeBehind = ".as";
+	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED + R_NON_INHERITED;
+	assertResults(
+			"asList[METHOD_REF]{asList(), Ljava.util.Arrays;, <T:Ljava.lang.Object;>([TT;)Ljava.util.List<TT;>;, asList, (arg0), "+ (relevance + R_CASE) +"}\n" +
+			"deepHashCode[METHOD_REF]{deepHashCode(), Ljava.util.Arrays;, ([Ljava.lang.Object;)I, deepHashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([B)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([C)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([D)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([F)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([I)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([J)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([Ljava.lang.Object;)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([S)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([Z)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}",
+			requestor.getResults());
+}
+public void testBug488441_6() throws JavaModelException {
+	this.workingCopies = new ICompilationUnit[1];
+	this.workingCopies[0] = getWorkingCopy(
+			"/Completion/src/test/Test.java",
+			"import java.util.Arrays;\n" +
+			"public class Try18 {\n" +
+			"	public void main(String[] args) {\n" +
+			"	String msg=\"\";\n" +
+			"	String[] parameters = {\"a\"};\n" +
+			"	System.out.println(msg + Arrays.aS);\n" +
+			"	}\n" +
+			"}\n");
+
+	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+	String str = this.workingCopies[0].getSource();
+	String completeBehind = ".aS";
+	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED + R_NON_INHERITED;
+	assertResults(
+			"asList[METHOD_REF]{asList(), Ljava.util.Arrays;, <T:Ljava.lang.Object;>([TT;)Ljava.util.List<TT;>;, asList, (arg0), "+ (relevance) +"}\n" +
+			"deepHashCode[METHOD_REF]{deepHashCode(), Ljava.util.Arrays;, ([Ljava.lang.Object;)I, deepHashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([B)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([C)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([D)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([F)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([I)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([J)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([Ljava.lang.Object;)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([S)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([Z)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}",
+			requestor.getResults());
+}
+public void testBug488441_7() throws JavaModelException {
+	this.workingCopies = new ICompilationUnit[1];
+	this.workingCopies[0] = getWorkingCopy(
+			"/Completion/src/test/Test.java",
+			"import java.util.Arrays;\n" +
+			"public class Try18 {\n" +
+			"	public void main(String[] args) {\n" +
+			"	String msg=\"\";\n" +
+			"	String[] parameters = {\"a\"};\n" +
+			"	System.out.println(Arrays.as);\n" +
+			"	}\n" +
+			"}\n");
+
+	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+	String str = this.workingCopies[0].getSource();
+	String completeBehind = ".as";
+	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED + R_NON_INHERITED;
+	assertResults(
+			"deepHashCode[METHOD_REF]{deepHashCode(), Ljava.util.Arrays;, ([Ljava.lang.Object;)I, deepHashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([B)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([C)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([D)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([F)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([I)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([J)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([Ljava.lang.Object;)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([S)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([Z)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"asList[METHOD_REF]{asList(), Ljava.util.Arrays;, <T:Ljava.lang.Object;>([TT;)Ljava.util.List<TT;>;, asList, (arg0), "+ (relevance + R_EXPECTED_TYPE + R_CASE) +"}",
+			requestor.getResults());
+}
+public void testBug488441_8() throws JavaModelException {
+	this.workingCopies = new ICompilationUnit[1];
+	this.workingCopies[0] = getWorkingCopy(
+			"/Completion/src/test/Test.java",
+			"import java.util.Arrays;\n" +
+			"public class Try18 {\n" +
+			"	public void main(String[] args) {\n" +
+			"	String msg=\"\";\n" +
+			"	String[] parameters = {\"a\"};\n" +
+			"	int i = Arrays.as;\n" +
+			"	}\n" +
+			"}\n");
+
+	CompletionTestsRequestor2 requestor = new CompletionTestsRequestor2(true);
+	String str = this.workingCopies[0].getSource();
+	String completeBehind = ".as";
+	int cursorLocation = str.lastIndexOf(completeBehind) + completeBehind.length();
+	this.workingCopies[0].codeComplete(cursorLocation, requestor, this.wcOwner);
+
+	int relevance = R_DEFAULT + R_RESOLVED + R_INTERESTING + R_NON_RESTRICTED + R_NON_INHERITED;
+	assertResults(
+			"asList[METHOD_REF]{asList(), Ljava.util.Arrays;, <T:Ljava.lang.Object;>([TT;)Ljava.util.List<TT;>;, asList, (arg0), "+ (relevance + R_CASE) +"}\n" +
+			"deepHashCode[METHOD_REF]{deepHashCode(), Ljava.util.Arrays;, ([Ljava.lang.Object;)I, deepHashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([B)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([C)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([D)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([F)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([I)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([J)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([Ljava.lang.Object;)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([S)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}\n" +
+			"hashCode[METHOD_REF]{hashCode(), Ljava.util.Arrays;, ([Z)I, hashCode, (arg0), "+ (relevance + R_SUBSTRING + R_EXACT_EXPECTED_TYPE) +"}",
+			requestor.getResults());
+}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/SuiteOfTestCases.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/SuiteOfTestCases.java
index 3917b75..94385de 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/SuiteOfTestCases.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/SuiteOfTestCases.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
@@ -14,6 +14,7 @@
 import java.lang.reflect.Modifier;
 import java.util.Set;
 
+import org.eclipse.jdt.internal.core.nd.indexer.Indexer;
 import org.eclipse.test.internal.performance.PerformanceMeterFactory;
 
 import junit.extensions.TestSetup;
@@ -29,6 +30,12 @@
 @SuppressWarnings("rawtypes")
 public class SuiteOfTestCases extends org.eclipse.jdt.core.tests.junit.extension.TestCase {
 
+	/**
+	 * Number of milliseconds that a test case can run for before we consider it to be potentially
+	 * deadlocked and dump out a stack trace. Currently set to 5 minutes.
+	 */
+	private static final long FROZEN_TEST_TIMEOUT_MS = 1000 * 60 * 5;
+
 	/*
 	 * A test suite that initialize the test case's fields once, then that copies the values
 	 * of these fields into each subsequent test case.
@@ -118,12 +125,27 @@
 	 * Setup the test suite once before all test cases run.
 	 */
 	public void setUpSuite() throws Exception {
+		Indexer.getInstance().enableAutomaticIndexing(false);
+		//Indexer.getInstance().waitForIndex(null);
 	}
 
 	/**
 	 * Tear down the test suite once after all test cases have run.
 	 */
 	public void tearDownSuite() throws Exception {
+		Indexer.getInstance().enableAutomaticIndexing(true);
+	}
+
+	@Override
+	protected void setUp() throws Exception {
+		FreezeMonitor.expectCompletionIn(FROZEN_TEST_TIMEOUT_MS);
+		super.setUp();
+	}
+
+	@Override
+	protected void tearDown() throws Exception {
+		FreezeMonitor.done();
+		super.tearDown();
 	}
 
 	/**
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/BTreeExpensiveTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/BTreeExpensiveTests.java
new file mode 100644
index 0000000..1557260
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/BTreeExpensiveTests.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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:
+ * Symbian - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.core.tests.nd;
+
+import junit.framework.Test;
+
+/**
+ * Tests which are too expensive to run as part of normal testing, but
+ * should be run after B-tree related development.
+ * <p>
+ * The 'Full Checking' tests perform a full validation of the B-tree
+ * invariants after each B-tree operation, and so are especially
+ * expensive and cpu hungry.
+ */
+public class BTreeExpensiveTests extends BTreeTests {
+	
+	public static Test suite() {
+		return suite(BTreeExpensiveTests.class);
+	}
+	
+	public void testBySortedSetMirror() throws Exception {
+		sortedMirrorTest(100);
+	}
+	
+	// @Override
+	@Override
+	public void testInsertion() throws Exception {
+		super.testInsertion();
+	}
+	
+	/*
+	 * N.B. Each of the following tests are quite expensive (i.e. > 10mins each on a 2Ghz machine)
+	 */
+	
+	public void testBySortedSetMirror1682762087() throws Exception {
+		System.out.println("1682762087 Full Checking");
+		trial(1682762087, true); // exposed bugs in 2a,b
+	}
+
+	public void testBySortedSetMirror322922974() throws Exception {
+		System.out.println("322922974 Full Checking");
+		trial(322922974, true); // exposed bugs in 3b(ii)
+	}
+
+	public void testBySortedSetMirror_588448152() throws Exception {
+		System.out.println("-588448152 Full Checking");
+		trial(-588448152, true); // exposed root-delete-on-merge problems
+	}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/BTreeTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/BTreeTests.java
new file mode 100644
index 0000000..42711e2
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/BTreeTests.java
@@ -0,0 +1,264 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 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:
+ *     Symbian - Initial implementation
+ *     Markus Schorn (Wind River Systems)
+ *******************************************************************************/
+package org.eclipse.jdt.core.tests.nd;
+
+import junit.framework.Test;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.tests.nd.util.BaseTestCase;
+import org.eclipse.jdt.internal.core.nd.Nd;
+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.IBTreeComparator;
+import org.eclipse.jdt.internal.core.nd.db.IBTreeVisitor;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Test insertion/deletion of records of a mock record type in a B-tree.
+ *
+ * @author aferguso
+ */
+public class BTreeTests extends BaseTestCase {
+	private static int DEBUG= 0;
+	protected File dbFile;
+	protected Nd nd;
+	protected Database db;
+	protected BTree btree;
+	protected int rootRecord;
+	protected IBTreeComparator comparator;
+
+	public static Test suite() {
+		return suite(BTreeTests.class);
+	}
+
+	// setUp is not used since we need to parameterize this method,
+	// and invoke it multiple times per Junit test
+	protected void init(int degree) throws Exception {
+		this.dbFile = File.createTempFile("ndtest", "db");
+		this.nd = DatabaseTestUtil.createEmptyNd(getName());
+		this.db = this.nd.getDB();
+		this.db.setExclusiveLock();
+		this.rootRecord = Database.DATA_AREA_OFFSET;
+		this.comparator = new BTMockRecordComparator();
+		this.btree = new BTree(this.nd, this.rootRecord, degree, this.comparator);
+	}
+
+	// tearDown is not used for the same reason as above
+	protected void finish() throws Exception {
+		this.db.close();
+		this.dbFile.deleteOnExit();
+	}
+
+
+	public void testBySortedSetMirrorLite() throws Exception {
+		sortedMirrorTest(8);
+	}
+
+	/**
+	 * Test random (but reproducible via known seed) sequences of insertions/deletions
+	 * and use TreeSet as a reference implementation to check behaviour against.
+	 * @throws Exception
+	 */
+	protected void sortedMirrorTest(int noTrials) throws Exception {
+		Random seeder = new Random(90210);
+
+		for (int i = 0; i < noTrials; i++) {
+			int seed = seeder.nextInt();
+			if (DEBUG > 0)
+				System.out.println("Iteration #" + i);
+			trial(seed, false);
+		}
+	}
+
+	/**
+	 * Test random (but reproducible via known seed) sequence of insertions
+	 * and use TreeSet as a reference implementation to check behaviour against.
+	 * @throws Exception
+	 */
+	public void testInsertion() throws Exception {
+		Random seeder = new Random();
+
+		for (int i = 0; i < 6; i++) {
+			int seed = seeder.nextInt();
+			if (DEBUG > 0)
+				System.out.println("Iteration #" + i);
+			trialImp(seed, false, new Random(seed * 2), 1);
+		}
+	}
+
+	/**
+	 * Bug 402177: BTree.insert should return the matching record if the new record was not inserted.
+	 */
+	public void testEquivalentRecordInsert_Bug402177() throws Exception {
+		init(8);
+		try {
+			BTMockRecord value1 = new BTMockRecord(this.db, 42);
+			BTMockRecord value2 = new BTMockRecord(this.db, 42);
+
+			long insert1 = this.btree.insert(value1.getRecord());
+			long insert2 = this.btree.insert(value2.getRecord());
+			assertEquals(insert1, insert2);
+		} finally {
+			finish();
+		}
+	}
+
+	/**
+	 * Insert/Delete a random number of records into/from the B-tree
+	 * @param seed the seed for obtaining the deterministic random testing
+	 * @param checkCorrectnessEachIteration if true, then on every single insertion/deletion check that the B-tree invariants
+	 * still hold
+	 * @throws Exception
+	 */
+	protected void trial(int seed, final boolean checkCorrectnessEachIteration) throws Exception {
+		Random random = new Random(seed);
+
+		// the probabilty that a particular iterations action will be an insertion
+		double pInsert = Math.min(0.5 + random.nextDouble(), 1);
+
+		trialImp(seed, checkCorrectnessEachIteration, random, pInsert);
+	}
+
+	private void trialImp(int seed, final boolean checkCorrectnessEachIteration, Random random,
+			double pInsert) throws Exception {
+		final int degree = 2 + random.nextInt(11);
+		final int nIterations = random.nextInt(100000);
+		final SortedSet<Integer> expected = new TreeSet<>();
+		final List<BTMockRecord> history = new ArrayList<>();
+
+		init(degree);
+
+		if (DEBUG > 0)
+			System.out.print("\t " + seed + " " + (nIterations/1000) + "K: ");
+		for (int i = 0; i < nIterations; i++) {
+			if (random.nextDouble() < pInsert) {
+				Integer value = new Integer(random.nextInt(Integer.MAX_VALUE));
+				boolean newEntry = expected.add(value);
+				if (newEntry) {
+					BTMockRecord btValue = new BTMockRecord(this.db, value.intValue());
+					history.add(btValue);
+					if (DEBUG > 1)
+						System.out.println("Add: " + value + " @ " + btValue.record);
+					this.btree.insert(btValue.getRecord());
+				}
+			} else {
+				if (!history.isEmpty()) {
+					int index = random.nextInt(history.size());
+					BTMockRecord btValue = history.get(index);
+					history.remove(index);
+					expected.remove(new Integer(btValue.intValue()));
+					if (DEBUG > 1)
+						System.out.println("Remove: " + btValue.intValue() + " @ " + btValue.record);
+					this.btree.delete(btValue.getRecord());
+				}
+			}
+			if (i % 1000 == 0 && DEBUG > 0) {
+				System.out.print(".");
+			}
+			if (checkCorrectnessEachIteration) {
+				assertBTreeMatchesSortedSet("[iteration " + i + "] ", this.btree, expected);
+				assertBTreeInvariantsHold("[iteration " + i + "] ");
+			}
+		}
+		if (DEBUG > 0)
+			System.out.println();
+
+		assertBTreeMatchesSortedSet("[Trial end] ", this.btree, expected);
+		assertBTreeInvariantsHold("[Trial end]");
+
+		finish();
+	}
+
+	public void assertBTreeInvariantsHold(String msg) throws CoreException {
+		String errorReport = this.btree.getInvariantsErrorReport();
+		if (!errorReport.equals("")) {
+			fail("Invariants do not hold: " + errorReport);
+		}
+	}
+
+	public void assertBTreeMatchesSortedSet(final String msg, BTree actual, SortedSet<Integer> expected) throws CoreException {
+		final Iterator<Integer> i = expected.iterator();
+		this.btree.accept(new IBTreeVisitor() {
+			int k;
+			@Override
+			public int compare(long record) {
+				return 0;
+			}
+
+			@Override
+			public boolean visit(long record) {
+				if (record != 0) {
+					BTMockRecord btValue = new BTMockRecord(record, BTreeTests.this.db);
+					if (i.hasNext()) {
+						Integer exp = i.next();
+						assertEquals(msg + " Differ at index: " + this.k, btValue.intValue(), exp.intValue());
+						this.k++;
+					} else {
+						fail("Sizes different");
+						return false;
+					}
+				}
+				return true;
+			}
+		});
+	}
+
+	private static class BTMockRecord {
+		public static final int VALUE_PTR = 0;
+		public static final int RECORD_SIZE = Database.INT_SIZE;
+		long record;
+		Database db;
+
+		/**
+		 * Make a new record
+		 */
+		public BTMockRecord(Database db, int value) throws CoreException {
+			this.db = db;
+			this.record = db.malloc(BTMockRecord.RECORD_SIZE, Database.POOL_MISC);
+			db.putInt(this.record + VALUE_PTR, value);
+		}
+
+		/**
+		 * Get an existing record
+		 */
+		public BTMockRecord(long record, Database db) {
+			this.db = db;
+			this.record = record;
+		}
+
+		public int intValue() {
+			return this.db.getInt(this.record);
+		}
+
+		public long getRecord() {
+			return this.record;
+		}
+	}
+
+	private class BTMockRecordComparator implements IBTreeComparator {
+		public BTMockRecordComparator() {
+		}
+
+		@Override
+		public int compare(Nd ndToCompare, long record1, long record2) {
+			Database dbToCompare = ndToCompare.getDB();
+			return dbToCompare.getInt(record1) - dbToCompare.getInt(record2);
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/DatabaseTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/DatabaseTest.java
new file mode 100644
index 0000000..9dd4557
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/DatabaseTest.java
@@ -0,0 +1,333 @@
+/*******************************************************************************
+ * 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 Software Systems - initial API and implementation
+ *     Andrew Ferguson (Symbian)
+ *     Markus Schorn (Wind River Systems)
+ *******************************************************************************/
+package org.eclipse.jdt.core.tests.nd;
+
+import java.io.File;
+import java.util.Random;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.tests.nd.util.BaseTestCase;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.NdNodeTypeRegistry;
+import org.eclipse.jdt.internal.core.nd.db.BTree;
+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.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;
+import org.eclipse.jdt.internal.core.nd.db.ShortString;
+
+import junit.framework.Test;
+
+/**
+ * Tests for the {@link Database} class.
+ */
+public class DatabaseTest extends BaseTestCase {
+	// This constant can be used to run the test with very large databases.
+	// Try, for example, setting it to Integer.MAX_VALUE * 7L;
+	private static final long TEST_OFFSET = 0;
+	private Nd nd;
+	protected Database db;
+	private static final int CURRENT_VERSION = 10;
+
+	@Override
+	protected void setUp() throws Exception {
+		super.setUp();
+		String testName = getName();
+		NdNodeTypeRegistry<NdNode> registry = new NdNodeTypeRegistry<>();
+		this.nd = new Nd(DatabaseTestUtil.getTempDbName(testName), new ChunkCache(), registry,
+				0, 100, CURRENT_VERSION);
+		this.db = this.nd.getDB();
+		this.db.setExclusiveLock();
+
+		// Allocate all database chunks up to TEST_OFFSET.
+		int count = 0;
+		for (long offset = 0; offset < TEST_OFFSET;) {
+			offset = this.db.malloc(Database.MAX_MALLOC_SIZE, Database.POOL_MISC);
+			if (++count >= 1000) {
+				this.db.flush();
+				count = 0;
+			}
+		}
+		this.db.flush();
+	}
+
+	public static Test suite() {
+		return BaseTestCase.suite(DatabaseTest.class);
+	}
+
+	@Override
+	protected void tearDown() throws Exception {
+		this.db.close();
+		if (!this.db.getLocation().delete()) {
+			this.db.getLocation().deleteOnExit();
+		}
+		this.db= null;
+	}
+
+	public void testBlockSizeAndFirstBlock() throws Exception {
+		assertEquals(CURRENT_VERSION, this.db.getVersion());
+
+		final int realsize = 42;
+		final int deltas = (realsize + Database.BLOCK_HEADER_SIZE + Database.BLOCK_SIZE_DELTA - 1) / Database.BLOCK_SIZE_DELTA;
+		final int blocksize = deltas * Database.BLOCK_SIZE_DELTA;
+		final int freeDeltas= Database.CHUNK_SIZE / Database.BLOCK_SIZE_DELTA - deltas;
+
+		long mem = this.db.malloc(realsize, Database.POOL_MISC);
+		assertEquals(-blocksize, this.db.getShort(mem - Database.BLOCK_HEADER_SIZE));
+		this.db.free(mem, Database.POOL_MISC);
+		assertEquals(blocksize, this.db.getShort(mem - Database.BLOCK_HEADER_SIZE));
+		assertEquals(mem, this.db.getRecPtr((deltas - Database.MIN_BLOCK_DELTAS +1 ) * Database.INT_SIZE));
+		assertEquals(mem + blocksize, this.db.getRecPtr((freeDeltas - Database.MIN_BLOCK_DELTAS + 1) * Database.INT_SIZE));
+	}
+
+	public void testBug192437() throws Exception {
+		File tmp= File.createTempFile("readOnlyEmpty", ".db");
+		try {
+			tmp.setReadOnly();
+
+			/* check opening a readonly file for rw access fails */
+			try {
+				new Database(tmp, ChunkCache.getSharedInstance(), 0, false);
+				fail("A readonly file should not be openable with write-access");
+			} catch (IndexException e) {
+				// we expect to get a failure here
+			}
+
+			/* check opening a readonly file for read access does not fail */
+			try {
+				new Database(tmp, ChunkCache.getSharedInstance(), 0, true);
+			} catch (IndexException e) {
+				fail("A readonly file should be readable by a permanently readonly database " + e);
+			}
+		} finally {
+			tmp.delete(); // this may be pointless on some platforms
+		}
+	}
+
+	public void testFreeBlockLinking() throws Exception {
+		final int realsize = 42;
+		final int deltas = (realsize + Database.BLOCK_HEADER_SIZE + Database.BLOCK_SIZE_DELTA - 1) / Database.BLOCK_SIZE_DELTA;
+
+		long mem1 = this.db.malloc(realsize, Database.POOL_MISC);
+		long mem2 = this.db.malloc(realsize, Database.POOL_MISC);
+		this.db.free(mem1, Database.POOL_MISC);
+		this.db.free(mem2, Database.POOL_MISC);
+		assertEquals(mem2, this.db.getRecPtr((deltas - Database.MIN_BLOCK_DELTAS + 1) * Database.INT_SIZE));
+		assertEquals(0, this.db.getRecPtr(mem2));
+		assertEquals(mem1, this.db.getRecPtr(mem2 + Database.INT_SIZE));
+		assertEquals(mem2, this.db.getRecPtr(mem1));
+		assertEquals(0, this.db.getRecPtr(mem1 + Database.INT_SIZE));
+	}
+
+	public void testSimpleAllocationLifecycle() throws Exception {
+		long mem1 = this.db.malloc(42, Database.POOL_MISC);
+		this.db.free(mem1, Database.POOL_MISC);
+		long mem2 = this.db.malloc(42, Database.POOL_MISC);
+		assertEquals(mem2, mem1);
+	}
+
+	private static class FindVisitor implements IBTreeVisitor {
+		private Database db;
+		private String key;
+		private long address;
+
+		public FindVisitor(Database db, String key) {
+			this.db = db;
+			this.key = key;
+		}
+
+		@Override
+		public int compare(long toCompare) {
+			return this.db.getString(this.db.getRecPtr(toCompare + 4)).compare(this.key, true);
+		}
+
+		@Override
+		public boolean visit(long toCompare) {
+			this.address = toCompare;
+			return false;
+		}
+
+		public long getRecord() {
+			return this.address;
+		}
+	}
+
+	public void testStringsInBTree() throws Exception {
+		String[] names = {
+				"ARLENE",
+				"BRET",
+				"CINDY",
+				"DENNIS",
+				"EMILY",
+				"FRANKLIN",
+				"GERT",
+				"HARVEY",
+				"IRENE",
+				"JOSE",
+				"KATRINA",
+				"LEE",
+				"MARIA",
+				"NATE",
+				"OPHELIA",
+				"PHILIPPE",
+				"RITA",
+				"STAN",
+				"TAMMY",
+				"VINCE",
+				"WILMA",
+				"ALPHA",
+				"BETA"
+		};
+
+		IBTreeComparator comparator = new IBTreeComparator() {
+			@Override
+			public int compare(Nd ndToCompare, long record1, long record2) {
+				IString string1 = DatabaseTest.this.db.getString(DatabaseTest.this.db.getRecPtr(record1 + 4));
+				IString string2 = DatabaseTest.this.db.getString(DatabaseTest.this.db.getRecPtr(record2 + 4));
+				return string1.compare(string2, true);
+			}
+		};
+
+		BTree btree = new BTree(this.nd, Database.DATA_AREA_OFFSET, comparator);
+		for (int i = 0; i < names.length; ++i) {
+			String name = names[i];
+			long record = this.db.malloc(8, Database.POOL_MISC);
+			this.db.putInt(record + 0, i);
+			IString string = this.db.newString(name);
+			this.db.putRecPtr(record + 4, string.getRecord());
+			btree.insert(record);
+		}
+
+		for (int i = 0; i < names.length; ++i) {
+			String name = names[i];
+			FindVisitor finder = new FindVisitor(this.db, name);
+			btree.accept(finder);
+			long record = finder.getRecord();
+			assertTrue(record != 0);
+			assertEquals(i, this.db.getInt(record));
+			IString rname = this.db.getString(this.db.getRecPtr(record + 4));
+			assertTrue(rname.equals(name));
+		}
+	}
+
+	private final int GT = 1, LT = -1, EQ = 0;
+
+	public void testShortStringComparison() throws CoreException {
+		Random r= new Random(90210);
+
+		assertCMP("",  this.EQ, "", true);
+		assertCMP("",  this.EQ, "", false);
+
+		doTrials(1000, 1, ShortString.MAX_BYTE_LENGTH / 2, r, true);
+		doTrials(1000, 1, ShortString.MAX_BYTE_LENGTH / 2, r, false);
+		doTrials(1000, 1, ShortString.MAX_BYTE_LENGTH, r, true);
+		doTrials(1000, 1, ShortString.MAX_BYTE_LENGTH, r, false);
+
+		assertCMP("a",  this.LT, "b", true);
+		assertCMP("aa", this.LT, "ab", true);
+		assertCMP("a",  this.EQ, "a", true);
+
+		assertCMP("a",  this.GT, "A", true);
+		assertCMP("aa", this.GT, "aA", true);
+		assertCMP("a",  this.GT, "B", true);
+
+		assertCMP("a",  this.EQ, "a", false);
+		assertCMP("a",  this.EQ, "A", false);
+	}
+
+	public void testLongStringComparison() throws CoreException {
+		Random r= new Random(314159265);
+		doTrials(100, ShortString.MAX_BYTE_LENGTH + 1, ShortString.MAX_BYTE_LENGTH * 2, r, true);
+		doTrials(100, ShortString.MAX_BYTE_LENGTH + 1, ShortString.MAX_BYTE_LENGTH * 2, r, false);
+	}
+
+	private void doTrials(int n, int min, int max, Random r, boolean caseSensitive) throws CoreException {
+//		long start = System.currentTimeMillis();
+		for (int i= 0; i < n; i++) {
+			String a = randomString(min, max, r);
+			String b = randomString(min, max, r);
+			int expected = caseSensitive ? a.compareTo(b) : a.compareToIgnoreCase(b);
+			assertCMP(a, expected, b, caseSensitive);
+		}
+//		System.out.print("Trials: " + n + " Max length: " + max + " ignoreCase: " + !caseSensitive);
+//		System.out.println(" Time: " + (System.currentTimeMillis() - start));
+	}
+
+	private String randomString(int min, int max, Random r) {
+		int len = min + r.nextInt(max - min);
+		return randomString(len, r);
+	}
+
+	private String randomString(int len, Random r) {
+		StringBuilder result = new StringBuilder(len);
+		for (int i= 0; i < len; i++) {
+			result.append(randomChar(r));
+		}
+		return result.toString();
+	}
+
+	private char randomChar(Random r) {
+		// We only match String.compareToIgnoreCase behavior within this limited range.
+		return (char) (32 + r.nextInt(40));
+	}
+
+	private void assertCMP(String a, int expected, String b, boolean caseSensitive) throws CoreException {
+		char[] acs = a.toCharArray();
+		char[] bcs = b.toCharArray();
+		IString aiss = this.db.newString(a);
+		IString biss = this.db.newString(b);
+		IString aisc = this.db.newString(acs);
+		IString bisc = this.db.newString(bcs);
+
+		assertEquals(a.hashCode(), aiss.hashCode());
+		assertEquals(a.hashCode(), aisc.hashCode());
+		assertEquals(b.hashCode(), biss.hashCode());
+		assertEquals(b.hashCode(), bisc.hashCode());
+
+		assertEquals(aiss, a);
+		assertEquals(aisc, a);
+		assertEquals(biss, b);
+		assertEquals(bisc, b);
+
+		assertSignEquals(expected, aiss.compare(bcs, caseSensitive));
+		assertSignEquals(expected, aiss.compare(biss, caseSensitive));
+		assertSignEquals(expected, aiss.compare(bisc, caseSensitive));
+		assertSignEquals(expected, aiss.compare(b, caseSensitive));
+		assertSignEquals(expected, aiss.comparePrefix(bcs, caseSensitive));
+
+		assertSignEquals(expected, -biss.compare(acs, caseSensitive));
+		assertSignEquals(expected, -biss.compare(aiss, caseSensitive));
+		assertSignEquals(expected, -biss.compare(aisc, caseSensitive));
+		assertSignEquals(expected, -biss.compare(a, caseSensitive));
+		assertSignEquals(expected, -biss.comparePrefix(acs, caseSensitive));
+
+		if (!caseSensitive && expected != 0) {
+			assertSignEquals(expected, aiss.compareCompatibleWithIgnoreCase(bcs));
+			assertSignEquals(expected, aiss.compareCompatibleWithIgnoreCase(biss));
+			assertSignEquals(expected, aiss.compareCompatibleWithIgnoreCase(bisc));
+
+			assertSignEquals(expected, -biss.compareCompatibleWithIgnoreCase(acs));
+			assertSignEquals(expected, -biss.compareCompatibleWithIgnoreCase(aiss));
+			assertSignEquals(expected, -biss.compareCompatibleWithIgnoreCase(aisc));
+		}
+	}
+
+	private void assertSignEquals(int a, int b) {
+		a= a < 0 ? -1 : (a > 0 ? 1 : 0);
+		b= b < 0 ? -1 : (b > 0 ? 1 : 0);
+		assertEquals(a, b);
+	}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/DatabaseTestUtil.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/DatabaseTestUtil.java
new file mode 100644
index 0000000..30040db
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/DatabaseTestUtil.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.core.tests.nd;
+
+import java.io.File;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Plugin;
+import org.eclipse.jdt.core.tests.Activator;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.NdNodeTypeRegistry;
+import org.eclipse.jdt.internal.core.nd.db.ChunkCache;
+
+/**
+ * 
+ */
+public class DatabaseTestUtil {
+
+	public static IPath getTestDir() {
+		Plugin plugin = Activator.getInstance();
+		
+		IPath path = plugin.getStateLocation().append("tests/");
+		File file = path.toFile();
+		if (!file.exists())
+			file.mkdir();
+		return path;
+	}
+
+	public static File getTempDbName(String testName) {
+		return DatabaseTestUtil.getTestDir().append(testName + System.currentTimeMillis() + ".dat").toFile();
+	}
+
+	/**
+	 * Creates an empty {@link Nd} with an empty type registry and randomly-named
+	 * database for the given test name
+	 * 
+	 * @param testName
+	 * @return the new {@link Nd}
+	 */
+	public static Nd createEmptyNd(String testName) {
+		NdNodeTypeRegistry<NdNode> registry = new NdNodeTypeRegistry<>();
+		return new Nd(DatabaseTestUtil.getTempDbName(testName), new ChunkCache(), registry, 0, 0, 0);
+	}
+
+	public static Nd createEmptyNd(String testName, NdNodeTypeRegistry<NdNode> registry) {
+		return new Nd(DatabaseTestUtil.getTempDbName(testName), new ChunkCache(), registry, 0, 0, 0);
+	}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/FieldBackPointerTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/FieldBackPointerTest.java
new file mode 100644
index 0000000..b2ccfad
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/FieldBackPointerTest.java
@@ -0,0 +1,379 @@
+/*******************************************************************************
+ * 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.core.tests.nd;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+import org.eclipse.jdt.core.tests.nd.util.BaseTestCase;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.NdNodeTypeRegistry;
+import org.eclipse.jdt.internal.core.nd.RawGrowableArray;
+import org.eclipse.jdt.internal.core.nd.field.FieldInt;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+import junit.framework.Test;
+
+public class FieldBackPointerTest extends BaseTestCase {
+	public static class ForwardPointerStruct extends NdNode {
+		public static final FieldManyToOne<BackPointerStruct> FORWARD;
+		public static final FieldManyToOne<BackPointerStruct> OWNER;
+
+		@SuppressWarnings("hiding")
+		public static final StructDef<ForwardPointerStruct> type;
+
+		static {
+			type = StructDef.create(ForwardPointerStruct.class, NdNode.type);
+
+			FORWARD = FieldManyToOne.create(type, BackPointerStruct.BACK);
+			OWNER = FieldManyToOne.createOwner(type, BackPointerStruct.OWNED);
+			type.done();
+		}
+
+		public ForwardPointerStruct(Nd nd) {
+			super(nd);
+		}
+
+		public ForwardPointerStruct(Nd nd, long record) {
+			super(nd, record);
+		}
+
+		public void setBp(BackPointerStruct toSet) {
+			FORWARD.put(getNd(), this.address, toSet);
+		}
+
+		public BackPointerStruct getBp() {
+			return FORWARD.get(getNd(), this.address);
+		}
+
+		public void setOwner(BackPointerStruct owner) {
+			OWNER.put(getNd(), this.address, owner);
+		}
+
+		public BackPointerStruct getOwner() {
+			return OWNER.get(getNd(), this.address);
+		}
+	}
+
+	public static class BackPointerStruct extends NdNode {
+		public static final FieldOneToMany<ForwardPointerStruct> BACK;
+		public static final FieldOneToMany<ForwardPointerStruct> OWNED;
+		public static final FieldInt SOMEINT;
+		
+		@SuppressWarnings("hiding")
+		public static final StructDef<BackPointerStruct> type;
+
+		static {
+			type = StructDef.create(BackPointerStruct.class, NdNode.type);
+
+			BACK = FieldOneToMany.create(type, ForwardPointerStruct.FORWARD, 2);
+			OWNED = FieldOneToMany.create(type, ForwardPointerStruct.OWNER, 0);
+			SOMEINT = type.addInt();
+			type.done();
+		}
+
+		public BackPointerStruct(Nd nd) {
+			super(nd);
+
+			// Fill with nonzero values to ensure that "OWNED" doesn't read beyond its boundary
+			SOMEINT.put(nd, this.address, 0xf0f0f0f0);
+		}
+
+		public BackPointerStruct(Nd nd, long record) {
+			super(nd, record);
+		}
+
+		public void ensureBackPointerCapacity(int capacity) {
+			BACK.ensureCapacity(getNd(), this.address, capacity);
+		}
+
+		public int getBackPointerCapacity() {
+			return BACK.getCapacity(getNd(), this.address);
+		}
+
+		public List<ForwardPointerStruct> getBackPointers() {
+			return BACK.asList(getNd(), this.address);
+		}
+
+		public List<ForwardPointerStruct> getOwned() {
+			return OWNED.asList(getNd(), this.address);
+		}
+
+		public int backPointerSize() {
+			return BACK.size(getNd(), this.address);
+		}
+
+		public boolean backPointersAreEmpty() {
+			return BACK.isEmpty(getNd(), this.address);
+		}
+
+		public boolean ownedPointersAreEmpty() {
+			return OWNED.isEmpty(getNd(), this.address);
+		}
+
+		public ForwardPointerStruct getBackPointer(int i) {
+			return BACK.get(getNd(), this.address, i);
+		}
+	}
+
+	ForwardPointerStruct fa;
+	ForwardPointerStruct fb;
+	ForwardPointerStruct fc;
+	ForwardPointerStruct fd;
+	BackPointerStruct ba;
+	BackPointerStruct bb;
+	private Nd nd;
+
+	@Override
+	protected void setUp() throws Exception {
+		super.setUp();
+
+		NdNodeTypeRegistry<NdNode> registry = new NdNodeTypeRegistry<>();
+		registry.register(0, BackPointerStruct.type.getFactory());
+		registry.register(1, ForwardPointerStruct.type.getFactory());
+		this.nd = DatabaseTestUtil.createEmptyNd(getName(), registry);
+		this.nd.getDB().setExclusiveLock();
+		this.ba = new BackPointerStruct(this.nd);
+		this.bb = new BackPointerStruct(this.nd);
+		this.fa = new ForwardPointerStruct(this.nd);
+		this.fb = new ForwardPointerStruct(this.nd);
+		this.fc = new ForwardPointerStruct(this.nd);
+		this.fd = new ForwardPointerStruct(this.nd);
+	}
+
+	public static Test suite() {
+		return BaseTestCase.suite(FieldBackPointerTest.class);
+	}
+
+	void assertBackPointers(BackPointerStruct bp, ForwardPointerStruct... fp) {
+		HashSet<ForwardPointerStruct> backPointers = new HashSet<>(bp.getBackPointers());
+		HashSet<ForwardPointerStruct> desired = new HashSet<>();
+
+		desired.addAll(Arrays.asList(fp));
+		assertEquals(desired, backPointers);
+	}
+
+	public void testWriteFollowedByReadReturnsSameThing() throws Exception {
+		this.fa.setBp(this.ba);
+		BackPointerStruct backpointer = this.fa.getBp();
+
+		assertEquals(this.ba, backpointer);
+	}
+
+	public void testListWithoutInlineElementsCanBeEmpty() throws Exception {
+		assertTrue(this.ba.ownedPointersAreEmpty());
+	}
+
+	public void testReadNull() throws Exception {
+		assertEquals(null, this.fa.getBp());
+	}
+	
+	public void testAssigningTheSamePointerTwiceIsANoop() throws Exception {
+		this.fa.setBp(this.ba);
+
+		assertBackPointers(this.ba, this.fa);
+
+		// Now do the same thing again
+		this.fa.setBp(this.ba);
+
+		assertBackPointers(this.ba, this.fa);
+	}
+	
+	public void testAssigningForwardPointerInsertsBackPointer() throws Exception {
+		this.fa.setBp(this.ba);
+
+		assertEquals(Arrays.asList(this.fa), this.ba.getBackPointers());
+		assertEquals(1, this.ba.backPointerSize());
+	}
+
+	public void testRemovesInlineElement() throws Exception {
+		this.fa.setBp(this.ba);
+		this.fb.setBp(this.ba);
+		this.fc.setBp(this.ba);
+		this.fd.setBp(this.ba);
+
+		assertEquals(4, this.ba.backPointerSize());
+		this.fb.setBp(null);
+		assertEquals(3, this.ba.backPointerSize());
+
+		assertBackPointers(this.ba, this.fa, this.fc, this.fd);
+	}
+
+	public void testRemovesElementFromGrowableBlock() throws Exception {
+		this.fa.setBp(this.ba);
+		this.fb.setBp(this.ba);
+		this.fc.setBp(this.ba);
+		this.fd.setBp(this.ba);
+
+		assertEquals(4, this.ba.backPointerSize());
+		this.fc.setBp(null);
+		assertEquals(3, this.ba.backPointerSize());
+
+		assertBackPointers(this.ba, this.fa, this.fb, this.fd);
+	}
+
+	public void testDestructingForwardPointerRemovesBackPointer() throws Exception {
+		this.fa.setBp(this.ba);
+		this.fb.setBp(this.ba);
+		this.fc.setBp(this.ba);
+
+		this.fb.delete();
+		this.nd.processDeletions();
+
+		assertBackPointers(this.ba, this.fa, this.fc);
+	}
+
+	public void testDestructingBackPointerClearsForwardPointers() throws Exception {
+		this.fa.setBp(this.ba);
+		this.fb.setBp(this.ba);
+		this.fc.setBp(this.ba);
+
+		this.ba.delete();
+		this.nd.processDeletions();
+
+		assertEquals(null, this.fa.getBp());
+		assertEquals(null, this.fb.getBp());
+		assertEquals(null, this.fc.getBp());
+	}
+
+	public void testElementsRemainInInsertionOrderIfNoRemovals() throws Exception {
+		this.fa.setBp(this.ba);
+		this.fb.setBp(this.ba);
+		this.fc.setBp(this.ba);
+		this.fd.setBp(this.ba);
+
+		assertEquals(Arrays.asList(this.fa, this.fb, this.fc, this.fd), this.ba.getBackPointers());
+	}
+
+	public void testDeletingOwnerDeletesOwned() throws Exception {
+		this.fa.setBp(this.ba);
+		this.fa.setOwner(this.bb);
+
+		this.fb.setBp(this.ba);
+		this.fb.setOwner(this.bb);
+
+		this.fc.setBp(this.ba);
+
+		this.bb.delete();
+		this.nd.processDeletions();
+
+		assertBackPointers(this.ba, this.fc);
+	}
+
+	public void testEnsureCapacityDoesNothingIfLessThanInlineElements() throws Exception {
+		this.ba.ensureBackPointerCapacity(1);
+		assertEquals(2, this.ba.getBackPointerCapacity());
+	}
+
+	public void testEnsureCapacityAllocatesPowersOfTwoPlusInlineSize() throws Exception {
+		this.ba.ensureBackPointerCapacity(60);
+		assertEquals(66, this.ba.getBackPointerCapacity());
+	}
+
+	public void testEnsureCapacityAllocatesMinimumSize() throws Exception {
+		this.ba.ensureBackPointerCapacity(3);
+		assertEquals(4, this.ba.getBackPointerCapacity());
+	}
+
+	public void testEnsureCapacityClampsToChunkSize() throws Exception {
+		this.ba.ensureBackPointerCapacity(RawGrowableArray.getMaxGrowableBlockSize() - 40);
+		assertEquals(RawGrowableArray.getMaxGrowableBlockSize() + 2, this.ba.getBackPointerCapacity());
+	}
+
+	public void testEnsureCapacityGrowsByMultiplesOfMaxBlockSizeOnceMetablockInUse() throws Exception {
+		int maxBlockSize = RawGrowableArray.getMaxGrowableBlockSize();
+		this.ba.ensureBackPointerCapacity(maxBlockSize * 3 - 100);
+		assertEquals(maxBlockSize * 3 + 2, this.ba.getBackPointerCapacity());
+	}
+
+	public void testAdditionsWontReduceCapacity() throws Exception {
+		int maxBlockSize = RawGrowableArray.getMaxGrowableBlockSize();
+		this.ba.ensureBackPointerCapacity(maxBlockSize);
+
+		this.fa.setBp(this.ba);
+		this.fb.setBp(this.ba);
+		this.fc.setBp(this.ba);
+		this.fd.setBp(this.ba);
+
+		assertEquals(maxBlockSize + 2, this.ba.getBackPointerCapacity());
+	}
+
+	public void testIsEmpty() throws Exception {
+		assertTrue(this.ba.backPointersAreEmpty());
+		this.fa.setBp(this.ba);
+		assertFalse(this.ba.backPointersAreEmpty());
+		this.fb.setBp(this.ba);
+		this.fc.setBp(this.ba);
+		this.fd.setBp(this.ba);
+		assertFalse(this.ba.backPointersAreEmpty());
+	}
+
+	public void testRemovalsReduceCapacity() throws Exception {
+		int maxBlockSize = RawGrowableArray.getMaxGrowableBlockSize();
+		this.ba.ensureBackPointerCapacity(maxBlockSize);
+
+		this.fa.setBp(this.ba);
+		this.fb.setBp(this.ba);
+		this.fc.setBp(this.ba);
+		assertEquals(maxBlockSize + 2, this.ba.getBackPointerCapacity());
+		
+		this.fb.setBp(null);
+		this.fc.setBp(null);
+
+		assertEquals(2, this.ba.getBackPointerCapacity());
+	}
+
+	public void testInsertEnoughToUseMetablock() throws Exception {
+		// We need enough instances to fill several full blocks since we don't reclaim
+		// memory until there are two unused blocks.
+		int numToAllocate = RawGrowableArray.getMaxGrowableBlockSize() * 4 + 1;
+		
+		List<ForwardPointerStruct> allocated = new ArrayList<>();
+
+		for (int count = 0; count < numToAllocate; count++) {
+			ForwardPointerStruct next = new ForwardPointerStruct(this.nd);
+
+			next.setBp(this.ba);
+			assertEquals(next, this.ba.getBackPointer(count));
+			allocated.add(next);
+			assertEquals(count + 1, this.ba.backPointerSize());
+		}
+
+		assertEquals(allocated.get(numToAllocate - 1), this.ba.getBackPointer(numToAllocate - 1));
+		assertEquals(numToAllocate, this.ba.backPointerSize());
+		
+		int correctSize = numToAllocate;
+		for (ForwardPointerStruct next : allocated) {
+			next.setBp(null);
+			assertEquals(--correctSize, this.ba.backPointerSize());
+		}
+
+		assertEquals(0, this.ba.backPointerSize());
+		assertEquals(2, this.ba.getBackPointerCapacity());
+	}
+
+	public void testGrowExistingMetablock() throws Exception {
+		int blockSize = RawGrowableArray.getMaxGrowableBlockSize();
+
+		this.ba.ensureBackPointerCapacity(2 * blockSize);
+
+		assertEquals(2 * blockSize + 2, this.ba.getBackPointerCapacity());
+
+		this.ba.ensureBackPointerCapacity(6 * blockSize);
+
+		assertEquals(6 * blockSize + 2, this.ba.getBackPointerCapacity());
+	}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/InheritenceTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/InheritenceTests.java
new file mode 100644
index 0000000..bb0db53
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/InheritenceTests.java
@@ -0,0 +1,244 @@
+/*******************************************************************************
+ * 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.core.tests.nd;
+
+import org.eclipse.jdt.core.tests.nd.util.BaseTestCase;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.NdNodeTypeRegistry;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+import junit.framework.Test;
+
+public class InheritenceTests extends BaseTestCase {
+	/**
+	 * Every other object in this test has a pointer to the deletion detector, so we can detect
+	 * which objects have been deleted by looking for the object in the backpointer list.
+	 */
+	public static class AllObjects extends NdNode {
+		public static final FieldOneToMany<BaseClass> BASE_CLASS_INSTANCES;
+		public static final FieldOneToMany<Reference> REFERENCE_INSTANCES;
+
+		@SuppressWarnings("hiding")
+		public static final StructDef<AllObjects> type;
+
+		static {
+			type = StructDef.create(AllObjects.class, NdNode.type);
+
+			BASE_CLASS_INSTANCES = FieldOneToMany.create(type, BaseClass.DELETION_DETECTOR, 0);
+			REFERENCE_INSTANCES = FieldOneToMany.create(type, Reference.DELETION_DETECTOR, 0);
+			type.done();
+		}
+
+		public AllObjects(Nd nd, long record) {
+			super(nd, record);
+		}
+
+		public AllObjects(Nd nd) {
+			super(nd);
+		}
+
+		boolean contains(BaseClass toTest) {
+			return BASE_CLASS_INSTANCES.asList(getNd(), this.address).contains(toTest);
+		}
+
+		boolean contains(Reference toTest) {
+			return REFERENCE_INSTANCES.asList(getNd(), this.address).contains(toTest);
+		}
+	}
+
+	public static class BaseClass extends NdNode {
+		public static final FieldOneToMany<Reference> INCOMING_REFERENCES;
+		public static final FieldOneToMany<Reference> OWNED_REFERENCES;
+		public static final FieldManyToOne<AllObjects> DELETION_DETECTOR;
+
+		@SuppressWarnings("hiding")
+		public static final StructDef<BaseClass> type;
+
+		static {
+			type = StructDef.create(BaseClass.class, NdNode.type);
+
+			INCOMING_REFERENCES = FieldOneToMany.create(type, Reference.BASE_CLASS_REFERENCE, 0);
+			OWNED_REFERENCES = FieldOneToMany.create(type, Reference.OWNER, 0);
+			DELETION_DETECTOR = FieldManyToOne.create(type, AllObjects.BASE_CLASS_INSTANCES);
+			type.useStandardRefCounting().done();
+		}
+
+		public BaseClass(Nd nd, AllObjects deletionDetector) {
+			super(nd);
+
+			DELETION_DETECTOR.put(nd, this.address, deletionDetector);
+		}
+
+		public BaseClass(Nd nd, long record) {
+			super(nd, record);
+		}
+	}
+
+	public static class SubClass extends BaseClass {
+		public static final FieldOneToMany<Reference> MORE_REFERENCES;
+
+		@SuppressWarnings("hiding")
+		public static final StructDef<SubClass> type;
+
+		static {
+			type = StructDef.create(SubClass.class, BaseClass.type);
+
+			MORE_REFERENCES = FieldOneToMany.create(type, Reference.SUB_CLASS_REFERENCE, 0);
+			type.useStandardRefCounting().done();
+		}
+
+		public SubClass(Nd nd, long record) {
+			super(nd, record);
+		}
+
+		public SubClass(Nd nd, AllObjects deletionDetector) {
+			super(nd, deletionDetector);
+		}
+	}
+	
+	public static class Reference extends NdNode {
+		public static final FieldManyToOne<BaseClass> BASE_CLASS_REFERENCE;
+		public static final FieldManyToOne<BaseClass> OWNER;
+		public static final FieldManyToOne<SubClass> SUB_CLASS_REFERENCE;
+		public static final FieldManyToOne<AllObjects> DELETION_DETECTOR;
+
+		@SuppressWarnings("hiding")
+		public static StructDef<Reference> type;
+
+		static {
+			type = StructDef.create(Reference.class, NdNode.type);
+
+			BASE_CLASS_REFERENCE = FieldManyToOne.create(type, BaseClass.INCOMING_REFERENCES);
+			OWNER = FieldManyToOne.createOwner(type, BaseClass.OWNED_REFERENCES);
+			SUB_CLASS_REFERENCE = FieldManyToOne.create(type, SubClass.MORE_REFERENCES);
+			DELETION_DETECTOR = FieldManyToOne.create(type, AllObjects.REFERENCE_INSTANCES);
+			type.done();
+		}
+
+		public Reference(Nd nd, long record) {
+			super(nd, record);
+		}
+
+		public Reference(Nd nd, AllObjects deletionDetector) {
+			super(nd);
+
+			DELETION_DETECTOR.put(nd, this.address, deletionDetector);
+		}
+
+		public void setBaseClassReference(BaseClass target) {
+			BASE_CLASS_REFERENCE.put(getNd(), this.address, target);
+		}
+
+		public void setOwner(BaseClass target) {
+			OWNER.put(getNd(), this.address, target);
+		}
+
+		public void setSubClassReference(SubClass target) {
+			SUB_CLASS_REFERENCE.put(getNd(), this.address, target);
+		}
+	}
+
+	AllObjects allObjects;
+	BaseClass baseClass;
+	SubClass subClass;
+	Reference refA;
+	Reference refB;
+	Reference refC;
+	private Nd nd;
+
+	@Override
+	protected void setUp() throws Exception {
+		super.setUp();
+
+		NdNodeTypeRegistry<NdNode> registry = new NdNodeTypeRegistry<>();
+		registry.register(0, BaseClass.type.getFactory());
+		registry.register(1, SubClass.type.getFactory());
+		registry.register(2, Reference.type.getFactory());
+		registry.register(3, AllObjects.type.getFactory());
+		this.nd = DatabaseTestUtil.createEmptyNd(getName(), registry);
+		this.nd.getDB().setExclusiveLock();
+
+		this.allObjects = new AllObjects(this.nd);
+		this.baseClass = new BaseClass(this.nd, this.allObjects);
+		this.subClass = new SubClass(this.nd, this.allObjects);
+
+		this.refA = new Reference(this.nd, this.allObjects);
+		this.refB = new Reference(this.nd, this.allObjects);
+		this.refC = new Reference(this.nd, this.allObjects);
+	}
+
+	public static Test suite() {
+		return BaseTestCase.suite(InheritenceTests.class);
+	}
+
+	public void testRemovingOnlyRefcountDeletesObject() {
+		assertTrue(this.allObjects.contains(this.subClass));
+		this.refA.setSubClassReference(this.subClass);
+		this.refA.setSubClassReference(null);
+		this.nd.processDeletions();
+		assertFalse(this.allObjects.contains(this.subClass));
+	}
+
+	public void testReferencesToBaseClassIncludedInRefCountA() {
+		// Test what happens when the subclass reference is removed first.
+		this.refA.setSubClassReference(this.subClass);
+		this.refB.setBaseClassReference(this.subClass);
+		assertTrue(this.allObjects.contains(this.subClass));
+		this.refA.setSubClassReference(null);
+		this.nd.processDeletions();
+		assertTrue(this.allObjects.contains(this.subClass));
+		this.refB.setBaseClassReference(null);
+		this.nd.processDeletions();
+		assertFalse(this.allObjects.contains(this.subClass));
+	}
+
+	public void testReferencesToBaseClassIncludedInRefCountB() {
+		// Test what happens when the base class reference is removed first.
+		this.refA.setSubClassReference(this.subClass);
+		this.refB.setBaseClassReference(this.subClass);
+		this.nd.processDeletions();
+		assertTrue(this.allObjects.contains(this.subClass));
+		this.refB.setBaseClassReference(null);
+		this.nd.processDeletions();
+		assertTrue(this.allObjects.contains(this.subClass));
+		this.refA.setSubClassReference(null);
+		this.nd.processDeletions();
+		assertFalse(this.allObjects.contains(this.subClass));
+	}
+
+	public void testOwnedPointersDontCountTowardsRefCount() {
+		this.refA.setOwner(this.subClass);
+		this.nd.processDeletions();
+		assertTrue(this.allObjects.contains(this.subClass));
+		this.refB.setBaseClassReference(this.subClass);
+		this.nd.processDeletions();
+		assertTrue(this.allObjects.contains(this.subClass));
+		assertTrue(this.allObjects.contains(this.refA));
+		this.refB.setBaseClassReference(null);
+		this.nd.processDeletions();
+		assertFalse(this.allObjects.contains(this.subClass));
+		assertFalse(this.allObjects.contains(this.refA));
+	}
+
+	public void testMultipleReferences() {
+		this.refA.setBaseClassReference(this.subClass);
+		this.refB.setBaseClassReference(this.subClass);
+		this.refA.setBaseClassReference(null);
+		this.nd.processDeletions();
+		assertTrue(this.allObjects.contains(this.subClass));
+		this.refB.setBaseClassReference(null);
+		this.nd.processDeletions();
+		assertFalse(this.allObjects.contains(this.subClass));
+	}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/Package.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/Package.java
new file mode 100644
index 0000000..7485b54
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/Package.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * 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.core.tests.nd;
+
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Plugin;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.JavaCore;
+
+/**
+ * @noreference This class is not intended to be referenced by clients
+ */
+/* package */ class Package {
+	public static String PLUGIN_ID = JavaCore.PLUGIN_ID;
+
+	/**
+	 * @noreference This method is not intended to be referenced by clients.
+	 */
+	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$
+		}
+	}
+	
+	/**
+	 * @noreference This method is not intended to be referenced by clients.
+	 */
+	public static void log(String message, Throwable e) {
+		log(createStatus(message, e));
+	}
+	
+	/**
+	 * @noreference This method is not intended to be referenced by clients.
+	 */
+	public static IStatus createStatus(String msg, Throwable e) {
+		return new Status(IStatus.ERROR, PLUGIN_ID, msg, e);
+	}
+
+	/**
+	 * @noreference This method is not intended to be referenced by clients.
+	 */
+	public static IStatus createStatus(String msg) {
+		return new Status(IStatus.ERROR, PLUGIN_ID, msg);
+	}
+
+	/**
+	 * @noreference This method is not intended to be referenced by clients.
+	 * 
+	 * Returns the appropriate ILog for this package
+	 */
+	public static ILog getLog() {
+		Plugin plugin = JavaCore.getPlugin();
+		if (plugin == null) {
+			return null;
+		}
+		return plugin.getLog();
+	}
+
+	/**
+	 * @noreference This method is not intended to be referenced by clients.
+	 */
+	public static void log(IStatus status) {
+		getLog().log(status);
+	}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/RunIndexTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/RunIndexTests.java
new file mode 100644
index 0000000..a6c2bbc
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/RunIndexTests.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * 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.core.tests.nd;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.eclipse.jdt.core.tests.junit.extension.TestCase;
+import org.eclipse.jdt.core.tests.nd.indexer.IndexerTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class RunIndexTests extends junit.framework.TestCase {
+public RunIndexTests(String name) {
+	super(name);
+}
+public static Class[] getAllTestClasses() {
+	return new Class[] {
+		BTreeTests.class,
+		DatabaseTest.class,
+		FieldBackPointerTest.class,
+		IndexerTest.class,
+		InheritenceTests.class,
+		SearchKeyTests.class
+	};
+}
+public static Test suite() {
+	TestSuite ts = new TestSuite(RunIndexTests.class.getName());
+
+	Class[] testClasses = getAllTestClasses();
+	// Reset forgotten subsets of tests
+	TestCase.TESTS_PREFIX = null;
+	TestCase.TESTS_NAMES = null;
+	TestCase.TESTS_NUMBERS = null;
+	TestCase.TESTS_RANGE = null;
+	TestCase.RUN_ONLY_ID = null;
+
+	for (int i = 0; i < testClasses.length; i++) {
+		Class testClass = testClasses[i];
+
+		// call the suite() method and add the resulting suite to the suite
+		try {
+			Method suiteMethod = testClass.getDeclaredMethod("suite", new Class[0]); //$NON-NLS-1$
+			Test suite = (Test)suiteMethod.invoke(null, new Object[0]);
+			ts.addTest(suite);
+		} catch (IllegalAccessException e) {
+			e.printStackTrace();
+		} catch (InvocationTargetException e) {
+			e.getTargetException().printStackTrace();
+		} catch (NoSuchMethodException e) {
+			e.printStackTrace();
+		}
+	}
+	return ts;
+}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/SearchKeyTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/SearchKeyTests.java
new file mode 100644
index 0000000..e48ae79
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/SearchKeyTests.java
@@ -0,0 +1,164 @@
+/*******************************************************************************
+ * 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.core.tests.nd;
+
+import org.eclipse.jdt.core.tests.nd.util.BaseTestCase;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.NdNodeTypeRegistry;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchIndex;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchKey;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+import junit.framework.Test;
+
+public class SearchKeyTests extends BaseTestCase {
+	private static final String SEARCH_STRING_B = "Yo";
+	private static final String SEARCH_STRING_A = "Heyguyswhatshappening";
+	private static final String SEARCH_STRING_C = "Shnoogins";
+
+	public static class TestSearchIndex {
+		public static final FieldSearchIndex<Element> NICKNAME_INDEX;
+		public static final FieldSearchIndex<Element> NAME_INDEX;
+
+		public static final StructDef<TestSearchIndex> type;
+
+		static {
+			type = StructDef.create(TestSearchIndex.class);
+			NICKNAME_INDEX = FieldSearchIndex.create(type, Element.NICKNAME);
+			NAME_INDEX = FieldSearchIndex.create(type, Element.NAME);
+			type.done();
+		}
+
+		private final long address;
+		private Nd nd;
+		
+		public TestSearchIndex(Nd dom, long address) {
+			this.address = address;
+			this.nd = dom;
+		}
+
+		public static TestSearchIndex getIndex(Nd nd) {
+			return new TestSearchIndex(nd, Database.DATA_AREA_OFFSET);
+		}
+
+		public Element findName(String searchString) {
+			return NAME_INDEX.findFirst(this.nd, this.address,
+					FieldSearchIndex.SearchCriteria.create(searchString.toCharArray()));
+		}
+
+		public Element findNickName(String searchString) {
+			return NICKNAME_INDEX.findFirst(this.nd, this.address,
+					FieldSearchIndex.SearchCriteria.create(searchString.toCharArray()));
+		}
+	}
+	
+	public static class Element extends NdNode {
+		public static final FieldSearchKey<TestSearchIndex> NAME;
+		public static final FieldSearchKey<TestSearchIndex> NICKNAME;
+
+		@SuppressWarnings("hiding")
+		public static StructDef<Element> type;
+
+		static {
+			type = StructDef.create(Element.class, NdNode.type);
+
+			NAME = FieldSearchKey.create(type, TestSearchIndex.NAME_INDEX);
+			NICKNAME = FieldSearchKey.create(type, TestSearchIndex.NICKNAME_INDEX);
+			type.done();
+		}
+
+		public Element(Nd nd, long record) {
+			super(nd, record);
+		}
+
+		public Element(Nd nd) {
+			super(nd);
+		}
+
+		public void setName(String searchStringA) {
+			NAME.put(getNd(), this.address, searchStringA);
+		}
+
+		public void setNickName(String searchStringA) {
+			NICKNAME.put(getNd(), this.address, searchStringA);
+		}
+	}
+
+	private Nd nd;
+	private Element elementA;
+	private Element elementB;
+	private TestSearchIndex index;
+
+	@Override
+	protected void setUp() throws Exception {
+		super.setUp();
+
+		NdNodeTypeRegistry<NdNode> registry = new NdNodeTypeRegistry<>();
+		registry.register(0, Element.type.getFactory());
+		this.nd = DatabaseTestUtil.createEmptyNd(getName(), registry);
+		this.nd.getDB().setExclusiveLock();
+
+		this.elementA = new Element(this.nd);
+		this.elementB = new Element(this.nd);
+		
+		this.index = TestSearchIndex.getIndex(this.nd);
+	}
+
+	public static Test suite() {
+		return BaseTestCase.suite(SearchKeyTests.class);
+	}
+
+	public void testSettingKeyCausesInsertionInSearchIndex() {
+		this.elementA.setName(SEARCH_STRING_A);
+		this.elementB.setName(SEARCH_STRING_B);
+
+		Element foundElementA = this.index.findName(SEARCH_STRING_A);
+		Element foundElementB = this.index.findName(SEARCH_STRING_B);
+		Element foundElementC = this.index.findName(SEARCH_STRING_C);
+
+		assertEquals(this.elementA, foundElementA);
+		assertEquals(this.elementB, foundElementB);
+		assertEquals(null, foundElementC);
+	}
+
+	public void testChangingSearchKeyAffectsIndex() {
+		this.elementA.setName(SEARCH_STRING_A);
+
+		Element foundElementA = this.index.findName(SEARCH_STRING_A);
+		Element foundElementB = this.index.findName(SEARCH_STRING_B);
+
+		assertEquals(null, foundElementB);
+		assertEquals(this.elementA, foundElementA);
+
+		this.elementA.setName(SEARCH_STRING_B);
+
+		foundElementA = this.index.findName(SEARCH_STRING_A);
+		foundElementB = this.index.findName(SEARCH_STRING_B);
+
+		assertEquals(this.elementA, foundElementB);
+		assertEquals(null, foundElementA);
+	}
+
+	public void testDeletingElementRemovesFromIndex() {
+		this.elementA.setName(SEARCH_STRING_A);
+		this.elementA.setNickName(SEARCH_STRING_B);
+
+		assertEquals(this.elementA, this.index.findName(SEARCH_STRING_A));
+		assertEquals(this.elementA, this.index.findNickName(SEARCH_STRING_B));
+
+		this.elementA.delete();
+		this.nd.processDeletions();
+		assertEquals(null, this.index.findName(SEARCH_STRING_A));
+		assertEquals(null, this.index.findNickName(SEARCH_STRING_B));
+	}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/indexer/IndexerTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/indexer/IndexerTest.java
new file mode 100644
index 0000000..f378a33
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/indexer/IndexerTest.java
@@ -0,0 +1,214 @@
+/*******************************************************************************
+ * 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.core.tests.nd.indexer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Semaphore;
+
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.jdt.core.IClassFile;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.IParent;
+import org.eclipse.jdt.core.tests.model.AbstractJavaModelTests;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.core.nd.IReader;
+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.indexer.IndexTester;
+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.NdType;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeId;
+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.java.model.IndexBinaryType;
+
+import junit.framework.Test;
+
+/**
+ * Tests for the {@link Database} class.
+ */
+public class IndexerTest extends AbstractJavaModelTests {
+
+	public IndexerTest(String name) {
+		super(name);
+	}
+
+	private static final String PROJECT_NAME = "IndexerTest";
+	private static JavaIndex index;
+
+	@Override
+	protected void setUp() throws Exception {
+		String testName = getName();
+		index = JavaIndexTestUtil.createTempIndex(testName);
+		super.setUp();
+	}
+
+	@Override
+	protected void tearDown() throws Exception {
+		deleteProject(PROJECT_NAME);
+		index.getNd().getPath().delete();
+		index = null;
+		super.tearDown();
+	}
+
+	public static Test suite() {
+		return buildModelTestSuite(IndexerTest.class);
+	}
+
+	/**
+	 * Verifies that if the index fails a read due to call to {@link Thread#interrupt()}, subsequent reads will
+	 * still succeed.
+	 */
+	public void testInterruptedException() throws Exception {
+		createJavaProject(PROJECT_NAME, new String[] {"src"}, new String[] {"JCL18_FULL"}, "bin", "1.8", true);
+		// Create an index
+		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+		Indexer indexer = new Indexer(index.getNd(), root);
+		indexer.rescan(SubMonitor.convert(null));
+		// Ensure we're starting with an empty page cache by creating a new
+		// Index accessor object on the same database
+		JavaIndex testIndex = JavaIndex
+				.getIndex(JavaIndex.createNd(index.getNd().getDB().getLocation(), new ChunkCache()));
+
+		Semaphore semaphore = new Semaphore(0);
+
+		boolean[] wasInterrupted = new boolean[1];
+		Thread newThread = new Thread(() -> {
+			try (IReader reader = testIndex.getNd().acquireReadLock()) {
+				Thread.currentThread().interrupt();
+				testIndex.findType("Ljava/util/List;".toCharArray());
+			} catch (OperationCanceledException e) {
+				wasInterrupted[0] = true;
+			} finally {
+				semaphore.release();
+			}
+		});
+
+		newThread.start();
+
+		semaphore.acquire();
+
+		assertTrue(wasInterrupted[0]);
+		try (IReader reader = testIndex.getNd().acquireReadLock()) {
+			NdTypeId type = testIndex.findType("Ljava/util/List;".toCharArray());
+			assertNotNull(type);
+		}
+	}
+
+	public void testSubclassesOfGenericTypeCanBeFound() throws Exception {
+		createJavaProject(PROJECT_NAME, new String[] {"src"}, new String[] {"JCL18_FULL"}, "bin", "1.8", true);
+		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+		Indexer indexer = new Indexer(index.getNd(), root);
+
+		indexer.rescan(SubMonitor.convert(null));
+
+		try (IReader reader = IndexerTest.index.getNd().acquireReadLock()) {
+			NdTypeId javaUtilList = IndexerTest.index.findType("Ljava/util/List;".toCharArray());
+			NdTypeId javaUtilArrayList = IndexerTest.index.findType("Ljava/util/ArrayList;".toCharArray());
+
+			boolean found = false;
+			List<NdType> subtypes = javaUtilList.getSubTypes();
+			for (NdType next : subtypes) {
+				if (Objects.equals(next.getTypeId(), javaUtilArrayList)) {
+					found = true;
+				}
+			}
+
+			assertTrue("ArrayList was found as a subtype of List", found);
+		}
+	}
+
+	private void collectAllClassFiles(List<? super IClassFile> result, IParent nextRoot) throws CoreException {
+		for (IJavaElement child : nextRoot.getChildren()) {
+			int type = child.getElementType();
+
+			if (type == IJavaElement.CLASS_FILE) {
+				result.add((IClassFile)child);
+			} else if (child instanceof IParent) {
+				IParent parent = (IParent) child;
+
+				collectAllClassFiles(result, parent);
+			}
+		}
+	}
+
+	public void testReadingAllClassesInIndexAreEquivalentToOriginalJarFiles() throws Exception {
+		IJavaProject javaProject = createJavaProject(PROJECT_NAME, new String[] {"src"}, new String[] {"JCL18_FULL"}, "bin", "1.8", true);
+		addClassFolder(javaProject, "lib", new String[] {
+				"p/Outer.java",
+				"import java.lang.annotation.*;\n" +
+				"\n" +
+				"@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE_USE) @interface A {}\n" +
+				"@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface M {}\n" +
+				"@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @interface P {}\n" +
+				"\n" +
+				"class Outer {\n" +
+				"    class Middle1 {\n" +
+				"        class Inner {}\n" +
+				"    }\n" +
+				"    static class Middle2 {\n" +
+				"        class Inner {}\n" +
+				"        static class Middle3 {\n" +
+				"            class Inner2{};\n" +
+				"        }\n" +
+				"    }\n" +
+				"    Middle1.@A Inner e1;\n" +
+				"    Middle2.@A Inner e2;\n" +
+				"    Middle2.Middle3.@A Inner2 e3;\n" +
+				"    @M void foo(@P Middle2.Middle3.@A Inner2 e3) {};\n" +
+				"    class Middle4 extends @A Middle1 {}\n" +
+				"}\n",
+			}, "1.8");
+
+		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+		Indexer indexer = new Indexer(index.getNd(), root);
+
+		indexer.rescan(SubMonitor.convert(null));
+
+		boolean foundAtLeastOneClass = false;
+		SubMonitor subMonitor = SubMonitor.convert(null);
+		JavaIndex localIndex = IndexerTest.index;
+		try (IReader reader = localIndex.getNd().acquireReadLock()) {
+			IPackageFragmentRoot[] roots = javaProject.getAllPackageFragmentRoots();
+			subMonitor.setWorkRemaining(roots.length);
+			for (IPackageFragmentRoot next : roots) {
+				SubMonitor iterationMon = subMonitor.split(1);
+				if (next.getKind() == IPackageFragmentRoot.K_BINARY) {
+					List<IClassFile> result = new ArrayList<>();
+					collectAllClassFiles(result, next);
+					iterationMon.setWorkRemaining(result.size());
+					for (IClassFile nextClass : result) {
+						SubMonitor classMon = iterationMon.split(1);
+						BinaryTypeDescriptor descriptor = BinaryTypeFactory.createDescriptor(nextClass);
+						IndexBinaryType indexedBinaryType = (IndexBinaryType)BinaryTypeFactory.readFromIndex(localIndex, descriptor, classMon);
+						ClassFileReader originalBinaryType = BinaryTypeFactory.rawReadType(descriptor, true);
+
+						if (!indexedBinaryType.exists()) {
+							throw new IllegalStateException("Unable to find class in index " + new String(descriptor.indexPath));
+						}
+						IndexTester.testType(originalBinaryType, indexedBinaryType);
+						foundAtLeastOneClass = true;
+					}
+				}
+			}
+		}
+		assertTrue("No classes found in the index", foundAtLeastOneClass);
+	}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/indexer/JavaIndexTestUtil.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/indexer/JavaIndexTestUtil.java
new file mode 100644
index 0000000..84a1cd7
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/indexer/JavaIndexTestUtil.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * 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.core.tests.nd.indexer;
+
+import java.io.File;
+
+import org.eclipse.jdt.core.tests.nd.DatabaseTestUtil;
+import org.eclipse.jdt.internal.core.nd.db.ChunkCache;
+import org.eclipse.jdt.internal.core.nd.java.JavaIndex;
+
+public class JavaIndexTestUtil {
+	public static JavaIndex createTempIndex(String id) {
+		File dbName = DatabaseTestUtil.getTempDbName(id);
+		return JavaIndex.getIndex(JavaIndex.createNd(dbName, new ChunkCache()));
+	}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/util/BaseTestCase.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/util/BaseTestCase.java
new file mode 100644
index 0000000..cb99bf2
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/util/BaseTestCase.java
@@ -0,0 +1,269 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 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
+ *     Andrew Ferguson (Symbian)
+ *     Sergey Prigogin (Google)
+ *******************************************************************************/
+package org.eclipse.jdt.core.tests.nd.util;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestFailure;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.ILogListener;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class BaseTestCase extends TestCase {
+	private static final String DEFAULT_INDEXER_TIMEOUT_SEC = "10";
+	private static final String INDEXER_TIMEOUT_PROPERTY = "indexer.timeout";
+	/**
+	 * Indexer timeout used by tests. To avoid this timeout expiring during debugging add
+	 * -Dindexer.timeout=some_large_number to VM arguments of the test launch configuration. 
+	 */
+	protected static final int INDEXER_TIMEOUT_SEC =
+			Integer.parseInt(System.getProperty(INDEXER_TIMEOUT_PROPERTY, DEFAULT_INDEXER_TIMEOUT_SEC));
+	protected static final int INDEXER_TIMEOUT_MILLISEC= INDEXER_TIMEOUT_SEC * 1000;
+	
+	private boolean fExpectFailure;
+	private int fBugNumber;
+	private int fExpectedLoggedNonOK;
+	private Deque<File> filesToDeleteOnTearDown= new ArrayDeque<>();
+
+	public BaseTestCase() {
+		super();
+	}
+
+	public BaseTestCase(String name) {
+		super(name);
+	}
+
+	public static NullProgressMonitor npm() {
+		return new NullProgressMonitor();
+	}
+
+	@Override
+	protected void setUp() throws Exception {
+		super.setUp();
+		//CModelListener.sSuppressUpdateOfLastRecentlyUsed= true;
+	}
+
+	@Override
+	protected void tearDown() throws Exception {
+		for (File file; (file = this.filesToDeleteOnTearDown.pollLast()) != null;) {
+			file.delete();
+		}
+		ResourceHelper.cleanUp();
+		//TestScannerProvider.clear();
+		super.tearDown();
+	}
+
+	protected void deleteOnTearDown(File file) {
+		this.filesToDeleteOnTearDown.add(file);
+	}
+
+	protected File createTempFile(String prefix, String suffix) throws IOException {
+		File file = File.createTempFile(prefix, suffix);
+		this.filesToDeleteOnTearDown.add(file);
+		return file;
+	}
+
+	protected File nonExistentTempFile(String prefix, String suffix) {
+		File file= new File(System.getProperty("java.io.tmpdir"),
+				prefix + System.currentTimeMillis() + suffix);
+		this.filesToDeleteOnTearDown.add(file);
+		return file;
+	}
+
+	public static TestSuite suite(Class<? extends BaseTestCase> clazz) {
+		return suite(clazz, null);
+	}
+
+	protected static TestSuite suite(Class<? extends BaseTestCase> clazz, String failingTestPrefix) {
+		TestSuite suite= new TestSuite(clazz);
+		Test failing= getFailingTests(clazz, failingTestPrefix);
+		if (failing != null) {
+			suite.addTest(failing);
+		}
+		return suite;
+	}
+
+	private static Test getFailingTests(Class<? extends BaseTestCase> clazz, String prefix) {
+		TestSuite suite= new TestSuite("Failing Tests");
+		HashSet<String> names= new HashSet<>();
+		Class<?> superClass= clazz;
+		while (Test.class.isAssignableFrom(superClass) && !TestCase.class.equals(superClass)) {
+			Method[] methods= superClass.getDeclaredMethods();
+			for (Method method : methods) {
+				addFailingMethod(suite, method, names, clazz, prefix);
+			}
+			superClass= superClass.getSuperclass();
+		}
+		if (suite.countTestCases() == 0) {
+			return null;
+		}
+		return suite;
+	}
+
+	private static void addFailingMethod(TestSuite suite, Method m, Set<String> names,
+			Class<? extends BaseTestCase> clazz, String prefix) {
+		String name = m.getName();
+		if (!names.add(name)) {
+			return;
+		}
+		if (name.startsWith("test") || (prefix != null && !name.startsWith(prefix))) {
+			return;
+		}
+		if (name.equals("tearDown") || name.equals("setUp") || name.equals("runBare")) {
+			return;
+		}
+		if (Modifier.isPublic(m.getModifiers())) {
+			Class<?>[] parameters = m.getParameterTypes();
+			Class<?> returnType = m.getReturnType();
+			if (parameters.length == 0 && returnType.equals(Void.TYPE)) {
+				Test test = TestSuite.createTest(clazz, name);
+				((BaseTestCase) test).setExpectFailure(0);
+				suite.addTest(test);
+			}
+		}
+	}
+
+	@Override
+	public void runBare() throws Throwable {
+		final List<IStatus> statusLog= Collections.synchronizedList(new ArrayList<>());
+		ILogListener logListener= new ILogListener() {
+			@Override
+			public void logging(IStatus status, String plugin) {
+				if (!status.isOK() && status.getSeverity() != IStatus.INFO) {
+					switch (status.getCode()) {
+					case IResourceStatus.NOT_FOUND_LOCAL:
+					case IResourceStatus.NO_LOCATION_LOCAL:
+					case IResourceStatus.FAILED_READ_LOCAL:
+					case IResourceStatus.RESOURCE_NOT_LOCAL:
+						// Logged by the resources plugin.
+						return;
+					}
+					statusLog.add(status);
+				}
+			}
+		};
+		final ILog log = Package.getLog();
+		if (log != null) { // Iff we don't run as a JUnit Plugin Test.
+			log.addLogListener(logListener);
+		}
+
+		Throwable testThrowable= null;
+		try {
+			try {
+				super.runBare();
+			} catch (Throwable e) {
+				testThrowable= e;
+			}
+
+			if (statusLog.size() != this.fExpectedLoggedNonOK) {
+				StringBuilder msg= new StringBuilder("Expected number (" + this.fExpectedLoggedNonOK + ") of ");
+				msg.append("Non-OK status objects in log differs from actual (" + statusLog.size() + ").\n");
+				Throwable cause= null;
+				if (!statusLog.isEmpty()) {
+					synchronized (statusLog) {
+						for (IStatus status : statusLog) {
+							IStatus[] ss= {status};
+							ss= status instanceof MultiStatus ? ((MultiStatus) status).getChildren() : ss;
+							for (IStatus s : ss) {
+								msg.append("\t" + s.getMessage() + " ");
+
+								Throwable t= s.getException();
+								cause= cause != null ? cause : t;
+								if (t != null) {
+									msg.append(t.getMessage() != null ? t.getMessage() : t.getClass().getCanonicalName());
+								}
+
+								msg.append("\n");
+							}
+						}
+					}
+				}
+				cause= cause != null ? cause : testThrowable;
+				AssertionFailedError afe= new AssertionFailedError(msg.toString());
+				afe.initCause(cause);
+				throw afe;
+			}
+		} finally {
+			if (log != null) {
+				log.removeLogListener(logListener);
+			}
+		}
+
+		if (testThrowable != null)
+			throw testThrowable;
+	}
+
+    @Override
+	public void run(TestResult result) {
+    	if (!this.fExpectFailure || Boolean.parseBoolean(System.getProperty("SHOW_EXPECTED_FAILURES"))) {
+    		super.run(result);
+    		return;
+    	}
+
+        result.startTest(this);
+
+        TestResult r = new TestResult();
+        super.run(r);
+        if (r.failureCount() == 1) {
+        	TestFailure failure= r.failures().nextElement();
+        	String msg= failure.exceptionMessage();
+        	if (msg != null && msg.startsWith("Method \"" + getName() + "\"")) {
+        		result.addFailure(this, new AssertionFailedError(msg));
+        	}
+        } else if (r.errorCount() == 0 && r.failureCount() == 0) {
+            String err = "Unexpected success of " + getName();
+            if (this.fBugNumber > 0) {
+                err += ", bug #" + this.fBugNumber;
+            }
+            result.addFailure(this, new AssertionFailedError(err));
+        }
+
+        result.endTest(this);
+    }
+
+    public void setExpectFailure(int bugNumber) {
+    	this.fExpectFailure= true;
+    	this.fBugNumber= bugNumber;
+    }
+
+    /**
+     * The last value passed to this method in the body of a testXXX method
+     * will be used to determine whether or not the presence of non-OK status objects
+     * in the log should fail the test. If the logged number of non-OK status objects
+     * differs from the last value passed, the test is failed. If this method is not called
+     * at all, the expected number defaults to zero.
+     * @param count the expected number of logged error and warning messages
+     */
+    public void setExpectedNumberOfLoggedNonOKStatusObjects(int count) {
+    	this.fExpectedLoggedNonOK= count;
+    }
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/util/Package.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/util/Package.java
new file mode 100644
index 0000000..4e2dbbd
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/util/Package.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * 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.core.tests.nd.util;
+
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Plugin;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.JavaCore;
+
+/**
+ * @noreference This class is not intended to be referenced by clients
+ */
+/* package */ class Package {
+	public static String PLUGIN_ID = JavaCore.PLUGIN_ID;
+
+	/**
+	 * @noreference This method is not intended to be referenced by clients.
+	 */
+	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$
+		}
+	}
+	
+	/**
+	 * @noreference This method is not intended to be referenced by clients.
+	 */
+	public static void log(String message, Throwable e) {
+		log(createStatus(message, e));
+	}
+	
+	/**
+	 * @noreference This method is not intended to be referenced by clients.
+	 */
+	public static IStatus createStatus(String msg, Throwable e) {
+		return new Status(IStatus.ERROR, PLUGIN_ID, msg, e);
+	}
+
+	/**
+	 * @noreference This method is not intended to be referenced by clients.
+	 */
+	public static IStatus createStatus(String msg) {
+		return new Status(IStatus.ERROR, PLUGIN_ID, msg);
+	}
+
+	/**
+	 * @noreference This method is not intended to be referenced by clients.
+	 * 
+	 * Returns the appropriate ILog for this package
+	 */
+	public static ILog getLog() {
+		Plugin plugin = JavaCore.getPlugin();
+		if (plugin == null) {
+			return null;
+		}
+		return plugin.getLog();
+	}
+
+	/**
+	 * @noreference This method is not intended to be referenced by clients.
+	 */
+	public static void log(IStatus status) {
+		getLog().log(status);
+	}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/util/ResourceHelper.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/util/ResourceHelper.java
new file mode 100644
index 0000000..5b2cd89
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/nd/util/ResourceHelper.java
@@ -0,0 +1,576 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2016 Andrew Gvozdev 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 Gvozdev - Initial API and implementation
+ *     James Blackburn (Broadcom Corp.)
+ *     Liviu Ionescu - bug 392416
+ *******************************************************************************/
+package org.eclipse.jdt.core.tests.nd.util;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.junit.Assert;
+
+/**
+ * This class contains utility methods for creating resources
+ * such as projects, files, folders etc. which are being used
+ * in test fixture of unit tests.
+ *
+ * Some classes with similar idea worth to look at:
+ * org.eclipse.core.filebuffers.tests.ResourceHelper,
+ * org.eclipse.cdt.ui.tests.text.ResourceHelper.
+ */
+public class ResourceHelper {
+	private final static IProgressMonitor NULL_MONITOR = new NullProgressMonitor();
+	private static final int MAX_RETRY= 5;
+
+	private final static Set<String> externalFilesCreated = new HashSet<String>();
+	private final static Set<IResource> resourcesCreated = new HashSet<IResource>();
+
+	/**
+	 * Creates a plain Eclipse project.
+	 *
+	 * @param projectName
+	 * @return  the project handle
+	 * @throws CoreException  if project could not be created
+	 */
+	public static IProject createProject(String projectName) throws CoreException {
+		IWorkspaceRoot root= ResourcesPlugin.getWorkspace().getRoot();
+		IProject project= root.getProject(projectName);
+		if (!project.exists()) {
+			project.create(NULL_MONITOR);
+		} else {
+			project.refreshLocal(IResource.DEPTH_INFINITE, null);
+		}
+
+		if (!project.isOpen())
+			project.open(NULL_MONITOR);
+
+		resourcesCreated.add(project);
+		return project;
+	}
+
+	/**
+	 * Deletes project by name.
+	 *
+	 * @param projectName
+	 * @throws CoreException
+	 */
+	public static void deleteProject(String projectName) throws CoreException {
+		IWorkspaceRoot root= ResourcesPlugin.getWorkspace().getRoot();
+		IProject project= root.getProject(projectName);
+		if (project.exists())
+			delete(project);
+	}
+
+	/**
+	 * Deletes given project with content.
+	 *
+	 * @param project
+	 * @throws CoreException
+	 */
+	public static void delete(final IProject project) throws CoreException {
+		delete(project, true);
+	}
+
+	/**
+	 * Deletes project.
+	 *
+	 * @param project
+	 * @param deleteContent  whether to delete project content
+	 * @throws CoreException
+	 */
+	public static void delete(final IProject project, boolean deleteContent) throws CoreException {
+		for (int i= 0; i < MAX_RETRY; i++) {
+			try {
+				project.delete(deleteContent, true, NULL_MONITOR);
+				i= MAX_RETRY;
+			} catch (CoreException x) {
+				if (i == MAX_RETRY - 1) {
+					Package.log(x.getStatus());
+				}
+				try {
+					Thread.sleep(1000); // sleep a second
+				} catch (InterruptedException e) {
+				}
+			}
+		}
+	}
+
+	/**
+	 * Creates a file with specified content.
+	 *
+	 * @param file - file name.
+	 * @param contents - contents of the file.
+	 * @return file handle.
+	 * @throws CoreException - if the file can't be created.
+	 */
+	public static IFile createFile(IFile file, String contents) throws CoreException {
+		if (contents == null) {
+			contents= "";
+		}
+
+		InputStream inputStream = new ByteArrayInputStream(contents.getBytes());
+		file.create(inputStream, true, NULL_MONITOR);
+		resourcesCreated.add(file);
+		return file;
+	}
+
+	/**
+	 * Creates new file from project root with empty content. The filename
+	 * can include relative path as a part of the name but the the path
+	 * has to be present on disk.
+	 *
+	 * @param project - project where to create the file.
+	 * @param name - filename.
+	 * @return file handle.
+	 * @throws CoreException if something goes wrong.
+	 */
+	public static IFile createFile(IProject project, String name) throws CoreException {
+		if (new Path(name).segmentCount() > 1)
+			createFolder(project, new Path(name).removeLastSegments(1).toString());
+		return createFile(project.getFile(name), null);
+	}
+
+	/**
+	 * Creates new file from workspace root with empty content. The filename
+	 * can include relative path as a part of the name but the the path
+	 * has to be present on disk.
+	 * The intention of the method is to create files which do not belong to any project.
+	 *
+	 * @param name - filename.
+	 * @return full path of the created file.
+	 *
+	 * @throws CoreException...
+	 * @throws IOException...
+	 */
+	public static IPath createWorkspaceFile(String name) throws CoreException, IOException {
+		IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
+		IPath fullPath = workspaceRoot.getLocation().append(name);
+		java.io.File file = new java.io.File(fullPath.toOSString());
+		if (!file.exists()) {
+			boolean result = file.createNewFile();
+			Assert.assertTrue(result);
+		}
+		Assert.assertTrue(file.exists());
+
+		externalFilesCreated.add(fullPath.toOSString());
+		workspaceRoot.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
+		return fullPath;
+	}
+
+	/**
+	 * Creates new folder from project root. The folder name
+	 * can include relative path as a part of the name.
+	 * Nonexistent parent directories are being created.
+	 *
+	 * @param project - project where to create the folder.
+	 * @param name - folder name.
+	 * @return folder handle.
+	 * @throws CoreException if something goes wrong.
+	 */
+	public static IFolder createFolder(IProject project, String name) throws CoreException {
+		final IPath p = new Path(name);
+		IContainer folder = project;
+		for (String seg : p.segments()) {
+			folder = folder.getFolder(new Path(seg));
+			if (!folder.exists())
+				((IFolder)folder).create(true, true, NULL_MONITOR);
+		}
+		resourcesCreated.add(folder);
+		return (IFolder)folder;
+	}
+
+	/**
+	 * Creates new folder from workspace root. The folder name
+	 * can include relative path as a part of the name.
+	 * Nonexistent parent directories are being created as per {@link File#mkdirs()}.
+	 * The intention of the method is to create folders which do not belong to any project.
+	 *
+	 * @param name - folder name.
+	 * @return absolute location of the folder on the file system.
+	 * @throws IOException if something goes wrong.
+	 */
+	public static IPath createWorkspaceFolder(String name) throws CoreException, IOException {
+		IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
+		IPath fullPath = workspaceRoot.getLocation().append(name);
+		java.io.File folder = new java.io.File(fullPath.toOSString());
+		if (!folder.exists()) {
+			boolean result = folder.mkdirs();
+			Assert.assertTrue(result);
+		}
+		Assert.assertTrue(folder.exists());
+
+		externalFilesCreated.add(fullPath.toOSString());
+		workspaceRoot.refreshLocal(IResource.DEPTH_INFINITE, NULL_MONITOR);
+		return fullPath;
+	}
+
+	/**
+	 * Creates new temporary folder with generated name from workspace root.
+	 *
+	 * @return absolute location of the folder on the file system.
+	 * @throws IOException if something goes wrong.
+	 */
+	public static IPath createTemporaryFolder() throws CoreException, IOException {
+		return ResourceHelper.createWorkspaceFolder("tmp/" + System.currentTimeMillis() + '.' + UUID.randomUUID());
+	}
+
+	/**
+	 * Creates new eclipse file-link from project root to file system file. The filename
+	 * can include relative path as a part of the name but the the path
+	 * has to be present on disk.
+	 *
+	 * @param project - project where to create the file.
+	 * @param fileLink - filename of the link being created.
+	 * @param realFile - file on the file system, the target of the link.
+	 * @return file handle.
+	 * @throws CoreException if something goes wrong.
+	 */
+	public static IFile createLinkedFile(IProject project, String fileLink, IPath realFile) throws CoreException {
+		IFile file = project.getFile(fileLink);
+		file.createLink(realFile, IResource.REPLACE, null);
+		Assert.assertTrue(file.exists());
+		resourcesCreated.add(file);
+		return file;
+	}
+
+	/**
+	 * Creates new eclipse file-link from project root to file system file. The filename
+	 * can include relative path as a part of the name but the the path
+	 * has to be present on disk.
+	 *
+	 * @param project - project where to create the file.
+	 * @param fileLink - filename of the link being created.
+	 * @param realFile - file on the file system, the target of the link.
+	 * @return file handle.
+	 * @throws CoreException if something goes wrong.
+	 */
+	public static IFile createLinkedFile(IProject project, String fileLink, String realFile) throws CoreException {
+		return createLinkedFile(project, fileLink, new Path(realFile));
+	}
+
+	/**
+	 * Creates new eclipse file-link from project root to EFS file.
+	 *
+	 * @param project - project where to create the file.
+	 * @param fileLink - filename of the link being created.
+	 * @param realFile - file on the EFS file system, the target of the link.
+	 * @return file handle.
+	 * @throws CoreException if something goes wrong.
+	 */
+	public static IFile createEfsFile(IProject project, String fileLink, URI realFile) throws CoreException {
+		IFile file= project.getFile(fileLink);
+		file.createLink(realFile, IResource.ALLOW_MISSING_LOCAL, NULL_MONITOR);
+		resourcesCreated.add(file);
+		return file;
+	}
+
+	/**
+	 * Creates new eclipse file-link from project root to EFS file.
+	 *
+	 * @param project - project where to create the file.
+	 * @param fileLink - filename of the link being created.
+	 * @param realFile - file on the EFS file system, the target of the link.
+	 * @return file handle.
+	 * @throws CoreException if something goes wrong.
+	 * @throws URISyntaxException if wrong URI syntax
+	 */
+	public static IFile createEfsFile(IProject project, String fileLink, String realFile) throws CoreException, URISyntaxException {
+		return createEfsFile(project,fileLink,new URI(realFile));
+	}
+
+	/**
+	 * Creates new eclipse folder-link from project root to file system folder. The folder name
+	 * can include relative path as a part of the name but the the path
+	 * has to be present on disk.
+	 *
+	 * @param project - project where to create the file.
+	 * @param folderLink - name of the link being created.
+	 * @param realFolder - folder on the file system, the target of the link.
+	 * @return file handle.
+	 * @throws CoreException if something goes wrong.
+	 */
+	public static IFolder createLinkedFolder(IProject project, String folderLink, IPath realFolder) throws CoreException {
+		IFolder folder = project.getFolder(folderLink);
+		folder.createLink(realFolder, IResource.REPLACE | IResource.ALLOW_MISSING_LOCAL, null);
+		Assert.assertTrue(folder.exists());
+		resourcesCreated.add(folder);
+		return folder;
+	}
+
+	/**
+	 * Creates new eclipse folder-link from project root to file system folder. The folder name
+	 * can include relative path as a part of the name but the the path
+	 * has to be present on disk.
+	 *
+	 * @param project - project where to create the file.
+	 * @param folderLink - name of the link being created.
+	 * @param realFolder - folder on the file system, the target of the link.
+	 * @return file handle.
+	 * @throws CoreException if something goes wrong.
+	 */
+	public static IFolder createLinkedFolder(IProject project, String folderLink, String realFolder) throws CoreException {
+		return createLinkedFolder(project, folderLink, new Path(realFolder));
+	}
+
+	/**
+	 * Creates new eclipse folder-link from project root to EFS folder.
+	 *
+	 * @param project - project where to create the folder.
+	 * @param folderLink - folder name of the link being created.
+	 * @param realFolder - folder on the EFS file system, the target of the link.
+	 * @return folder handle.
+	 * @throws CoreException if something goes wrong.
+	 */
+	public static IFolder createEfsFolder(IProject project, String folderLink, URI realFolder) throws CoreException {
+		IFolder folder= project.getFolder(folderLink);
+		if (folder.exists()) {
+			Assert.assertEquals("Folder with the same name but different location already exists",
+					realFolder, folder.getLocationURI());
+			return folder;
+		}
+
+		folder.createLink(realFolder, IResource.ALLOW_MISSING_LOCAL, new NullProgressMonitor());
+		resourcesCreated.add(folder);
+		return folder;
+	}
+
+	/**
+	 * Creates new eclipse folder-link from project root to EFS folder.
+	 *
+	 * @param project - project where to create the folder.
+	 * @param folderLink - folder name of the link being created.
+	 * @param realFolder - folder on the EFS file system, the target of the link.
+	 * @return folder handle.
+	 * @throws CoreException if something goes wrong.
+	 * @throws URISyntaxException if wrong URI syntax
+	 */
+	public static IFolder createEfsFolder(IProject project, String folderLink, String realFolder) throws CoreException, URISyntaxException {
+		return createEfsFolder(project,folderLink,new URI(realFolder));
+	}
+
+	/**
+	 * Checks if symbolic links are supported on the system.
+	 * Used in particular by method {@link #createSymbolicLink(IPath, IPath)}
+	 * and other flavors to create symbolic links.
+	 *
+	 * Note that Windows links .lnk are not supported here.
+	 * @return {@code true} if symbolic links are suppoted, {@code false} otherwise.
+	 */
+	public static boolean isSymbolicLinkSupported() {
+		return ! Platform.getOS().equals(Platform.OS_WIN32);
+	}
+
+	/**
+	 * Creates new symbolic file system link from file or folder on project root
+	 * to another file system file. The filename can include relative path
+	 * as a part of the name but the the path has to be present on disk.
+	 *
+	 * @param project - project where to create the file.
+	 * @param linkName - name of the link being created.
+	 * @param realPath - file or folder on the file system, the target of the link.
+	 * @return file handle.
+	 *
+	 * @throws UnsupportedOperationException on Windows where links are not supported.
+	 * @throws IOException...
+	 * @throws CoreException...
+	 */
+	public static IResource createSymbolicLink(IProject project, String linkName, IPath realPath)
+			throws IOException, CoreException, UnsupportedOperationException {
+		if (!isSymbolicLinkSupported()) {
+			throw new UnsupportedOperationException("Windows links .lnk are not supported.");
+		}
+
+		Assert.assertTrue("Path for symbolic link does not exist: [" + realPath.toOSString() + "]",
+				new File(realPath.toOSString()).exists());
+
+		IPath linkedPath = project.getLocation().append(linkName);
+		createSymbolicLink(linkedPath, realPath);
+
+		IResource resource = project.getFile(linkName);
+		resource.refreshLocal(IResource.DEPTH_ZERO, null);
+
+		if (!resource.exists()) {
+			resource = project.getFolder(linkName);
+			resource.refreshLocal(IResource.DEPTH_ZERO, null);
+		}
+		Assert.assertTrue("Failed to create resource form symbolic link", resource.exists());
+
+		externalFilesCreated.add(linkedPath.toOSString());
+		ResourcesPlugin.getWorkspace().getRoot().refreshLocal(IResource.DEPTH_INFINITE, NULL_MONITOR);
+		return resource;
+	}
+
+	/**
+	 * Creates new symbolic file system link from file or folder to another filesystem file.
+	 * The target path has to be present on disk.
+	 *
+	 * @param linkPath - filesystem path of the link being created.
+	 * @param realPath - file or folder on the file system, the target of the link.
+	 *
+	 * @throws UnsupportedOperationException on Windows where links are not supported.
+	 * @throws IOException if execution of the command fails.
+	 */
+	public static void createSymbolicLink(IPath linkPath, IPath realPath) throws IOException {
+		if (!isSymbolicLinkSupported()) {
+			throw new UnsupportedOperationException("Windows links .lnk are not supported.");
+		}
+
+		String command[] = { "ln", "-s", realPath.toOSString(), linkPath.toOSString()};
+		Process process = Runtime.getRuntime().exec(command);
+
+		// Wait for up to 2.5s...
+		for (int i = 0; i < 5; i++) {
+			try {
+				Assert.assertTrue("ln process exited with non-zero status", process.waitFor() == 0);
+				// If exitValue succeeded, then the process has exited successfully.
+				break;
+			} catch (InterruptedException e) {
+				// Clear interrupted state, see Java bug http://bugs.sun.com/view_bug.do?bug_id=6420270
+				Thread.interrupted();
+			}
+			// Wait for a 500ms before checking again.
+			try { Thread.sleep(500); } catch (InterruptedException e) {/*don't care*/}
+		}
+		Assert.assertTrue("Symbolic link not created, command=[" + command + "]", linkPath.toFile().exists());
+	}
+
+	/**
+	 * Creates new symbolic file system link from file or folder on project root
+	 * to another file system file. The filename can include relative path
+	 * as a part of the name but the the path has to be present on disk.
+	 *
+	 * @param project - project where to create the file.
+	 * @param linkName - name of the link being created.
+	 * @param realPath - file or folder on the file system, the target of the link.
+	 * @return file handle.
+	 *
+	 * @throws UnsupportedOperationException on Windows where links are not supported.
+	 * @throws IOException...
+	 * @throws CoreException...
+	 */
+	public static IResource createSymbolicLink(IProject project, String linkName, String realPath)
+			throws IOException, CoreException, UnsupportedOperationException {
+		return createSymbolicLink(project, linkName, new Path(realPath));
+	}
+
+	/**
+	 * Get contents of file on file-system.
+	 *
+	 * @param fullPath - full path to the file on the file-system.
+	 * @return contents of the file.
+	 * @throws IOException on IO problem.
+	 */
+	public static String getContents(IPath fullPath) throws IOException {
+		FileInputStream stream = new FileInputStream(fullPath.toFile());
+		try {
+			// Avoid using java.nio.channels.FileChannel,
+			// see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4715154
+			Reader reader = new BufferedReader(new InputStreamReader(stream, Charset.defaultCharset()));
+			StringBuilder builder = new StringBuilder();
+			char[] buffer = new char[8192];
+			int read;
+			while ((read = reader.read(buffer, 0, buffer.length)) > 0) {
+				builder.append(buffer, 0, read);
+			}
+			return builder.toString();
+		} finally {
+			stream.close();
+		}
+	}
+
+	/**
+	 * Get contents of file on file-system.
+	 *
+	 * @param fullPath - full path to the file on the file-system.
+	 * @return contents of the file.
+	 * @throws IOException on IO problem.
+	 */
+	public static String getContents(String fullPath) throws IOException {
+		return getContents(new Path(fullPath));
+	}
+
+	/**
+	 * Clean-up any files created as part of a unit test.
+	 * This method removes *all* Workspace IResources and any external
+	 * files / folders created with the #createWorkspaceFile #createWorkspaceFolder
+	 * methods in this class
+	 */
+	public static void cleanUp() throws CoreException, IOException {
+		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+		root.refreshLocal(IResource.DEPTH_INFINITE, NULL_MONITOR);
+
+		// Delete all external files & folders created using ResourceHelper
+		for (String loc : externalFilesCreated) {
+			File f = new File(loc);
+			if (f.exists())
+				deleteRecursive(f);
+		}
+		externalFilesCreated.clear();
+
+		// Remove IResources created by this helper
+		for (IResource r : resourcesCreated) {
+			if (r.exists()) {
+				try {
+					r.delete(true, NULL_MONITOR);
+				} catch (CoreException e) {
+					// Ignore
+				}
+			}
+		}
+		resourcesCreated.clear();
+	}
+
+	/**
+	 * Recursively delete a directory / file
+	 *
+	 * For safety this method only deletes files created under the workspace
+	 */
+	private static final void deleteRecursive(File f) throws IllegalArgumentException {
+		// Ensure that the file being deleted is a child of the workspace
+		// root to prevent anything nasty happening
+		if (!f.getAbsolutePath().startsWith(
+				ResourcesPlugin.getWorkspace().getRoot().getLocation().toFile().getAbsolutePath())) {
+			throw new IllegalArgumentException("File must exist within the workspace!");
+		}
+
+		if (f.isDirectory()) {
+			for (File f1 : f.listFiles()) {
+				deleteRecursive(f1);
+			}
+		}
+		f.delete();
+	}
+}
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);
+				}
+
+				skipChar(wrapper, '>');
+			}
+			result = typeSignature;
+
+			if (parentTypeOrNull != null) {
+				typeSignature.setGenericDeclaringType(parentTypeOrNull);
+			}
+
+			if (genericSignature[wrapper.start] == '.') {
+				// Don't check for a semicolon if we hit this branch since the recursive call to parseClassTypeSignature
+				// will do this
+				checkForSemicolon = false;
+				// Identifiers shouldn't start with '.'
+				skipChar(wrapper, '.');
+				result = parseClassTypeSignature(typeSignature, wrapper);
+			}
+		}
+
+		if (checkForSemicolon) {
+			skipChar(wrapper, ';');
+		}
+
+		return result;
+	}
+
+	private NdTypeId createTypeIdFromFieldDescriptor(char[] typeName) {
+		if (typeName == null) {
+			return null;
+		}
+		return this.index.createTypeId(typeName);
+	}
+
+	/**
+	 * Creates a method ID given a method selector, method descriptor, and binary type name
+	 */
+	private NdMethodId createMethodId(char[] binaryTypeName, char[] methodSelector, char[] methodDescriptor) {
+		if (methodSelector == null || binaryTypeName == null || methodDescriptor == null) {
+			return null;
+		}
+
+		char[] methodId = JavaNames.getMethodId(binaryTypeName, methodSelector, methodDescriptor);
+		return this.index.createMethodId(methodId);
+	}
+
+	/**
+	 * Creates a method ID given a method name (which is a method selector followed by a method descriptor.
+	 */
+	private NdMethodId createMethodId(char[] binaryTypeName, char[] methodName) {
+		if (methodName == null || binaryTypeName == null) {
+			return null;
+		}
+
+		char[] methodId = JavaNames.getMethodId(binaryTypeName, methodName);
+		return this.index.createMethodId(methodId);
+	}
+
+	private void initTypeAnnotation(NdTypeAnnotation annotation, IBinaryTypeAnnotation next) {
+		int[] typePath = next.getTypePath();
+		if (typePath != null && typePath.length > 0) {
+			byte[] bytePath = new byte[typePath.length];
+			for (int idx = 0; idx < bytePath.length; idx++) {
+				bytePath[idx] = (byte)typePath[idx];
+			}
+			annotation.setPath(bytePath);
+		}
+		int targetType = next.getTargetType();
+		annotation.setTargetType(targetType);
+		switch (targetType) {
+			case AnnotationTargetTypeConstants.METHOD_TYPE_PARAMETER:
+			case AnnotationTargetTypeConstants.CLASS_TYPE_PARAMETER:
+				annotation.setTargetInfo(next.getTypeParameterIndex());
+				break;				
+			case AnnotationTargetTypeConstants.CLASS_EXTENDS:
+				annotation.setTargetInfo(next.getSupertypeIndex());
+				break;
+			case AnnotationTargetTypeConstants.CLASS_TYPE_PARAMETER_BOUND:
+			case AnnotationTargetTypeConstants.METHOD_TYPE_PARAMETER_BOUND:
+				annotation.setTargetInfo((byte)next.getTypeParameterIndex(), (byte)next.getBoundIndex());
+				break;
+			case AnnotationTargetTypeConstants.FIELD:
+			case AnnotationTargetTypeConstants.METHOD_RETURN:
+			case AnnotationTargetTypeConstants.METHOD_RECEIVER:
+				break;
+			case AnnotationTargetTypeConstants.METHOD_FORMAL_PARAMETER :
+				annotation.setTargetInfo(next.getMethodFormalParameterIndex());
+				break;
+			case AnnotationTargetTypeConstants.THROWS :
+				annotation.setTargetInfo(next.getThrowsTypeIndex());
+				break;
+
+			default:
+				throw new IllegalStateException("Target type not handled " + targetType); //$NON-NLS-1$
+		}
+		initAnnotation(annotation, next.getAnnotation());
+	}
+
+	private void initAnnotation(NdAnnotation annotation, IBinaryAnnotation next) {
+		annotation.setType(createTypeIdFromBinaryName(next.getTypeName())); 
+
+		IBinaryElementValuePair[] pairs = next.getElementValuePairs();
+
+		if (pairs != null) {
+			for (IBinaryElementValuePair element : pairs) {
+				NdAnnotationValuePair nextPair = new NdAnnotationValuePair(annotation, element.getName());
+				nextPair.setValue(createConstantFromMixedType(element.getValue()));
+			}
+		}
+	}
+
+	private void logInfo(String string) {
+		if (ENABLE_LOGGING) {
+			Package.logInfo(string);
+		}
+	}
+
+	private NdTypeId createTypeIdFromBinaryName(char[] binaryName) {
+		if (binaryName == null) {
+			return null;
+		}
+
+		return this.index.createTypeId(JavaNames.binaryNameToFieldDescriptor(binaryName));
+	}
+
+	/**
+	 *
+	 * @param value
+	 *            accepts all values returned from {@link IBinaryElementValuePair#getValue()}
+	 */
+	public NdConstant createConstantFromMixedType(Object value) {
+		if (value instanceof Constant) {
+			Constant constant = (Constant) value;
+
+			return NdConstant.create(getNd(), constant);
+		} else if (value instanceof ClassSignature) {
+			ClassSignature signature = (ClassSignature) value;
+
+			char[] binaryName = JavaNames.binaryNameToFieldDescriptor(signature.getTypeName());
+			NdTypeSignature typeId = this.index.createTypeId(binaryName);
+			return NdConstantClass.create(getNd(), typeId);
+		} else if (value instanceof IBinaryAnnotation) {
+			IBinaryAnnotation binaryAnnotation = (IBinaryAnnotation) value;
+
+			NdAnnotationInConstant annotation = new NdAnnotationInConstant(getNd());
+			initAnnotation(annotation, binaryAnnotation);
+			return NdConstantAnnotation.create(getNd(), annotation);
+		} else if (value instanceof Object[]) {
+			NdConstantArray result = new NdConstantArray(getNd());
+			Object[] array = (Object[]) value;
+
+			for (Object next : array) {
+				NdConstant nextConstant = createConstantFromMixedType(next);
+				nextConstant.setParent(result);
+			}
+			return result;
+		} else if (value instanceof EnumConstantSignature) {
+			EnumConstantSignature signature = (EnumConstantSignature) value;
+
+			NdConstantEnum result = NdConstantEnum.create(createTypeIdFromBinaryName(signature.getTypeName()),
+					new String(signature.getEnumConstantName()));
+
+			return result;
+		}
+		throw new IllegalStateException("Unknown constant type " + value.getClass().getName()); //$NON-NLS-1$
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/GenericSignatures.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/GenericSignatures.java
new file mode 100644
index 0000000..caca5a2
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/GenericSignatures.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * 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.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.lookup.SignatureWrapper;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+
+/**
+ * Contains static factory methods for constructing {@link SignatureWrapper} from various types.
+ */
+public class GenericSignatures {
+	private static final char[][] EMPTY_CHAR_ARRAY_ARRAY = new char[0][];
+
+	public static SignatureWrapper getGenericSignature(IBinaryMethod next) {
+		char[] signature = next.getGenericSignature();
+		if (signature == null) {
+			signature = next.getMethodDescriptor();
+		}
+
+		return new SignatureWrapper(signature);
+	}
+
+	/**
+	 * Returns the generic signature for the given field. If the field has no generic signature, one is generated
+	 * from the type's field descriptor.
+	 */
+	public static SignatureWrapper getGenericSignature(IBinaryType binaryType) {
+		char[][] interfaces = binaryType.getInterfaceNames();
+		if (interfaces == null) {
+			interfaces = EMPTY_CHAR_ARRAY_ARRAY;
+		}
+		char[] genericSignature = binaryType.getGenericSignature();
+		if (genericSignature == null) {
+			int startIndex = binaryType.getSuperclassName() != null ? 3 : 0;
+			char[][] toCatenate = new char[startIndex + (interfaces.length * 3)][];
+			char[] prefix = new char[]{'L'};
+			char[] suffix = new char[]{';'};
+
+			if (binaryType.getSuperclassName() != null) {
+				toCatenate[0] = prefix;
+				toCatenate[1] = binaryType.getSuperclassName();
+				toCatenate[2] = suffix;
+			}
+
+			for (int idx = 0; idx < interfaces.length; idx++) {
+				int catIndex = startIndex + idx * 3;
+				toCatenate[catIndex] = prefix;
+				toCatenate[catIndex + 1] = interfaces[idx];
+				toCatenate[catIndex + 2] = suffix;
+			}
+
+			genericSignature = CharArrayUtils.concat(toCatenate);
+		}
+
+		SignatureWrapper signatureWrapper = new SignatureWrapper(genericSignature);
+		return signatureWrapper;
+	}
+
+	/**
+	 * Returns the generic signature for the given field. If the field has no generic signature, one is generated
+	 * from the type's field descriptor.
+	 */
+	static SignatureWrapper getGenericSignatureFor(IBinaryField nextField) {
+		char[] signature = nextField.getGenericSignature();
+		if (signature == null) {
+			signature = nextField.getTypeName();
+		}
+		return new SignatureWrapper(signature);
+	}
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/HierarchicalASTVisitor.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/HierarchicalASTVisitor.java
new file mode 100644
index 0000000..66f82de
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/HierarchicalASTVisitor.java
@@ -0,0 +1,1139 @@
+/*******************************************************************************
+ * 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
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.indexer;
+
+import org.eclipse.jdt.core.dom.*;
+
+/**
+ *
+ * <p>
+ * This class provides a convenient behaviour-only extension mechanism for the ASTNode hierarchy. If
+ * you feel like you would like to add a method to the ASTNode hierarchy (or a subtree of the
+ * hierarchy), and you want to have different implementations of it at different points in the
+ * hierarchy, simply create a HierarchicalASTVisitor representing the new method and all its
+ * implementations, locating each implementation within the right visit(XX) method. If you wanted to
+ * add a method implementation to abstract class Foo, an ASTNode descendant, put your implementation
+ * in visit(Foo). This class will provide appropriate dispatch, just as if the method
+ * implementations had been added to the ASTNode hierarchy.
+ * </p>
+ *
+ * <p>
+ * <b>Details:<b>
+ * </p>
+ *
+ * <p>
+ * This class has a visit(XX node) method for every class (concrete or abstract) XX in the ASTNode
+ * hierarchy. In this class' default implementations of these methods, the method corresponding to a
+ * given ASTNode descendant class will call (and return the return value of) the visit(YY) method
+ * for it's superclass YY, with the exception of the visit(ASTNode) method which simply returns
+ * true, since ASTNode doesn't have a superclass that is within the ASTNode hierarchy.
+ * </p>
+ *
+ * <p>
+ * Because of this organization, when visit(XX) methods are overridden in a subclass, and the
+ * visitor is applied to a node, only the most specialized overridden method implementation for the
+ * node's type will be called, unless this most specialized method calls other visit methods (this
+ * is discouraged) or, (preferably) calls super.visit(XX node), (the reference type of the parameter
+ * must be XX) which will invoke this class' implementation of the method, which will, in turn,
+ * invoke the visit(YY) method corresponding to the superclass, YY.
+ * </p>
+ *
+ * <p>
+ * Thus, the dispatching behaviour achieved when HierarchicalASTVisitors' visit(XX) methods,
+ * corresponding to a particular concrete or abstract ASTNode descendant class, are overridden is
+ * exactly analogous to the dispatching behaviour obtained when method implementations are added to
+ * the same ASTNode descendant classes.
+ * </p>
+ *
+ */
+/*
+ * IMPORTANT NOTE:
+ *
+ * The structure and behaviour of this class is
+ * verified reflectively by
+ * org.eclipse.jdt.ui.tests.core.HierarchicalASTVisitorTest
+ *
+ */
+public abstract class HierarchicalASTVisitor extends ASTVisitor {
+//TODO: check callers for handling of comments
+
+//---- Begin ASTNode Hierarchy -------------------------------------
+
+	/**
+	 * Visits the given AST node.
+	 * <p>
+	 * The default implementation does nothing and return true. Subclasses may reimplement.
+	 * </p>
+	 *
+	 * @param node the node to visit
+	 * @return <code>true</code> if the children of this node should be visited, and
+	 *         <code>false</code> if the children of this node should be skipped
+	 */
+	public boolean visit(ASTNode node) {
+		return true;
+	}
+
+	/**
+	 * End of visit the given AST node.
+	 * <p>
+	 * The default implementation does nothing. Subclasses may reimplement.
+	 * </p>
+	 *
+	 * @param node the node to visit
+	 */
+	public void endVisit(ASTNode node) {
+		// do nothing
+	}
+
+	@Override
+	public boolean visit(AnonymousClassDeclaration node) {
+		return visit((ASTNode)node);
+	}
+
+	@Override
+	public void endVisit(AnonymousClassDeclaration node) {
+		endVisit((ASTNode)node);
+	}
+
+//---- Begin BodyDeclaration Hierarchy ---------------------------
+	public boolean visit(BodyDeclaration node) {
+		return visit((ASTNode)node);
+	}
+
+	public void endVisit(BodyDeclaration node) {
+		endVisit((ASTNode)node);
+	}
+
+	//---- Begin AbstractTypeDeclaration Hierarchy ---------------------------
+	public boolean visit(AbstractTypeDeclaration node) {
+		return visit((BodyDeclaration)node);
+	}
+
+	public void endVisit(AbstractTypeDeclaration node) {
+		endVisit((BodyDeclaration)node);
+	}
+
+	@Override
+	public boolean visit(AnnotationTypeDeclaration node) {
+		return visit((AbstractTypeDeclaration)node);
+	}
+
+	@Override
+	public void endVisit(AnnotationTypeDeclaration node) {
+		endVisit((AbstractTypeDeclaration)node);
+	}
+
+	@Override
+	public boolean visit(EnumDeclaration node) {
+		return visit((AbstractTypeDeclaration)node);
+	}
+
+	@Override
+	public void endVisit(EnumDeclaration node) {
+		endVisit((AbstractTypeDeclaration)node);
+	}
+
+	@Override
+	public boolean visit(TypeDeclaration node) {
+		return visit((AbstractTypeDeclaration)node);
+	}
+
+	@Override
+	public void endVisit(TypeDeclaration node) {
+		endVisit((AbstractTypeDeclaration)node);
+	}
+
+	//---- End AbstractTypeDeclaration Hierarchy ---------------------------
+
+	@Override
+	public boolean visit(AnnotationTypeMemberDeclaration node) {
+		return visit((BodyDeclaration)node);
+	}
+
+	@Override
+	public void endVisit(AnnotationTypeMemberDeclaration node) {
+		endVisit((BodyDeclaration)node);
+	}
+
+	@Override
+	public boolean visit(EnumConstantDeclaration node) {
+		return visit((BodyDeclaration)node);
+	}
+
+	@Override
+	public void endVisit(EnumConstantDeclaration node) {
+		endVisit((BodyDeclaration)node);
+	}
+
+	@Override
+	public boolean visit(FieldDeclaration node) {
+		return visit((BodyDeclaration)node);
+	}
+
+	@Override
+	public void endVisit(FieldDeclaration node) {
+		endVisit((BodyDeclaration)node);
+	}
+
+	@Override
+	public boolean visit(Initializer node) {
+		return visit((BodyDeclaration)node);
+	}
+
+	@Override
+	public void endVisit(Initializer node) {
+		endVisit((BodyDeclaration)node);
+	}
+
+	@Override
+	public boolean visit(MethodDeclaration node) {
+		return visit((BodyDeclaration)node);
+	}
+
+	@Override
+	public void endVisit(MethodDeclaration node) {
+		endVisit((BodyDeclaration)node);
+	}
+
+//---- End BodyDeclaration Hierarchy -----------------------------
+
+	@Override
+	public boolean visit(CatchClause node) {
+		return visit((ASTNode)node);
+	}
+
+	@Override
+	public void endVisit(CatchClause node) {
+		endVisit((ASTNode)node);
+	}
+
+//---- Begin Comment Hierarchy ----------------------------------
+	public boolean visit(Comment node) {
+		return visit((ASTNode)node);
+	}
+
+	public void endVisit(Comment node) {
+		endVisit((ASTNode)node);
+	}
+
+	@Override
+	public boolean visit(BlockComment node) {
+		return visit((Comment)node);
+	}
+
+	@Override
+	public void endVisit(BlockComment node) {
+		endVisit((Comment)node);
+	}
+
+	@Override
+	public boolean visit(Javadoc node) {
+		return visit((Comment)node);
+	}
+
+	@Override
+	public void endVisit(Javadoc node) {
+		endVisit((Comment)node);
+	}
+
+	@Override
+	public boolean visit(LineComment node) {
+		return visit((Comment)node);
+	}
+
+	@Override
+	public void endVisit(LineComment node) {
+		endVisit((Comment)node);
+	}
+
+//---- End Comment Hierarchy -----------------------------
+
+	@Override
+	public boolean visit(CompilationUnit node) {
+		return visit((ASTNode)node);
+	}
+
+	@Override
+	public void endVisit(CompilationUnit node) {
+		endVisit((ASTNode)node);
+	}
+
+	@Override
+	public boolean visit(Dimension node) {
+		return visit((ASTNode)node);
+	}
+
+	@Override
+	public void endVisit(Dimension node) {
+		endVisit((ASTNode)node);
+	}
+
+//---- Begin Expression Hierarchy ----------------------------------
+	public boolean visit(Expression node) {
+		return visit((ASTNode)node);
+	}
+
+	public void endVisit(Expression node) {
+		endVisit((ASTNode)node);
+	}
+
+	//---- Begin Annotation Hierarchy ----------------------------------
+	public boolean visit(Annotation node) {
+		return visit((Expression)node);
+	}
+
+	public void endVisit(Annotation node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(MarkerAnnotation node) {
+		return visit((Annotation)node);
+	}
+
+	@Override
+	public void endVisit(MarkerAnnotation node) {
+		endVisit((Annotation)node);
+	}
+
+	@Override
+	public boolean visit(NormalAnnotation node) {
+		return visit((Annotation)node);
+	}
+
+	@Override
+	public void endVisit(NormalAnnotation node) {
+		endVisit((Annotation)node);
+	}
+
+	@Override
+	public boolean visit(SingleMemberAnnotation node) {
+		return visit((Annotation)node);
+	}
+
+	@Override
+	public void endVisit(SingleMemberAnnotation node) {
+		endVisit((Annotation)node);
+	}
+
+	//---- End Annotation Hierarchy -----------------------------
+
+	@Override
+	public boolean visit(ArrayAccess node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(ArrayAccess node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(ArrayCreation node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(ArrayCreation node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(ArrayInitializer node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(ArrayInitializer node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(Assignment node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(Assignment node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(BooleanLiteral node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(BooleanLiteral node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(CastExpression node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(CastExpression node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(CharacterLiteral node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(CharacterLiteral node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(ClassInstanceCreation node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(ClassInstanceCreation node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(ConditionalExpression node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(ConditionalExpression node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(FieldAccess node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(FieldAccess node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(InfixExpression node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(InfixExpression node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(InstanceofExpression node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(InstanceofExpression node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(LambdaExpression node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(LambdaExpression node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(MethodInvocation node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(MethodInvocation node) {
+		endVisit((Expression)node);
+	}
+
+	//---- Begin MethodReference Hierarchy ----------------------------------
+	public boolean visit(MethodReference node) {
+		return visit((Expression)node);
+	}
+
+	public void endVisit(MethodReference node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(CreationReference node) {
+		return visit((MethodReference)node);
+	}
+
+	@Override
+	public void endVisit(CreationReference node) {
+		endVisit((MethodReference)node);
+	}
+
+	@Override
+	public boolean visit(ExpressionMethodReference node) {
+		return visit((MethodReference)node);
+	}
+
+	@Override
+	public void endVisit(ExpressionMethodReference node) {
+		endVisit((MethodReference)node);
+	}
+
+	@Override
+	public boolean visit(SuperMethodReference node) {
+		return visit((MethodReference)node);
+	}
+
+	@Override
+	public void endVisit(SuperMethodReference node) {
+		endVisit((MethodReference)node);
+	}
+
+	@Override
+	public boolean visit(TypeMethodReference node) {
+		return visit((MethodReference)node);
+	}
+
+	@Override
+	public void endVisit(TypeMethodReference node) {
+		endVisit((MethodReference)node);
+	}
+
+	//---- End MethodReference Hierarchy ------------------------------------
+
+	//---- Begin Name Hierarchy ----------------------------------
+	public boolean visit(Name node) {
+		return visit((Expression)node);
+	}
+
+	public void endVisit(Name node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(QualifiedName node) {
+		return visit((Name)node);
+	}
+
+	@Override
+	public void endVisit(QualifiedName node) {
+		endVisit((Name)node);
+	}
+
+	@Override
+	public boolean visit(SimpleName node) {
+		return visit((Name)node);
+	}
+
+	@Override
+	public void endVisit(SimpleName node) {
+		endVisit((Name)node);
+	}
+
+	//---- End Name Hierarchy ------------------------------------
+
+	@Override
+	public boolean visit(NullLiteral node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(NullLiteral node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(NumberLiteral node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(NumberLiteral node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(ParenthesizedExpression node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(ParenthesizedExpression node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(PostfixExpression node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(PostfixExpression node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(PrefixExpression node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(PrefixExpression node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(StringLiteral node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(StringLiteral node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(SuperFieldAccess node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(SuperFieldAccess node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(SuperMethodInvocation node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(SuperMethodInvocation node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(ThisExpression node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(ThisExpression node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(TypeLiteral node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(TypeLiteral node) {
+		endVisit((Expression)node);
+	}
+
+	@Override
+	public boolean visit(VariableDeclarationExpression node) {
+		return visit((Expression)node);
+	}
+
+	@Override
+	public void endVisit(VariableDeclarationExpression node) {
+		endVisit((Expression)node);
+	}
+
+	//---- End Expression Hierarchy ----------------------------------
+
+	@Override
+	public boolean visit(ImportDeclaration node) {
+		return visit((ASTNode)node);
+	}
+
+	@Override
+	public void endVisit(ImportDeclaration node) {
+		endVisit((ASTNode)node);
+	}
+
+	@Override
+	public boolean visit(MemberRef node) {
+		return visit((ASTNode)node);
+	}
+
+	@Override
+	public void endVisit(MemberRef node) {
+		endVisit((ASTNode)node);
+	}
+
+	@Override
+	public boolean visit(MemberValuePair node) {
+		return visit((ASTNode)node);
+	}
+
+	@Override
+	public void endVisit(MemberValuePair node) {
+		endVisit((ASTNode)node);
+	}
+
+	@Override
+	public boolean visit(MethodRef node) {
+		return visit((ASTNode)node);
+	}
+
+	@Override
+	public void endVisit(MethodRef node) {
+		endVisit((ASTNode)node);
+	}
+
+	@Override
+	public boolean visit(MethodRefParameter node) {
+		return visit((ASTNode)node);
+	}
+
+	@Override
+	public void endVisit(MethodRefParameter node) {
+		endVisit((ASTNode)node);
+	}
+
+	@Override
+	public boolean visit(Modifier node) {
+		return visit((ASTNode)node);
+	}
+
+	@Override
+	public void endVisit(Modifier node) {
+		endVisit((ASTNode)node);
+	}
+
+	@Override
+	public boolean visit(PackageDeclaration node) {
+		return visit((ASTNode)node);
+	}
+
+	@Override
+	public void endVisit(PackageDeclaration node) {
+		endVisit((ASTNode)node);
+	}
+
+//---- Begin Statement Hierarchy ---------------------------------
+	public boolean visit(Statement node) {
+		return visit((ASTNode)node);
+	}
+
+	public void endVisit(Statement node) {
+		endVisit((ASTNode)node);
+	}
+
+
+	@Override
+	public boolean visit(AssertStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(AssertStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(Block node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(Block node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(BreakStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(BreakStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(ConstructorInvocation node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(ConstructorInvocation node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(ContinueStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(ContinueStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(DoStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(DoStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(EmptyStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(EmptyStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(EnhancedForStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(EnhancedForStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(ExpressionStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(ExpressionStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(ForStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(ForStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(IfStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(IfStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(LabeledStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(LabeledStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(ReturnStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(ReturnStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(SuperConstructorInvocation node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(SuperConstructorInvocation node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(SwitchCase node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(SwitchCase node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(SwitchStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(SwitchStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(SynchronizedStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(SynchronizedStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(ThrowStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(ThrowStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(TryStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(TryStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(TypeDeclarationStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(TypeDeclarationStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(VariableDeclarationStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(VariableDeclarationStatement node) {
+		endVisit((Statement)node);
+	}
+
+	@Override
+	public boolean visit(WhileStatement node) {
+		return visit((Statement)node);
+	}
+
+	@Override
+	public void endVisit(WhileStatement node) {
+		endVisit((Statement)node);
+	}
+
+//---- End Statement Hierarchy ----------------------------------
+
+	@Override
+	public boolean visit(TagElement node) {
+		return visit((ASTNode)node);
+	}
+
+	@Override
+	public void endVisit(TagElement node) {
+		endVisit((ASTNode)node);
+	}
+
+	@Override
+	public boolean visit(TextElement node) {
+		return visit((ASTNode)node);
+	}
+
+	@Override
+	public void endVisit(TextElement node) {
+		endVisit((ASTNode)node);
+	}
+
+
+//---- Begin Type Hierarchy --------------------------------------
+	public boolean visit(Type node) {
+		return visit((ASTNode)node);
+	}
+
+	public void endVisit(Type node) {
+		endVisit((ASTNode)node);
+	}
+
+//---- Begin Annotatable Type Hierarchy --------------------------------------
+	public boolean visit(AnnotatableType node) {
+		return visit((Type)node);
+	}
+
+	public void endVisit(AnnotatableType node) {
+		endVisit((Type)node);
+	}
+
+	@Override
+	public boolean visit(NameQualifiedType node) {
+		return visit((AnnotatableType)node);
+	}
+
+	@Override
+	public void endVisit(NameQualifiedType node) {
+		endVisit((AnnotatableType)node);
+	}
+
+	@Override
+	public boolean visit(PrimitiveType node) {
+		return visit((AnnotatableType)node);
+	}
+
+	@Override
+	public void endVisit(PrimitiveType node) {
+		endVisit((AnnotatableType)node);
+	}
+
+	@Override
+	public boolean visit(QualifiedType node) {
+		return visit((AnnotatableType)node);
+	}
+
+	@Override
+	public void endVisit(QualifiedType node) {
+		endVisit((AnnotatableType)node);
+	}
+
+	@Override
+	public boolean visit(SimpleType node) {
+		return visit((AnnotatableType)node);
+	}
+
+	@Override
+	public void endVisit(SimpleType node) {
+		endVisit((AnnotatableType)node);
+	}
+
+	@Override
+	public boolean visit(WildcardType node) {
+		return visit((AnnotatableType)node);
+	}
+
+	@Override
+	public void endVisit(WildcardType node) {
+		endVisit((AnnotatableType)node);
+	}
+//---- End Annotatable Type Hierarchy --------------------------------------
+
+	@Override
+	public boolean visit(ArrayType node) {
+		return visit((Type)node);
+	}
+
+	@Override
+	public void endVisit(ArrayType node) {
+		endVisit((Type)node);
+	}
+
+	@Override
+	public boolean visit(IntersectionType node) {
+		return visit((Type)node);
+	}
+
+	@Override
+	public void endVisit(IntersectionType node) {
+		endVisit((Type)node);
+	}
+
+	@Override
+	public boolean visit(ParameterizedType node) {
+		return visit((Type)node);
+	}
+
+	@Override
+	public void endVisit(ParameterizedType node) {
+		endVisit((Type)node);
+	}
+
+	@Override
+	public boolean visit(UnionType node) {
+		return visit((Type)node);
+	}
+
+	@Override
+	public void endVisit(UnionType node) {
+		endVisit((Type)node);
+	}
+
+//---- End Type Hierarchy ----------------------------------------
+
+	@Override
+	public boolean visit(TypeParameter node) {
+		return visit((ASTNode)node);
+	}
+
+	@Override
+	public void endVisit(TypeParameter node) {
+		endVisit((ASTNode)node);
+	}
+
+
+//---- Begin VariableDeclaration Hierarchy ---------------------------
+	public boolean visit(VariableDeclaration node) {
+		return visit((ASTNode)node);
+	}
+
+	public void endVisit(VariableDeclaration node) {
+		endVisit((ASTNode)node);
+	}
+
+	@Override
+	public boolean visit(SingleVariableDeclaration node) {
+		return visit((VariableDeclaration)node);
+	}
+
+	@Override
+	public void endVisit(SingleVariableDeclaration node) {
+		endVisit((VariableDeclaration)node);
+	}
+
+	@Override
+	public boolean visit(VariableDeclarationFragment node) {
+		return visit((VariableDeclaration)node);
+	}
+
+	@Override
+	public void endVisit(VariableDeclarationFragment node) {
+		endVisit((VariableDeclaration)node);
+	}
+
+//---- End VariableDeclaration Hierarchy -----------------------------
+//---- End ASTNode Hierarchy -----------------------------------------
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/IndexTester.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/IndexTester.java
new file mode 100644
index 0000000..eba1d71
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/IndexTester.java
@@ -0,0 +1,454 @@
+/*******************************************************************************
+ * 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.util.Arrays;
+import java.util.Objects;
+
+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.impl.DoubleConstant;
+import org.eclipse.jdt.internal.compiler.impl.FloatConstant;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+
+public class IndexTester {
+
+	private static final class TypeAnnotationWrapper {
+		private IBinaryTypeAnnotation annotation;
+
+		public TypeAnnotationWrapper(IBinaryTypeAnnotation next) {
+			this.annotation = next;
+		}
+
+		@Override
+		public int hashCode() {
+			int hashCode;
+			int[] typePath = this.annotation.getTypePath();
+
+			hashCode = Arrays.hashCode(typePath);
+			hashCode = hashCode * 31 + this.annotation.getTargetType();
+			hashCode = hashCode * 31 + this.annotation.getTypeParameterIndex();
+			return hashCode;
+		}
+
+		@Override
+		public String toString() {
+			return this.annotation.toString();
+		}
+
+		@Override
+		public boolean equals(Object obj) {
+			if (obj.getClass() != TypeAnnotationWrapper.class) {
+				return false;
+			}
+
+			TypeAnnotationWrapper wrapper = (TypeAnnotationWrapper) obj;
+			IBinaryTypeAnnotation otherAnnotation = wrapper.annotation;
+
+			int[] typePath = this.annotation.getTypePath();
+			int[] otherTypePath = otherAnnotation.getTypePath();
+
+			if (!Arrays.equals(typePath, otherTypePath)) {
+				return false;
+			}
+
+			if (this.annotation.getTargetType() != otherAnnotation.getTargetType()) {
+				return false;
+			}
+
+			if (this.annotation.getBoundIndex() != otherAnnotation.getBoundIndex()) {
+				return false;
+			}
+
+			if (this.annotation.getMethodFormalParameterIndex() != otherAnnotation.getMethodFormalParameterIndex()) {
+				return false;
+			}
+
+			if (this.annotation.getSupertypeIndex() != otherAnnotation.getSupertypeIndex()) {
+				return false;
+			}
+
+			if (this.annotation.getThrowsTypeIndex() != otherAnnotation.getThrowsTypeIndex()) {
+				return false;
+			}
+
+			if (this.annotation.getTypeParameterIndex() != otherAnnotation.getTypeParameterIndex()) {
+				return false;
+			}
+
+			return IndexTester.isEqual(this.annotation.getAnnotation(), otherAnnotation.getAnnotation());
+		}
+	}
+
+	public static void testType(IBinaryType expected, IBinaryType actual) {
+		String contextPrefix = safeString(actual.getName());
+
+		IBinaryTypeAnnotation[] expectedTypeAnnotations = expected.getTypeAnnotations();
+		IBinaryTypeAnnotation[] actualTypeAnnotations = actual.getTypeAnnotations();
+
+		compareTypeAnnotations(contextPrefix, expectedTypeAnnotations, actualTypeAnnotations);
+
+		IBinaryAnnotation[] expectedBinaryAnnotations = expected.getAnnotations();
+		IBinaryAnnotation[] actualBinaryAnnotations = actual.getAnnotations();
+
+		compareAnnotations(contextPrefix, expectedBinaryAnnotations, actualBinaryAnnotations);
+
+		compareGenericSignatures(contextPrefix + ": The generic signature did not match", //$NON-NLS-1$
+				expected.getGenericSignature(), actual.getGenericSignature());
+
+		assertEquals(contextPrefix + ": The enclosing method name did not match", expected.getEnclosingMethod(), //$NON-NLS-1$
+				actual.getEnclosingMethod());
+		assertEquals(contextPrefix + ": The enclosing method name did not match", expected.getEnclosingTypeName(), //$NON-NLS-1$
+				actual.getEnclosingTypeName());
+
+		IBinaryField[] expectedFields = expected.getFields();
+		IBinaryField[] actualFields = actual.getFields();
+
+		if (expectedFields != actualFields) {
+			if (expectedFields == null && actualFields != null) {
+				throw new IllegalStateException(contextPrefix + "Expected fields was null -- actual fields were not"); //$NON-NLS-1$
+			}
+			if (expectedFields.length != actualFields.length) {
+				throw new IllegalStateException(
+						contextPrefix + "The expected and actual number of fields did not match"); //$NON-NLS-1$
+			}
+
+			for (int fieldIdx = 0; fieldIdx < actualFields.length; fieldIdx++) {
+				compareFields(contextPrefix, expectedFields[fieldIdx], actualFields[fieldIdx]);
+			}
+		}
+
+		// Commented this out because the "expected" values often appear to be invalid paths when the "actual"
+		// ones are correct.
+		assertEquals("The file name did not match", expected.getFileName(), actual.getFileName()); //$NON-NLS-1$
+		assertEquals("The interface names did not match", expected.getInterfaceNames(), actual.getInterfaceNames()); //$NON-NLS-1$
+
+		// Member types are not expected to match during indexing since the index uses discovered cross-references,
+		// not the member types encoded in the .class file.
+		// expected.getMemberTypes() != actual.getMemberTypes()
+
+		IBinaryMethod[] expectedMethods = expected.getMethods();
+		IBinaryMethod[] actualMethods = actual.getMethods();
+
+		if (expectedMethods != actualMethods) {
+			if (expectedMethods == null || actualMethods == null) {
+				throw new IllegalStateException("One of the method arrays was null"); //$NON-NLS-1$
+			}
+
+			if (expectedMethods.length != actualMethods.length) {
+				throw new IllegalStateException("The number of methods didn't match"); //$NON-NLS-1$
+			}
+
+			for (int i = 0; i < actualMethods.length; i++) {
+				IBinaryMethod actualMethod = actualMethods[i];
+				IBinaryMethod expectedMethod = expectedMethods[i];
+
+				compareMethods(contextPrefix, expectedMethod, actualMethod);
+			}
+		}
+
+		assertEquals("The missing type names did not match", expected.getMissingTypeNames(), //$NON-NLS-1$
+				actual.getMissingTypeNames());
+		assertEquals("The modifiers don't match", expected.getModifiers(), actual.getModifiers()); //$NON-NLS-1$
+		assertEquals("The names don't match.", expected.getName(), actual.getName()); //$NON-NLS-1$
+		assertEquals("The source name doesn't match", expected.getSourceName(), actual.getSourceName()); //$NON-NLS-1$
+		assertEquals("The superclass name doesn't match", expected.getSuperclassName(), actual.getSuperclassName()); //$NON-NLS-1$
+		assertEquals("The tag bits don't match.", expected.getTagBits(), actual.getTagBits()); //$NON-NLS-1$
+
+		compareTypeAnnotations(contextPrefix, expected.getTypeAnnotations(), actual.getTypeAnnotations());
+	}
+
+	private static <T> void assertEquals(String message, T o1, T o2) {
+		if (!isEqual(o1, o2)) {
+			throw new IllegalStateException(message + ": expected = " + getString(o1) + ", actual = " + getString(o2)); //$NON-NLS-1$//$NON-NLS-2$
+		}
+	}
+
+	private static String getString(Object object) {
+		if (object instanceof char[]) {
+			char[] charArray = (char[]) object;
+
+			return new String(charArray);
+		}
+		return object.toString();
+	}
+
+	static <T> boolean isEqual(T o1, T o2) {
+		if (o1 == o2) {
+			return true;
+		}
+
+		if (o1 == null || o2 == null) {
+			return false;
+		}
+
+		if (o1 instanceof ClassSignature) {
+			if (!(o2 instanceof ClassSignature)) {
+				return false;
+			}
+
+			ClassSignature sig1 = (ClassSignature) o1;
+			ClassSignature sig2 = (ClassSignature) o2;
+
+			return Arrays.equals(sig1.getTypeName(), sig2.getTypeName());
+		}
+
+		if (o1 instanceof IBinaryAnnotation) {
+			IBinaryAnnotation binaryAnnotation = (IBinaryAnnotation) o1;
+			IBinaryAnnotation otherBinaryAnnotation = (IBinaryAnnotation) o2;
+			IBinaryElementValuePair[] elementValuePairs = binaryAnnotation.getElementValuePairs();
+			IBinaryElementValuePair[] otherElementValuePairs = otherBinaryAnnotation.getElementValuePairs();
+
+			if (elementValuePairs.length != otherElementValuePairs.length) {
+				return false;
+			}
+
+			for (int idx = 0; idx < elementValuePairs.length; idx++) {
+				IBinaryElementValuePair next = elementValuePairs[idx];
+				IBinaryElementValuePair otherNext = otherElementValuePairs[idx];
+
+				char[] nextName = next.getName();
+				char[] otherNextName = otherNext.getName();
+
+				if (!Arrays.equals(nextName, otherNextName)) {
+					return false;
+				}
+
+				if (!isEqual(next.getValue(), otherNext.getValue())) {
+					return false;
+				}
+			}
+			return true;
+		}
+
+		if (o1 instanceof IBinaryTypeAnnotation) {
+			IBinaryTypeAnnotation binaryAnnotation = (IBinaryTypeAnnotation)o1;
+			IBinaryTypeAnnotation otherBinaryAnnotation = (IBinaryTypeAnnotation)o2;
+
+			return new TypeAnnotationWrapper(binaryAnnotation).equals(new TypeAnnotationWrapper(otherBinaryAnnotation));
+		}
+
+		if (o1 instanceof Constant) {
+			if (!(o2 instanceof Constant)) {
+				return false;
+			}
+
+			if (o1 instanceof DoubleConstant && o2 instanceof DoubleConstant) {
+				DoubleConstant d1 = (DoubleConstant) o1;
+				DoubleConstant d2 = (DoubleConstant) o2;
+
+				if (Double.isNaN(d1.doubleValue()) && Double.isNaN(d2.doubleValue())) {
+					return true;
+				}
+			}
+
+			if (o1 instanceof FloatConstant && o2 instanceof FloatConstant) {
+				FloatConstant d1 = (FloatConstant) o1;
+				FloatConstant d2 = (FloatConstant) o2;
+
+				if (Float.isNaN(d1.floatValue()) && Float.isNaN(d2.floatValue())) {
+					return true;
+				}
+			}
+
+			Constant const1 = (Constant) o1;
+			Constant const2 = (Constant) o2;
+
+			return const1.hasSameValue(const2);
+		}
+
+		if (o1 instanceof EnumConstantSignature) {
+			if (!(o2 instanceof EnumConstantSignature)) {
+				return false;
+			}
+
+			EnumConstantSignature enum1 = (EnumConstantSignature) o1;
+			EnumConstantSignature enum2 = (EnumConstantSignature) o2;
+
+			return Arrays.equals(enum1.getEnumConstantName(), enum2.getEnumConstantName())
+					&& Arrays.equals(enum1.getTypeName(), enum2.getTypeName());
+		}
+
+		if (o1 instanceof char[]) {
+			char[] c1 = (char[]) o1;
+			char[] c2 = (char[]) o2;
+
+			return CharArrayUtils.equals(c1, c2);
+		}
+
+		if (o1 instanceof char[][]) {
+			char[][] c1 = (char[][]) o1;
+			char[][] c2 = (char[][]) o2;
+
+			return CharArrayUtils.equals(c1, c2);
+		}
+
+		if (o1 instanceof char[][][]) {
+			char[][][] c1 = (char[][][]) o1;
+			char[][][] c2 = (char[][][]) o2;
+
+			if (c1.length != c2.length) {
+				return false;
+			}
+
+			for (int i = 0; i < c1.length; i++) {
+				if (!isEqual(c1[i], c2[i])) {
+					return false;
+				}
+			}
+			return true;
+		}
+
+		if (o1 instanceof Object[]) {
+			Object[] a1 = (Object[]) o1;
+			Object[] a2 = (Object[]) o2;
+
+			if (a1.length != a2.length) {
+				return false;
+			}
+
+			for (int idx = 0; idx < a1.length; idx++) {
+				if (!isEqual(a1[idx], a2[idx])) {
+					return false;
+				}
+			}
+			return true;
+		}
+
+		return Objects.equals(o1, o2);
+	}
+
+	private static void compareMethods(String contextPrefix, IBinaryMethod expectedMethod, IBinaryMethod actualMethod) {
+		contextPrefix = contextPrefix + "." + safeString(expectedMethod.getSelector()); //$NON-NLS-1$
+		compareAnnotations(contextPrefix, expectedMethod.getAnnotations(), actualMethod.getAnnotations());
+
+		assertEquals(contextPrefix + ": The argument names didn't match.", expectedMethod.getArgumentNames(), //$NON-NLS-1$
+				actualMethod.getArgumentNames());
+
+		assertEquals(contextPrefix + ": The default values didn't match.", expectedMethod.getDefaultValue(), //$NON-NLS-1$
+				actualMethod.getDefaultValue());
+
+		assertEquals(contextPrefix + ": The exception type names did not match.", //$NON-NLS-1$
+				expectedMethod.getExceptionTypeNames(), actualMethod.getExceptionTypeNames());
+
+		compareGenericSignatures(contextPrefix + ": The method's generic signature did not match", //$NON-NLS-1$
+				expectedMethod.getGenericSignature(), actualMethod.getGenericSignature());
+
+		assertEquals(contextPrefix + ": The method descriptors did not match.", expectedMethod.getMethodDescriptor(), //$NON-NLS-1$
+				actualMethod.getMethodDescriptor());
+		assertEquals(contextPrefix + ": The modifiers didn't match.", expectedMethod.getModifiers(), //$NON-NLS-1$
+				actualMethod.getModifiers());
+
+		char[] classFileName = "".toCharArray(); //$NON-NLS-1$
+		int minAnnotatedParameters = Math.min(expectedMethod.getAnnotatedParametersCount(),
+				actualMethod.getAnnotatedParametersCount());
+		for (int idx = 0; idx < minAnnotatedParameters; idx++) {
+			compareAnnotations(contextPrefix, expectedMethod.getParameterAnnotations(idx, classFileName),
+					actualMethod.getParameterAnnotations(idx, classFileName));
+		}
+		for (int idx = minAnnotatedParameters; idx < expectedMethod.getAnnotatedParametersCount(); idx++) {
+			compareAnnotations(contextPrefix, new IBinaryAnnotation[0],
+					expectedMethod.getParameterAnnotations(idx, classFileName));
+		}
+		for (int idx = minAnnotatedParameters; idx < actualMethod.getAnnotatedParametersCount(); idx++) {
+			compareAnnotations(contextPrefix, new IBinaryAnnotation[0],
+					actualMethod.getParameterAnnotations(idx, classFileName));
+		}
+
+		assertEquals(contextPrefix + ": The selectors did not match", expectedMethod.getSelector(), //$NON-NLS-1$
+				actualMethod.getSelector());
+		assertEquals(contextPrefix + ": The tag bits did not match", expectedMethod.getTagBits(), //$NON-NLS-1$
+				actualMethod.getTagBits());
+
+		compareTypeAnnotations(contextPrefix, expectedMethod.getTypeAnnotations(), actualMethod.getTypeAnnotations());
+	}
+
+	/**
+	 * The index always provides complete generic signatures whereas some or all of the generic signature is optional
+	 * for class files, so the generic signatures are expected to differ in certain situations.
+	 */
+	private static void compareGenericSignatures(String message, char[] expected, char[] actual) {
+		assertEquals(message, expected, actual);
+	}
+
+	private static void compareTypeAnnotations(String contextPrefix, IBinaryTypeAnnotation[] expectedTypeAnnotations,
+			IBinaryTypeAnnotation[] actualTypeAnnotations) {
+		if (expectedTypeAnnotations == null) {
+			if (actualTypeAnnotations != null) {
+				throw new IllegalStateException(contextPrefix + ": Expected null for the annotation list but found: " //$NON-NLS-1$
+						+ actualTypeAnnotations.toString());
+			}
+			return;
+		}
+
+		assertEquals(contextPrefix + ": The expected and actual number of type annotations did not match", //$NON-NLS-1$
+				expectedTypeAnnotations.length, actualTypeAnnotations.length);
+
+		for (int idx = 0; idx < expectedTypeAnnotations.length; idx++) {
+			assertEquals(contextPrefix + ": Type annotation number " + idx + " did not match", //$NON-NLS-1$//$NON-NLS-2$
+					expectedTypeAnnotations[idx], actualTypeAnnotations[idx]);
+		}
+	}
+
+	private static void compareAnnotations(String contextPrefix, IBinaryAnnotation[] expectedBinaryAnnotations,
+			IBinaryAnnotation[] actualBinaryAnnotations) {
+		if (expectedBinaryAnnotations == null || expectedBinaryAnnotations.length == 0) {
+			if (actualBinaryAnnotations != null && actualBinaryAnnotations.length != 0) {
+				throw new IllegalStateException(contextPrefix + ": Expected null for the binary annotations"); //$NON-NLS-1$
+			} else {
+				return;
+			}
+		}
+		if (actualBinaryAnnotations == null) {
+			throw new IllegalStateException(contextPrefix + ": Actual null for the binary annotations"); //$NON-NLS-1$
+		}
+		if (expectedBinaryAnnotations.length != actualBinaryAnnotations.length) {
+			throw new IllegalStateException(
+					contextPrefix + ": The expected and actual number of annotations differed. Expected: " //$NON-NLS-1$
+							+ expectedBinaryAnnotations.length + ", actual: " + actualBinaryAnnotations.length); //$NON-NLS-1$
+		}
+
+		for (int idx = 0; idx < expectedBinaryAnnotations.length; idx++) {
+			if (!isEqual(expectedBinaryAnnotations[idx], actualBinaryAnnotations[idx])) {
+				throw new IllegalStateException(contextPrefix + ": An annotation had an unexpected value"); //$NON-NLS-1$
+			}
+		}
+	}
+
+	private static void compareFields(String contextPrefix, IBinaryField field1, IBinaryField field2) {
+		contextPrefix = contextPrefix + "." + safeString(field1.getName()); //$NON-NLS-1$
+		compareAnnotations(contextPrefix, field1.getAnnotations(), field2.getAnnotations());
+		assertEquals(contextPrefix + ": Constants not equal", field1.getConstant(), field2.getConstant()); //$NON-NLS-1$
+		compareGenericSignatures(contextPrefix + ": The generic signature did not match", field1.getGenericSignature(), //$NON-NLS-1$
+				field2.getGenericSignature());
+		assertEquals(contextPrefix + ": The modifiers did not match", field1.getModifiers(), field2.getModifiers()); //$NON-NLS-1$
+		assertEquals(contextPrefix + ": The tag bits did not match", field1.getTagBits(), field2.getTagBits()); //$NON-NLS-1$
+		assertEquals(contextPrefix + ": The names did not match", field1.getName(), field2.getName()); //$NON-NLS-1$
+
+		compareTypeAnnotations(contextPrefix, field1.getTypeAnnotations(), field2.getTypeAnnotations());
+		assertEquals(contextPrefix + ": The type names did not match", field1.getTypeName(), field2.getTypeName()); //$NON-NLS-1$
+	}
+
+	private static String safeString(char[] name) {
+		if (name == null) {
+			return "<unnamed>"; //$NON-NLS-1$
+		}
+		return new String(name);
+	}
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Indexer.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Indexer.java
new file mode 100644
index 0000000..b8f4978
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Indexer.java
@@ -0,0 +1,1069 @@
+/*******************************************************************************
+ * 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.io.File;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.ICoreRunnable;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jdt.core.IClassFile;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaElementDelta;
+import org.eclipse.jdt.core.IJavaModelStatusConstants;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.IParent;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.env.IDependent;
+import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
+import org.eclipse.jdt.internal.core.JarPackageFragmentRoot;
+import org.eclipse.jdt.internal.core.JavaElementDelta;
+import org.eclipse.jdt.internal.core.JavaModel;
+import org.eclipse.jdt.internal.core.JavaModelManager;
+import org.eclipse.jdt.internal.core.nd.IReader;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.java.FileFingerprint;
+import org.eclipse.jdt.internal.core.nd.java.FileFingerprint.FingerprintTestResult;
+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.NdBinding;
+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.NdTypeId;
+import org.eclipse.jdt.internal.core.nd.java.NdWorkspaceLocation;
+import org.eclipse.jdt.internal.core.nd.java.TypeRef;
+import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeDescriptor;
+import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeFactory;
+import org.eclipse.jdt.internal.core.nd.java.model.IndexBinaryType;
+import org.eclipse.jdt.internal.core.search.processing.IJob;
+
+public final class Indexer {
+	private Nd nd;
+	private IWorkspaceRoot root;
+
+	private static Indexer indexer;
+	public static boolean DEBUG;
+	public static boolean DEBUG_ALLOCATIONS;
+	public static boolean DEBUG_TIMING;
+	public static boolean DEBUG_INSERTIONS;
+	public static boolean DEBUG_SELFTEST;
+
+	/**
+	 * True iff automatic reindexing (that is, the {@link #rescanAll()} method) is disabled
+	 * Synchronize on {@link #automaticIndexingMutex} while accessing.
+	 */
+	private boolean enableAutomaticIndexing = true;
+	/**
+	 * True iff any code tried to schedule reindexing while automatic reindexing was disabled.
+	 * Synchronize on {@link #automaticIndexingMutex} while accessing.
+	 */
+	private boolean indexerDirtiedWhileDisabled = false;
+	private final Object automaticIndexingMutex = new Object();
+
+	/**
+	 * Enable this to index the content of output folders, in cases where that content exists and
+	 * is up-to-date. This is much faster than indexing source files directly.
+	 */
+	public static boolean EXPERIMENTAL_INDEX_OUTPUT_FOLDERS;
+	private static final Object mutex = new Object();
+	private static final long MS_TO_NS = 1000000;
+
+	private Object listenersMutex = new Object();
+	/**
+	 * Listener list. Copy-on-write. Synchronize on "listenersMutex" before accessing.
+	 */
+	private Set<Listener> listeners = Collections.newSetFromMap(new WeakHashMap<Listener, Boolean>());
+
+	private Job rescanJob = Job.create(Messages.Indexer_updating_index_job_name, new ICoreRunnable() {
+		@Override
+		public void run(IProgressMonitor monitor) throws CoreException {
+			rescan(monitor);
+		}
+	});
+
+	public static interface Listener {
+		void consume(IndexerEvent event);
+	}
+
+	public static Indexer getInstance() {
+		synchronized (mutex) {
+			if (indexer == null) {
+				indexer = new Indexer(JavaIndex.getGlobalNd(), ResourcesPlugin.getWorkspace().getRoot());
+			}
+			return indexer;
+		}
+	}
+
+	/**
+	 * Enables or disables the "rescanAll" method. When set to false, rescanAll does nothing
+	 * and indexing will only be triggered when invoking {@link #waitForIndex}.
+	 * <p>
+	 * Normally the indexer runs automatically and asynchronously when resource changes occur.
+	 * However, if this variable is set to false the indexer only runs when someone invokes
+	 * {@link #waitForIndex(IProgressMonitor)}. This can be used to eliminate race conditions
+	 * when running the unit tests, since indexing will not occur unless it is triggered
+	 * explicitly.
+	 * <p>
+	 * Synchronize on {@link #automaticIndexingMutex} before accessing. 
+	 */
+	public void enableAutomaticIndexing(boolean enabled) {
+		boolean runRescan = false;
+		synchronized (this.automaticIndexingMutex) {
+			if (this.enableAutomaticIndexing == enabled) {
+				return;
+			}
+			this.enableAutomaticIndexing = enabled;
+			if (enabled && this.indexerDirtiedWhileDisabled) {
+				runRescan = true;
+			}
+		}
+
+		if (runRescan) {
+			// Force a rescan when re-enabling automatic indexing since we may have missed an update
+			this.rescanJob.schedule();
+		}
+
+		if (!enabled) {
+			// Wait for any existing indexing operations to finish when disabling automatic indexing since
+			// we only want explicitly-triggered indexing operations to run after the method returns
+			try {
+				this.rescanJob.join(0, null);
+			} catch (OperationCanceledException | InterruptedException e) {
+				// Don't care
+			}
+		}
+	}
+
+	/**
+	 * Amount of time (milliseconds) unreferenced files are allowed to sit in the index before they are discarded.
+	 * Making this too short will cause some operations (classpath modifications, closing/reopening projects, etc.)
+	 * to become more expensive. Making this too long will waste space in the database.
+	 * <p>
+	 * The value of this is stored in the JDT core preference called "garbageCleanupTimeoutMs". The default value
+	 * is 3 days.
+	 */
+	private static long getGarbageCleanupTimeout() {
+		return Platform.getPreferencesService().getLong(JavaCore.PLUGIN_ID, "garbageCleanupTimeoutMs", //$NON-NLS-1$
+				1000 * 60 * 60 * 24 * 3,
+				null);
+	}
+
+	/**
+	 * Amount of time (milliseconds) before we update the "used" timestamp on a file in the index. We don't update
+	 * the timestamps every update since doing so would be unnecessarily inefficient... but if any of the timestamps
+	 * is older than this update period, we refresh it.
+	 */
+	private static long getUsageTimestampUpdatePeriod() {
+		return getGarbageCleanupTimeout() / 4;
+	}
+
+	public void rescan(IProgressMonitor monitor) throws CoreException {
+		SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
+
+		synchronized (this.automaticIndexingMutex) {
+			this.indexerDirtiedWhileDisabled = false;
+		}
+
+		long startTimeNs = System.nanoTime();
+		long currentTimeMs = System.currentTimeMillis();
+		if (DEBUG) {
+			Package.logInfo("Indexer running rescan"); //$NON-NLS-1$
+		}
+
+		// Gather all the IPackageFragmentRoots in the workspace
+		List<IJavaElement> unfilteredIndexables = getAllIndexableObjectsInWorkspace(subMonitor.split(3));
+
+		int totalIndexables = unfilteredIndexables.size();
+		// Remove all duplicate indexables (jars which are referenced by more than one project)
+		Map<IPath, List<IJavaElement>> allIndexables = removeDuplicatePaths(unfilteredIndexables);
+
+		long startGarbageCollectionNs = System.nanoTime();
+
+		// Remove all files in the index which aren't referenced in the workspace
+		int gcFiles = cleanGarbage(currentTimeMs, allIndexables.keySet(), subMonitor.split(4));
+
+		long startFingerprintTestNs = System.nanoTime();
+
+		Map<IPath, FingerprintTestResult> fingerprints = testFingerprints(allIndexables.keySet(), subMonitor.split(7));
+		Set<IPath> indexablesWithChanges = new HashSet<>(getIndexablesThatHaveChanged(allIndexables.keySet(), fingerprints));
+
+		long startIndexingNs = System.nanoTime();
+
+		int classesIndexed = 0;
+		SubMonitor loopMonitor = subMonitor.split(80).setWorkRemaining(indexablesWithChanges.size());
+		for (IPath next : indexablesWithChanges) {
+			classesIndexed += rescanArchive(currentTimeMs, next, allIndexables.get(next), fingerprints.get(next).getNewFingerprint(),
+					loopMonitor.split(1));
+		}
+
+		long endIndexingNs = System.nanoTime();
+
+		Map<IPath, List<IJavaElement>> pathsToUpdate = new HashMap<>();
+
+		for (IPath next : allIndexables.keySet()) {
+			if (!indexablesWithChanges.contains(next)) {
+				pathsToUpdate.put(next, allIndexables.get(next));
+				continue;
+			}
+		}
+
+		updateResourceMappings(pathsToUpdate, subMonitor.split(5));
+
+		// Flush the database to disk
+		this.nd.acquireWriteLock(subMonitor.split(4));
+		try {
+			this.nd.getDB().flush();
+		} finally {
+			this.nd.releaseWriteLock();
+		}
+
+		fireDelta(indexablesWithChanges, subMonitor.split(1));
+
+		if (DEBUG) {
+			Package.logInfo("Rescan finished"); //$NON-NLS-1$
+		}
+
+		long endResourceMappingNs = System.nanoTime();
+
+		long fingerprintTimeMs = (startIndexingNs - startFingerprintTestNs) / MS_TO_NS;
+		long locateIndexablesTimeMs = (startGarbageCollectionNs - startTimeNs) / MS_TO_NS;
+		long garbageCollectionMs = (startFingerprintTestNs - startGarbageCollectionNs) / MS_TO_NS;
+		long indexingTimeMs = (endIndexingNs - startIndexingNs) / MS_TO_NS;
+		long resourceMappingTimeMs = (endResourceMappingNs - endIndexingNs) / MS_TO_NS;
+
+		double averageGcTimeMs = gcFiles == 0 ? 0 : (double)garbageCollectionMs / (double)gcFiles;
+		double averageIndexTimeMs = classesIndexed == 0 ? 0 : (double)indexingTimeMs / (double)classesIndexed;
+		double averageFingerprintTimeMs = allIndexables.size() == 0 ? 0 : (double)fingerprintTimeMs / (double)allIndexables.size();
+		double averageResourceMappingMs = pathsToUpdate.size() == 0 ? 0 : (double)resourceMappingTimeMs / (double)pathsToUpdate.size();
+
+		if (DEBUG_TIMING) {
+			Package.logInfo(
+					"Indexing done.\n" //$NON-NLS-1$
+					+ "  Located " + totalIndexables + " indexables in " + locateIndexablesTimeMs + "ms\n" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+					+ "  Collected garbage from " + gcFiles + " files in " +  garbageCollectionMs + "ms, average time = " + averageGcTimeMs + "ms\n" //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$
+					+ "  Tested " + allIndexables.size() + " fingerprints in " + fingerprintTimeMs + "ms, average time = " + averageFingerprintTimeMs + "ms\n" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+					+ "  Indexed " + classesIndexed + " classes in " + indexingTimeMs + "ms, average time = " + averageIndexTimeMs + "ms\n" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+					+ "  Updated " + pathsToUpdate.size() + " paths in " + resourceMappingTimeMs + "ms, average time = " + averageResourceMappingMs + "ms\n"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$
+		}
+
+		if (DEBUG_ALLOCATIONS) {
+			try (IReader readLock = this.nd.acquireReadLock()) {
+				this.nd.getDB().reportFreeBlocks();
+				this.nd.getDB().getMemoryStats().printMemoryStats(this.nd.getTypeRegistry());
+			}
+		}
+	}
+
+	private void fireDelta(Set<IPath> indexablesWithChanges, IProgressMonitor monitor) {
+		SubMonitor subMonitor = SubMonitor.convert(monitor, 1);
+		IProject[] projects = this.root.getProjects();
+
+		List<IProject> projectsToScan = new ArrayList<>();
+
+		for (IProject next : projects) {
+			if (next.isOpen()) {
+				projectsToScan.add(next);
+			}
+		}
+		JavaModel model = JavaModelManager.getJavaModelManager().getJavaModel();
+		boolean hasChanges = false;
+		JavaElementDelta delta = new JavaElementDelta(model);
+		SubMonitor projectLoopMonitor = subMonitor.split(1).setWorkRemaining(projectsToScan.size());
+		for (IProject project : projectsToScan) {
+			projectLoopMonitor.split(1);
+			try {
+				if (project.isOpen() && project.isNatureEnabled(JavaCore.NATURE_ID)) {
+					IJavaProject javaProject = JavaCore.create(project);
+
+					IPackageFragmentRoot[] roots = javaProject.getAllPackageFragmentRoots();
+
+					for (IPackageFragmentRoot next : roots) {
+						if (next.isArchive()) {
+							IPath location = JavaIndex.getLocationForElement(next);
+
+							if (indexablesWithChanges.contains(location)) {
+								hasChanges = true;
+								delta.changed(next,
+										IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_ARCHIVE_CONTENT_CHANGED);
+							}
+						}
+					}
+				}
+			} catch (CoreException e) {
+				Package.log(e);
+			}
+		}
+
+		if (hasChanges) {
+			fireChange(IndexerEvent.createChange(delta));
+		}
+	}
+
+	private void updateResourceMappings(Map<IPath, List<IJavaElement>> pathsToUpdate, IProgressMonitor monitor) {
+		SubMonitor subMonitor = SubMonitor.convert(monitor, pathsToUpdate.keySet().size());
+
+		JavaIndex index = JavaIndex.getIndex(this.nd);
+
+		for (Entry<IPath, List<IJavaElement>> entry : pathsToUpdate.entrySet()) {
+			SubMonitor iterationMonitor = subMonitor.split(1).setWorkRemaining(10);
+
+			this.nd.acquireWriteLock(iterationMonitor.split(1));
+			try {
+				NdResourceFile resourceFile = index.getResourceFile(entry.getKey().toString().toCharArray());
+				if (resourceFile == null) {
+					continue;
+				}
+
+				attachWorkspaceFilesToResource(entry.getValue(), resourceFile);
+			} finally {
+				this.nd.releaseWriteLock();
+			}
+
+		}
+	}
+
+	/**
+	 * Clean up unneeded files here, but only do so if it's been a long time since the file was last referenced. Being
+	 * too eager about removing old files means that operations which temporarily cause a file to become unreferenced
+	 * will run really slowly. also eagerly clean up any partially-indexed files we discover during the scan. That is,
+	 * if we discover a file with a timestamp of 0, it indicates that the indexer or all of Eclipse crashed midway
+	 * through indexing the file. Such garbage should be cleaned up as soon as possible, since it will never be useful.
+	 *
+	 * @param currentTimeMillis timestamp of the time at which the indexing operation started
+	 * @param allIndexables list of all referenced java roots
+	 * @param monitor progress monitor
+	 * @return the number of indexables in the index, prior to garbage collection
+	 */
+	private int cleanGarbage(long currentTimeMillis, Collection<IPath> allIndexables, IProgressMonitor monitor) {
+		JavaIndex index = JavaIndex.getIndex(this.nd);
+
+		int result = 0; 
+		HashSet<IPath> paths = new HashSet<>();
+		paths.addAll(allIndexables);
+		SubMonitor subMonitor = SubMonitor.convert(monitor, 3);
+
+		List<NdResourceFile> garbage = new ArrayList<>();
+		List<NdResourceFile> needsUpdate = new ArrayList<>();
+
+		long usageTimestampUpdatePeriod = getUsageTimestampUpdatePeriod();
+		long garbageCleanupTimeout = getGarbageCleanupTimeout();
+		// Build up the list of NdResourceFiles that either need to be garbage collected or
+		// have their read timestamps updated.
+		try (IReader reader = this.nd.acquireReadLock()) {
+			List<NdResourceFile> resourceFiles = index.getAllResourceFiles();
+
+			result = resourceFiles.size();
+			SubMonitor testMonitor = subMonitor.split(1).setWorkRemaining(resourceFiles.size());
+			for (NdResourceFile next : resourceFiles) {
+				testMonitor.split(1);
+				if (!next.isDoneIndexing()) {
+					garbage.add(next);
+				} else {
+					IPath nextPath = new Path(next.getLocation().toString());
+					long timeLastUsed = next.getTimeLastUsed();
+					long timeSinceLastUsed = currentTimeMillis - timeLastUsed;
+
+					if (paths.contains(nextPath)) {
+						if (timeSinceLastUsed > usageTimestampUpdatePeriod) {
+							needsUpdate.add(next);
+						}
+					} else {
+						if (timeSinceLastUsed > garbageCleanupTimeout) {
+							garbage.add(next);
+						}
+					}
+				}
+			}
+		}
+
+		SubMonitor deleteMonitor = subMonitor.split(1).setWorkRemaining(garbage.size());
+		for (NdResourceFile next : garbage) {
+			deleteResource(next, deleteMonitor.split(1));
+		}
+
+		SubMonitor updateMonitor = subMonitor.split(1).setWorkRemaining(needsUpdate.size());
+		for (NdResourceFile next : needsUpdate) {
+			this.nd.acquireWriteLock(updateMonitor.split(1));
+			try {
+				if (next.isInIndex()) {
+					next.setTimeLastUsed(currentTimeMillis);
+				}
+			} finally {
+				this.nd.releaseWriteLock();
+			}
+		}
+
+		return result;
+	}
+
+	/**
+	 * Performs a non-atomic delete of the given resource file. First, it marks the file as being invalid
+	 * (by clearing out its timestamp). Then it deletes the children of the resource file, one child at a time.
+	 * Once all the children are deleted, the resource itself is deleted. The result on the database is exactly
+	 * the same as if the caller had called toDelete.delete(), but doing it this way ensures that a write lock
+	 * will never be held for a nontrivial amount of time.
+	 */
+	protected void deleteResource(NdResourceFile toDelete, IProgressMonitor monitor) {
+		SubMonitor deletionMonitor = SubMonitor.convert(monitor, 10);
+
+		this.nd.acquireWriteLock(deletionMonitor.split(1));
+		try {
+			if (toDelete.isInIndex()) {
+				toDelete.markAsInvalid();
+			}
+		} finally {
+			this.nd.releaseWriteLock();
+		}
+
+		for (;;) {
+			this.nd.acquireWriteLock(deletionMonitor.split(1));
+			try {
+				if (!toDelete.isInIndex()) {
+					break;
+				}
+		
+				int numChildren = toDelete.getBindingCount();
+				deletionMonitor.setWorkRemaining(numChildren + 1);
+				if (numChildren == 0) {
+					break;
+				}
+
+				NdBinding nextDeletion = toDelete.getBinding(numChildren - 1);
+				if (DEBUG_INSERTIONS) {
+					if (nextDeletion instanceof NdType) {
+						NdType type = (NdType)nextDeletion;
+						Package.logInfo("Deleting " + type.getTypeId().getFieldDescriptor().getString() + " from "  //$NON-NLS-1$//$NON-NLS-2$
+								+ new String(toDelete.getLocation().getString()) + " " + toDelete.address); //$NON-NLS-1$
+					}
+				}
+				nextDeletion.delete();
+			} finally {
+				this.nd.releaseWriteLock();
+			}
+		}
+
+		this.nd.acquireWriteLock(deletionMonitor.split(1));
+		try {
+			if (toDelete.isInIndex()) {
+				toDelete.delete();
+			}
+		} finally {
+			this.nd.releaseWriteLock();
+		}
+	}
+
+	private Map<IPath, List<IJavaElement>> removeDuplicatePaths(List<IJavaElement> allIndexables) {
+		Map<IPath, List<IJavaElement>> paths = new HashMap<>();
+
+		HashSet<IPath> workspacePaths = new HashSet<IPath>();
+		for (IJavaElement next : allIndexables) {
+			IPath nextPath = JavaIndex.getLocationForElement(next);
+			IPath workspacePath = getWorkspacePathForRoot(next);
+
+			List<IJavaElement> value = paths.get(nextPath);
+
+			if (value == null) {
+				value = new ArrayList<IJavaElement>();
+				paths.put(nextPath, value);
+			} else {
+				if (workspacePath != null) {
+					if (workspacePaths.contains(workspacePath)) {
+						continue;
+					}
+					if (!workspacePath.isEmpty()) {
+						Package.logInfo("Found duplicate workspace path for " + workspacePath.toString()); //$NON-NLS-1$
+					}
+					workspacePaths.add(workspacePath);
+				}
+			}
+
+			value.add(next);
+		}
+
+		return paths;
+	}
+
+	private IPath getWorkspacePathForRoot(IJavaElement next) {
+		IResource resource = next.getResource();
+		if (resource != null) {
+			return resource.getFullPath();
+		}
+		return Path.EMPTY;
+	}
+
+	private Map<IPath, FingerprintTestResult> testFingerprints(Collection<IPath> allIndexables,
+			IProgressMonitor monitor) throws CoreException {
+		SubMonitor subMonitor = SubMonitor.convert(monitor, allIndexables.size());
+		Map<IPath, FingerprintTestResult> result = new HashMap<>();
+
+		for (IPath next : allIndexables) {
+			result.put(next, testForChanges(next, subMonitor.split(1)));
+		}
+
+		return result;
+	}
+
+	/**
+	 * Rescans an archive (a jar, zip, or class file on the filesystem). Returns the number of classes indexed.
+	 * @throws JavaModelException
+	 */
+	private int rescanArchive(long currentTimeMillis, IPath thePath, List<IJavaElement> elementsMappingOntoLocation,
+			FileFingerprint fingerprint, IProgressMonitor monitor) throws JavaModelException {
+		if (elementsMappingOntoLocation.isEmpty()) {
+			return 0;
+		}
+
+		IJavaElement element = elementsMappingOntoLocation.get(0);
+		SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
+
+		String pathString = thePath.toString();
+		JavaIndex javaIndex = JavaIndex.getIndex(this.nd);
+
+		File theFile = thePath.toFile();
+		if (!(theFile.exists() && theFile.isFile())) {
+			if (DEBUG) {
+				Package.log("the file " + pathString + " does not exist", null); //$NON-NLS-1$ //$NON-NLS-2$
+			}
+			return 0;
+		}
+
+		NdResourceFile resourceFile;
+
+		this.nd.acquireWriteLock(subMonitor.split(5));
+		try {
+			resourceFile = new NdResourceFile(this.nd);
+			resourceFile.setTimeLastUsed(currentTimeMillis);
+			resourceFile.setLocation(pathString);
+			IPackageFragmentRoot packageFragmentRoot = (IPackageFragmentRoot) element
+					.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
+			IPath rootPathString = JavaIndex.getLocationForElement(packageFragmentRoot);
+			if (!rootPathString.equals(thePath)) {
+				resourceFile.setPackageFragmentRoot(rootPathString.toString().toCharArray());
+			}
+			attachWorkspaceFilesToResource(elementsMappingOntoLocation, resourceFile);
+		} finally {
+			this.nd.releaseWriteLock();
+		}
+
+		if (DEBUG) {
+			Package.logInfo("rescanning " + thePath.toString() + ", " + fingerprint); //$NON-NLS-1$ //$NON-NLS-2$
+		}
+		int result;
+		try {
+			result = addElement(resourceFile, element, subMonitor.split(50));
+		} catch (JavaModelException e) {
+			if (DEBUG) {
+				Package.log("the file " + pathString + " cannot be indexed due to a recoverable error", null); //$NON-NLS-1$ //$NON-NLS-2$
+			}
+			// If this file can't be indexed due to a recoverable error, delete the NdResourceFile entry for it.
+			this.nd.acquireWriteLock(subMonitor.split(5));
+			try {
+				if (resourceFile.isInIndex()) {
+					resourceFile.delete();
+				}
+			} finally {
+				this.nd.releaseWriteLock();
+			}
+			return 0;
+		} catch (RuntimeException e) {
+			if (DEBUG) {
+				Package.log("A RuntimeException occurred while indexing " + pathString, e); //$NON-NLS-1$
+			}
+			throw e;
+		}
+
+		List<NdResourceFile> allResourcesWithThisPath = Collections.emptyList();
+		// Now update the timestamp and delete all older versions of this resource that exist in the index
+		this.nd.acquireWriteLock(subMonitor.split(1));
+		try {
+			if (resourceFile.isInIndex()) {
+				resourceFile.setFingerprint(fingerprint);
+				allResourcesWithThisPath = javaIndex.findResourcesWithPath(pathString);
+			}
+		} finally {
+			this.nd.releaseWriteLock();
+		}
+
+		SubMonitor deletionMonitor = subMonitor.split(40).setWorkRemaining(allResourcesWithThisPath.size() - 1);
+		for (NdResourceFile next : allResourcesWithThisPath) {
+			if (!next.equals(resourceFile)) {
+				deleteResource(next, deletionMonitor.split(1));
+			}
+		}
+
+		return result;
+	}
+
+	private void attachWorkspaceFilesToResource(List<IJavaElement> elementsMappingOntoLocation,
+			NdResourceFile resourceFile) {
+		for (IJavaElement next : elementsMappingOntoLocation) {
+			IResource nextResource = next.getResource();
+			if (nextResource != null) {
+				new NdWorkspaceLocation(this.nd, resourceFile,
+						nextResource.getFullPath().toString().toCharArray());
+			}
+		}
+	}
+
+	/**
+	 * Adds an archive to the index, under the given NdResourceFile.
+	 */
+	private int addElement(NdResourceFile resourceFile, IJavaElement element, IProgressMonitor monitor)
+			throws JavaModelException {
+		SubMonitor subMonitor = SubMonitor.convert(monitor);
+
+		if (element instanceof JarPackageFragmentRoot) {
+			JarPackageFragmentRoot jarRoot = (JarPackageFragmentRoot) element;
+
+			IPath workspacePath = jarRoot.getPath();
+			IPath location = JavaIndex.getLocationForElement(jarRoot);
+
+			int classesIndexed = 0;
+			try (ZipFile zipFile = new ZipFile(JavaModelManager.getLocalFile(jarRoot.getPath()))) {
+				// Used for the error-handling unit tests
+				if (JavaModelManager.throwIoExceptionsInGetZipFile) {
+					if (DEBUG) {
+						Package.logInfo("Throwing simulated IOException for error handling test case"); //$NON-NLS-1$
+					}
+					throw new IOException();
+				}
+				subMonitor.setWorkRemaining(zipFile.size());
+
+				for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements();) {
+					SubMonitor nextEntry = subMonitor.split(1).setWorkRemaining(2);
+					ZipEntry member = e.nextElement();
+					if (member.isDirectory()) {
+						continue;
+					}
+					nextEntry.split(1);
+					String fileName = member.getName();
+
+					boolean classFileName = org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(fileName);
+					if (classFileName) {
+						String binaryName = fileName.substring(0, fileName.length() - SuffixConstants.SUFFIX_STRING_class.length());
+						char[] fieldDescriptor = JavaNames.binaryNameToFieldDescriptor(binaryName.toCharArray());
+						String indexPath = jarRoot.getHandleIdentifier() + IDependent.JAR_FILE_ENTRY_SEPARATOR + binaryName;
+						BinaryTypeDescriptor descriptor = new BinaryTypeDescriptor(location.toString().toCharArray(), fieldDescriptor,
+								workspacePath.toString().toCharArray(), indexPath.toCharArray());
+						try {
+							byte[] contents = org.eclipse.jdt.internal.compiler.util.Util.getZipEntryByteContent(member, zipFile);
+							ClassFileReader classFileReader = new ClassFileReader(contents, descriptor.indexPath, true);
+							if (addClassToIndex(resourceFile, descriptor.fieldDescriptor, descriptor.indexPath,
+									classFileReader, nextEntry.split(1))) {
+								classesIndexed++;
+							}
+						} catch (CoreException | ClassFormatException exception) {
+							Package.log("Unable to index " + descriptor.toString(), exception); //$NON-NLS-1$
+						}
+					}
+				}
+			} catch (ZipException e) {
+				Package.log("The zip file " + jarRoot.getPath() + " was corrupt", e);  //$NON-NLS-1$//$NON-NLS-2$
+				// Indicates a corrupt zip file. Treat this like an empty zip file.
+			} catch (IOException ioException) {
+				throw new JavaModelException(ioException, IJavaModelStatusConstants.IO_EXCEPTION);
+			} catch (CoreException coreException) {
+				throw new JavaModelException(coreException);
+			}
+
+			if (DEBUG && classesIndexed == 0) {
+				Package.logInfo("The path " + element.getPath() + " contained no class files"); //$NON-NLS-1$ //$NON-NLS-2$
+			}
+			return classesIndexed;
+		} else if (element instanceof IClassFile) {
+			IClassFile classFile = (IClassFile)element;
+
+			SubMonitor iterationMonitor = subMonitor.split(1);
+			BinaryTypeDescriptor descriptor = BinaryTypeFactory.createDescriptor(classFile);
+
+			boolean indexed = false;
+			try {
+				ClassFileReader classFileReader = BinaryTypeFactory.rawReadType(descriptor, true);
+				if (classFileReader != null) {
+					indexed = addClassToIndex(resourceFile, descriptor.fieldDescriptor, descriptor.indexPath,
+							classFileReader, iterationMonitor);
+				}
+			} catch (CoreException | ClassFormatException e) {
+				Package.log("Unable to index " + classFile.toString(), e); //$NON-NLS-1$
+			}
+
+			return indexed ? 1 : 0;
+		} else {
+			Package.logInfo("Unable to index elements of type " + element); //$NON-NLS-1$
+			return 0;
+		}
+	}
+
+	private boolean addClassToIndex(NdResourceFile resourceFile, char[] fieldDescriptor, char[] indexPath,
+			ClassFileReader binaryType, IProgressMonitor monitor) throws ClassFormatException, CoreException {
+		SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
+		ClassFileToIndexConverter converter = new ClassFileToIndexConverter(resourceFile);
+
+		boolean indexed = false;
+		this.nd.acquireWriteLock(subMonitor.split(5));
+		try {
+			if (resourceFile.isInIndex()) {
+				if (DEBUG_INSERTIONS) {
+					Package.logInfo("Inserting " + new String(fieldDescriptor) + " into " + resourceFile.getLocation().getString() + " " + resourceFile.address); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
+				}
+				converter.addType(binaryType, fieldDescriptor, subMonitor.split(45));
+				indexed = true;
+			}
+		} finally {
+			this.nd.releaseWriteLock();
+		}
+
+		if (DEBUG_SELFTEST && indexed) {
+			// When this debug flag is on, we test everything written to the index by reading it back immediately after indexing
+			// and comparing it with the original class file.
+			JavaIndex index = JavaIndex.getIndex(this.nd);
+			try (IReader readLock = this.nd.acquireReadLock()) {
+				NdTypeId typeId = index.findType(fieldDescriptor);
+				NdType targetType = null;
+				if (typeId != null) {
+					List<NdType> implementations = typeId.getTypes();
+					for (NdType nextType : implementations) {
+						NdResourceFile nextResourceFile = nextType.getResourceFile();
+						if (nextResourceFile.equals(resourceFile)) {
+							targetType = nextType;
+							break;
+						}
+					}
+				}
+
+				if (targetType != null) {
+					IndexBinaryType actualType = new IndexBinaryType(TypeRef.create(targetType), indexPath);
+					IndexTester.testType(binaryType, actualType);
+				} else {
+					Package.logInfo("Could not find class in index immediately after indexing it: " + new String(indexPath)); //$NON-NLS-1$
+				}
+			} catch (RuntimeException e) {
+				Package.log("Error during indexing: " + new String(indexPath), e); //$NON-NLS-1$
+			}
+		}
+		return indexed;
+	}
+
+	private List<IJavaElement> getAllIndexableObjectsInWorkspace(IProgressMonitor monitor) throws CoreException {
+		SubMonitor subMonitor = SubMonitor.convert(monitor, 2);
+		List<IJavaElement> allIndexables = new ArrayList<>();
+		IProject[] projects = this.root.getProjects();
+
+		List<IProject> projectsToScan = new ArrayList<>();
+
+		for (IProject next : projects) {
+			if (next.isOpen()) {
+				projectsToScan.add(next);
+			}
+		}
+
+		Set<IPath> scannedPaths = new HashSet<>();
+		Set<IResource> resourcesToScan = new HashSet<>();
+		SubMonitor projectLoopMonitor = subMonitor.split(1).setWorkRemaining(projectsToScan.size());
+		for (IProject project : projectsToScan) {
+			SubMonitor iterationMonitor = projectLoopMonitor.split(1);
+			try {
+				if (project.isOpen() && project.isNatureEnabled(JavaCore.NATURE_ID)) {
+					IJavaProject javaProject = JavaCore.create(project);
+
+					IClasspathEntry[] entries = javaProject.getRawClasspath();
+
+					if (EXPERIMENTAL_INDEX_OUTPUT_FOLDERS) {
+						IPath defaultOutputLocation = javaProject.getOutputLocation();
+						for (IClasspathEntry next : entries) {
+							IPath nextOutputLocation = next.getOutputLocation();
+	
+							if (nextOutputLocation == null) {
+								nextOutputLocation = defaultOutputLocation;
+							}
+	
+							IResource resource = this.root.findMember(nextOutputLocation);
+							if (resource != null) {
+								resourcesToScan.add(resource);
+							}
+						}
+					}
+
+					IPackageFragmentRoot[] projectRoots = javaProject.getAllPackageFragmentRoots();
+					SubMonitor rootLoopMonitor = iterationMonitor.setWorkRemaining(projectRoots.length);
+					for (IPackageFragmentRoot nextRoot : projectRoots) {
+						rootLoopMonitor.split(1);
+						if (!nextRoot.exists()) {
+							continue;
+						}
+						IPath filesystemPath = JavaIndex.getLocationForElement(nextRoot);
+						if (scannedPaths.contains(filesystemPath)) {
+							continue;
+						}
+						scannedPaths.add(filesystemPath);
+						if (nextRoot.getKind() == IPackageFragmentRoot.K_BINARY) {
+							if (nextRoot.isArchive()) {
+								allIndexables.add(nextRoot);
+							} else {
+								collectAllClassFiles(allIndexables, nextRoot);
+							}
+						} else {
+							collectAllClassFiles(allIndexables, nextRoot);
+						}
+					}
+				}
+			} catch (CoreException e) {
+				Package.log(e);
+			}
+		}
+
+		collectAllClassFiles(allIndexables, resourcesToScan, subMonitor.split(1));
+		return allIndexables;
+	}
+
+	private void collectAllClassFiles(List<? super IClassFile> result, Collection<? extends IResource> toScan,
+			IProgressMonitor monitor) {
+		SubMonitor subMonitor = SubMonitor.convert(monitor);
+
+		ArrayDeque<IResource> resources = new ArrayDeque<>();
+		resources.addAll(toScan);
+
+		while (!resources.isEmpty()) {
+			subMonitor.setWorkRemaining(Math.max(resources.size(), 3000)).split(1);
+			IResource next = resources.removeFirst();
+
+			if (next instanceof IContainer) {
+				IContainer container = (IContainer)next;
+
+				try {
+					for (IResource nextChild : container.members()) {
+						resources.addLast(nextChild);
+					}
+				} catch (CoreException e) {
+					// If an error occurs in one resource, skip it and move on to the next
+					Package.log(e);
+				}
+			} else if (next instanceof IFile) {
+				IFile file = (IFile) next;
+
+				String extension = file.getFileExtension();
+				if (Objects.equals(extension, "class")) { //$NON-NLS-1$
+					IJavaElement element = JavaCore.create(file);
+
+					if (element instanceof IClassFile) {
+						result.add((IClassFile)element);
+					}
+				}
+			}
+		}
+	}
+
+	private void collectAllClassFiles(List<? super IClassFile> result, IParent nextRoot) throws CoreException {
+		for (IJavaElement child : nextRoot.getChildren()) {
+			try {
+				int type = child.getElementType();
+				if (!child.exists()) {
+					continue;
+				}
+				if (type == IJavaElement.COMPILATION_UNIT) {
+					continue;
+				}
+
+				if (type == IJavaElement.CLASS_FILE) {
+					result.add((IClassFile)child);
+				} else if (child instanceof IParent) {
+					IParent parent = (IParent) child;
+
+					collectAllClassFiles(result, parent);
+				}
+			} catch (CoreException e) {
+				// Log exceptions, then continue with the next child
+				Package.log(e);
+			}
+		}
+	}
+
+	/**
+	 * Given a list of fragment roots, returns the subset of roots that have changed since the last time they were
+	 * indexed.
+	 */
+	private List<IPath> getIndexablesThatHaveChanged(Collection<IPath> indexables,
+			Map<IPath, FingerprintTestResult> fingerprints) {
+		List<IPath> indexablesWithChanges = new ArrayList<>();
+		for (IPath next : indexables) {
+			FingerprintTestResult testResult = fingerprints.get(next);
+
+			if (!testResult.matches()) {
+				indexablesWithChanges.add(next);
+			}
+		}
+		return indexablesWithChanges;
+	}
+
+	private FingerprintTestResult testForChanges(IPath thePath, IProgressMonitor monitor) throws CoreException {
+		SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
+		JavaIndex javaIndex = JavaIndex.getIndex(this.nd);
+		String pathString = thePath.toString();
+
+		subMonitor.split(50);
+		NdResourceFile resourceFile = null;
+		FileFingerprint fingerprint = FileFingerprint.getEmpty();
+		this.nd.acquireReadLock();
+		try {
+			resourceFile = javaIndex.getResourceFile(pathString.toCharArray());
+
+			if (resourceFile != null) {
+				fingerprint = resourceFile.getFingerprint();
+			}
+		} finally {
+			this.nd.releaseReadLock();
+		}
+
+		FingerprintTestResult result = fingerprint.test(thePath, subMonitor.split(40));
+
+		// If this file hasn't changed but its timestamp has, write an updated fingerprint to the database
+		if (resourceFile != null && result.matches() && result.needsNewFingerprint()) {
+			this.nd.acquireWriteLock(subMonitor.split(10));
+			try {
+				if (resourceFile.isInIndex()) {
+					if (DEBUG) {
+						Package.logInfo(
+								"Writing updated fingerprint for " + thePath + ": " + result.getNewFingerprint()); //$NON-NLS-1$//$NON-NLS-2$
+					}
+					resourceFile.setFingerprint(result.getNewFingerprint());
+				}
+			} finally {
+				this.nd.releaseWriteLock();
+			}
+		}
+		return result;
+	}
+
+	public Indexer(Nd toPopulate, IWorkspaceRoot workspaceRoot) {
+		this.nd = toPopulate;
+		this.root = workspaceRoot;
+	}
+
+	public void rescanAll() {
+		if (DEBUG) {
+			Package.logInfo("Scheduling rescanAll now"); //$NON-NLS-1$
+		}
+		synchronized (this.automaticIndexingMutex) {
+			if (!this.enableAutomaticIndexing) {
+				if (!this.indexerDirtiedWhileDisabled) {
+					this.indexerDirtiedWhileDisabled = true;
+				}
+				return;
+			}
+		}
+		this.rescanJob.schedule();
+	}
+
+	/**
+	 * Adds the given listener. It will be notified when Nd changes. No strong references
+	 * will be retained to the listener.
+	 */
+	public void addListener(Listener newListener) {
+		synchronized (this.listenersMutex) {
+			Set<Listener> oldListeners = this.listeners;
+			this.listeners = Collections.newSetFromMap(new WeakHashMap<Listener, Boolean>());
+			this.listeners.addAll(oldListeners);
+			this.listeners.add(newListener);
+		}
+	}
+
+	public void removeListener(Listener oldListener) {
+		synchronized (this.listenersMutex) {
+			if (!this.listeners.contains(oldListener)) {
+				return;
+			}
+			Set<Listener> oldListeners = this.listeners;
+			this.listeners = Collections.newSetFromMap(new WeakHashMap<Listener, Boolean>());
+			this.listeners.addAll(oldListeners);
+			this.listeners.remove(oldListener);
+		}
+	}
+
+	private void fireChange(IndexerEvent event) {
+		Set<Listener> localListeners;
+		synchronized (this.listenersMutex) {
+			localListeners = this.listeners;
+		}
+
+		for (Listener next : localListeners) {
+			next.consume(event);
+		}
+	}
+
+	public void waitForIndex(IProgressMonitor monitor) {
+		try {
+			boolean shouldRescan = false;
+			synchronized (this.automaticIndexingMutex) {
+				if (!this.enableAutomaticIndexing && this.indexerDirtiedWhileDisabled) {
+					shouldRescan = true;
+				}
+			}
+			if (shouldRescan) {
+				this.rescanJob.schedule();
+			}
+			this.rescanJob.join(0, monitor);
+		} catch (InterruptedException e) {
+			throw new OperationCanceledException();
+		}
+	}
+
+	public void waitForIndex(int waitingPolicy, IProgressMonitor monitor) {
+		switch (waitingPolicy) {
+			case IJob.ForceImmediate: {
+				break;
+			}
+			case IJob.CancelIfNotReady: {
+				if (this.rescanJob.getState() != Job.NONE) {
+					throw new OperationCanceledException();
+				}
+				break;
+			}
+			case IJob.WaitUntilReady: {
+				waitForIndex(monitor);
+				break;
+			}
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/IndexerEvent.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/IndexerEvent.java
new file mode 100644
index 0000000..1a29d5d
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/IndexerEvent.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * 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.jdt.core.IJavaElementDelta;
+
+public class IndexerEvent {
+	final IJavaElementDelta delta;
+
+	private IndexerEvent(IJavaElementDelta delta) {
+		this.delta = delta;
+	}
+
+	public static IndexerEvent createChange(IJavaElementDelta delta) {
+		return new IndexerEvent(delta);
+	}
+
+	public IJavaElementDelta getDelta() {
+		return this.delta;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Messages.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Messages.java
new file mode 100644
index 0000000..78b4f97
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Messages.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.indexer;
+
+import org.eclipse.osgi.util.NLS;
+
+public class Messages extends NLS {
+	private static final String BUNDLE_NAME = "org.eclipse.jdt.internal.core.nd.indexer.messages"; //$NON-NLS-1$
+	public static String Indexer_updating_index_job_name;
+	static {
+		// initialize resource bundle
+		NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+	}
+
+	private Messages() {
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Package.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Package.java
new file mode 100644
index 0000000..df19a3c
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/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.indexer;
+
+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 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 logInfo(String message) {
+		log(new Status(IStatus.INFO, PLUGIN_ID, message));
+	}
+
+	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/indexer/messages.properties b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/messages.properties
new file mode 100644
index 0000000..f835143
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/messages.properties
@@ -0,0 +1 @@
+Indexer_updating_index_job_name=Updating index
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/ClasspathResolver.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/ClasspathResolver.java
new file mode 100644
index 0000000..5e5f638
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/ClasspathResolver.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.java;
+
+/**
+ * Used for filtering and disambiguating bindings in the index to match the classpath.
+ */
+public interface ClasspathResolver {
+	public static final int NOT_ON_CLASSPATH = -1;
+
+	/**
+	 * Returns the priority of the given resource file on the classpath or {@link #NOT_ON_CLASSPATH} if the given file
+	 * is not onthe classpath. In the event that the same fully-qualified class name is found in multiple resource
+	 * files, the one with the higher priority number is preferred.
+	 */
+	int resolve(NdResourceFile sourceOfReference, NdResourceFile toTest);
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/FileFingerprint.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/FileFingerprint.java
new file mode 100644
index 0000000..f1b0262
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/FileFingerprint.java
@@ -0,0 +1,250 @@
+/*******************************************************************************
+ * 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.java;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileInfo;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.jdt.internal.core.nd.StreamHasher;
+
+public class FileFingerprint {
+	/**
+	 * Sentinel value for {@link #time} indicating a nonexistent fingerprint. This is used for the timestamp of
+	 * nonexistent files and for the {@link #getEmpty()} singleton.
+	 */
+	public static final long NEVER_MODIFIED = 0;
+
+	/**
+	 * Sentinel value for {@link #time} indicating that the timestamp was not recorded as part of the fingerprint.
+	 * This is normally used to indicate that the file's timestamp was so close to the current system time at the time
+	 * the fingerprint was computed that subsequent changes in the file might not be detected. In such cases, timestamps
+	 * are an unreliable method for determining if the file has changed and so are not included as part of the fingerprint.
+	 */
+	public static final long UNKNOWN = 1;
+
+	/**
+	 * Worst-case accuracy of filesystem timestamps, among all supported platforms (this is currently 1s on linux, 2s on
+	 * FAT systems).
+	 */
+	private static final long WORST_FILESYSTEM_TIMESTAMP_ACCURACY_MS = 2000;
+
+	private long time;
+	private long hash;
+	private long size;
+
+	private static final FileFingerprint EMPTY = new FileFingerprint(NEVER_MODIFIED,0,0);
+
+	public static final FileFingerprint getEmpty() {
+		return EMPTY;
+	}
+
+	public static final FileFingerprint create(IPath path, IProgressMonitor monitor) throws CoreException {
+		return getEmpty().test(path, monitor).getNewFingerprint();
+	}
+
+	public FileFingerprint(long time, long size, long hash) {
+		super();
+		this.time = time;
+		this.size = size;
+		this.hash = hash;
+	}
+
+	public long getTime() {
+		return this.time;
+	}
+
+	public long getHash() {
+		return this.hash;
+	}
+
+	public long getSize() {
+		return this.size;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + (int) (this.hash ^ (this.hash >>> 32));
+		result = prime * result + (int) (this.size ^ (this.size >>> 32));
+		result = prime * result + (int) (this.time ^ (this.time >>> 32));
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		FileFingerprint other = (FileFingerprint) obj;
+		if (this.hash != other.hash)
+			return false;
+		if (this.size != other.size)
+			return false;
+		if (this.time != other.time)
+			return false;
+		return true;
+	}
+
+	public static class FingerprintTestResult {
+		private boolean matches;
+		private boolean needsNewFingerprint;
+		private FileFingerprint newFingerprint;
+
+		public FingerprintTestResult(boolean matches, boolean needsNewFingerprint, FileFingerprint newFingerprint) {
+			super();
+			this.matches = matches;
+			this.newFingerprint = newFingerprint;
+			this.needsNewFingerprint = needsNewFingerprint;
+		}
+
+		public boolean needsNewFingerprint() {
+			return this.needsNewFingerprint;
+		}
+
+		public boolean matches() {
+			return this.matches;
+		}
+
+		public FileFingerprint getNewFingerprint() {
+			return this.newFingerprint;
+		}
+
+		@Override
+		public String toString() {
+			return "FingerprintTestResult [matches=" + this.matches + ", needsNewFingerprint="  //$NON-NLS-1$//$NON-NLS-2$
+					+ this.needsNewFingerprint + ", newFingerprint=" + this.newFingerprint + "]";  //$NON-NLS-1$//$NON-NLS-2$
+		}
+	}
+
+	/**
+	 * Compares the given File with the receiver. If the fingerprint matches (ie: the file
+	 */
+	public FingerprintTestResult test(IPath path, IProgressMonitor monitor) throws CoreException {
+		SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
+		long currentTime = System.currentTimeMillis();
+		IFileStore store = EFS.getLocalFileSystem().getStore(path);
+		IFileInfo fileInfo = store.fetchInfo();
+
+		long lastModified = fileInfo.getLastModified();
+		if (Math.abs(currentTime - lastModified) < WORST_FILESYSTEM_TIMESTAMP_ACCURACY_MS) {
+			// If the file was modified so recently that it's within our ability to measure it, don't include
+			// the timestamp as part of the fingerprint. If another change were to happen to the file immediately
+			// afterward, we might not be able to detect it using the timestamp.
+			lastModified = UNKNOWN;
+		}
+		subMonitor.split(5);
+
+		long fileSize = fileInfo.getLength();
+		subMonitor.split(5);
+		if (lastModified != UNKNOWN && lastModified == this.time && fileSize == this.size) {
+			return new FingerprintTestResult(true, false, this);
+		}
+
+		long hashCode;
+		try {
+			hashCode = fileSize == 0 ? 0 : computeHashCode(path.toFile(), fileSize, subMonitor.split(90));
+		} catch (IOException e) {
+			throw new CoreException(Package.createStatus("An error occurred computing a hash code", e)); //$NON-NLS-1$
+		}
+		boolean matches = (hashCode == this.hash && fileSize == this.size);
+
+		FileFingerprint newFingerprint = new FileFingerprint(lastModified, fileSize, hashCode);
+		return new FingerprintTestResult(matches, !equals(newFingerprint), newFingerprint);
+	}
+
+	private long computeHashCode(File toTest, long fileSize, IProgressMonitor monitor) throws IOException {
+		final int BUFFER_SIZE = 2048;
+		char[] charBuffer = new char[BUFFER_SIZE];
+		byte[] byteBuffer = new byte[BUFFER_SIZE * 2];
+
+		SubMonitor subMonitor = SubMonitor.convert(monitor, (int) (fileSize / (BUFFER_SIZE * 2)));
+		StreamHasher hasher = new StreamHasher();
+		try {
+			InputStream inputStream = new FileInputStream(toTest);
+			try {
+				while (true) {
+					subMonitor.split(1);
+					int bytesRead = readUntilBufferFull(inputStream, byteBuffer);
+
+					if (bytesRead < byteBuffer.length) {
+						charBuffer = new char[(bytesRead + 1) / 2];
+						copyByteArrayToCharArray(charBuffer, byteBuffer, bytesRead);
+						hasher.addChunk(charBuffer);
+						break;
+					}
+
+					copyByteArrayToCharArray(charBuffer, byteBuffer, bytesRead);
+					hasher.addChunk(charBuffer);
+				}
+			} finally {
+				inputStream.close();
+			}
+
+		} catch (FileNotFoundException e) {
+			return 0;
+		}
+
+		return hasher.computeHash();
+	}
+
+	private void copyByteArrayToCharArray(char[] charBuffer, byte[] byteBuffer, int bytesToCopy) {
+		for (int ch = 0; ch < bytesToCopy / 2; ch++) {
+			char next = (char) (byteBuffer[ch * 2] + byteBuffer[ch * 2 + 1]);
+			charBuffer[ch] = next;
+		}
+
+		if (bytesToCopy % 2 != 0) {
+			charBuffer[bytesToCopy / 2] = (char) byteBuffer[bytesToCopy - 1];
+		}
+	}
+
+	int readUntilBufferFull(InputStream inputStream, byte[] buffer) throws IOException {
+		int bytesRead = 0;
+		while (bytesRead < buffer.length) {
+			int thisRead = inputStream.read(buffer, bytesRead, buffer.length - bytesRead);
+
+			if (thisRead == -1) {
+				return bytesRead;
+			}
+
+			bytesRead += thisRead;
+		}
+		return bytesRead;
+	}
+
+	private static String getTimeString(long timestamp) {
+		if (timestamp == UNKNOWN) {
+			return "UNKNOWN"; //$NON-NLS-1$
+		} else if (timestamp == NEVER_MODIFIED) {
+			return "NEVER_MODIFIED"; //$NON-NLS-1$
+		}
+		return Long.toString(timestamp);
+	}
+
+	@Override
+	public String toString() {
+		return "FileFingerprint [time=" + getTimeString(this.time) + ", size=" + this.size + ", hash=" + this.hash + "]";    //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/IndexFilter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/IndexFilter.java
new file mode 100644
index 0000000..cc6a90d
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/IndexFilter.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 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
+ *     Andrew Ferguson (Symbian)
+ *     Bryan Wilkinson (QNX)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.dom.IBinding;
+
+/**
+ * Can be subclassed and used for queries in the index.
+ */
+public class IndexFilter {
+	public static final IndexFilter ALL = new IndexFilter();
+
+	/**
+	 * Get an IndexFilter that accepts everything
+	 *
+	 * @return an IndexFilter instance
+	 */
+	public static IndexFilter getFilter() {
+		return new IndexFilter();
+	}
+
+	/**
+	 * Determines whether or not a binding is valid.
+	 *
+	 * @param binding the binding being checked for validity
+	 * @return whether or not the binding is valid
+	 * @throws CoreException
+	 */
+	public boolean acceptBinding(IBinding binding) throws CoreException {
+		return true;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaIndex.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaIndex.java
new file mode 100644
index 0000000..006aeff
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaIndex.java
@@ -0,0 +1,300 @@
+/*******************************************************************************
+ * 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.java;
+
+import java.io.File;
+import java.util.List;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.IPreferencesService;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.NdNodeTypeRegistry;
+import org.eclipse.jdt.internal.core.nd.db.ChunkCache;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchIndex;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchIndex.IResultRank;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchIndex.SearchCriteria;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+
+public class JavaIndex {
+	// Version constants
+	static final int CURRENT_VERSION = Nd.version(1, 37);
+	static final int MAX_SUPPORTED_VERSION = Nd.version(1, 37);
+	static final int MIN_SUPPORTED_VERSION = Nd.version(1, 37);
+
+	// Fields for the search header
+	public static final FieldSearchIndex<NdResourceFile> FILES;
+	public static final FieldSearchIndex<NdTypeId> SIMPLE_INDEX;
+	public static final FieldSearchIndex<NdTypeId> TYPES;
+	public static final FieldSearchIndex<NdMethodId> METHODS;
+
+	public static final StructDef<JavaIndex> type;
+
+	static {
+		type = StructDef.create(JavaIndex.class);
+		FILES = FieldSearchIndex.create(type, NdResourceFile.FILENAME);
+		SIMPLE_INDEX = FieldSearchIndex.create(type, NdTypeId.SIMPLE_NAME);
+		TYPES = FieldSearchIndex.create(type, NdTypeId.FIELD_DESCRIPTOR);
+		METHODS = FieldSearchIndex.create(type, NdMethodId.METHOD_NAME);
+		type.done();
+
+		// This struct needs to fit within the first database chunk.
+		assert type.getFactory().getRecordSize() <= Database.CHUNK_SIZE;
+	}
+
+	private final static class BestResourceFile implements FieldSearchIndex.IResultRank {
+		public BestResourceFile() {
+		}
+
+		@Override
+		public long getRank(Nd resourceFileNd, long resourceFileAddress) {
+			return NdResourceFile.TIME_LAST_SCANNED.get(resourceFileNd, resourceFileAddress);
+		}
+	}
+
+	private static final BestResourceFile bestResourceFile = new BestResourceFile();
+	private final long address;
+	private Nd nd;
+	private IResultRank anyResult = new IResultRank() {
+		@Override
+		public long getRank(Nd dom, long address1) {
+			return 1;
+		}
+	};
+	private static Nd globalNd;
+	private static final String INDEX_FILENAME = "index.db"; //$NON-NLS-1$
+	private final static Object ndMutex = new Object();
+
+	public JavaIndex(Nd dom, long address) {
+		this.address = address;
+		this.nd = dom;
+	}
+
+	/**
+	 * Returns the most-recently-scanned resource file with the given name or null if none
+	 */
+	public NdResourceFile getResourceFile(char[] location) {
+		return FILES.findBest(this.nd, this.address, FieldSearchIndex.SearchCriteria.create(location),
+				bestResourceFile);
+	}
+
+	/**
+	 * Returns true iff the given resource file is up-to-date with the filesystem. Returns false
+	 * if the argument is out-of-date with the file system or null.
+	 * 
+	 * @param file the index file to look up or null
+	 * @throws CoreException 
+	 */
+	public boolean isUpToDate(NdResourceFile file) throws CoreException {
+		if (file != null && file.isDoneIndexing()) {
+			// TODO(sxenos): It would be much more efficient to mark files as being in one
+			// of three states: unknown, dirty, or clean. Files would start in the unknown
+			// state and move into the dirty state when we see them in a java model change
+			// event. They would move into the clean state after passing this sort of
+			// fingerprint test... but by caching the state of all tested files (in memory),
+			// it would eliminate the vast majority of these (slow) fingerprint tests.
+			
+			Path locationPath = new Path(file.getLocation().getString());
+			if (file.getFingerprint().test(locationPath, null).matches()) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public List<NdResourceFile> findResourcesWithPath(String thePath) {
+		return FILES.findAll(this.nd, this.address, FieldSearchIndex.SearchCriteria.create(thePath.toCharArray()));
+	}
+
+	public List<NdResourceFile> getAllResourceFiles() {
+		return FILES.asList(this.nd, this.address);
+	}
+
+	public NdTypeId findType(char[] fieldDescriptor) {
+		SearchCriteria searchCriteria = SearchCriteria.create(fieldDescriptor);
+		return TYPES.findBest(this.nd, this.address, searchCriteria, this.anyResult);
+	}
+
+	public boolean visitFieldDescriptorsStartingWith(char[] fieldDescriptorPrefix, FieldSearchIndex.Visitor<NdTypeId> visitor) {
+		SearchCriteria searchCriteria = SearchCriteria.create(fieldDescriptorPrefix).prefix(true);
+		return TYPES.visitAll(this.nd, this.address, searchCriteria, visitor);
+	}
+
+	/**
+	 * Returns a type ID or creates a new one if it does not exist. The caller must
+	 * attach a reference to it after calling this method or it may leak.
+	 */
+	public NdTypeId createTypeId(char[] fieldDescriptor) {
+		NdTypeId existingType = findType(fieldDescriptor);
+
+		if (existingType != null) {
+			return existingType;
+		}
+
+		if (fieldDescriptor.length > 1) {
+			if (fieldDescriptor[0] == 'L') {
+				if (fieldDescriptor[fieldDescriptor.length - 1] != ';') {
+					throw new IllegalStateException(new String(fieldDescriptor) + " is not a valid field descriptor"); //$NON-NLS-1$
+				}
+			}
+		}
+
+		NdTypeId result = new NdTypeId(this.nd, fieldDescriptor);
+		if (!CharArrayUtils.equals(result.getFieldDescriptor().getChars(), fieldDescriptor)) {
+			throw new IllegalStateException("Field descriptor didn't match"); //$NON-NLS-1$
+		}
+		return result;
+	}
+
+	public Nd getNd() {
+		return this.nd;
+	}
+
+	public NdMethodId findMethodId(char[] methodId) {
+		SearchCriteria searchCriteria = SearchCriteria.create(methodId);
+
+		return METHODS.findBest(this.nd, this.address, searchCriteria, this.anyResult);
+	}
+
+	public NdMethodId createMethodId(char[] methodId) {
+		NdMethodId existingMethod = findMethodId(methodId);
+
+		if (existingMethod != null) {
+			return existingMethod;
+		}
+
+		return new NdMethodId(this.nd, methodId);
+	}
+
+	/**
+	 * Returns the absolute filesystem location of the given element or null if none
+	 */
+	public static IPath getLocationForElement(IJavaElement next) {
+		IResource resource = next.getResource();
+
+		if (resource != null) {
+			return resource.getLocation() == null ? new Path("") : resource.getLocation(); //$NON-NLS-1$
+		}
+
+		return next.getPath();
+	}
+
+	public static boolean isEnabled() {
+		IPreferencesService preferenceService = Platform.getPreferencesService();
+		if (preferenceService == null) {
+			return true;
+		}
+		return !preferenceService.getBoolean(JavaCore.PLUGIN_ID, "disableNewJavaIndex", false, //$NON-NLS-1$
+				null);
+	}
+
+	public static Nd createNd(File databaseFile, ChunkCache chunkCache) {
+		return new Nd(databaseFile, chunkCache, createTypeRegistry(),
+				MIN_SUPPORTED_VERSION, MAX_SUPPORTED_VERSION, CURRENT_VERSION);
+	}
+
+	public static Nd getGlobalNd() {
+		Nd localNd;
+		synchronized (ndMutex) {
+			localNd = globalNd;
+		}
+
+		if (localNd != null) {
+			return localNd;
+		}
+
+		localNd = createNd(getDBFile(), ChunkCache.getSharedInstance());
+
+		synchronized (ndMutex) {
+			if (globalNd == null) {
+				globalNd = localNd;
+			}
+			return globalNd;
+		}
+	}
+
+	public static JavaIndex getIndex(Nd nd) {
+		return new JavaIndex(nd, Database.DATA_AREA_OFFSET);
+	}
+
+	public static JavaIndex getIndex() {
+		return getIndex(getGlobalNd());
+	}
+
+	public static int getCurrentVersion() {
+		return CURRENT_VERSION;
+	}
+
+	static File getDBFile() {
+		IPath stateLocation = JavaCore.getPlugin().getStateLocation();
+		return stateLocation.append(INDEX_FILENAME).toFile();
+	}
+
+	static NdNodeTypeRegistry<NdNode> createTypeRegistry() {
+		NdNodeTypeRegistry<NdNode> registry = new NdNodeTypeRegistry<>();
+		registry.register(0x0001, NdAnnotation.type.getFactory());
+		registry.register(0x0004, NdAnnotationInConstant.type.getFactory());
+		registry.register(0x0008, NdAnnotationInMethod.type.getFactory());
+		registry.register(0x000c, NdAnnotationInMethodParameter.type.getFactory());
+		registry.register(0x0010, NdAnnotationInType.type.getFactory());
+		registry.register(0x0014, NdAnnotationInVariable.type.getFactory());
+		registry.register(0x0020, NdAnnotationValuePair.type.getFactory());
+		registry.register(0x0028, NdBinding.type.getFactory());
+		registry.register(0x0030, NdComplexTypeSignature.type.getFactory());
+		registry.register(0x0038, NdConstant.type.getFactory());
+		registry.register(0x0040, NdConstantAnnotation.type.getFactory());
+		registry.register(0x0050, NdConstantArray.type.getFactory());
+		registry.register(0x0060, NdConstantBoolean.type.getFactory());
+		registry.register(0x0070, NdConstantByte.type.getFactory());
+		registry.register(0x0080, NdConstantChar.type.getFactory());
+		registry.register(0x0090, NdConstantClass.type.getFactory());
+		registry.register(0x00A0, NdConstantDouble.type.getFactory());
+		registry.register(0x00B0, NdConstantEnum.type.getFactory());
+		registry.register(0x00C0, NdConstantFloat.type.getFactory());
+		registry.register(0x00D0, NdConstantInt.type.getFactory());
+		registry.register(0x00E0, NdConstantLong.type.getFactory());
+		registry.register(0x00F0, NdConstantShort.type.getFactory());
+		registry.register(0x0100, NdConstantString.type.getFactory());
+		registry.register(0x0110, NdMethod.type.getFactory());
+		registry.register(0x0120, NdMethodException.type.getFactory());
+		registry.register(0x0130, NdMethodId.type.getFactory());
+		registry.register(0x0140, NdMethodParameter.type.getFactory());
+		registry.register(0x0150, NdResourceFile.type.getFactory());
+		registry.register(0x0160, NdTreeNode.type.getFactory());
+		registry.register(0x0170, NdType.type.getFactory());
+		registry.register(0x0180, NdTypeAnnotation.type.getFactory());
+		registry.register(0x0184, NdTypeAnnotationInMethod.type.getFactory());
+		registry.register(0x0188, NdTypeAnnotationInType.type.getFactory());
+		registry.register(0x018c, NdTypeAnnotationInVariable.type.getFactory());
+		registry.register(0x0190, NdTypeArgument.type.getFactory());
+		registry.register(0x0194, NdTypeBound.type.getFactory());
+		registry.register(0x01A0, NdTypeInterface.type.getFactory());
+		registry.register(0x01B0, NdTypeParameter.type.getFactory());
+		registry.register(0x01C0, NdTypeSignature.type.getFactory());
+		registry.register(0x01D0, NdTypeId.type.getFactory());
+		registry.register(0x01E0, NdTypeInterface.type.getFactory());
+		registry.register(0x01F0, NdVariable.type.getFactory());
+		registry.register(0x0200, NdWorkspaceLocation.type.getFactory());
+		return registry;
+	}
+
+	public void rebuildIndex() {
+		// TODO: delete and recreate the index
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaNames.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaNames.java
new file mode 100644
index 0000000..0211cb8
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaNames.java
@@ -0,0 +1,222 @@
+package org.eclipse.jdt.internal.core.nd.java;
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.core.search.IJavaSearchScope;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+public class JavaNames {
+	private static final char[] CLASS_FILE_SUFFIX = ".class".toCharArray(); //$NON-NLS-1$
+	public static final char[] FIELD_DESCRIPTOR_PREFIX = new char[] { 'L' };
+	private static final char[] FIELD_DESCRIPTOR_SUFFIX = new char[] { ';' };
+	private static final char[] METHOD_ID_SEPARATOR = new char[] { '#' };
+	private static final char[] JAR_FILE_ENTRY_SEPARATOR = IJavaSearchScope.JAR_FILE_ENTRY_SEPARATOR.toCharArray();
+	public static final char[] ARRAY_FIELD_DESCRIPTOR_PREFIX = new char[] { '[' };
+
+	/**
+	 * Converts a java binary name to a simple name.
+	 */
+	public static char[] binaryNameToSimpleName(char[] binaryName) {
+		int skipIndex = Math.max(
+				Math.max(CharOperation.lastIndexOf('$', binaryName), CharOperation.lastIndexOf('.', binaryName)),
+				CharOperation.lastIndexOf('/', binaryName)) + 1;
+
+		return CharArrayUtils.subarray(binaryName, skipIndex);
+	}
+
+	/**
+	 * Given the binary name of a class, returns the jar-relative path of the class file within that
+	 * jar, including the .class extension.
+	 */
+	public static char[] binaryNameToResourceRelativePath(char[] binaryName) {
+		return CharOperation.concat(binaryName, CLASS_FILE_SUFFIX);
+	}
+
+	public static char[] fullyQualifiedNameToBinaryName(char[] fullyQualifiedName) {
+		return CharOperation.replaceOnCopy(fullyQualifiedName, '.', '/');
+	}
+
+	public static char[] fullyQualifiedNameToFieldDescriptor(char[] fullyQualifiedName) {
+		char[] result = CharArrayUtils.concat(FIELD_DESCRIPTOR_PREFIX, fullyQualifiedName, FIELD_DESCRIPTOR_SUFFIX);
+		CharOperation.replace(result, '.', '/');
+		return result;
+	}
+
+	/**
+	 * Given a NdType, returns its identifier in the form accepted by {@link IJavaSearchScope#encloses(String)}
+	 */
+	public static char[] getIndexPathFor(NdType type, IWorkspaceRoot root) {
+		NdResourceFile resourceFile = type.getResourceFile();
+
+		char[] binaryName = type.getTypeId().getBinaryName();
+
+		char[] workspaceLocation = null;
+		if (root != null) {
+			workspaceLocation = resourceFile.getAnyOpenWorkspaceLocation(root).toString().toCharArray();
+		}
+
+		if (workspaceLocation == null || workspaceLocation.length == 0) {
+			workspaceLocation = resourceFile.getLocation().getChars();
+		}
+
+		return CharArrayUtils.concat(workspaceLocation, JAR_FILE_ENTRY_SEPARATOR,
+				binaryNameToResourceRelativePath(binaryName));
+	}
+
+	/**
+	 * Converts a binary name to a field descriptor (without the trailing ';')
+	 */
+	public static char[] binaryNameToFieldDescriptor(char[] binaryName) {
+		return CharArrayUtils.concat(FIELD_DESCRIPTOR_PREFIX, binaryName, FIELD_DESCRIPTOR_SUFFIX);
+	}
+
+	/**
+	 * Converts a field descriptor to a simple class name. Returns null if the given field descriptor
+	 * doesn't refer to a class or is badly-formed.
+	 */
+	public static char[] fieldDescriptorToSimpleName(char[] fieldDescriptor) {
+		if (!CharArrayUtils.startsWith(fieldDescriptor, 'L')) {
+			return null;
+		}
+
+		if (!CharArrayUtils.endsWith(fieldDescriptor, ';')) {
+			return null;
+		}
+
+		int separatorPosition = CharArrayUtils.lastIndexOf('/', fieldDescriptor);
+		if (separatorPosition == -1) {
+			separatorPosition = 0;
+		}
+
+		char[] className = CharArrayUtils.subarray(fieldDescriptor, separatorPosition + 1, fieldDescriptor.length - 1);
+		return className;
+	}
+	
+	/**
+	 * Converts a field descriptor to a java name. If fullyQualified is true, it returns a fully qualified class name.
+	 * If it is false, it returns a source name.
+	 */
+	public static char[] fieldDescriptorToJavaName(char[] fieldDescriptor, boolean fullyQualified) {
+		int arrayCount = 0;
+		CharArrayBuffer result = new CharArrayBuffer();
+		for(int scanPosition = 0; scanPosition < fieldDescriptor.length; scanPosition++) {
+			char nextChar = fieldDescriptor[scanPosition];
+
+			switch (nextChar) {
+				case 'B' : result.append("byte"); break; //$NON-NLS-1$
+				case 'C' : result.append("char"); break; //$NON-NLS-1$
+				case 'D' : result.append("double"); break; //$NON-NLS-1$
+				case 'F' : result.append("float"); break; //$NON-NLS-1$
+				case 'I' : result.append("int"); break; //$NON-NLS-1$
+				case 'J' : result.append("long"); break; //$NON-NLS-1$
+				case 'L' : {
+					int end = fieldDescriptor.length - 1;
+					char[] binaryName = CharArrayUtils.subarray(fieldDescriptor, scanPosition + 1, end);
+					if (fullyQualified) {
+						// Modify the binaryName string in-place to change it into a fully qualified name
+						CharOperation.replace(binaryName, '/', '.');
+						result.append(binaryName);
+					} else {
+						result.append(binaryNameToSimpleName(binaryName));
+					}
+					scanPosition += binaryName.length;
+					break;
+				}
+				case 'S' : result.append("short"); break; //$NON-NLS-1$
+				case 'Z' : result.append("boolean"); break; //$NON-NLS-1$
+				case '[' : arrayCount++; break;
+			}
+		}
+
+		while (--arrayCount >= 0) {
+			result.append("[]"); //$NON-NLS-1$
+		}
+
+		return CharArrayUtils.notNull(result.getContents());
+	}
+
+	public static char[] binaryNameToFullyQualifiedName(char[] binaryName) {
+		return CharOperation.replaceOnCopy(binaryName, '/', '.');
+	}
+
+	/**
+	 * Returns a method id (suitable for constructing a {@link NdMethodId}) given a field descriptor for its parent type
+	 * and a combined method selector and method descriptor for the method
+	 *
+	 * @param parentTypeBinaryName a field descriptor of the sort returned by the other *ToFieldDescriptor methods.
+	 * @param methodSelectorAndDescriptor a method selector and descriptor of the form returned by {@link IBinaryType#getEnclosingMethod()}
+	 * @return a method id suitable for looking up a {@link NdMethodId}
+	 */
+	public static char[] getMethodId(char[] parentTypeBinaryName, char[] methodSelectorAndDescriptor) {
+		return CharArrayUtils.concat(FIELD_DESCRIPTOR_PREFIX, parentTypeBinaryName, METHOD_ID_SEPARATOR,
+				methodSelectorAndDescriptor);
+	}
+
+	public static char[] getMethodId(char[] parentTypeBinaryName, char[] methodSelector, char[] methodDescriptor) {
+		return CharArrayUtils.concat(FIELD_DESCRIPTOR_PREFIX, parentTypeBinaryName, METHOD_ID_SEPARATOR, methodSelector,
+				methodDescriptor);
+	}
+
+	/**
+	 * Given a field descriptor, if the field descriptor points to a class this returns the binary name of the class. If
+	 * the field descriptor points to any other type, this returns the empty string. The field descriptor may optionally
+	 * contain a trailing ';'.
+	 *
+	 * @param fieldDescriptor
+	 * @return ""
+	 */
+	public static char[] fieldDescriptorToBinaryName(char[] fieldDescriptor) {
+		if (CharArrayUtils.startsWith(fieldDescriptor, 'L')) {
+			int end = fieldDescriptor.length - 1;
+			return CharArrayUtils.subarray(fieldDescriptor, 1, end);
+		}
+		return CharArrayUtils.EMPTY_CHAR_ARRAY;
+	}
+
+	/**
+	 * Given a simple name, this returns the source name for the type. Note that this won't work for classes that
+	 * contain a $ in their source name.
+	 */
+	public static char[] simpleNameToSourceName(char[] chars) {
+		int lastSlash = CharOperation.lastIndexOf('/', chars);
+		int lastDollar = CharOperation.lastIndexOf('$', chars);
+		int lastDot = CharOperation.lastIndexOf('.', chars);
+		int startPosition = Math.max(Math.max(lastSlash, lastDollar), lastDot) + 1;
+		while (startPosition < chars.length && Character.isDigit(chars[startPosition])) {
+			startPosition++;
+		}
+		return CharArrayUtils.subarray(chars, startPosition);
+	}
+
+	/**
+	 * Returns true iff the given method selector is a constructor.
+	 */
+	public static boolean isConstructor(char[] selector) {
+		return selector[0] == '<' && selector.length == 6; // Can only match <init>
+	}
+
+	/**
+	 * Returns true iff the given method selector is clinit.
+	 */
+	public static boolean isClinit(char[] selector) {
+		return selector[0] == '<' && selector.length == 8; // Can only match <clinit>
+	}
+
+	public static String classFilePathToBinaryName(String classFilePath) {
+		if (classFilePath.endsWith(".class")) { //$NON-NLS-1$
+			return classFilePath.substring(0, classFilePath.length() - 6);
+		}
+		return classFilePath;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotation.java
new file mode 100644
index 0000000..9715c10
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotation.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * 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.java;
+
+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.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdAnnotation extends NdNode {
+	public static final FieldManyToOne<NdTypeSignature> ANNOTATION_TYPE;
+	public static final FieldOneToMany<NdAnnotationValuePair> ELEMENT_VALUE_PAIRS;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdAnnotation> type;
+
+	static {
+		type = StructDef.create(NdAnnotation.class, NdNode.type);
+		ANNOTATION_TYPE = FieldManyToOne.create(type, NdTypeSignature.ANNOTATIONS_OF_THIS_TYPE);
+		ELEMENT_VALUE_PAIRS = FieldOneToMany.create(type, NdAnnotationValuePair.APPLIES_TO);
+		type.done();
+	}
+
+	public NdAnnotation(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdAnnotation(Nd nd) {
+		super(nd);
+	}
+
+	public NdTypeSignature getType() {
+		return ANNOTATION_TYPE.get(getNd(), this.address);
+	}
+
+	public void setType(NdTypeSignature type) {
+		ANNOTATION_TYPE.put(getNd(), this.address, type);
+	}
+
+	public List<NdAnnotationValuePair> getElementValuePairs() {
+		return ELEMENT_VALUE_PAIRS.asList(getNd(), this.address);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInConstant.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInConstant.java
new file mode 100644
index 0000000..2328a49
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInConstant.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdAnnotationInConstant extends NdAnnotation {
+	public static final FieldOneToOne<NdConstantAnnotation> OWNER;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdAnnotationInConstant> type;
+
+	static {
+		type = StructDef.create(NdAnnotationInConstant.class, NdAnnotation.type);
+		OWNER = FieldOneToOne.createOwner(type, NdConstantAnnotation.class, NdConstantAnnotation.VALUE);
+		type.done();
+	}
+
+	public NdAnnotationInConstant(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdAnnotationInConstant(Nd nd) {
+		super(nd);
+	}
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethod.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethod.java
new file mode 100644
index 0000000..e7e48eb
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethod.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdAnnotationInMethod extends NdAnnotation {
+	public static final FieldManyToOne<NdMethod> OWNER;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdAnnotationInMethod> type;
+
+	static {
+		type = StructDef.create(NdAnnotationInMethod.class, NdAnnotation.type);
+		OWNER = FieldManyToOne.createOwner(type, NdMethod.ANNOTATIONS);
+		type.done();
+	}
+
+	public NdAnnotationInMethod(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdAnnotationInMethod(Nd nd, NdMethod owner) {
+		super(nd);
+
+		OWNER.put(getNd(), this.address, owner);
+	}
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethodParameter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethodParameter.java
new file mode 100644
index 0000000..0a4f3fb
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethodParameter.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdAnnotationInMethodParameter extends NdAnnotation {
+	public static final FieldManyToOne<NdMethodParameter> OWNER;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdAnnotationInMethodParameter> type;
+
+	static {
+		type = StructDef.create(NdAnnotationInMethodParameter.class, NdAnnotation.type);
+		OWNER = FieldManyToOne.createOwner(type, NdMethodParameter.ANNOTATIONS);
+		type.done();
+	}
+
+	public NdAnnotationInMethodParameter(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdAnnotationInMethodParameter(Nd nd, NdMethodParameter owner) {
+		super(nd);
+
+		OWNER.put(getNd(), this.address, owner);
+	}
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInType.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInType.java
new file mode 100644
index 0000000..c220ed9
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInType.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdAnnotationInType extends NdAnnotation {
+	public static final FieldManyToOne<NdType> OWNER;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdAnnotationInType> type;
+
+	static {
+		type = StructDef.create(NdAnnotationInType.class, NdAnnotation.type);
+		OWNER = FieldManyToOne.createOwner(type, NdType.ANNOTATIONS);
+		type.done();
+	}
+
+	public NdAnnotationInType(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdAnnotationInType(Nd nd, NdType owner) {
+		super(nd);
+
+		OWNER.put(getNd(), this.address, owner);
+	}
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInVariable.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInVariable.java
new file mode 100644
index 0000000..378b2d4
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInVariable.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdAnnotationInVariable extends NdAnnotation {
+	public static final FieldManyToOne<NdVariable> OWNER;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdAnnotationInVariable> type;
+
+	static {
+		type = StructDef.create(NdAnnotationInVariable.class, NdAnnotation.type);
+		OWNER = FieldManyToOne.createOwner(type, NdVariable.ANNOTATIONS);
+		type.done();
+	}
+
+	public NdAnnotationInVariable(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdAnnotationInVariable(Nd nd, NdVariable owner) {
+		super(nd);
+
+		OWNER.put(getNd(), this.address, owner);
+	}
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationValuePair.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationValuePair.java
new file mode 100644
index 0000000..f62ceb3
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationValuePair.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdAnnotationValuePair extends NdNode {
+	public static final FieldManyToOne<NdAnnotation> APPLIES_TO;
+	public static final FieldString NAME;
+	public static final FieldOneToOne<NdConstant> VALUE;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdAnnotationValuePair> type;
+
+	static {
+		type = StructDef.create(NdAnnotationValuePair.class, NdNode.type);
+		APPLIES_TO = FieldManyToOne.createOwner(type, NdAnnotation.ELEMENT_VALUE_PAIRS);
+		NAME = type.addString();
+		VALUE = FieldOneToOne.create(type, NdConstant.class, NdConstant.PARENT_ANNOTATION_VALUE);
+		type.done();
+	}
+
+	public NdAnnotationValuePair(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdAnnotationValuePair(NdAnnotation annotation, char[] name) {
+		super(annotation.getNd());
+		Nd nd = annotation.getNd();
+		APPLIES_TO.put(nd, this.address, annotation);
+		NAME.put(nd, this.address, name);
+	}
+
+	public NdAnnotation getAnnotation() {
+		return APPLIES_TO.get(getNd(), this.address);
+	}
+
+	public IString getName() {
+		return NAME.get(getNd(), this.address);
+	}
+
+	public void setName(String name) {
+		NAME.put(getNd(), this.address, name);
+	}
+
+	/**
+	 * Returns the value of this annotation or null if none
+	 */
+	public NdConstant getValue() {
+		return VALUE.get(getNd(), this.address);
+	}
+
+	public void setValue(NdConstant value) {
+		VALUE.put(getNd(), this.address, value);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdBinding.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdBinding.java
new file mode 100644
index 0000000..25938a1
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdBinding.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * 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.java;
+
+import java.util.List;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.jdt.core.Flags;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldInt;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+/**
+ * Base class for bindings in the {@link Nd}.
+ */
+public abstract class NdBinding extends NdNode implements IAdaptable {
+	public static final FieldInt MODIFIERS;
+	public static final FieldOneToMany<NdTypeParameter> TYPE_PARAMETERS;
+	public static final FieldManyToOne<NdResourceFile> FILE;
+	public static final FieldOneToMany<NdVariable> VARIABLES;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdBinding> type;
+
+	static {
+		type = StructDef.create(NdBinding.class, NdNode.type);
+		MODIFIERS = type.addInt();
+		TYPE_PARAMETERS = FieldOneToMany.create(type, NdTypeParameter.PARENT);
+		FILE = FieldManyToOne.createOwner(type, NdResourceFile.ALL_NODES);
+		VARIABLES = FieldOneToMany.create(type, NdVariable.PARENT);
+		type.done();
+	}
+
+	public NdBinding(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdBinding(Nd nd, NdResourceFile resource) {
+		super(nd);
+
+		FILE.put(nd, this.address, resource);
+	}
+
+	public List<NdVariable> getVariables() {
+		return VARIABLES.asList(getNd(), this.address);
+	}
+
+	/**
+	 * Tests whether this binding has one of the flags defined in {@link Flags}
+	 */
+	public boolean hasModifier(int toTest) {
+		return (MODIFIERS.get(getNd(), this.address) & toTest) != 0;
+	}
+
+	/**
+	 * Sets the modifiers for this binding (defined in {@link Flags})
+	 */
+	public void setModifiers(int toSet) {
+		MODIFIERS.put(getNd(), this.address, toSet);
+	}
+
+	public int getModifiers() {
+		return MODIFIERS.get(getNd(), this.address);
+	}
+
+	@Override
+	@SuppressWarnings({ "unchecked", "rawtypes" })
+	public Object getAdapter(Class adapter) {
+		if (adapter.isAssignableFrom(NdBinding.class))
+			return this;
+
+		return null;
+	}
+
+	public final int getBindingConstant() {
+		return getNodeType();
+	}
+
+	public void setFile(NdResourceFile file) {
+		FILE.put(getNd(), this.address, file);
+	}
+
+	public NdResourceFile getFile() {
+		return FILE.get(getNd(), this.address);
+	}
+
+	public char[][] getTypeParameterSignatures() {
+		List<NdTypeParameter> parameters = getTypeParameters();
+		char[][] result = new char[parameters.size()][];
+
+		int idx = 0;
+		for (NdTypeParameter next : parameters) {
+			char[] nextContents = getSignatureFor(next);
+			result[idx] = nextContents;
+			idx++;
+		}
+		return result;
+	}
+
+	private char[] getSignatureFor(NdTypeParameter next) {
+		CharArrayBuffer nextArray = new CharArrayBuffer();
+		next.getSignature(nextArray);
+		char[] nextContents = nextArray.getContents();
+		return nextContents;
+	}
+
+	public List<NdTypeParameter> getTypeParameters() {
+		return TYPE_PARAMETERS.asList(getNd(), this.address);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdComplexTypeSignature.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdComplexTypeSignature.java
new file mode 100644
index 0000000..b348f4e
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdComplexTypeSignature.java
@@ -0,0 +1,201 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+/**
+ * Represents a type signature that is anything other than a trivial reference to a concrete
+ * type. If a type reference includes annotations, generic arguments, wildcards, or is a
+ * type variable, this object represents it.
+ * <p>
+ * Arrays are encoded in a special way. The RAW_TYPE points to a sentinel type called '['
+ * and the first type argument holds the array type.
+ */
+public class NdComplexTypeSignature extends NdTypeSignature {
+	public static final FieldString VARIABLE_IDENTIFIER;
+	public static final FieldManyToOne<NdTypeId> RAW_TYPE;
+	public static final FieldOneToMany<NdTypeArgument> TYPE_ARGUMENTS;
+	public static final FieldManyToOne<NdComplexTypeSignature> DECLARING_TYPE;
+	public static final FieldOneToMany<NdComplexTypeSignature> DECLARED_TYPES;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdComplexTypeSignature> type;
+
+	static {
+		type = StructDef.create(NdComplexTypeSignature.class, NdTypeSignature.type);
+		VARIABLE_IDENTIFIER = type.addString();
+		RAW_TYPE = FieldManyToOne.create(type, NdTypeId.USED_AS_COMPLEX_TYPE);
+		TYPE_ARGUMENTS = FieldOneToMany.create(type, NdTypeArgument.PARENT);
+		DECLARING_TYPE = FieldManyToOne.create(type, null);
+		DECLARED_TYPES = FieldOneToMany.create(type, DECLARING_TYPE);
+
+		type.useStandardRefCounting().done();
+	}
+
+	public NdComplexTypeSignature(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdComplexTypeSignature(Nd nd) {
+		super(nd);
+	}
+
+	@Override
+	public NdTypeId getRawType() {
+		return RAW_TYPE.get(getNd(), this.address);
+	}
+
+	public void setVariableIdentifier(char[] variableIdentifier) {
+		VARIABLE_IDENTIFIER.put(getNd(), this.address, variableIdentifier);
+	}
+
+	/**
+	 * If this type is a type variable, this returns the variable's identifier.
+	 */
+	public IString getVariableIdentifier() {
+		return VARIABLE_IDENTIFIER.get(getNd(), this.address);
+	}
+
+	public void setRawType(NdTypeId rawType) {
+		RAW_TYPE.put(getNd(), this.address, rawType);
+	}
+
+	public void setGenericDeclaringType(NdComplexTypeSignature enclosingType) {
+		DECLARING_TYPE.put(getNd(), this.address, enclosingType);
+	}
+
+	/**
+	 * Returns the declaring type (as reported by the type's generic signature).
+	 * Not to be confused with the declaring type as stored in the class file.
+	 * That is stored in {@link NdType#getDeclaringType}. Any class that is
+	 * nested inside another class with generic arguments will have one of
+	 * these. Classes nested inside non-generic classes won't have one of these,
+	 * and neither will non-nested classes.
+	 */
+	public NdComplexTypeSignature getGenericDeclaringType() {
+		return DECLARING_TYPE.get(getNd(), this.address);
+	}
+
+	@Override
+	public List<NdTypeArgument> getTypeArguments() {
+		return TYPE_ARGUMENTS.asList(getNd(), this.address);
+	}
+
+	@Override
+	public NdTypeSignature getArrayDimensionType() {
+		if (isArrayType()) {
+			long size = TYPE_ARGUMENTS.size(getNd(), this.address);
+
+			if (size != 1) {
+				throw new IndexException("Array types should have exactly one argument"); //$NON-NLS-1$
+			}
+
+			return TYPE_ARGUMENTS.get(getNd(), this.address, 0).getType();
+		}
+		return null;
+	}
+
+	@Override
+	public void getSignature(CharArrayBuffer result, boolean includeTrailingSemicolon) {
+		NdComplexTypeSignature parentSignature = getGenericDeclaringType();
+
+		if (isTypeVariable()) {
+			result.append('T');
+			result.append(getVariableIdentifier().getChars());
+			if (includeTrailingSemicolon) {
+				result.append(';');
+			}
+			return;
+		}
+
+		NdTypeSignature arrayDimension = getArrayDimensionType();
+		if (arrayDimension != null) {
+			result.append('[');
+			arrayDimension.getSignature(result);
+			return;
+		}
+		if (parentSignature != null) {
+			parentSignature.getSignature(result, false);
+			result.append('.');
+			char[] simpleName = getRawType().getSimpleName().getChars();
+			result.append(simpleName);
+		} else {
+			result.append(getRawType().getFieldDescriptorWithoutTrailingSemicolon());
+		}
+
+		List<NdTypeArgument> arguments = getTypeArguments();
+		if (!arguments.isEmpty()) {
+			result.append('<');
+			for (NdTypeArgument next : arguments) {
+				next.getSignature(result);
+			}
+			result.append('>');
+		}
+		if (includeTrailingSemicolon) {
+			result.append(';');
+		}
+	}
+
+	@Override
+	public boolean isTypeVariable() {
+		return getVariableIdentifier().length() != 0;
+	}
+
+	@Override
+	public List<NdTypeSignature> getDeclaringTypeChain() {
+		NdComplexTypeSignature declaringType = getGenericDeclaringType();
+
+		if (declaringType == null) {
+			return Collections.singletonList((NdTypeSignature)this);
+		}
+
+		List<NdTypeSignature> result = new ArrayList<>();
+		computeDeclaringTypes(result);
+		return result;
+	}
+
+	private void computeDeclaringTypes(List<NdTypeSignature> result) {
+		NdComplexTypeSignature declaringType = getGenericDeclaringType();
+
+		if (declaringType != null) {
+			declaringType.computeDeclaringTypes(result);
+		}
+
+		result.add(this);
+	}
+
+	@Override
+	public boolean isArrayType() {
+		NdTypeId rawType = getRawType();
+
+		if (rawType == null) {
+			return false;
+		}
+
+		if (rawType.getFieldDescriptor().comparePrefix(JavaNames.ARRAY_FIELD_DESCRIPTOR_PREFIX, true) == 0) { // $NON-NLS-1$
+			return true;
+		}
+
+		return false;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstant.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstant.java
new file mode 100644
index 0000000..96e6045
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstant.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public abstract class NdConstant extends NdNode {
+	// Parent pointers. Only one will be non-null.
+	// TODO(sxenos): Create something like a union to hold these, to eliminate this
+	// sparse data
+	public static final FieldManyToOne<NdConstantArray> PARENT_ARRAY;
+	public static final FieldOneToOne<NdAnnotationValuePair> PARENT_ANNOTATION_VALUE;
+	public static final FieldOneToOne<NdVariable> PARENT_VARIABLE;
+	public static final FieldOneToOne<NdMethod> PARENT_METHOD;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdConstant> type;
+
+	static {
+		type = StructDef.createAbstract(NdConstant.class, NdNode.type);
+		PARENT_ARRAY = FieldManyToOne.createOwner(type, NdConstantArray.ELEMENTS);
+		PARENT_ANNOTATION_VALUE = FieldOneToOne.createOwner(type, NdAnnotationValuePair.class,
+				NdAnnotationValuePair.VALUE);
+		PARENT_VARIABLE = FieldOneToOne.createOwner(type, NdVariable.class, NdVariable.CONSTANT);
+		PARENT_METHOD = FieldOneToOne.createOwner(type, NdMethod.class, NdMethod.DEFAULT_VALUE);
+		type.done();
+	}
+
+	public NdConstant(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	protected NdConstant(Nd nd) {
+		super(nd);
+	}
+
+	public static NdConstant create(Nd nd, Constant constant) {
+		if (constant == Constant.NotAConstant) {
+			return null;
+		}
+
+		switch (constant.typeID()) {
+			case TypeIds.T_boolean:
+				return NdConstantBoolean.create(nd, constant.booleanValue());
+			case TypeIds.T_byte:
+				return NdConstantByte.create(nd, constant.byteValue());
+			case TypeIds.T_char:
+				return NdConstantChar.create(nd, constant.charValue());
+			case TypeIds.T_double:
+				return NdConstantDouble.create(nd, constant.doubleValue());
+			case TypeIds.T_float:
+				return NdConstantFloat.create(nd, constant.floatValue());
+			case TypeIds.T_int:
+				return NdConstantInt.create(nd, constant.intValue());
+			case TypeIds.T_long:
+				return NdConstantLong.create(nd, constant.longValue());
+			case TypeIds.T_short:
+				return NdConstantShort.create(nd, constant.shortValue());
+			case TypeIds.T_JavaLangString:
+				return NdConstantString.create(nd, constant.stringValue());
+			default:
+				throw new IllegalArgumentException("Unknown typeID() " + constant.typeID()); //$NON-NLS-1$
+		}
+	}
+
+	public void setParent(NdConstantArray result) {
+		PARENT_ARRAY.put(getNd(), this.address, result);
+	}
+
+	/**
+	 * Returns the {@link Constant} corresponding to the value of this {@link NdConstant} or null if the receiver
+	 * corresponds to a {@link Constant}.
+	 */
+	public abstract Constant getConstant();
+
+	public String toString() {
+		try {
+			return getConstant().toString();
+		} catch (RuntimeException e) {
+			// This is called most often from the debugger, so we want to return something meaningful even
+			// if the code is buggy, the database is corrupt, or we don't have a read lock.
+			return super.toString();
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantAnnotation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantAnnotation.java
new file mode 100644
index 0000000..a69e52c
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantAnnotation.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantAnnotation extends NdConstant {
+	public static final FieldOneToOne<NdAnnotationInConstant> VALUE;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdConstantAnnotation> type;
+
+	static {
+		type = StructDef.create(NdConstantAnnotation.class, NdConstant.type);
+		VALUE = FieldOneToOne.create(type, NdAnnotationInConstant.class, NdAnnotationInConstant.OWNER);
+		type.done();
+	}
+
+	public NdConstantAnnotation(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	protected NdConstantAnnotation(Nd nd) {
+		super(nd);
+	}
+
+	public static NdConstantAnnotation create(Nd nd, NdAnnotationInConstant value) {
+		NdConstantAnnotation result = new NdConstantAnnotation(nd);
+		result.setValue(value);
+		return result;
+	}
+
+	public void setValue(NdAnnotationInConstant value) {
+		VALUE.put(getNd(), this.address, value);
+	}
+
+	public NdAnnotation getValue() {
+		return VALUE.get(getNd(), this.address);
+	}
+
+	@Override
+	public Constant getConstant() {
+		return null;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantArray.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantArray.java
new file mode 100644
index 0000000..b9833ce
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantArray.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.java;
+
+import java.util.List;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantArray extends NdConstant {
+	public static final FieldOneToMany<NdConstant> ELEMENTS;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdConstantArray> type;
+
+	static {
+		type = StructDef.create(NdConstantArray.class, NdConstant.type);
+		ELEMENTS = FieldOneToMany.create(type, NdConstant.PARENT_ARRAY, 2);
+		type.done();
+	}
+
+	public NdConstantArray(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdConstantArray(Nd nd) {
+		super(nd);
+	}
+
+	public List<NdConstant> getValue() {
+		return ELEMENTS.asList(getNd(), this.address);
+	}
+
+	@Override
+	public Constant getConstant() {
+		return null;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantBoolean.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantBoolean.java
new file mode 100644
index 0000000..564f7a7
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantBoolean.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.compiler.impl.BooleanConstant;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantBoolean extends NdConstant {
+	public static final FieldByte VALUE;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdConstantBoolean> type;
+
+	static {
+		type = StructDef.create(NdConstantBoolean.class, NdConstant.type);
+		VALUE = type.addByte();
+		type.done();
+	}
+
+	public NdConstantBoolean(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	protected NdConstantBoolean(Nd nd) {
+		super(nd);
+	}
+
+	public static NdConstantBoolean create(Nd nd, boolean value) {
+		NdConstantBoolean result = new NdConstantBoolean(nd);
+		result.setValue(value);
+		return result;
+	}
+
+	public void setValue(boolean value) {
+		VALUE.put(getNd(), this.address, value ? (byte)1 : (byte)0);
+	}
+
+	public boolean getValue() {
+		return VALUE.get(getNd(), this.address) != 0;
+	}
+
+	@Override
+	public Constant getConstant() {
+		return BooleanConstant.fromValue(getValue());
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantByte.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantByte.java
new file mode 100644
index 0000000..77c99ec
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantByte.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.compiler.impl.ByteConstant;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantByte extends NdConstant {
+	public static final FieldByte VALUE;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdConstantByte> type;
+
+	static {
+		type = StructDef.create(NdConstantByte.class, NdConstant.type);
+		VALUE = type.addByte();
+		type.done();
+	}
+
+	public NdConstantByte(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	protected NdConstantByte(Nd nd) {
+		super(nd);
+	}
+
+	public static NdConstantByte create(Nd nd, byte value) {
+		NdConstantByte result = new NdConstantByte(nd);
+		result.setValue(value);
+		return result;
+	}
+
+	public void setValue(byte value) {
+		VALUE.put(getNd(), this.address, value);
+	}
+
+	public byte getValue() {
+		return VALUE.get(getNd(), this.address);
+	}
+
+	@Override
+	public Constant getConstant() {
+		return ByteConstant.fromValue(getValue());
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantChar.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantChar.java
new file mode 100644
index 0000000..f54975a
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantChar.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.compiler.impl.CharConstant;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldChar;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantChar extends NdConstant {
+	public static final FieldChar VALUE;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdConstantChar> type;
+
+	static {
+		type = StructDef.create(NdConstantChar.class, NdConstant.type);
+		VALUE = type.addChar();
+		type.done();
+	}
+
+	public NdConstantChar(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	protected NdConstantChar(Nd nd) {
+		super(nd);
+	}
+
+	public static NdConstantChar create(Nd nd, char value) {
+		NdConstantChar result = new NdConstantChar(nd);
+		result.setValue(value);
+		return result;
+	}
+
+	public void setValue(char value) {
+		VALUE.put(getNd(), this.address, value);
+	}
+
+	public char getValue() {
+		return VALUE.get(getNd(), this.address);
+	}
+
+	@Override
+	public Constant getConstant() {
+		return CharConstant.fromValue(getValue());
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantClass.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantClass.java
new file mode 100644
index 0000000..fc6fc6c
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantClass.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantClass extends NdConstant {
+	public static final FieldManyToOne<NdTypeSignature> VALUE;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdConstantClass> type;
+
+	static {
+		type = StructDef.create(NdConstantClass.class, NdConstant.type);
+		VALUE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_CONSTANT);
+		type.done();
+	}
+
+	public NdConstantClass(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	protected NdConstantClass(Nd nd) {
+		super(nd);
+	}
+
+	public static NdConstantClass create(Nd nd, NdTypeSignature value) {
+		NdConstantClass result = new NdConstantClass(nd);
+		result.setValue(value);
+		return result;
+	}
+
+	public void setValue(NdTypeSignature value) {
+		VALUE.put(getNd(), this.address, value);
+	}
+
+	public NdTypeSignature getValue() {
+		return VALUE.get(getNd(), this.address);
+	}
+
+	@Override
+	public Constant getConstant() {
+		return null;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantDouble.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantDouble.java
new file mode 100644
index 0000000..56354ba
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantDouble.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.impl.DoubleConstant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldDouble;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantDouble extends NdConstant {
+	public static final FieldDouble VALUE;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdConstantDouble> type;
+
+	static {
+		type = StructDef.create(NdConstantDouble.class, NdConstant.type);
+		VALUE = type.addDouble();
+		type.done();
+	}
+
+	public NdConstantDouble(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	protected NdConstantDouble(Nd nd) {
+		super(nd);
+	}
+
+	public static NdConstantDouble create(Nd nd, double value) {
+		NdConstantDouble result = new NdConstantDouble(nd);
+		result.setValue(value);
+		return result;
+	}
+
+	public void setValue(double value) {
+		VALUE.put(getNd(), this.address, value);
+	}
+
+	public double getValue() {
+		return VALUE.get(getNd(), this.address);
+	}
+
+	@Override
+	public Constant getConstant() {
+		return DoubleConstant.fromValue(getValue());
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantEnum.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantEnum.java
new file mode 100644
index 0000000..c4e9c8e
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantEnum.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantEnum extends NdConstant {
+	public static final FieldManyToOne<NdTypeSignature> ENUM_TYPE;
+	public static final FieldString ENUM_VALUE;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdConstantEnum> type;
+
+	static {
+		type = StructDef.create(NdConstantEnum.class, NdConstant.type);
+		ENUM_TYPE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_ENUM_CONSTANT);
+		ENUM_VALUE = type.addString();
+		type.done();
+	}
+
+	public NdConstantEnum(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	protected NdConstantEnum(Nd nd) {
+		super(nd);
+	}
+
+	public static NdConstantEnum create(NdTypeSignature enumType, String enumValue) {
+		NdConstantEnum result = new NdConstantEnum(enumType.getNd());
+		result.setEnumType(enumType);
+		result.setEnumValue(enumValue);
+		return result;
+	}
+
+	public void setEnumType(NdTypeSignature enumType) {
+		ENUM_TYPE.put(getNd(), this.address, enumType);
+	}
+
+	public void setEnumValue(String enumType) {
+		ENUM_VALUE.put(getNd(), this.address, enumType);
+	}
+
+	public NdTypeSignature getType() {
+		return ENUM_TYPE.get(getNd(), this.address);
+	}
+
+	public char[] getValue() {
+		return ENUM_VALUE.get(getNd(), this.address).getChars();
+	}
+
+	@Override
+	public Constant getConstant() {
+		return null;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantFloat.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantFloat.java
new file mode 100644
index 0000000..731405e
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantFloat.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.impl.FloatConstant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldFloat;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantFloat extends NdConstant {
+	public static final FieldFloat VALUE;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdConstantFloat> type;
+
+	static {
+		type = StructDef.create(NdConstantFloat.class, NdConstant.type);
+		VALUE = type.addFloat();
+		type.done();
+	}
+
+	public NdConstantFloat(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	protected NdConstantFloat(Nd nd) {
+		super(nd);
+	}
+
+	public static NdConstantFloat create(Nd nd, float value) {
+		NdConstantFloat result = new NdConstantFloat(nd);
+		result.setValue(value);
+		return result;
+	}
+
+	public void setValue(float value) {
+		VALUE.put(getNd(), this.address, value);
+	}
+
+	public float getValue() {
+		return VALUE.get(getNd(), this.address);
+	}
+
+	@Override
+	public Constant getConstant() {
+		return FloatConstant.fromValue(getValue());
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantInt.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantInt.java
new file mode 100644
index 0000000..f663675
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantInt.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.impl.IntConstant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldInt;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantInt extends NdConstant {
+	public static final FieldInt VALUE;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdConstantInt> type;
+
+	static {
+		type = StructDef.create(NdConstantInt.class, NdConstant.type);
+		VALUE = type.addInt();
+		type.done();
+	}
+
+	public NdConstantInt(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	protected NdConstantInt(Nd nd) {
+		super(nd);
+	}
+
+	public static NdConstantInt create(Nd nd, int value) {
+		NdConstantInt result = new NdConstantInt(nd);
+		result.setValue(value);
+		return result;
+	}
+
+	public void setValue(int value) {
+		VALUE.put(getNd(), this.address, value);
+	}
+
+	public int getValue() {
+		return VALUE.get(getNd(), this.address);
+	}
+
+	@Override
+	public Constant getConstant() {
+		return IntConstant.fromValue(getValue());
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantLong.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantLong.java
new file mode 100644
index 0000000..9bed546
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantLong.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.impl.LongConstant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldLong;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantLong extends NdConstant {
+	public static final FieldLong VALUE;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdConstantLong> type;
+
+	static {
+		type = StructDef.create(NdConstantLong.class, NdConstant.type);
+		VALUE = type.addLong();
+		type.done();
+	}
+
+	public NdConstantLong(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	protected NdConstantLong(Nd nd) {
+		super(nd);
+	}
+
+	public static NdConstantLong create(Nd nd, long value) {
+		NdConstantLong result = new NdConstantLong(nd);
+		result.setValue(value);
+		return result;
+	}
+
+	public void setValue(long value) {
+		VALUE.put(getNd(), this.address, value);
+	}
+
+	public long getValue() {
+		return VALUE.get(getNd(), this.address);
+	}
+
+	@Override
+	public Constant getConstant() {
+		return LongConstant.fromValue(getValue());
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantShort.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantShort.java
new file mode 100644
index 0000000..3c9fa0f
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantShort.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.impl.ShortConstant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldShort;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantShort extends NdConstant {
+	public static final FieldShort VALUE;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdConstantShort> type;
+
+	static {
+		type = StructDef.create(NdConstantShort.class, NdConstant.type);
+		VALUE = type.addShort();
+		type.done();
+	}
+
+	public NdConstantShort(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	protected NdConstantShort(Nd nd) {
+		super(nd);
+	}
+
+	public static NdConstantShort create(Nd nd, short value) {
+		NdConstantShort result = new NdConstantShort(nd);
+		result.setValue(value);
+		return result;
+	}
+
+	public void setValue(short value) {
+		VALUE.put(getNd(), this.address, value);
+	}
+
+	public short getValue() {
+		return VALUE.get(getNd(), this.address);
+	}
+
+	@Override
+	public Constant getConstant() {
+		return ShortConstant.fromValue(getValue());
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantString.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantString.java
new file mode 100644
index 0000000..4c1aeff
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantString.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.impl.StringConstant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantString extends NdConstant {
+	public static final FieldString VALUE;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdConstantString> type;
+
+	static {
+		type = StructDef.create(NdConstantString.class, NdConstant.type);
+		VALUE = type.addString();
+		type.done();
+	}
+
+	public NdConstantString(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	protected NdConstantString(Nd nd) {
+		super(nd);
+	}
+
+	public static NdConstantString create(Nd nd, String value) {
+		NdConstantString result = new NdConstantString(nd);
+		result.setValue(value);
+		return result;
+	}
+
+	public void setValue(String value) {
+		VALUE.put(getNd(), this.address, value);
+	}
+
+	public IString getValue() {
+		return VALUE.get(getNd(), this.address);
+	}
+
+	@Override
+	public Constant getConstant() {
+		return StringConstant.fromValue(getValue().getString());
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethod.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethod.java
new file mode 100644
index 0000000..77991be
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethod.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.java;
+
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldLong;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldShort;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+public class NdMethod extends NdBinding {
+	public static final FieldManyToOne<NdMethodId> METHOD_ID;
+	public static final FieldShort METHOD_FLAGS;
+	public static final FieldManyToOne<NdType> PARENT;
+	public static final FieldOneToMany<NdVariable> DECLARED_VARIABLES;
+	public static final FieldOneToMany<NdMethodParameter> PARAMETERS;
+	public static final FieldOneToOne<NdConstant> DEFAULT_VALUE;
+	public static final FieldOneToMany<NdMethodException> EXCEPTIONS;
+	public static final FieldManyToOne<NdTypeSignature> RETURN_TYPE;
+	public static final FieldLong TAG_BITS;
+	public static final FieldOneToMany<NdAnnotationInMethod> ANNOTATIONS;
+	public static final FieldOneToMany<NdTypeAnnotationInMethod> TYPE_ANNOTATIONS;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdMethod> type;
+
+	static {
+		type = StructDef.create(NdMethod.class, NdBinding.type);
+		METHOD_ID = FieldManyToOne.create(type, NdMethodId.METHODS);
+		METHOD_FLAGS = type.addShort();
+		PARENT = FieldManyToOne.create(type, NdType.METHODS);
+		PARAMETERS = FieldOneToMany.create(type, NdMethodParameter.PARENT);
+		DECLARED_VARIABLES = FieldOneToMany.create(type, NdVariable.DECLARING_METHOD);
+		DEFAULT_VALUE = FieldOneToOne.create(type, NdConstant.class, NdConstant.PARENT_METHOD);
+		EXCEPTIONS = FieldOneToMany.create(type, NdMethodException.PARENT);
+		RETURN_TYPE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_RETURN_TYPE);
+		TAG_BITS = type.addLong();
+		ANNOTATIONS = FieldOneToMany.create(type, NdAnnotationInMethod.OWNER);
+		TYPE_ANNOTATIONS = FieldOneToMany.create(type, NdTypeAnnotationInMethod.OWNER);
+		type.done();
+	}
+
+	public static final byte FLG_GENERIC_SIGNATURE_PRESENT = 0x0001;
+	public static final byte FLG_THROWS_SIGNATURE_PRESENT = 0x0002;
+
+	public NdMethod(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdMethod(NdType parent) {
+		super(parent.getNd(), parent.getFile());
+
+		PARENT.put(getNd(), this.address, parent);
+	}
+
+	public NdMethodId getMethodId() {
+		return METHOD_ID.get(getNd(), this.address);
+	}
+
+	/**
+	 * Returns method parameter names that were not defined by the compiler.
+	 */
+	public char[][] getParameterNames() {
+		List<NdMethodParameter> params = getMethodParameters();
+
+		// Use index to count the "real" parameters.
+		int index = 0;
+		char[][] result = new char[params.size()][];
+		for (int idx = 0; idx < result.length; idx++) {
+			NdMethodParameter param = params.get(idx);
+			if (!param.isCompilerDefined()) {
+				result[index] = param.getName().getChars();
+				index++;
+			}
+		}
+		return CharArrayUtils.subarray(result, 0, index);
+	}
+
+	public List<NdMethodParameter> getMethodParameters() {
+		return PARAMETERS.asList(getNd(), this.address);
+	}
+
+	public List<NdAnnotationInMethod> getAnnotations() {
+		return ANNOTATIONS.asList(getNd(), this.address);
+	}
+
+	public void setDefaultValue(NdConstant value) {
+		DEFAULT_VALUE.put(getNd(), this.address, value);
+	}
+
+	public NdConstant getDefaultValue() {
+		return DEFAULT_VALUE.get(getNd(), this.address);
+	}
+
+	public void setReturnType(NdTypeSignature createTypeSignature) {
+		RETURN_TYPE.put(getNd(), this.address, createTypeSignature);
+	}
+
+	public void setMethodId(NdMethodId methodId) {
+		METHOD_ID.put(getNd(), this.address, methodId);
+	}
+
+	public List<NdTypeAnnotationInMethod> getTypeAnnotations() {
+		return TYPE_ANNOTATIONS.asList(getNd(), this.address);
+	}
+
+	public List<NdMethodException> getExceptions() {
+		return EXCEPTIONS.asList(getNd(), this.address);
+	}
+
+	/**
+	 * Returns the return type for this method or null if the method returns void
+	 */
+	public NdTypeSignature getReturnType() {
+		return RETURN_TYPE.get(getNd(), this.address);
+	}
+
+	public int getFlags() {
+		return METHOD_FLAGS.get(getNd(), this.address);
+	}
+
+	public boolean hasAllFlags(int flags) {
+		int ourFlags = getFlags();
+
+		return (ourFlags & flags) == flags;
+	}
+
+	public void setFlags(int flags) {
+		METHOD_FLAGS.put(getNd(), this.address, (short) (getFlags() | flags));
+	}
+
+	public void setTagBits(long bits) {
+		TAG_BITS.put(getNd(), this.address, bits);
+	}
+
+	public long getTagBits() {
+		return TAG_BITS.get(getNd(), this.address);
+	}
+
+	public String toString() {
+		try {
+			CharArrayBuffer arrayBuffer = new CharArrayBuffer();
+			arrayBuffer.append(getMethodId().getSelector());
+			getGenericSignature(arrayBuffer, true);
+			return arrayBuffer.toString();
+		} catch (RuntimeException e) {
+			// This is called most often from the debugger, so we want to return something meaningful even
+			// if the code is buggy, the database is corrupt, or we don't have a read lock.
+			return super.toString();
+		}
+	}
+
+	public void getGenericSignature(CharArrayBuffer result, boolean includeExceptions) {
+		NdTypeParameter.getSignature(result, getTypeParameters());
+
+		result.append('(');
+		for (NdMethodParameter next : getMethodParameters()) {
+			// Compiler-defined arguments don't show up in the generic signature
+			if (!next.isCompilerDefined()) {
+				next.getType().getSignature(result);
+			}
+		}
+		result.append(')');
+		NdTypeSignature returnType = getReturnType();
+		if (returnType == null) {
+			result.append('V');
+		} else {
+			returnType.getSignature(result);
+		}
+		if (includeExceptions) {
+			List<NdMethodException> exceptions = getExceptions();
+			for (NdMethodException next : exceptions) {
+				result.append('^');
+				next.getExceptionType().getSignature(result);
+			}
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodException.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodException.java
new file mode 100644
index 0000000..4c7cfe5
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodException.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.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdMethodException extends NdNode {
+
+	public static final FieldManyToOne<NdMethod> PARENT;
+	public static final FieldManyToOne<NdTypeSignature> EXCEPTION_TYPE;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdMethodException> type;
+
+	static {
+		type = StructDef.create(NdMethodException.class, NdNode.type);
+		PARENT = FieldManyToOne.createOwner(type, NdMethod.EXCEPTIONS);
+		EXCEPTION_TYPE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_EXCEPTION);
+		type.done();
+	}
+
+	public NdMethodException(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdMethodException(NdMethod method, NdTypeSignature createTypeSignature) {
+		super(method.getNd());
+
+		PARENT.put(getNd(), this.address, method);
+		EXCEPTION_TYPE.put(getNd(), this.address, createTypeSignature);
+	}
+
+	public NdTypeSignature getExceptionType() {
+		return EXCEPTION_TYPE.get(getNd(), this.address);
+	}
+
+	public NdMethod getParent() {
+		return PARENT.get(getNd(), this.address);
+	}
+
+	public String toString() {
+		try {
+			return getExceptionType().toString();
+		} catch (RuntimeException e) {
+			// This is called most often from the debugger, so we want to return something meaningful even
+			// if the code is buggy, the database is corrupt, or we don't have a read lock.
+			return super.toString();
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodId.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodId.java
new file mode 100644
index 0000000..f554b80
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodId.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * 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.java;
+
+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.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchKey;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+
+/**
+ * Represents the fully-qualified signature a method. Holds back-pointers to all the entities that refer to the name,
+ * along with pointers to all methods that have this fully-qualified name. Note that this isn't the class declaration
+ * itself. If there are multiple jar files containing a class of the same fully-qualified name, there may also be
+ * multiple methods with the same method ID.
+ */
+public class NdMethodId extends NdNode {
+	public static final FieldSearchKey<JavaIndex> METHOD_NAME;
+	public static final FieldOneToMany<NdMethod> METHODS;
+	public static final FieldOneToMany<NdType> DECLARED_TYPES;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdMethodId> type;
+
+	static {
+		type = StructDef.create(NdMethodId.class, NdNode.type);
+		METHOD_NAME = FieldSearchKey.create(type, JavaIndex.METHODS);
+		METHODS = FieldOneToMany.create(type, NdMethod.METHOD_ID, 2);
+		DECLARED_TYPES = FieldOneToMany.create(type, NdType.DECLARING_METHOD);
+
+		type.useStandardRefCounting().done();
+	}
+
+	public NdMethodId(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	/**
+	 *
+	 * @param nd
+	 * @param methodIdentifier a field descriptor for the method type followed by a "#" followed by a method selector
+	 *  followed by method descriptor. For example, "Lorg/eclipse/MyClass#foo()Ljava/lang/Object;V"
+	 */
+	public NdMethodId(Nd nd, char[] methodIdentifier) {
+		super(nd);
+
+		METHOD_NAME.put(nd, this.address, methodIdentifier);
+	}
+
+	public List<NdType> getDeclaredTypes() {
+		return DECLARED_TYPES.asList(getNd(), this.address);
+	}
+
+	/**
+	 * Returns the field descriptor for the type (without a trailing ';') followed by a # followed by the method
+	 * selector followed by the method descriptor. For example, "Lorg/eclipse/MyClass#foo()Ljava/lang/Object;V"
+	 */
+	public IString getMethodName() {
+		return METHOD_NAME.get(getNd(), this.address);
+	}
+
+	public char[] getSelector() {
+		char[] name = getMethodName().getChars();
+		int selectorStart = CharArrayUtils.indexOf('#', name) + 1;
+		int selectorEnd = CharArrayUtils.indexOf('(', name, selectorStart, name.length);
+		if (selectorEnd == -1) {
+			selectorEnd = name.length;
+		}
+		return CharArrayUtils.subarray(name, selectorStart, selectorEnd);
+	}
+
+	public boolean isConstructor() {
+		return JavaNames.isConstructor(getSelector());
+	}
+
+	public char[] getMethodDescriptor() {
+		char[] name = getMethodName().getChars();
+		int descriptorStart = CharArrayUtils.indexOf('(', name, 0, name.length);
+		return CharArrayUtils.subarray(name, descriptorStart, name.length);
+	}
+
+	public boolean isClInit() {
+		return JavaNames.isClinit(getSelector());
+	}
+
+	public String toString() {
+		try {
+			return new String(getSelector());
+		} catch (RuntimeException e) {
+			// This is called most often from the debugger, so we want to return something meaningful even
+			// if the code is buggy, the database is corrupt, or we don't have a read lock.
+			return super.toString();
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodParameter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodParameter.java
new file mode 100644
index 0000000..bdcd832
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodParameter.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import 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.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+public class NdMethodParameter extends NdNode {
+	public static final FieldManyToOne<NdMethod> PARENT;
+	public static final FieldManyToOne<NdTypeSignature> ARGUMENT_TYPE;
+	public static final FieldString NAME;
+	public static final FieldOneToMany<NdAnnotationInMethodParameter> ANNOTATIONS;
+	public static final FieldByte FLAGS;
+
+	private static final byte FLG_COMPILER_DEFINED = 0x01;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdMethodParameter> type;
+
+	static {
+		type = StructDef.create(NdMethodParameter.class, NdNode.type);
+		PARENT = FieldManyToOne.create(type, NdMethod.PARAMETERS);
+		ARGUMENT_TYPE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_METHOD_ARGUMENT);
+		NAME = type.addString();
+		ANNOTATIONS = FieldOneToMany.create(type, NdAnnotationInMethodParameter.OWNER);
+		FLAGS = type.addByte();
+		type.done();
+	}
+
+	public NdMethodParameter(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdMethodParameter(NdMethod parent, NdTypeSignature argumentType) {
+		super(parent.getNd());
+
+		PARENT.put(getNd(), this.address, parent);
+		ARGUMENT_TYPE.put(getNd(), this.address, argumentType);
+	}
+
+	public NdTypeSignature getType() {
+		return ARGUMENT_TYPE.get(getNd(), this.address);
+	}
+
+	public void setName(char[] name) {
+		NAME.put(getNd(), this.address, name);
+	}
+
+	public IString getName() {
+		return NAME.get(getNd(), this.address);
+	}
+
+	public List<NdAnnotationInMethodParameter> getAnnotations() {
+		return ANNOTATIONS.asList(getNd(), this.address);
+	}
+
+	private void setFlag(byte flagConstant, boolean value) {
+		int oldFlags = FLAGS.get(getNd(), this.address);
+		int newFlags = ((oldFlags & ~flagConstant) | (value ? flagConstant : 0));
+		FLAGS.put(getNd(), this.address, (byte) newFlags);
+	}
+
+	private boolean getFlag(byte flagConstant) {
+		return (FLAGS.get(getNd(), this.address) & flagConstant) != 0;
+	}
+
+	public void setCompilerDefined(boolean isCompilerDefined) {
+		setFlag(FLG_COMPILER_DEFINED, isCompilerDefined);
+	}
+
+	public boolean isCompilerDefined() {
+		return getFlag(FLG_COMPILER_DEFINED);
+	}
+
+	public String toString() {
+		try {
+			CharArrayBuffer buf = new CharArrayBuffer();
+			buf.append(getType().toString());
+			buf.append(" "); //$NON-NLS-1$
+			buf.append(getName().toString());
+			return buf.toString();
+		} catch (RuntimeException e) {
+			// This is called most often from the debugger, so we want to return something meaningful even
+			// if the code is buggy, the database is corrupt, or we don't have a read lock.
+			return super.toString();
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdResourceFile.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdResourceFile.java
new file mode 100644
index 0000000..14e2e53
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdResourceFile.java
@@ -0,0 +1,260 @@
+/*******************************************************************************
+ * 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.java;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+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.IString;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+import org.eclipse.jdt.internal.core.nd.field.FieldLong;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany.Visitor;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchIndex.IResultRank;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchIndex.SearchCriteria;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchKey;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+/**
+ * Represents a source of java classes (such as a .jar or .class file).
+ */
+public class NdResourceFile extends NdTreeNode {
+	public static final FieldSearchKey<JavaIndex> FILENAME;
+	public static final FieldOneToMany<NdBinding> ALL_NODES;
+	public static final FieldLong TIME_LAST_USED;
+	public static final FieldLong TIME_LAST_SCANNED;
+	public static final FieldLong SIZE_LAST_SCANNED;
+	public static final FieldLong HASHCODE_LAST_SCANNED;
+	public static final FieldOneToMany<NdWorkspaceLocation> WORKSPACE_MAPPINGS;
+	public static final FieldString JAVA_ROOT;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdResourceFile> type;
+
+	static {
+		type = StructDef.create(NdResourceFile.class, NdTreeNode.type);
+		FILENAME = FieldSearchKey.create(type, JavaIndex.FILES);
+		ALL_NODES = FieldOneToMany.create(type, NdBinding.FILE, 16);
+		TIME_LAST_USED = type.addLong();
+		TIME_LAST_SCANNED = type.addLong();
+		SIZE_LAST_SCANNED = type.addLong();
+		HASHCODE_LAST_SCANNED = type.addLong();
+		WORKSPACE_MAPPINGS = FieldOneToMany.create(type, NdWorkspaceLocation.RESOURCE);
+		JAVA_ROOT = type.addString();
+		type.done();
+	}
+
+	public NdResourceFile(Nd dom, long address) {
+		super(dom, address);
+	}
+
+	public NdResourceFile(Nd nd) {
+		super(nd, null);
+	}
+
+	public List<NdTreeNode> getChildren() {
+		return CHILDREN.asList(this.getNd(), this.address);
+	}
+
+	/**
+	 * Determines whether this file is still in the index. If a {@link NdResourceFile} instance is retained while the
+	 * database lock is released and reobtained, this method should be invoked to ensure that the {@link NdResourceFile}
+	 * has not been deleted in the meantime.
+	 */
+	public boolean isInIndex() {
+		try {
+			Nd nd = getNd();
+			// In the common case where the resource file was deleted and the memory hasn't yet been reused,
+			// this will fail.
+			if (NODE_TYPE.get(nd, this.address) != nd.getNodeType(getClass())) {
+				return false;
+			}
+
+			char[] filename = FILENAME.get(getNd(), this.address).getChars();
+
+			NdResourceFile result = JavaIndex.FILES.findBest(nd, Database.DATA_AREA_OFFSET,
+					SearchCriteria.create(filename), new IResultRank() {
+						@Override
+						public long getRank(Nd testNd, long testAddress) {
+							if (testAddress == NdResourceFile.this.address) {
+								return 1;
+							}
+							return -1;
+						}
+					});
+
+			return (this.equals(result));
+		} catch (IndexException e) {
+			// Read errors are expected here. It's possible that the resource file has been deleted and something
+			// new was written to this address, in which case we may be reading random gibberish from the database.
+			// This is likely to cause an exception.
+			return false;
+		}
+	}
+
+	public List<IPath> getAllWorkspaceLocations() {
+		final List<IPath> result = new ArrayList<>();
+
+		WORKSPACE_MAPPINGS.accept(getNd(), this.address, new Visitor<NdWorkspaceLocation>() {
+			@Override
+			public void visit(int index, NdWorkspaceLocation toVisit) {
+				result.add(new Path(toVisit.getPath().getString()));
+			}
+		});
+
+		return result;
+	}
+
+	public IPath getFirstWorkspaceLocation() {
+		if (WORKSPACE_MAPPINGS.isEmpty(getNd(), this.address)) {
+			return Path.EMPTY;
+		}
+
+		return new Path(WORKSPACE_MAPPINGS.get(getNd(), this.address, 0).getPath().toString());
+	}
+
+	public IPath getAnyOpenWorkspaceLocation(IWorkspaceRoot root) {
+		int numMappings = WORKSPACE_MAPPINGS.size(getNd(), this.address);
+
+		for (int mapping = 0; mapping < numMappings; mapping++) {
+			NdWorkspaceLocation nextMapping = WORKSPACE_MAPPINGS.get(getNd(), this.address, mapping);
+
+			IPath nextPath = new Path(nextMapping.getPath().getString());
+			if (nextPath.isEmpty()) {
+				continue;
+			}
+
+			IProject project = root.getProject(nextPath.segment(0));
+			if (project.isOpen()) {
+				return nextPath;
+			}
+		}
+
+		return Path.EMPTY;
+	}
+
+	/**
+	 * Returns a workspace path to this resource if possible and the absolute filesystem location if not.
+	 */
+	public IPath getPath() {
+		IPath workspacePath = getFirstWorkspaceLocation();
+
+		if (workspacePath.isEmpty()) {
+			return new Path(getLocation().getString());
+		}
+
+		return workspacePath;
+	}
+
+	public List<NdWorkspaceLocation> getWorkspaceMappings() {
+		return WORKSPACE_MAPPINGS.asList(getNd(), this.address);
+	}
+
+	public IString getLocation() {
+		return FILENAME.get(getNd(), this.address);
+	}
+
+	public void setLocation(String filename) {
+		FILENAME.put(getNd(), this.address, filename);
+	}
+
+	public FileFingerprint getFingerprint() {
+		return new FileFingerprint(
+				getTimeLastScanned(),
+				getSizeLastScanned(),
+				getHashcodeLastScanned());
+	}
+
+	private long getHashcodeLastScanned() {
+		return HASHCODE_LAST_SCANNED.get(getNd(), this.address);
+	}
+
+	/**
+	 * Returns true iff the indexer has finished writing the contents of this file to the index. Returns false if
+	 * indexing may still be going on. If this returns false, readers should ignore all contents of this file.
+	 *
+	 * @return true iff the contents of this file are usable
+	 */
+	public boolean isDoneIndexing() {
+		return getTimeLastScanned() != 0;
+	}
+
+	public long getTimeLastScanned() {
+		return TIME_LAST_SCANNED.get(getNd(), this.address);
+	}
+
+	public long getSizeLastScanned() {
+		return SIZE_LAST_SCANNED.get(getNd(), this.address);
+	}
+
+	public long getTimeLastUsed() {
+		return TIME_LAST_USED.get(getNd(), this.address);
+	}
+
+	public void setTimeLastUsed(long timeLastUsed) {
+		TIME_LAST_USED.put(getNd(), this.address, timeLastUsed);
+	}
+
+	public void setFingerprint(FileFingerprint newFingerprint) {
+		TIME_LAST_SCANNED.put(getNd(), this.address, newFingerprint.getTime());
+		HASHCODE_LAST_SCANNED.put(getNd(), this.address, newFingerprint.getHash());
+		SIZE_LAST_SCANNED.put(getNd(), this.address, newFingerprint.getSize());
+	}
+
+	public void setPackageFragmentRoot(char[] javaRoot) {
+		JAVA_ROOT.put(getNd(), this.address, javaRoot);
+	}
+
+	/**
+	 * Returns the absolute path to the java root for this .jar or .class file. If this is a .jar file, it returns its
+	 * own filename.
+	 */
+	public IString getPackageFragmentRoot() {
+		IString javaRoot = JAVA_ROOT.get(getNd(), this.address);
+		if (javaRoot.length() == 0) {
+			return getLocation();
+		}
+		return javaRoot;
+	}
+
+	public void markAsInvalid() {
+		TIME_LAST_SCANNED.put(getNd(), this.address, 0);
+	}
+
+	public int getBindingCount() {
+		return ALL_NODES.size(getNd(), this.address);
+	}
+
+	public List<NdBinding> getBindings() {
+		return ALL_NODES.asList(getNd(), this.address);
+	}
+
+	public NdBinding getBinding(int index) {
+		return ALL_NODES.get(getNd(), this.address, index);
+	}
+
+	public String toString() {
+		try {
+			return FILENAME.get(getNd(), this.address).toString();
+		} catch (RuntimeException e) {
+			// This is called most often from the debugger, so we want to return something meaningful even
+			// if the code is buggy, the database is corrupt, or we don't have a read lock.
+			return super.toString();
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTreeNode.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTreeNode.java
new file mode 100644
index 0000000..d9e33db
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTreeNode.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+/**
+ * {@link NdTreeNode} elements form a tree of nodes rooted at a {@link NdResourceFile}. Each node contains a list of
+ * children which it declares and has a pointer to the most specific node which declares it.
+ */
+public abstract class NdTreeNode extends NdNode {
+	public static final FieldManyToOne<NdTreeNode> PARENT;
+	public static final FieldOneToMany<NdTreeNode> CHILDREN;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdTreeNode> type;
+
+	static {
+		type = StructDef.create(NdTreeNode.class, NdNode.type);
+		PARENT = FieldManyToOne.create(type, null);
+		CHILDREN = FieldOneToMany.create(type, PARENT, 16);
+		type.done();
+	}
+
+	public NdTreeNode(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	protected NdTreeNode(Nd nd, NdTreeNode parent) {
+		super(nd);
+
+		PARENT.put(nd, this.address, parent == null ? 0 : parent.address);
+	}
+
+	public int getChildrenCount() {
+		return CHILDREN.size(getNd(), this.address);
+	}
+
+	public NdTreeNode getChild(int index) {
+		return CHILDREN.get(getNd(), this.address, index);
+	}
+
+	/**
+	 * Returns the closest ancestor of the given type, or null if none. Note that
+	 * this looks for an exact match. It will not return subtypes of the given type.
+	 */
+	@SuppressWarnings("unchecked")
+	public <T extends NdTreeNode> T getAncestorOfType(Class<T> ancestorType) {
+		long targetType = getNd().getNodeType(ancestorType);
+
+		Nd nd = getNd();
+		long current = PARENT.getAddress(nd, this.address);
+
+		while (current != 0) {
+			short currentType = NODE_TYPE.get(nd, current);
+
+			if (currentType == targetType) {
+				NdNode result = load(nd, current);
+
+				if (ancestorType.isInstance(result)) {
+					return (T) result;
+				} else {
+					throw new IndexException("The node at address " + current +  //$NON-NLS-1$
+							" should have been an instance of " + ancestorType.getName() +  //$NON-NLS-1$
+							" but was an instance of " + result.getClass().getName()); //$NON-NLS-1$
+				}
+			}
+
+			current = PARENT.getAddress(nd, current);
+		}
+
+		return null;
+	}
+
+	NdTreeNode getParentNode() {
+		return PARENT.get(getNd(), this.address);
+	}
+
+	public NdBinding getParentBinding() throws IndexException {
+		NdNode parent= getParentNode();
+		if (parent instanceof NdBinding) {
+			return (NdBinding) parent;
+		}
+		return null;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdType.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdType.java
new file mode 100644
index 0000000..5d28363
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdType.java
@@ -0,0 +1,266 @@
+/*******************************************************************************
+ * 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.java;
+
+import java.util.List;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.internal.core.nd.INdVisitor;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.FieldLong;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+
+public class NdType extends NdBinding {
+	public static final FieldManyToOne<NdTypeId> TYPENAME;
+	public static final FieldManyToOne<NdTypeSignature> SUPERCLASS;
+	public static final FieldOneToMany<NdTypeInterface> INTERFACES;
+	public static final FieldManyToOne<NdTypeId> DECLARING_TYPE;
+	public static final FieldManyToOne<NdMethodId> DECLARING_METHOD;
+	public static final FieldOneToMany<NdMethod> METHODS;
+	public static final FieldOneToMany<NdTypeAnnotationInType> TYPE_ANNOTATIONS;
+	public static final FieldOneToMany<NdAnnotationInType> ANNOTATIONS;
+	public static final FieldString MISSING_TYPE_NAMES;
+	public static final FieldString SOURCE_FILE_NAME;
+	public static final FieldString INNER_CLASS_SOURCE_NAME;
+	public static final FieldByte FLAGS;
+	public static final FieldLong TAG_BITS;
+	/**
+	 * Binary name that was recorded in the .class file if different from the binary
+	 * name that was determined by the .class's name and location. This is only set for
+	 * .class files that have been moved to the wrong folder.
+	 */
+	public static final FieldString FIELD_DESCRIPTOR_FROM_CLASS;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdType> type;
+
+	static {
+		type = StructDef.create(NdType.class, NdBinding.type);
+		TYPENAME = FieldManyToOne.create(type, NdTypeId.TYPES);
+		DECLARING_TYPE = FieldManyToOne.create(type, NdTypeId.DECLARED_TYPES);
+		INTERFACES = FieldOneToMany.create(type, NdTypeInterface.APPLIES_TO);
+		SUPERCLASS = FieldManyToOne.create(type, NdTypeSignature.SUBCLASSES);
+		DECLARING_METHOD = FieldManyToOne.create(type, NdMethodId.DECLARED_TYPES);
+		METHODS = FieldOneToMany.create(type, NdMethod.PARENT, 6);
+		TYPE_ANNOTATIONS = FieldOneToMany.create(type, NdTypeAnnotationInType.OWNER);
+		ANNOTATIONS = FieldOneToMany.create(type, NdAnnotationInType.OWNER);
+		MISSING_TYPE_NAMES = type.addString();
+		SOURCE_FILE_NAME = type.addString();
+		INNER_CLASS_SOURCE_NAME = type.addString();
+		FLAGS = type.addByte();
+		TAG_BITS = type.addLong();
+		FIELD_DESCRIPTOR_FROM_CLASS = type.addString();
+		type.done();
+	}
+
+	public static final byte FLG_TYPE_ANONYMOUS 	= 0x0001;
+	public static final byte FLG_TYPE_LOCAL 		= 0x0002;
+	public static final byte FLG_TYPE_MEMBER 		= 0x0004;
+	public static final byte FLG_GENERIC_SIGNATURE_PRESENT = 0x0008;
+
+	public NdType(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdType(Nd nd, NdResourceFile resource) {
+		super(nd, resource);
+	}
+
+	/**
+	 * Called to populate the cache for the bindings in the class scope.
+	 */
+	public void acceptUncached(INdVisitor visitor) throws CoreException {
+		super.accept(visitor);
+	}
+
+	public NdTypeId getTypeId() {
+		return TYPENAME.get(getNd(), this.address);
+	}
+
+	public void setTypeId(NdTypeId typeId) {
+		TYPENAME.put(getNd(), this.address, typeId);
+	}
+
+	/**
+	 * Sets the source name for this type.
+	 */
+	public void setSourceNameOverride(char[] sourceName) {
+		char[] oldSourceName = getSourceName();
+		if (!CharArrayUtils.equals(oldSourceName, sourceName)) {
+			INNER_CLASS_SOURCE_NAME.put(getNd(), this.address, sourceName);
+		}
+	}
+
+	public IString getSourceNameOverride() {
+		return INNER_CLASS_SOURCE_NAME.get(getNd(), this.address);
+	}
+
+	public long getResourceAddress() {
+		return FILE.getAddress(getNd(), this.address);
+	}
+
+	public void setSuperclass(NdTypeSignature superclassTypeName) {
+		SUPERCLASS.put(getNd(), this.address, superclassTypeName);
+	}
+
+	public NdTypeSignature getSuperclass() {
+		return SUPERCLASS.get(getNd(), this.address);
+	}
+
+	public List<NdTypeInterface> getInterfaces() {
+		return INTERFACES.asList(getNd(), this.address);
+	}
+
+	public NdResourceFile getResourceFile() {
+		return FILE.get(getNd(), this.address);
+	}
+
+	public void setDeclaringMethod(NdMethodId createMethodId) {
+		DECLARING_METHOD.put(getNd(), this.address, createMethodId);
+	}
+
+	/**
+	 * @param createTypeIdFromBinaryName
+	 */
+	public void setDeclaringType(NdTypeId createTypeIdFromBinaryName) {
+		DECLARING_TYPE.put(getNd(), this.address, createTypeIdFromBinaryName);
+	}
+
+	public NdTypeId getDeclaringType() {
+		return DECLARING_TYPE.get(getNd(), this.address);
+	}
+
+	/**
+	 * Sets the missing type names (if any) for this class. The names are encoded in a comma-separated list.
+	 */
+	public void setMissingTypeNames(char[] contents) {
+		MISSING_TYPE_NAMES.put(getNd(), this.address, contents);
+	}
+
+	/**
+	 * Returns the missing type names as a comma-separated list
+	 */
+	public IString getMissingTypeNames() {
+		return MISSING_TYPE_NAMES.get(getNd(), this.address);
+	}
+
+	public void setSourceFileName(char[] sourceFileName) {
+		SOURCE_FILE_NAME.put(getNd(), this.address, sourceFileName);
+	}
+
+	public IString getSourceFileName() {
+		return SOURCE_FILE_NAME.get(getNd(), this.address);
+	}
+
+	public void setAnonymous(boolean anonymous) {
+		setFlag(FLG_TYPE_ANONYMOUS, anonymous);
+	}
+
+	public void setIsLocal(boolean local) {
+		setFlag(FLG_TYPE_LOCAL, local);
+	}
+
+	public void setIsMember(boolean member) {
+		setFlag(FLG_TYPE_MEMBER, member);
+	}
+
+	public boolean isAnonymous() {
+		return getFlag(FLG_TYPE_ANONYMOUS);
+	}
+
+	public boolean isLocal() {
+		return getFlag(FLG_TYPE_LOCAL);
+	}
+
+	public boolean isMember() {
+		return getFlag(FLG_TYPE_MEMBER);
+	}
+
+	public void setFlag(byte flagConstant, boolean value) {
+		int oldFlags = FLAGS.get(getNd(), this.address);
+		int newFlags =  ((oldFlags & ~flagConstant) | (value ? flagConstant : 0));
+		FLAGS.put(getNd(), this.address, (byte)newFlags);
+	}
+
+	public boolean getFlag(byte flagConstant) {
+		return (FLAGS.get(getNd(), this.address) & flagConstant) != 0;
+	}
+
+	public char[] getSourceName() {
+		IString sourceName = getSourceNameOverride();
+		if (sourceName.length() != 0) {
+			return sourceName.getChars();
+		}
+		char[] simpleName = getTypeId().getSimpleNameCharArray();
+		return JavaNames.simpleNameToSourceName(simpleName);
+	}
+
+	public NdMethodId getDeclaringMethod() {
+		return DECLARING_METHOD.get(getNd(), this.address);
+	}
+
+	@Override
+	public List<NdTypeParameter> getTypeParameters() {
+		return TYPE_PARAMETERS.asList(getNd(), this.address);
+	}
+
+	public List<NdTypeAnnotationInType> getTypeAnnotations() {
+		return TYPE_ANNOTATIONS.asList(getNd(), this.address);
+	}
+
+	public List<NdAnnotationInType> getAnnotations() {
+		return ANNOTATIONS.asList(getNd(), this.address);
+	}
+
+	public List<NdMethod> getMethods() {
+		return METHODS.asList(getNd(), this.address);
+	}
+
+	@Override
+	public String toString() {
+		try {
+			return "class " + new String(getSourceName()); //$NON-NLS-1$
+		} catch (RuntimeException e) {
+			return super.toString();
+		}
+	}
+
+	public void setTagBits(long tagBits) {
+		TAG_BITS.put(getNd(), this.address, tagBits);
+	}
+
+	public long getTagBits() {
+		return TAG_BITS.get(getNd(), this.address);
+	}
+
+	public void setFieldDescriptorFromClass(char[] fieldDescriptorFromClass) {
+		FIELD_DESCRIPTOR_FROM_CLASS.put(getNd(), this.address, fieldDescriptorFromClass);
+	}
+
+	/**
+	 * Returns the field descriptor for this type, based on the binary type information stored in the
+	 * .class file itself. Note that this may differ from the field descriptor of this type's typeId in
+	 * the event that the .class file has been moved.
+	 */
+	public IString getFieldDescriptor() {
+		IString descriptorFromClass = FIELD_DESCRIPTOR_FROM_CLASS.get(getNd(), this.address);
+		if (descriptorFromClass.length() == 0) {
+			return getTypeId().getFieldDescriptor();
+		}
+		return descriptorFromClass;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotation.java
new file mode 100644
index 0000000..e9bfa4a
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotation.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.compiler.codegen.AnnotationTargetTypeConstants;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.FieldPointer;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdTypeAnnotation extends NdAnnotation {
+	public static final FieldByte TARGET_TYPE;
+	public static final FieldByte TARGET_ARG0;
+	public static final FieldByte TARGET_ARG1;
+	public static final FieldByte PATH_LENGTH;
+	public static final FieldPointer PATH;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdTypeAnnotation> type;
+
+	static {
+		type = StructDef.create(NdTypeAnnotation.class, NdAnnotation.type);
+		TARGET_TYPE = type.addByte();
+		TARGET_ARG0 = type.addByte();
+		TARGET_ARG1 = type.addByte();
+		PATH_LENGTH = type.addByte();
+		PATH = type.addPointer();
+		type.done();
+	}
+
+	private static final byte[] NO_TYPE_PATH = new byte[0];
+
+	public NdTypeAnnotation(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdTypeAnnotation(Nd nd) {
+		super(nd);
+	}
+
+	public void setPath(byte[] path) {
+		freePath();
+		Nd nd = getNd();
+		PATH_LENGTH.put(nd, this.address, (byte)path.length);
+		if (path.length > 0) {
+			long pathArray = nd.getDB().malloc(path.length, Database.POOL_MISC);
+			PATH.put(nd, this.address, pathArray);
+			nd.getDB().putBytes(pathArray, path, path.length);
+		}
+	}
+
+	public void setTargetInfo(int arg) {
+		TARGET_ARG0.put(getNd(), this.address, (byte)((arg >> 8) & 0xff));
+		TARGET_ARG1.put(getNd(), this.address, (byte)(arg & 0xff));
+	}
+
+	public byte getTargetInfoArg0() {
+		return TARGET_ARG0.get(getNd(), this.address);
+	}
+
+	public byte getTargetInfoArg1() {
+		return TARGET_ARG1.get(getNd(), this.address);
+	}
+
+	public int getTarget() {
+		int arg0 = TARGET_ARG0.get(getNd(), this.address) & 0xff;
+		int arg1 = TARGET_ARG1.get(getNd(), this.address) & 0xff;
+		int result = (arg0 << 8) | arg1;
+		return result;
+	}
+
+	public void setTargetInfo(byte arg0, byte arg1) {
+		TARGET_ARG0.put(getNd(), this.address, arg0);
+		TARGET_ARG1.put(getNd(), this.address, arg1);
+	}
+
+	/**
+	 * @param targetType one of the constants from {@link AnnotationTargetTypeConstants}
+	 */
+	public void setTargetType(int targetType) {
+		TARGET_TYPE.put(getNd(), this.address, (byte)targetType);
+	}
+
+	/**
+	 * @return one of the constants from {@link AnnotationTargetTypeConstants}
+	 */
+	public int getTargetType() {
+		return TARGET_TYPE.get(getNd(), this.address);
+	}
+
+	public byte[] getTypePath() {
+		long pathPointer = PATH.get(getNd(), this.address);
+		if (pathPointer == 0) {
+			return NO_TYPE_PATH;
+		}
+		int pathLength = PATH_LENGTH.get(getNd(), this.address);
+		byte[] result = new byte[pathLength];
+		getNd().getDB().getBytes(pathPointer, result);
+		return result;
+	}
+
+	@Override
+	public void destruct() {
+		freePath();
+		super.destruct();
+	}
+
+	private void freePath() {
+		Nd nd = getNd();
+		long pathPointer = PATH.get(nd, this.address);
+		nd.getDB().free(pathPointer, Database.POOL_MISC);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInMethod.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInMethod.java
new file mode 100644
index 0000000..894a1d5
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInMethod.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdTypeAnnotationInMethod extends NdTypeAnnotation {
+	public static final FieldManyToOne<NdMethod> OWNER;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdTypeAnnotationInMethod> type;
+
+	static {
+		type = StructDef.create(NdTypeAnnotationInMethod.class, NdTypeAnnotation.type);
+		OWNER = FieldManyToOne.createOwner(type, NdMethod.TYPE_ANNOTATIONS);
+		type.done();
+	}
+
+	public NdTypeAnnotationInMethod(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdTypeAnnotationInMethod(Nd nd, NdMethod variable) {
+		super(nd);
+
+		OWNER.put(getNd(), this.address, variable);
+	}
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInType.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInType.java
new file mode 100644
index 0000000..7aff109
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInType.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdTypeAnnotationInType extends NdTypeAnnotation {
+	public static final FieldManyToOne<NdType> OWNER;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdTypeAnnotationInType> type;
+
+	static {
+		type = StructDef.create(NdTypeAnnotationInType.class, NdTypeAnnotation.type);
+		OWNER = FieldManyToOne.createOwner(type, NdType.TYPE_ANNOTATIONS);
+		type.done();
+	}
+
+	public NdTypeAnnotationInType(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdTypeAnnotationInType(Nd nd, NdType type) {
+		super(nd);
+
+		OWNER.put(getNd(), this.address, type);
+	}
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInVariable.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInVariable.java
new file mode 100644
index 0000000..eb591fe
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInVariable.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdTypeAnnotationInVariable extends NdTypeAnnotation {
+	public static final FieldManyToOne<NdVariable> OWNER;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdTypeAnnotationInVariable> type;
+
+	static {
+		type = StructDef.create(NdTypeAnnotationInVariable.class, NdTypeAnnotation.type);
+		OWNER = FieldManyToOne.createOwner(type, NdVariable.TYPE_ANNOTATIONS);
+		type.done();
+	}
+
+	public NdTypeAnnotationInVariable(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdTypeAnnotationInVariable(Nd nd, NdVariable variable) {
+		super(nd);
+
+		OWNER.put(getNd(), this.address, variable);
+	}
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeArgument.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeArgument.java
new file mode 100644
index 0000000..37af73a
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeArgument.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+public class NdTypeArgument extends NdNode {
+	public static final FieldManyToOne<NdComplexTypeSignature> PARENT;
+	public static final FieldManyToOne<NdTypeSignature> TYPE_SIGNATURE;
+	public static final FieldByte WILDCARD;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdTypeArgument> type;
+
+	static {
+		type = StructDef.create(NdTypeArgument.class, NdNode.type);
+		PARENT = FieldManyToOne.createOwner(type, NdComplexTypeSignature.TYPE_ARGUMENTS);
+		TYPE_SIGNATURE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_TYPE_ARGUMENT);
+		WILDCARD = type.addByte();
+		type.done();
+	}
+
+	public static final int WILDCARD_NONE = 0;
+	public static final int WILDCARD_EXTENDS = 1;
+	public static final int WILDCARD_SUPER = 2;
+	public static final int WILDCARD_QUESTION = 3;
+
+	public NdTypeArgument(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdTypeArgument(Nd nd, NdComplexTypeSignature typeSignature) {
+		super(nd);
+
+		PARENT.put(nd, this.address, typeSignature);
+	}
+
+	/**
+	 * Sets the wildcard to use, one of the WILDCARD_* constants.
+	 *
+	 * @param wildcard
+	 */
+	public void setWildcard(int wildcard) {
+		WILDCARD.put(getNd(), this.address, (byte) wildcard);
+	}
+
+	public void setType(NdTypeSignature typeSignature) {
+		TYPE_SIGNATURE.put(getNd(), this.address, typeSignature);
+	}
+
+	public int getWildcard() {
+		return WILDCARD.get(getNd(), this.address);
+	}
+
+	public NdComplexTypeSignature getParent() {
+		return PARENT.get(getNd(), this.address);
+	}
+
+	public NdTypeSignature getType() {
+		return TYPE_SIGNATURE.get(getNd(), this.address);
+	}
+
+	public void getSignature(CharArrayBuffer result) {
+		switch (getWildcard()) {
+			case NdTypeArgument.WILDCARD_EXTENDS: result.append('-'); break;
+			case NdTypeArgument.WILDCARD_QUESTION: result.append('*'); return;
+			case NdTypeArgument.WILDCARD_SUPER: result.append('+'); break;
+		}
+
+		NdTypeSignature theType = getType();
+		if (theType != null) {
+			theType.getSignature(result);
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeBound.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeBound.java
new file mode 100644
index 0000000..c6c827e
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeBound.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.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+/**
+ * Represents the bound on a generic parameter (a ClassBound or InterfaceBound in
+ * the sense of the Java VM spec Java SE 8 Edition, section 4.7.9.1).
+ */
+public class NdTypeBound extends NdNode {
+	public static final FieldManyToOne<NdTypeParameter> PARENT;
+	public static final FieldManyToOne<NdTypeSignature> TYPE;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdTypeBound> type;
+
+	static {
+		type = StructDef.create(NdTypeBound.class, NdNode.type);
+		PARENT = FieldManyToOne.createOwner(type, NdTypeParameter.BOUNDS);
+		TYPE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_TYPE_BOUND);
+
+		type.done();
+	}
+
+	public NdTypeBound(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdTypeBound(NdTypeParameter parent, NdTypeSignature signature) {
+		super(parent.getNd());
+
+		PARENT.put(getNd(), this.address, parent);
+		TYPE.put(getNd(), this.address, signature);
+	}
+
+	public NdTypeParameter getParent() {
+		return PARENT.get(getNd(), this.address);
+	}
+
+	public NdTypeSignature getType() {
+		return TYPE.get(getNd(), this.address);
+	}
+
+	public void getSignature(CharArrayBuffer result) {
+		result.append(':');
+		getType().getSignature(result);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeId.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeId.java
new file mode 100644
index 0000000..a132f97
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeId.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchKey;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+public class NdTypeId extends NdTypeSignature {
+	public static final FieldSearchKey<JavaIndex> FIELD_DESCRIPTOR;
+	public static final FieldSearchKey<JavaIndex> SIMPLE_NAME;
+	public static final FieldOneToMany<NdType> TYPES;
+	public static final FieldOneToMany<NdComplexTypeSignature> USED_AS_COMPLEX_TYPE;
+	public static final FieldOneToMany<NdType> DECLARED_TYPES;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdTypeId> type;
+
+	private String fName;
+
+	static {
+		type = StructDef.create(NdTypeId.class, NdTypeSignature.type);
+		FIELD_DESCRIPTOR = FieldSearchKey.create(type, JavaIndex.TYPES);
+		SIMPLE_NAME = FieldSearchKey.create(type, JavaIndex.SIMPLE_INDEX);
+		TYPES = FieldOneToMany.create(type, NdType.TYPENAME, 2);
+		USED_AS_COMPLEX_TYPE = FieldOneToMany.create(type, NdComplexTypeSignature.RAW_TYPE);
+		DECLARED_TYPES = FieldOneToMany.create(type, NdType.DECLARING_TYPE);
+		type.useStandardRefCounting().done();
+	}
+
+	public NdTypeId(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdTypeId(Nd nd, char[] fieldDescriptor) {
+		super(nd);
+
+		char[] simpleName = JavaNames.fieldDescriptorToJavaName(fieldDescriptor, false);
+		FIELD_DESCRIPTOR.put(nd, this.address, fieldDescriptor);
+		SIMPLE_NAME.put(nd, this.address, simpleName);
+	}
+
+	@Override
+	public List<NdType> getSubTypes() {
+		List<NdType> result = new ArrayList<>();
+		result.addAll(super.getSubTypes());
+		for (NdComplexTypeSignature next : getComplexTypes()) {
+			result.addAll(next.getSubTypes());
+		}
+		return result;
+	}
+
+	public List<NdComplexTypeSignature> getComplexTypes() {
+		return USED_AS_COMPLEX_TYPE.asList(getNd(), this.address);
+	}
+
+	public NdType findTypeByResourceAddress(long resourceAddress) {
+		int size = TYPES.size(getNd(), this.address);
+		for (int idx = 0; idx < size; idx++) {
+			NdType next = TYPES.get(getNd(), this.address, idx);
+
+			if (next.getResourceAddress() == resourceAddress) {
+				return next;
+			}
+		}
+		return null;
+	}
+
+	public List<NdType> getTypes() {
+		return TYPES.asList(getNd(), this.address);
+	}
+
+	/**
+	 * Returns the field descriptor.
+	 */
+	public IString getFieldDescriptor() {
+		return FIELD_DESCRIPTOR.get(getNd(), this.address);
+	}
+
+	public char[] getFieldDescriptorWithoutTrailingSemicolon() {
+		char[] fieldDescriptor = getFieldDescriptor().getChars();
+
+		int end = fieldDescriptor.length;
+		if (fieldDescriptor.length > 0 && fieldDescriptor[end - 1] == ';') {
+			end--;
+		}
+
+		return CharArrayUtils.subarray(fieldDescriptor, 0, end);
+	}
+
+	public char[] getBinaryName() {
+		return JavaNames.fieldDescriptorToBinaryName(getFieldDescriptor().getChars());
+	}
+
+	public IString getSimpleName() {
+		return SIMPLE_NAME.get(getNd(), this.address);
+	}
+
+	public char[] getSimpleNameCharArray() {
+		if (this.fName == null) {
+			this.fName= getSimpleName().getString();
+		}
+		return this.fName.toCharArray();
+	}
+
+	public boolean hasFieldDescriptor(String name) {
+		return this.getFieldDescriptor().compare(name, true) == 0;
+	}
+
+	public boolean hasSimpleName(String name) {
+		if (this.fName != null)
+			return this.fName.equals(name);
+
+		return getSimpleName().equals(name);
+	}
+
+	public void setSimpleName(String name) {
+		if (Objects.equals(name, this.fName)) {
+			return;
+		}
+		this.fName = name;
+		SIMPLE_NAME.put(getNd(), this.address, name);
+	}
+
+	public List<NdType> getDeclaredTypes() {
+		return DECLARED_TYPES.asList(getNd(), this.address);
+	}
+
+	@Override
+	public NdTypeId getRawType() {
+		return this;
+	}
+
+	@Override
+	public void getSignature(CharArrayBuffer result, boolean includeTrailingSemicolon) {
+		if (includeTrailingSemicolon) {
+			result.append(getFieldDescriptor().getChars());
+		} else {
+			result.append(getFieldDescriptorWithoutTrailingSemicolon());
+		}
+	}
+
+	@Override
+	public boolean isTypeVariable() {
+		return false;
+	}
+
+	@Override
+	public List<NdTypeSignature> getDeclaringTypeChain() {
+		return Collections.singletonList((NdTypeSignature)this);
+	}
+
+	@Override
+	public NdTypeSignature getArrayDimensionType() {
+		return null;
+	}
+
+	@Override
+	public List<NdTypeArgument> getTypeArguments() {
+		return Collections.emptyList();
+	}
+
+	@Override
+	public boolean isArrayType() {
+		return false;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeInterface.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeInterface.java
new file mode 100644
index 0000000..ca8ae77
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeInterface.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * 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.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+/**
+ * Represents one interface implemented by a specific type. This is an intermediate object between a {@link NdType} and
+ * the {@link NdTypeId}s corresponding to its interfaces, which is necessary in order to implement the many-to-many
+ * relationship between them.
+ */
+public class NdTypeInterface extends NdNode {
+	public static final FieldManyToOne<NdType> APPLIES_TO;
+	public static final FieldManyToOne<NdTypeSignature> IMPLEMENTS;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdTypeInterface> type;
+
+	static {
+		type = StructDef.create(NdTypeInterface.class, NdNode.type);
+		APPLIES_TO = FieldManyToOne.createOwner(type, NdType.INTERFACES);
+		IMPLEMENTS = FieldManyToOne.create(type, NdTypeSignature.IMPLEMENTATIONS);
+		type.done();
+	}
+	
+	public NdTypeInterface(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdTypeInterface(Nd nd, NdType targetType, NdTypeSignature makeTypeId) {
+		super(nd);
+
+		APPLIES_TO.put(nd, this.address, targetType);
+		IMPLEMENTS.put(nd, this.address, makeTypeId);
+	}
+
+	public NdType getImplementation() {
+		return APPLIES_TO.get(getNd(), this.address);
+	}
+
+	public NdTypeSignature getInterface() {
+		return IMPLEMENTS.get(getNd(), this.address);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeParameter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeParameter.java
new file mode 100644
index 0000000..fda4676
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeParameter.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import 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.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+/**
+ * Represents a TypeParameter, as described in Section 4.7.9.1 of the java VM specification, Java SE 8 edititon.
+ */
+public class NdTypeParameter extends NdNode {
+	public static final FieldManyToOne<NdBinding> PARENT;
+	public static final FieldString IDENTIFIER;
+	public static final FieldOneToMany<NdTypeBound> BOUNDS;
+	public static final FieldByte TYPE_PARAMETER_FLAGS;
+
+	public static final byte FLG_FIRST_BOUND_IS_A_CLASS = 0x01;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdTypeParameter> type;
+
+	static {
+		type = StructDef.create(NdTypeParameter.class, NdNode.type);
+		PARENT = FieldManyToOne.createOwner(type, NdBinding.TYPE_PARAMETERS);
+		IDENTIFIER = type.addString();
+		BOUNDS = FieldOneToMany.create(type, NdTypeBound.PARENT);
+		TYPE_PARAMETER_FLAGS = type.addByte();
+
+		type.done();
+	}
+
+	public NdTypeParameter(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdTypeParameter(NdBinding parent, char[] identifier) {
+		super(parent.getNd());
+
+		PARENT.put(getNd(), this.address, parent);
+		IDENTIFIER.put(getNd(), this.address, identifier);
+	}
+
+	public char[] getIdentifier() {
+		return IDENTIFIER.get(getNd(), this.address).getChars();
+	}
+
+	public void setFirstBoundIsClass(boolean isClass) {
+		setFlag(FLG_FIRST_BOUND_IS_A_CLASS, isClass);
+	}
+
+	public boolean isFirstBoundAClass() {
+		return (TYPE_PARAMETER_FLAGS.get(getNd(), this.address) & FLG_FIRST_BOUND_IS_A_CLASS) != 0;
+	}
+
+	private void setFlag(byte flag, boolean value) {
+		byte oldValue = TYPE_PARAMETER_FLAGS.get(getNd(), this.address);
+		byte newValue;
+		if (value) {
+			newValue = (byte) (oldValue | flag);
+		} else {
+			newValue = (byte) (oldValue & ~flag);
+		}
+		TYPE_PARAMETER_FLAGS.put(getNd(), this.address, newValue);
+	}
+
+	public List<NdTypeBound> getBounds() {
+		return BOUNDS.asList(getNd(), this.address);
+	}
+
+	public void getSignature(CharArrayBuffer result) {
+		result.append(getIdentifier());
+
+		List<NdTypeBound> bounds = getBounds();
+
+		// If none of the bounds are classes and there is at least one bound, then insert a double-colon
+		// in the type signature.
+		if (!bounds.isEmpty() && !isFirstBoundAClass()) {
+			result.append(':');
+		}
+
+		for (NdTypeBound next : bounds) {
+			next.getSignature(result);
+		}
+	}
+
+	public static void getSignature(CharArrayBuffer buffer, List<NdTypeParameter> params) {
+		if (!params.isEmpty()) {
+			buffer.append('<');
+			for (NdTypeParameter next : params) {
+				next.getSignature(buffer);
+			}
+			buffer.append('>');
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeSignature.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeSignature.java
new file mode 100644
index 0000000..2079636
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeSignature.java
@@ -0,0 +1,157 @@
+/*******************************************************************************
+ * 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.java;
+
+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.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+/**
+ * Corresponds roughly to a JavaTypeSignature, as described in section 4.7.9.1 of the Java VM spec version 4, with the
+ * addition of annotations and backpointers to locations where the type is used.
+ * <p>
+ * Holds back-pointers to all the entities that refer to the name, along with pointers to all classes that have this
+ * name. Note that this isn't the class declaration itself. The same index can hold multiple jar files, some of which
+ * may contain classes with the same name. All classes that use this fully-qualified name point to the same
+ * {@link NdTypeSignature}.
+ * <p>
+ * Other entities should refer to a type via its TypeId if there is any possiblity that the type may change based on the
+ * classpath. It should refer to the type directly if there is no possibility for a type lookup. For example, nested
+ * classes refer to their enclosing class directly since they live in the same file and there is no possibility for the
+ * enclosing class to change based on the classpath. Classes refer to their base class via its TypeId since the parent
+ * class might live in a different jar and need to be resolved on the classpath.
+ */
+public abstract class NdTypeSignature extends NdNode {
+	public static final FieldOneToMany<NdType> SUBCLASSES;
+	public static final FieldOneToMany<NdAnnotation> ANNOTATIONS_OF_THIS_TYPE;
+	public static final FieldOneToMany<NdTypeInterface> IMPLEMENTATIONS;
+	public static final FieldOneToMany<NdVariable> VARIABLES_OF_TYPE;
+	public static final FieldOneToMany<NdConstantClass> USED_AS_CONSTANT;
+	public static final FieldOneToMany<NdConstantEnum> USED_AS_ENUM_CONSTANT;
+	public static final FieldOneToMany<NdTypeArgument> USED_AS_TYPE_ARGUMENT;
+	public static final FieldOneToMany<NdTypeBound> USED_AS_TYPE_BOUND;
+	public static final FieldOneToMany<NdMethodParameter> USED_AS_METHOD_ARGUMENT;
+	public static final FieldOneToMany<NdMethodException> USED_AS_EXCEPTION;
+	public static final FieldOneToMany<NdMethod> USED_AS_RETURN_TYPE;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdTypeSignature> type;
+
+	static {
+		type = StructDef.createAbstract(NdTypeSignature.class, NdNode.type);
+		SUBCLASSES = FieldOneToMany.create(type, NdType.SUPERCLASS);
+		ANNOTATIONS_OF_THIS_TYPE = FieldOneToMany.create(type, NdAnnotation.ANNOTATION_TYPE);
+		IMPLEMENTATIONS = FieldOneToMany.create(type, NdTypeInterface.IMPLEMENTS);
+		VARIABLES_OF_TYPE = FieldOneToMany.create(type, NdVariable.TYPE);
+		USED_AS_CONSTANT = FieldOneToMany.create(type, NdConstantClass.VALUE);
+		USED_AS_ENUM_CONSTANT = FieldOneToMany.create(type, NdConstantEnum.ENUM_TYPE);
+		USED_AS_TYPE_ARGUMENT = FieldOneToMany.create(type, NdTypeArgument.TYPE_SIGNATURE);
+		USED_AS_TYPE_BOUND = FieldOneToMany.create(type, NdTypeBound.TYPE);
+		USED_AS_METHOD_ARGUMENT = FieldOneToMany.create(type, NdMethodParameter.ARGUMENT_TYPE);
+		USED_AS_EXCEPTION = FieldOneToMany.create(type, NdMethodException.EXCEPTION_TYPE);
+		USED_AS_RETURN_TYPE = FieldOneToMany.create(type, NdMethod.RETURN_TYPE);
+		type.useStandardRefCounting().done();
+	}
+
+	public NdTypeSignature(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdTypeSignature(Nd nd) {
+		super(nd);
+	}
+
+	public List<NdType> getSubclasses() {
+		return SUBCLASSES.asList(getNd(), this.address);
+	}
+
+	public List<NdTypeInterface> getImplementations() {
+		return IMPLEMENTATIONS.asList(getNd(), this.address);
+	}
+
+	/**
+	 * Returns all subclasses (for classes) and implementations (for interfaces) of this type
+	 */
+	public List<NdType> getSubTypes() {
+		List<NdType> result = new ArrayList<>();
+		result.addAll(getSubclasses());
+
+		for (NdTypeInterface next : getImplementations()) {
+			result.add(next.getImplementation());
+		}
+
+		return result;
+	}
+
+	/**
+	 * Returns the raw version of this type, if one exists. That is, the version of this type
+	 * without any generic arguments or annotations, which the java runtime sees. Returns null
+	 * of this signature doesn't have a raw type, for example if it is a type variable.
+	 */
+	public abstract NdTypeId getRawType();
+
+	public final void getSignature(CharArrayBuffer result) {
+		getSignature(result, true);
+	}
+
+	public abstract void getSignature(CharArrayBuffer result, boolean includeTrailingSemicolon);
+
+	/**
+	 * Returns true iff this is an array type signature (ie: that getArrayDimensionType() will return a non-null
+	 * answer). Note that this only returns true for the type signature that holds the reference to the array dimension
+	 * type. The raw type for that signature will return false, even though it has a field descriptor starting with '['.
+	 * <p>
+	 * In other words:
+	 *
+	 * <pre>
+	 * NdVariable someVariable = getSomeVariableWithAnArrayType()
+	 * System.out.println(someVariable.getType().isArrayType()); // true
+	 * System.out.println(someVariable.getType().getRawType().isArrayType()); // false
+	 * </pre>
+	 */
+	public abstract boolean isArrayType();
+
+	public abstract boolean isTypeVariable();
+
+	/**
+	 * Returns the chain of declaring generic types. The first element in the chain is a top-level type and the
+	 * receiver is the last element in the chain.
+	 */
+	public abstract List<NdTypeSignature> getDeclaringTypeChain();
+
+	/**
+	 * If the receiver is an array type, it returns the signature of the array's next dimension. Returns null if
+	 * this is not an array type.
+	 */
+	public abstract NdTypeSignature getArrayDimensionType();
+
+	/**
+	 * Returns the type arguments for this type signature, if any. Returns the empty list if none.
+	 */
+	public abstract List<NdTypeArgument> getTypeArguments();
+
+	public String toString() {
+		try {
+			CharArrayBuffer result = new CharArrayBuffer();
+			getSignature(result);
+			return result.toString();
+		} catch (RuntimeException e) {
+			// This is called most often from the debugger, so we want to return something meaningful even
+			// if the code is buggy, the database is corrupt, or we don't have a read lock.
+			return super.toString();
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdVariable.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdVariable.java
new file mode 100644
index 0000000..e85f805
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdVariable.java
@@ -0,0 +1,141 @@
+/*******************************************************************************
+ * 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.java;
+
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.FieldInt;
+import org.eclipse.jdt.internal.core.nd.field.FieldLong;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdVariable extends NdBinding {
+	public static final FieldManyToOne<NdTypeSignature> TYPE;
+	public static final FieldInt VARIABLE_ID;
+	public static final FieldManyToOne<NdMethod> DECLARING_METHOD;
+	public static final FieldManyToOne<NdBinding> PARENT;
+	public static final FieldString NAME;
+	public static final FieldOneToOne<NdConstant> CONSTANT;
+	public static final FieldLong TAG_BITS;
+	public static final FieldByte VARIABLE_FLAGS;
+	public static final FieldOneToMany<NdAnnotationInVariable> ANNOTATIONS;
+	public static final FieldOneToMany<NdTypeAnnotationInVariable> TYPE_ANNOTATIONS;
+
+	@SuppressWarnings("hiding")
+	public static StructDef<NdVariable> type;
+
+	public static final byte FLG_GENERIC_SIGNATURE_PRESENT 	= 0x01;
+
+	static {
+		type = StructDef.create(NdVariable.class, NdBinding.type);
+		TYPE = FieldManyToOne.create(type, NdTypeSignature.VARIABLES_OF_TYPE);
+		VARIABLE_ID = type.addInt();
+		DECLARING_METHOD = FieldManyToOne.create(type, NdMethod.DECLARED_VARIABLES);
+		PARENT = FieldManyToOne.create(type, NdBinding.VARIABLES);
+		NAME = type.addString();
+		CONSTANT = FieldOneToOne.create(type, NdConstant.class, NdConstant.PARENT_VARIABLE);
+		TAG_BITS = type.addLong();
+		VARIABLE_FLAGS = type.addByte();
+		ANNOTATIONS = FieldOneToMany.create(type, NdAnnotationInVariable.OWNER);
+		TYPE_ANNOTATIONS = FieldOneToMany.create(type, NdTypeAnnotationInVariable.OWNER);
+		type.done();
+	}
+
+	public NdVariable(Nd nd, long bindingRecord) {
+		super(nd, bindingRecord);
+	}
+
+	public NdVariable(NdBinding parent) {
+		super(parent.getNd(), parent.getFile());
+
+		PARENT.put(getNd(), this.address, parent);
+	}
+
+	public boolean hasVariableFlag(int toTest) {
+		return (VARIABLE_FLAGS.get(getNd(), this.address) & toTest) != 0;
+	}
+
+	public void setVariableFlag(byte toSet) {
+		int newFlags = VARIABLE_FLAGS.get(getNd(), this.address) | toSet;
+		VARIABLE_FLAGS.put(getNd(), this.address, (byte)newFlags);
+	}
+
+	public void setName(char[] name) {
+		NAME.put(getNd(), this.address, name);
+	}
+
+	public IString getName() {
+		return NAME.get(getNd(), this.address);
+	}
+
+	public void setType(NdTypeSignature typeId) {
+		TYPE.put(getNd(), this.address, typeId);
+	}
+
+	public void setConstant(NdConstant constant) {
+		CONSTANT.put(getNd(), this.address, constant);
+	}
+
+	public NdConstant getConstant() {
+		return CONSTANT.get(getNd(), this.address);
+	}
+
+	public NdTypeSignature getType() {
+		return TYPE.get(getNd(), this.address);
+	}
+
+	public long getTagBits() {
+		return TAG_BITS.get(getNd(), this.address);
+	}
+
+	public void setTagBits(long tagBits) {
+		TAG_BITS.put(getNd(), this.address, tagBits);
+	}
+
+	public List<NdTypeAnnotationInVariable> getTypeAnnotations() {
+		return TYPE_ANNOTATIONS.asList(getNd(), this.address);
+	}
+
+	public List<NdAnnotationInVariable> getAnnotations() {
+		return ANNOTATIONS.asList(getNd(), this.address);
+	}
+
+	public String toString() {
+		try {
+			StringBuilder result = new StringBuilder();
+			NdTypeSignature localType = getType();
+			if (localType != null) {
+				result.append(localType.toString());
+				result.append(" "); //$NON-NLS-1$
+			}
+			IString name = getName();
+			if (name != null) {
+				result.append(name.toString());
+			}
+			NdConstant constant = getConstant();
+			if (constant != null) {
+				result.append(" = "); //$NON-NLS-1$
+				result.append(constant.toString());
+			}
+			return result.toString();
+		} catch (RuntimeException e) {
+			// This is called most often from the debugger, so we want to return something meaningful even
+			// if the code is buggy, the database is corrupt, or we don't have a read lock.
+			return super.toString();
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdWorkspaceLocation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdWorkspaceLocation.java
new file mode 100644
index 0000000..8e52b8b
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdWorkspaceLocation.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+/**
+ * Holds a location in the Eclipse workspace where a given resource was found. Note that a given
+ * resource might be mapped to multiple locations in the workspace.
+ */
+public class NdWorkspaceLocation extends NdNode {
+	public static final FieldManyToOne<NdResourceFile> RESOURCE;
+	public static final FieldString PATH;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdWorkspaceLocation> type;
+
+	static {
+		type = StructDef.create(NdWorkspaceLocation.class, NdNode.type);
+		RESOURCE = FieldManyToOne.createOwner(type, NdResourceFile.WORKSPACE_MAPPINGS);
+		PATH = type.addString();
+		type.done();
+	}
+
+	public NdWorkspaceLocation(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdWorkspaceLocation(Nd nd, NdResourceFile resource, char[] path) {
+		super(nd);
+
+		RESOURCE.put(getNd(), this.address, resource);
+		PATH.put(getNd(), this.address, path);
+	}
+
+	public IString getPath() {
+		return PATH.get(getNd(), this.address);
+	}
+
+	public NdResourceFile getResourceFile() {
+		return RESOURCE.get(getNd(), this.address);
+	}
+
+	public String toString() {
+		try {
+			return getPath().toString();
+		} catch (RuntimeException e) {
+			// This is called most often from the debugger, so we want to return something meaningful even
+			// if the code is buggy, the database is corrupt, or we don't have a read lock.
+			return super.toString();
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/Package.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/Package.java
new file mode 100644
index 0000000..9903317
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/Package.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.java;
+
+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 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/java/TagTreeReader.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/TagTreeReader.java
new file mode 100644
index 0000000..9ad5a60
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/TagTreeReader.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * 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.java;
+
+import java.util.HashMap;
+import java.util.Map;
+
+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.IndexException;
+
+public abstract class TagTreeReader {
+	public static final int[] UNUSED_RESULT = new int[1];
+
+	public static abstract class TagHandler<T> {
+		abstract public T read(Nd nd, long address, TagTreeReader reader, int[] bytesRead);
+		abstract public void write(Nd nd, long address, TagTreeReader reader, T toWrite, int[] bytesWritten);
+		abstract public int getSize(Nd nd, T object, TagTreeReader reader);
+		public void destruct(Nd nd, long address, TagTreeReader reader) {
+			// Nothing to do by default
+		}
+	}
+
+	public static abstract class FixedSizeTagHandler<T> extends TagHandler<T> {
+		protected abstract T read(Nd nd, long address);
+		protected abstract void write(Nd nd, long address, T value);
+		protected abstract int getSize();
+		protected void destruct(Nd nd, long address) {
+			// Nothing to do by default
+		}
+
+		public final T read(Nd nd, long address, TagTreeReader reader, int[] bytesRead) {
+			bytesRead[0] = getSize();
+			return read(nd, address);
+		}
+
+		@Override
+		public final void write(Nd nd, long address, TagTreeReader reader, T value, int[] bytesWritten) {
+			bytesWritten[0] = getSize();
+			write(nd, address, value);
+		}
+
+		@Override
+		public final int getSize(Nd nd, T object, TagTreeReader reader) {
+			return getSize();
+		}
+
+		@Override
+		public final void destruct(Nd nd, long address, TagTreeReader reader) {
+			destruct(nd, address);
+		}
+	}
+
+	private TagHandler<?> readers[] = new TagHandler[256];
+	private Map<TagHandler<?>, Integer> values = new HashMap<>();
+
+	public final void add(byte key, TagHandler<?> reader) {
+		this.readers[key] = reader;
+		this.values.put(reader, (int) key);
+	}
+
+	public final Object read(Nd nd, long address) {
+		return read(nd, address, UNUSED_RESULT);
+	}
+
+	public final Object read(Nd nd, long address, int[] bytesRead) {
+		long readAddress = address;
+		Database db = nd.getDB();
+		byte nextByte = db.getByte(address);
+		readAddress += Database.BYTE_SIZE;
+		TagHandler<?> reader = this.readers[nextByte];
+		if (reader == null) {
+			throw new IndexException("Found unknown tag with value " + nextByte + " at address " + address); //$NON-NLS-1$//$NON-NLS-2$
+		}
+
+		return reader.read(nd, readAddress, this, bytesRead);
+	}
+
+	protected abstract byte getKeyFor(Object toWrite);
+
+	public final void write(Nd nd, long address, Object toWrite) {
+		write(nd, address, toWrite, UNUSED_RESULT);
+	}
+
+	@SuppressWarnings("unchecked")
+	public final void write(Nd nd, long address, Object toWrite, int[] bytesWritten) {
+		byte key = getKeyFor(toWrite);
+
+		@SuppressWarnings("rawtypes")
+		TagHandler handler = this.readers[key];
+
+		if (handler == null) {
+			throw new IndexException("Invalid key " + key + " returned from getKeyFor(...)"); //$NON-NLS-1$//$NON-NLS-2$
+		}
+
+		handler.write(nd, address, this, toWrite, bytesWritten);
+	}
+
+	public final void destruct(Nd nd, long address) {
+		Database db = nd.getDB();
+		long readAddress = address;
+		byte nextByte = db.getByte(readAddress);
+		readAddress += Database.BYTE_SIZE;
+
+		TagHandler<?> handler = this.readers[nextByte];
+		if (handler == null) {
+			throw new IndexException("Found unknown tag with value " + nextByte + " at address " + address); //$NON-NLS-1$//$NON-NLS-2$
+		}
+
+		handler.destruct(nd, readAddress, this);
+	}
+
+	@SuppressWarnings("unchecked")
+	public final int getSize(Nd nd, Object toMeasure) {
+		byte key = getKeyFor(toMeasure);
+
+		@SuppressWarnings("rawtypes")
+		TagHandler handler = this.readers[key];
+		if (handler == null) {
+			throw new IndexException("Attempted to get size of object " + toMeasure.toString() + " with unknown key " //$NON-NLS-1$//$NON-NLS-2$
+					+ key);
+		}
+
+		return handler.getSize(nd, toMeasure, this);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/TypeRef.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/TypeRef.java
new file mode 100644
index 0000000..4e65b4b
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/TypeRef.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * 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.java;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+import org.eclipse.jdt.internal.core.nd.DatabaseRef;
+import org.eclipse.jdt.internal.core.nd.IReader;
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+/**
+ * Holds a reference to an NdType that can be retained while releasing and reacquiring a read lock.
+ */
+public final class TypeRef implements Supplier<NdType> {
+	final DatabaseRef<NdType> ref;
+	final char[] fileName;
+	final char[] fieldDescriptor;
+	final TypeSupplier typeSupplier = new TypeSupplier();
+	private final class TypeSupplier implements Supplier<NdType> {
+		public TypeSupplier() {
+		}
+
+		@Override
+		public NdType get() {
+			NdTypeId typeId = JavaIndex.getIndex(TypeRef.this.ref.getNd()).findType(TypeRef.this.fieldDescriptor);
+
+			if (typeId == null) {
+				return null;
+			}
+
+			List<NdType> implementations = typeId.getTypes();
+			for (NdType next : implementations) {
+				NdResourceFile nextResourceFile = next.getResourceFile();
+				if (nextResourceFile.getLocation().compare(TypeRef.this.fileName, false) == 0) {
+					if (nextResourceFile.isDoneIndexing()) {
+						return next;
+					}
+				}
+			}
+			return null;
+		}
+	}
+
+	private TypeRef(NdType type) {
+		super();
+		this.fieldDescriptor = type.getTypeId().getRawType().getFieldDescriptor().getChars();
+		this.fileName = type.getResourceFile().getLocation().getChars();
+		this.ref = new DatabaseRef<NdType>(type.getNd(), this.typeSupplier, type);
+	}
+
+	private TypeRef(Nd nd, char[] resourcePath, char[] fieldDescriptor) {
+		super();
+		this.fieldDescriptor = fieldDescriptor;
+		this.fileName = resourcePath;
+		this.ref = new DatabaseRef<NdType>(nd, this.typeSupplier);
+	}
+
+	public char[] getFieldDescriptor() {
+		return this.fieldDescriptor;
+	}
+
+	public char[] getFileName() {
+		return this.fileName;
+	}
+
+	/**
+	 * Creates a {@link DatabaseRef} to the given {@link NdType}.
+	 */
+	public static TypeRef create(NdType type) {
+		return new TypeRef(type);
+	}
+
+	/**
+	 * Creates a {@link DatabaseRef} to the {@link NdType} with the given resource path and field descriptor.
+	 */
+	public static TypeRef create(Nd nd, char[] resourcePath, char[] fieldDescriptor) {
+		return new TypeRef(nd, resourcePath, fieldDescriptor);
+	}
+
+	public IReader lock() {
+		return this.ref.lock();
+	}
+
+	@Override
+	public NdType get() {
+		return this.ref.get();
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeDescriptor.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeDescriptor.java
new file mode 100644
index 0000000..393537b
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeDescriptor.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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.java.model;
+
+import org.eclipse.jdt.internal.compiler.env.IDependent;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+
+/**
+ * Holds a lightweight identifier for an IBinaryType, with sufficient information to either read it from
+ * disk or read it from the index.
+ */
+public final class BinaryTypeDescriptor {
+	public final char[] indexPath;
+	public final char[] fieldDescriptor;
+	public final char[] location;
+	public final char[] workspacePath;
+
+	/**
+	 * Constructs a new descriptor
+	 * 
+	 * @param location
+	 *            location where the archive (.jar or .class) can be found in the local filesystem
+	 * @param fieldDescriptor
+	 *            field descriptor for the type (see the JVM specification)
+	 * @param workspacePath
+	 *            location where the archive (.jar or class) can be found in the workspace. If it is not in the
+	 *            workspace, this is the path where it can be found on the local filesystem.
+	 * @param indexPath
+	 *            index path for the new type (workspace-or-local path to jar optionally followed by a | and a relative
+	 *            path within the .jar)
+	 */
+	public BinaryTypeDescriptor(char[] location, char[] fieldDescriptor, char[] workspacePath, char[] indexPath) {
+		super();
+		this.location = location;
+		this.fieldDescriptor = fieldDescriptor;
+		this.indexPath = indexPath;
+		this.workspacePath = workspacePath;
+	}
+
+	public boolean isInJarFile() {
+		return CharArrayUtils.indexOf(IDependent.JAR_FILE_ENTRY_SEPARATOR, this.indexPath) != -1;
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeFactory.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeFactory.java
new file mode 100644
index 0000000..d6d3981
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeFactory.java
@@ -0,0 +1,228 @@
+/*******************************************************************************
+ * 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.java.model;
+
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jdt.core.IClassFile;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaModelStatusConstants;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+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.ClassFile;
+import org.eclipse.jdt.internal.core.JarPackageFragmentRoot;
+import org.eclipse.jdt.internal.core.JavaModelManager;
+import org.eclipse.jdt.internal.core.PackageFragment;
+import org.eclipse.jdt.internal.core.nd.IReader;
+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.NdResourceFile;
+import org.eclipse.jdt.internal.core.nd.java.NdType;
+import org.eclipse.jdt.internal.core.nd.java.TypeRef;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+import org.eclipse.jdt.internal.core.util.Util;
+
+public class BinaryTypeFactory {
+	public static final class NotInIndexException extends Exception {
+		private static final long serialVersionUID = 2859848007651528256L;
+
+		public NotInIndexException() {
+		}
+	}
+	
+	private final static char[] PACKAGE_INFO = "package-info".toCharArray(); //$NON-NLS-1$
+
+	/**
+	 * Returns a descriptor for the given class within the given package fragment, or null if the fragment doesn't have
+	 * a location on the filesystem.
+	 */
+	private static BinaryTypeDescriptor createDescriptor(PackageFragment pkg, ClassFile classFile) {
+		String name = classFile.getName();
+		IJavaElement root = pkg.getParent();
+		IPath location = JavaIndex.getLocationForElement(root);
+		String entryName = Util.concatWith(pkg.names, classFile.getElementName(), '/');
+		char[] fieldDescriptor = CharArrayUtils.concat(new char[] { 'L' },
+				Util.concatWith(pkg.names, name, '/').toCharArray(), new char[] { ';' });
+		IPath workspacePath = root.getPath();
+		String indexPath;
+
+		if (location == null) {
+			return null;
+		}
+
+		if (root instanceof JarPackageFragmentRoot) {
+			// The old version returned this, but it doesn't conform to the spec on IBinaryType.getFileName():
+			indexPath = root.getHandleIdentifier() + IDependent.JAR_FILE_ENTRY_SEPARATOR + entryName;
+			// Version that conforms to the JavaDoc spec on IBinaryType.getFileName() -- note that this breaks
+			// InlineMethodTests in the JDT UI project. Need to investigate why before using it.
+			//indexPath = workspacePath.toString() + IDependent.JAR_FILE_ENTRY_SEPARATOR + entryName;
+		} else {
+			location = location.append(entryName);
+			indexPath = workspacePath.append(entryName).toString();
+			workspacePath = classFile.resource().getFullPath();
+		}
+
+		return new BinaryTypeDescriptor(location.toString().toCharArray(), fieldDescriptor,
+				workspacePath.toString().toCharArray(), indexPath.toCharArray());
+	}
+
+	public static BinaryTypeDescriptor createDescriptor(IClassFile classFile) {
+		ClassFile concreteClass = (ClassFile)classFile;
+		PackageFragment parent = (PackageFragment) classFile.getParent();
+
+		return createDescriptor(parent, concreteClass);
+	}
+
+	public static BinaryTypeDescriptor createDescriptor(IType type) {
+		return createDescriptor(type.getClassFile());
+	}
+
+	public static IBinaryType create(IClassFile classFile, IProgressMonitor monitor) throws JavaModelException, ClassFormatException {
+		BinaryTypeDescriptor descriptor = createDescriptor(classFile);
+		return readType(descriptor, monitor);
+	}
+
+	/**
+	 * Reads the given binary type. If the type can be found in the index with a fingerprint that exactly matches
+	 * the file on disk, the type is read from the index. Otherwise the type is read from disk. Returns null if
+	 * no such type exists.
+	 * @throws ClassFormatException 
+	 */
+	public static IBinaryType readType(BinaryTypeDescriptor descriptor, 
+			IProgressMonitor monitor) throws JavaModelException, ClassFormatException {
+		
+		if (JavaIndex.isEnabled()) {
+			try {
+				return readFromIndex(JavaIndex.getIndex(), descriptor, monitor);
+			} catch (NotInIndexException e) {
+				// fall back to reading the zip file, below
+			}
+		}
+
+		return rawReadType(descriptor, true);
+	}
+
+	/**
+	 * Read the class file from disk, circumventing the index's cache. This should only be used by callers
+	 * that need to read information from the class file which aren't present in the index (such as method bodies).
+	 * 
+	 * @return the newly-created ClassFileReader or null if the given class file does not exist.
+	 * @throws ClassFormatException if the class file existed but was corrupt
+	 * @throws JavaModelException if unable to read the class file due to a transient failure
+	 */
+	public static ClassFileReader rawReadType(BinaryTypeDescriptor descriptor, boolean fullyInitialize) throws JavaModelException, ClassFormatException {
+		if (descriptor == null) {
+			return null;
+		}
+		if (descriptor.isInJarFile()) {
+			ZipFile zip = null;
+			try {
+				zip = JavaModelManager.getJavaModelManager().getZipFile(new Path(new String(descriptor.workspacePath)));
+				char[] entryNameCharArray = CharArrayUtils.concat(
+						JavaNames.fieldDescriptorToBinaryName(descriptor.fieldDescriptor), SuffixConstants.SUFFIX_class);
+				String entryName = new String(entryNameCharArray);
+				ZipEntry ze = zip.getEntry(entryName);
+				if (ze != null) {
+					byte contents[];
+					try {
+						contents = org.eclipse.jdt.internal.compiler.util.Util.getZipEntryByteContent(ze, zip);
+					} catch (IOException ioe) {
+						throw new JavaModelException(ioe, IJavaModelStatusConstants.IO_EXCEPTION);
+					}
+					return new ClassFileReader(contents, descriptor.indexPath, fullyInitialize);
+				}
+			} catch (CoreException e) {
+				throw new JavaModelException(e);
+			} finally {
+				JavaModelManager.getJavaModelManager().closeZipFile(zip);
+			}
+		} else {
+			IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(new String(descriptor.workspacePath)));
+			byte[] contents = Util.getResourceContentsAsByteArray(file);
+			return new ClassFileReader(contents, file.getFullPath().toString().toCharArray(), fullyInitialize);
+		}
+		return null;
+	}
+
+	/**
+	 * Tries to read the given IBinaryType from the index. The return value is lightweight and may be cached
+	 * with minimal memory cost. Returns an IBinaryType if the type was found in the index and the index
+	 * was up-to-date. Throws a NotInIndexException if the index does not contain an up-to-date cache of the
+	 * requested file. Returns null if the index contains an up-to-date cache of the requested file and it was
+	 * able to determine that the requested class does not exist in that file.
+	 */
+	public static IBinaryType readFromIndex(JavaIndex index, BinaryTypeDescriptor descriptor, IProgressMonitor monitor) throws JavaModelException, NotInIndexException {
+		char[] className = JavaNames.fieldDescriptorToSimpleName(descriptor.fieldDescriptor);
+
+		// If the new index is enabled, check if we have this class file cached in the index already		
+		char[] fieldDescriptor = descriptor.fieldDescriptor;
+
+		if (!CharArrayUtils.equals(PACKAGE_INFO, className)) {
+			Nd nd = index.getNd();
+
+			// We don't currently cache package-info files in the index
+			if (descriptor.location != null) {
+				// Acquire a read lock on the index
+				try (IReader lock = nd.acquireReadLock()) {
+					try {
+						TypeRef typeRef = TypeRef.create(nd, descriptor.location, fieldDescriptor);
+						NdType type = typeRef.get();
+
+						if (type == null) {
+							// If we couldn't find the type in the index, determine whether the cause is
+							// that the type is known not to exist or whether the resource just hasn't
+							// been indexed yet
+
+							NdResourceFile resourceFile = index.getResourceFile(descriptor.location);
+							if (index.isUpToDate(resourceFile)) {
+								return null;
+							}
+							throw new NotInIndexException();
+						}
+						NdResourceFile resourceFile = type.getResourceFile();
+						if (index.isUpToDate(resourceFile)) {
+							IndexBinaryType result = new IndexBinaryType(typeRef, descriptor.indexPath);
+
+							// We already have the database lock open and have located the element, so we may as
+							// well prefetch the inexpensive attributes.
+							result.initSimpleAttributes();
+
+							return result;
+						}
+						throw new NotInIndexException();
+					} catch (CoreException e) {
+						throw new JavaModelException(e);
+					}
+				} catch (IndexException e) {
+					// Index corrupted. Rebuild it.
+					index.rebuildIndex();
+				}
+			}
+		}
+		
+		throw new NotInIndexException();
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/ITypeAnnotationBuilder.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/ITypeAnnotationBuilder.java
new file mode 100644
index 0000000..39e9501
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/ITypeAnnotationBuilder.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * 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.java.model;
+
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+
+public interface ITypeAnnotationBuilder {
+	ITypeAnnotationBuilder toField();
+	ITypeAnnotationBuilder toThrows(int rank);
+	ITypeAnnotationBuilder toTypeArgument(int rank);
+	ITypeAnnotationBuilder toMethodParameter(short index);
+	ITypeAnnotationBuilder toSupertype(short index);
+	ITypeAnnotationBuilder toTypeParameterBounds(boolean isClassTypeParameter, int parameterRank);
+	ITypeAnnotationBuilder toTypeBound(short boundIndex);
+	ITypeAnnotationBuilder toTypeParameter(boolean isClassTypeParameter, int rank);
+	ITypeAnnotationBuilder toMethodReturn();
+	ITypeAnnotationBuilder toReceiver();
+	ITypeAnnotationBuilder toWildcardBound();
+	ITypeAnnotationBuilder toNextArrayDimension();
+	ITypeAnnotationBuilder toNextNestedType();
+
+	IBinaryTypeAnnotation build(IBinaryAnnotation annotation);
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryField.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryField.java
new file mode 100644
index 0000000..3c1f763
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryField.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * 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.java.model;
+
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryField;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+
+public class IndexBinaryField implements IBinaryField {
+	private int modifiers;
+	private IBinaryAnnotation[] annotations;
+	private IBinaryTypeAnnotation[] typeAnnotations;
+	private Constant constant;
+	private char[] genericSignature;
+	private char[] name;
+	private long tagBits;
+	private char[] typeName;
+
+	public IndexBinaryField(IBinaryAnnotation[] annotations, Constant constant, char[] genericSignature, int modifiers,
+			char[] name, long tagBits, IBinaryTypeAnnotation[] typeAnnotations, char[] fieldDescriptor) {
+		super();
+		this.modifiers = modifiers;
+		this.annotations = annotations;
+		this.typeAnnotations = typeAnnotations;
+		this.constant = constant;
+		this.genericSignature = genericSignature;
+		this.name = name;
+		this.tagBits = tagBits;
+		this.typeName = fieldDescriptor;
+	}
+
+	@Override
+	public int getModifiers() {
+		return this.modifiers;
+	}
+
+	@Override
+	public IBinaryAnnotation[] getAnnotations() {
+		return this.annotations;
+	}
+
+	@Override
+	public IBinaryTypeAnnotation[] getTypeAnnotations() {
+		return this.typeAnnotations;
+	}
+
+	@Override
+	public Constant getConstant() {
+		return this.constant;
+	}
+
+	@Override
+	public char[] getGenericSignature() {
+		return this.genericSignature;
+	}
+
+	@Override
+	public char[] getName() {
+		return this.name;
+	}
+
+	@Override
+	public long getTagBits() {
+		return this.tagBits;
+	}
+
+	@Override
+	public char[] getTypeName() {
+		return this.typeName;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryMethod.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryMethod.java
new file mode 100644
index 0000000..5ce3c4c
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryMethod.java
@@ -0,0 +1,182 @@
+/*******************************************************************************
+ * 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.java.model;
+
+import org.eclipse.jdt.internal.compiler.classfmt.BinaryTypeFormatter;
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+
+public final class IndexBinaryMethod implements IBinaryMethod {
+	private int modifiers;
+	private boolean isConstructor;
+	private char[][] argumentNames;
+	private IBinaryAnnotation[] annotations;
+	private Object defaultValue;
+	private char[][] exceptionTypeNames;
+	private char[] genericSignature;
+	private char[] methodDescriptor;
+	private IBinaryAnnotation[][] parameterAnnotations;
+	private char[] selector;
+	private long tagBits;
+	private boolean isClInit;
+	private IBinaryTypeAnnotation[] typeAnnotations;
+
+	public static IndexBinaryMethod create() {
+		return new IndexBinaryMethod();
+	}
+
+	public IndexBinaryMethod setModifiers(int modifiers) {
+		this.modifiers = modifiers;
+		return this;
+	}
+
+	public IndexBinaryMethod setIsConstructor(boolean isConstructor) {
+		this.isConstructor = isConstructor;
+		return this;
+	}
+
+	public IndexBinaryMethod setArgumentNames(char[][] argumentNames) {
+		this.argumentNames = argumentNames;
+		return this;
+	}
+
+	public IndexBinaryMethod setAnnotations(IBinaryAnnotation[] annotations) {
+		this.annotations = annotations;
+		return this;
+	}
+
+	public IndexBinaryMethod setDefaultValue(Object defaultValue) {
+		this.defaultValue = defaultValue;
+		return this;
+	}
+
+	public IndexBinaryMethod setExceptionTypeNames(char[][] exceptionTypeNames) {
+		this.exceptionTypeNames = exceptionTypeNames;
+		return this;
+	}
+
+	public IndexBinaryMethod setGenericSignature(char[] genericSignature) {
+		this.genericSignature = genericSignature;
+		return this;
+	}
+
+	public IndexBinaryMethod setMethodDescriptor(char[] methodDescriptor) {
+		this.methodDescriptor = methodDescriptor;
+		return this;
+	}
+
+	public IndexBinaryMethod setParameterAnnotations(IBinaryAnnotation[][] parameterAnnotations) {
+		this.parameterAnnotations = parameterAnnotations;
+		return this;
+	}
+
+	public IndexBinaryMethod setSelector(char[] selector) {
+		this.selector = selector;
+		return this;
+	}
+
+	public IndexBinaryMethod setTagBits(long tagBits) {
+		this.tagBits = tagBits;
+		return this;
+	}
+
+	public IndexBinaryMethod setIsClInit(boolean isClInit) {
+		this.isClInit = isClInit;
+		return this;
+	}
+
+	public IndexBinaryMethod setTypeAnnotations(IBinaryTypeAnnotation[] typeAnnotations) {
+		this.typeAnnotations = typeAnnotations;
+		return this;
+	}
+
+	@Override
+	public int getModifiers() {
+		return this.modifiers;
+	}
+
+	@Override
+	public boolean isConstructor() {
+		return this.isConstructor;
+	}
+
+	@Override
+	public char[][] getArgumentNames() {
+		return this.argumentNames;
+	}
+
+	@Override
+	public IBinaryAnnotation[] getAnnotations() {
+		return this.annotations;
+	}
+
+	@Override
+	public Object getDefaultValue() {
+		return this.defaultValue;
+	}
+
+	@Override
+	public char[][] getExceptionTypeNames() {
+		return this.exceptionTypeNames;
+	}
+
+	@Override
+	public char[] getGenericSignature() {
+		return this.genericSignature;
+	}
+
+	@Override
+	public char[] getMethodDescriptor() {
+		return this.methodDescriptor;
+	}
+
+	@Override
+	public IBinaryAnnotation[] getParameterAnnotations(int index, char[] classFileName) {
+		if (this.parameterAnnotations == null || this.parameterAnnotations.length <= index) {
+			return null;
+		}
+		return this.parameterAnnotations[index];
+	}
+
+	@Override
+	public int getAnnotatedParametersCount() {
+		if (this.parameterAnnotations == null) {
+			return 0;
+		}
+		return this.parameterAnnotations.length;
+	}
+
+	@Override
+	public char[] getSelector() {
+		return this.selector;
+	}
+
+	@Override
+	public long getTagBits() {
+		return this.tagBits;
+	}
+
+	@Override
+	public boolean isClinit() {
+		return this.isClInit;
+	}
+
+	@Override
+	public IBinaryTypeAnnotation[] getTypeAnnotations() {
+		return this.typeAnnotations;
+	}
+
+	@Override
+	public String toString() {
+		return BinaryTypeFormatter.methodToString(this);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryNestedType.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryNestedType.java
new file mode 100644
index 0000000..40d8a54
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryNestedType.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * 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.java.model;
+
+import org.eclipse.jdt.internal.compiler.env.IBinaryNestedType;
+
+public class IndexBinaryNestedType implements IBinaryNestedType {
+	private char[] enclosingTypeName;
+	private char[] name;
+	private int modifiers;
+
+	public IndexBinaryNestedType(char[] name, char[] enclosingTypeName, int modifiers) {
+		super();
+		this.name = name;
+		this.enclosingTypeName = enclosingTypeName;
+		this.modifiers = modifiers;
+	}
+
+	@Override
+	public char[] getEnclosingTypeName() {
+		return this.enclosingTypeName;
+	}
+
+	@Override
+	public int getModifiers() {
+		return this.modifiers;
+	}
+
+	@Override
+	public char[] getName() {
+		return this.name;
+	}
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryType.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryType.java
new file mode 100644
index 0000000..aaa7576
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryType.java
@@ -0,0 +1,672 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.internal.compiler.classfmt.BinaryTypeFormatter;
+import org.eclipse.jdt.internal.compiler.classfmt.ElementValuePairInfo;
+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.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.ExternalAnnotationStatus;
+import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
+import org.eclipse.jdt.internal.core.nd.IReader;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+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.NdAnnotationValuePair;
+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.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.TypeRef;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+/**
+ * Implementation of {@link IBinaryType} that reads all its content from the index
+ */
+public class IndexBinaryType implements IBinaryType {
+	private final TypeRef typeRef;
+
+	private boolean simpleAttributesInitialized;
+	private char[] enclosingMethod;
+	private char[] enclosingType;
+	private char[] fileName;
+	private char[] superclassName;
+	private int modifiers;
+	private boolean isAnonymous;
+	private boolean isLocal;
+	private boolean isMember;
+
+	private long tagBits;
+
+	private char[] binaryTypeName;
+
+	private static final IBinaryAnnotation[] NO_ANNOTATIONS = new IBinaryAnnotation[0];
+	private static final int[] NO_PATH = new int[0];
+
+	public IndexBinaryType(TypeRef type, char[] indexPath) {
+		this.typeRef = type;
+		this.fileName = indexPath;
+	}
+
+	public boolean exists() {
+		return this.typeRef.get() != null;
+	}
+
+	@Override
+	public int getModifiers() {
+		initSimpleAttributes();
+
+		return this.modifiers;
+	}
+
+	@Override
+	public boolean isBinaryType() {
+		return true;
+	}
+
+	@Override
+	public char[] getFileName() {
+		return this.fileName;
+	}
+
+	@Override
+	public IBinaryAnnotation[] getAnnotations() {
+		try (IReader rl = this.typeRef.lock()) {
+			NdType type = this.typeRef.get();
+			if (type != null) {
+				return toAnnotationArray(this.typeRef.get().getAnnotations());
+			} else {
+				return NO_ANNOTATIONS;
+			}
+		}
+	}
+
+	private static IBinaryAnnotation[] toAnnotationArray(List<? extends NdAnnotation> annotations) {
+		if (annotations.isEmpty()) {
+			return NO_ANNOTATIONS;
+		}
+		IBinaryAnnotation[] result = new IBinaryAnnotation[annotations.size()];
+
+		for (int idx = 0; idx < result.length; idx++) {
+			result[idx] = createBinaryAnnotation(annotations.get(idx));
+		}
+		return result;
+	}
+
+	@Override
+	public IBinaryTypeAnnotation[] getTypeAnnotations() {
+		try (IReader rl = this.typeRef.lock()) {
+			NdType type = this.typeRef.get();
+			if (type != null) {
+				return createBinaryTypeAnnotations(type.getTypeAnnotations());
+			}
+		}
+		return null;
+	}
+
+	@Override
+	public char[] getEnclosingMethod() {
+		initSimpleAttributes();
+
+		return this.enclosingMethod;
+	}
+
+	@Override
+	public char[] getEnclosingTypeName() {
+		initSimpleAttributes();
+
+		return this.enclosingType;
+	}
+
+	@Override
+	public IBinaryField[] getFields() {
+		try (IReader rl = this.typeRef.lock()) {
+			NdType type = this.typeRef.get();
+			if (type != null) {
+				List<NdVariable> variables = type.getVariables();
+
+				if (variables.isEmpty()) {
+					return null;
+				}
+
+				IBinaryField[] result = new IBinaryField[variables.size()];
+				for (int fieldIdx = 0; fieldIdx < variables.size(); fieldIdx++) {
+					result[fieldIdx] = createBinaryField(variables.get(fieldIdx));
+				}
+				return result;
+			} else {
+				return null;
+			}
+		}
+	}
+
+	@Override
+	public char[] getGenericSignature() {
+		try (IReader rl = this.typeRef.lock()) {
+			NdType type = this.typeRef.get();
+			if (type != null) {
+				if (!type.getFlag(NdType.FLG_GENERIC_SIGNATURE_PRESENT)) {
+					return null;
+				}
+				CharArrayBuffer buffer = new CharArrayBuffer();
+				NdTypeParameter.getSignature(buffer, type.getTypeParameters());
+				NdTypeSignature superclass = type.getSuperclass();
+				if (superclass != null) {
+					superclass.getSignature(buffer);
+				}
+				for (NdTypeInterface nextInterface : type.getInterfaces()) {
+					nextInterface.getInterface().getSignature(buffer);
+				}
+				return buffer.getContents();
+			} else {
+				return null;
+			}
+		}
+	}
+
+	@Override
+	public char[][] getInterfaceNames() {
+		try (IReader rl = this.typeRef.lock()) {
+			NdType type = this.typeRef.get();
+			if (type != null) {
+				List<NdTypeInterface> interfaces = type.getInterfaces();
+
+				if (interfaces.isEmpty()) {
+					return null;
+				}
+
+				char[][] result = new char[interfaces.size()][];
+				for (int idx = 0; idx < interfaces.size(); idx++) {
+					NdTypeSignature nextInterface = interfaces.get(idx).getInterface();
+
+					result[idx] = nextInterface.getRawType().getBinaryName();
+				}
+				return result;
+			} else {
+				return null;
+			}
+		}
+	}
+
+	@Override
+	public IBinaryNestedType[] getMemberTypes() {
+		try (IReader rl = this.typeRef.lock()) {
+			NdType type = this.typeRef.get();
+			if (type != null) {
+				List<NdType> declaredTypes = type.getTypeId().getDeclaredTypes();
+				if (declaredTypes.isEmpty()) {
+					return null;
+				}
+
+				NdResourceFile resFile = type.getResourceFile();
+				IString javaRoot = resFile.getPackageFragmentRoot();
+
+				// Filter out all the declared types which are at different java roots (only keep the ones belonging
+				// to the same .jar file or to another .class file in the same folder).
+				List<IBinaryNestedType> result = new ArrayList<>();
+				for (NdType next : declaredTypes) {
+					NdResourceFile nextResFile = next.getResourceFile();
+
+					if (nextResFile.getPackageFragmentRoot().compare(javaRoot, true) == 0) {
+						result.add(createBinaryNestedType(next));
+					}
+				}
+				return result.isEmpty() ? null : result.toArray(new IBinaryNestedType[result.size()]);
+			} else {
+				return null;
+			}
+		}
+	}
+
+	private IBinaryNestedType createBinaryNestedType(NdType next) {
+		return new IndexBinaryNestedType(next.getTypeId().getBinaryName(), next.getDeclaringType().getBinaryName(),
+				next.getModifiers());
+	}
+
+	@Override
+	public IBinaryMethod[] getMethods() {
+		try (IReader rl = this.typeRef.lock()) {
+			NdType type = this.typeRef.get();
+			if (type != null) {
+				List<NdMethod> methods = type.getMethods();
+
+				if (methods.isEmpty()) {
+					return null;
+				}
+
+				IBinaryMethod[] result = new IBinaryMethod[methods.size()];
+				for (int idx = 0; idx < result.length; idx++) {
+					result[idx] = createBinaryMethod(methods.get(idx));
+				}
+
+				return result;
+			} else {
+				return null;
+			}
+		}
+	}
+
+	@Override
+	public char[][][] getMissingTypeNames() {
+		try (IReader rl = this.typeRef.lock()) {
+			NdType type = this.typeRef.get();
+			if (type != null) {
+				IString string = type.getMissingTypeNames();
+				if (string.length() == 0) {
+					return null;
+				}
+				char[] missingTypeNames = string.getChars();
+				char[][] paths = CharOperation.splitOn(',', missingTypeNames);
+				char[][][] result = new char[paths.length][][];
+				for (int idx = 0; idx < paths.length; idx++) {
+					result[idx] = CharOperation.splitOn('/', paths[idx]);
+				}
+				return result;
+			} else {
+				return null;
+			}
+		}
+	}
+
+	@Override
+	public char[] getName() {
+		initSimpleAttributes();
+
+		return this.binaryTypeName;
+	}
+
+	@Override
+	public char[] getSourceName() {
+		try (IReader rl = this.typeRef.lock()) {
+			NdType type = this.typeRef.get();
+			if (type != null) {
+				return type.getSourceName();
+			} else {
+				return new char[0];
+			}
+		}
+	}
+
+	@Override
+	public char[] getSuperclassName() {
+		initSimpleAttributes();
+
+		return this.superclassName;
+	}
+
+	@Override
+	public long getTagBits() {
+		initSimpleAttributes();
+
+		return this.tagBits;
+	}
+
+	@Override
+	public boolean isAnonymous() {
+		initSimpleAttributes();
+
+		return this.isAnonymous;
+	}
+
+	@Override
+	public boolean isLocal() {
+		initSimpleAttributes();
+
+		return this.isLocal;
+	}
+
+	@Override
+	public boolean isMember() {
+		initSimpleAttributes();
+
+		return this.isMember;
+	}
+
+	@Override
+	public char[] sourceFileName() {
+		try (IReader rl = this.typeRef.lock()) {
+			NdType type = this.typeRef.get();
+			if (type != null) {
+				char[] result = type.getSourceFileName().getChars();
+				if (result.length == 0) {
+					return null;
+				}
+				return result;
+			} else {
+				return null;
+			}
+		}
+	}
+
+	@Override
+	public ITypeAnnotationWalker enrichWithExternalAnnotationsFor(ITypeAnnotationWalker walker, Object member,
+			LookupEnvironment environment) {
+		return walker;
+	}
+
+	private IBinaryMethod createBinaryMethod(NdMethod ndMethod) {
+		NdMethodId methodId = ndMethod.getMethodId();
+
+		return IndexBinaryMethod.create().setAnnotations(toAnnotationArray(ndMethod.getAnnotations()))
+				.setModifiers(ndMethod.getModifiers()).setIsConstructor(methodId.isConstructor())
+				.setArgumentNames(getArgumentNames(ndMethod)).setDefaultValue(unpackValue(ndMethod.getDefaultValue()))
+				.setExceptionTypeNames(getExceptionTypeNames(ndMethod))
+				.setGenericSignature(getGenericSignatureFor(ndMethod))
+				.setMethodDescriptor(methodId.getMethodDescriptor())
+				.setParameterAnnotations(getParameterAnnotations(ndMethod))
+				.setSelector(ndMethod.getMethodId().getSelector()).setTagBits(ndMethod.getTagBits())
+				.setIsClInit(methodId.isClInit()).setTypeAnnotations(createBinaryTypeAnnotations(ndMethod.getTypeAnnotations()));
+	}
+
+	private static IBinaryTypeAnnotation[] createBinaryTypeAnnotations(List<? extends NdTypeAnnotation> typeAnnotations) {
+		if (typeAnnotations.isEmpty()) {
+			return null;
+		}
+		IBinaryTypeAnnotation[] result = new IBinaryTypeAnnotation[typeAnnotations.size()];
+		int idx = 0;
+		for (NdTypeAnnotation next : typeAnnotations) {
+			IBinaryAnnotation annotation = createBinaryAnnotation(next);
+			int[] typePath = getTypePath(next.getTypePath());
+			int info = 0;
+			int info2 = 0;
+			switch (next.getTargetType()) {
+				case AnnotationTargetTypeConstants.CLASS_TYPE_PARAMETER:
+				case AnnotationTargetTypeConstants.METHOD_TYPE_PARAMETER:
+					info = next.getTargetInfoArg0();
+					break;
+				case AnnotationTargetTypeConstants.CLASS_EXTENDS:
+					info = next.getTarget();
+					break;
+				case AnnotationTargetTypeConstants.CLASS_TYPE_PARAMETER_BOUND:
+				case AnnotationTargetTypeConstants.METHOD_TYPE_PARAMETER_BOUND:
+					info = next.getTargetInfoArg0();
+					info2 = next.getTargetInfoArg1();
+					break;
+				case AnnotationTargetTypeConstants.FIELD:
+				case AnnotationTargetTypeConstants.METHOD_RETURN:
+				case AnnotationTargetTypeConstants.METHOD_RECEIVER:
+					break;
+				case AnnotationTargetTypeConstants.METHOD_FORMAL_PARAMETER :
+					info = next.getTargetInfoArg0();
+					break;
+				case AnnotationTargetTypeConstants.THROWS :
+					info = next.getTarget();
+					break;
+
+				default:
+					throw new IllegalStateException("Target type not handled " + next.getTargetType()); //$NON-NLS-1$
+			}
+			result[idx++] = new IndexBinaryTypeAnnotation(next.getTargetType(), info, info2, typePath, annotation);
+		}
+		return result;
+	}
+
+	private static int[] getTypePath(byte[] typePath) {
+		if (typePath.length == 0) {
+			return NO_PATH;
+		}
+		int[] result = new int[typePath.length];
+		for (int idx = 0; idx < typePath.length; idx++) {
+			result[idx] = typePath[idx];
+		}
+		return result;
+	}
+
+	private static char[] getGenericSignatureFor(NdMethod method) {
+		if (!method.hasAllFlags(NdMethod.FLG_GENERIC_SIGNATURE_PRESENT)) {
+			return null;
+		}
+		CharArrayBuffer result = new CharArrayBuffer();
+		method.getGenericSignature(result, method.hasAllFlags(NdMethod.FLG_THROWS_SIGNATURE_PRESENT));
+		return result.getContents();
+	}
+	
+	private char[][] getArgumentNames(NdMethod ndMethod) {
+		// Unlike what its JavaDoc says, IBinaryType returns an empty array if no argument names are available, so
+		// we replicate this weird undocumented corner case here.
+		char[][] result = ndMethod.getParameterNames();
+		int lastNonEmpty = -1;
+		for (int idx = 0; idx < result.length; idx++) {
+			if (result[idx] != null && result[idx].length != 0) {
+				lastNonEmpty = idx;
+			}
+		}
+
+		if (lastNonEmpty != result.length - 1) {
+			char[][] newResult = new char[lastNonEmpty + 1][];
+			System.arraycopy(result, 0, newResult, 0, lastNonEmpty + 1);
+			return newResult;
+		}
+		return result;
+	}
+
+	private IBinaryAnnotation[][] getParameterAnnotations(NdMethod ndMethod) {
+		List<NdMethodParameter> parameters = ndMethod.getMethodParameters();
+		if (parameters.isEmpty()) {
+			return null;
+		}
+
+		IBinaryAnnotation[][] result = new IBinaryAnnotation[parameters.size()][];
+		for (int idx = 0; idx < parameters.size(); idx++) {
+			NdMethodParameter next = parameters.get(idx);
+
+			result[idx] = toAnnotationArray(next.getAnnotations());
+		}
+
+		// int newLength = result.length;
+		// while (newLength > 0 && result[newLength - 1] == null) {
+		// --newLength;
+		// }
+		//
+		// if (newLength < result.length) {
+		// if (newLength == 0) {
+		// return null;
+		// }
+		// IBinaryAnnotation[][] newResult = new IBinaryAnnotation[newLength][];
+		// System.arraycopy(result, 0, newResult, 0, newLength);
+		// result = newResult;
+		// }
+
+		return result;
+	}
+
+	private char[][] getExceptionTypeNames(NdMethod ndMethod) {
+		List<NdMethodException> exceptions = ndMethod.getExceptions();
+
+		// Although the JavaDoc for IBinaryMethod says that the exception list will be null if empty,
+		// the implementation in MethodInfo returns an empty array rather than null. We copy the
+		// same behavior here in case something is relying on it. Uncomment the following if the "null"
+		// version is deemed correct.
+
+		// if (exceptions.isEmpty()) {
+		// return null;
+		// }
+
+		char[][] result = new char[exceptions.size()][];
+		for (int idx = 0; idx < exceptions.size(); idx++) {
+			NdMethodException next = exceptions.get(idx);
+
+			result[idx] = next.getExceptionType().getRawType().getBinaryName();
+		}
+		return result;
+	}
+
+	public static IBinaryField createBinaryField(NdVariable ndVariable) {
+		char[] name = ndVariable.getName().getChars();
+		Constant constant = null;
+		NdConstant ndConstant = ndVariable.getConstant();
+		if (ndConstant != null) {
+			constant = ndConstant.getConstant();
+		}
+		if (constant == null) {
+			constant = Constant.NotAConstant;
+		}
+
+		NdTypeSignature type = ndVariable.getType();
+
+		IBinaryTypeAnnotation[] typeAnnotationArray = createBinaryTypeAnnotations(ndVariable.getTypeAnnotations());
+
+		IBinaryAnnotation[] annotations = toAnnotationArray(ndVariable.getAnnotations());
+
+		CharArrayBuffer signature = new CharArrayBuffer();
+		if (ndVariable.hasVariableFlag(NdVariable.FLG_GENERIC_SIGNATURE_PRESENT)) {
+			type.getSignature(signature);
+		}
+
+		long tagBits = ndVariable.getTagBits();
+		return new IndexBinaryField(annotations, constant, signature.getContents(), ndVariable.getModifiers(), name,
+				tagBits, typeAnnotationArray, type.getRawType().getFieldDescriptor().getChars());
+	}
+
+	public static IBinaryAnnotation createBinaryAnnotation(NdAnnotation ndAnnotation) {
+		List<NdAnnotationValuePair> elementValuePairs = ndAnnotation.getElementValuePairs();
+
+		final IBinaryElementValuePair[] resultingPair = new IBinaryElementValuePair[elementValuePairs.size()];
+
+		for (int idx = 0; idx < elementValuePairs.size(); idx++) {
+			NdAnnotationValuePair next = elementValuePairs.get(idx);
+
+			resultingPair[idx] = new ElementValuePairInfo(next.getName().getChars(), unpackValue(next.getValue()));
+		}
+
+		final char[] binaryName = JavaNames.fieldDescriptorToBinaryName(
+				ndAnnotation.getType().getRawType().getFieldDescriptor().getChars());
+
+		return new IBinaryAnnotation() {
+			@Override
+			public char[] getTypeName() {
+				return binaryName;
+			}
+
+			@Override
+			public IBinaryElementValuePair[] getElementValuePairs() {
+				return resultingPair;
+			}
+
+			@Override
+			public String toString() {
+				return BinaryTypeFormatter.annotationToString(this);
+			}
+		};
+	}
+
+	public void initSimpleAttributes() {
+		if (!this.simpleAttributesInitialized) {
+			this.simpleAttributesInitialized = true;
+
+			try (IReader rl = this.typeRef.lock()) {
+				NdType type = this.typeRef.get();
+				if (type != null) {
+					NdMethodId methodId = type.getDeclaringMethod();
+
+					if (methodId != null) {
+						char[] methodName = methodId.getMethodName().getChars();
+						int startIdx = CharArrayUtils.lastIndexOf('#', methodName);
+						this.enclosingMethod = CharArrayUtils.subarray(methodName, startIdx + 1);
+						this.enclosingType = CharArrayUtils.subarray(methodName, 1, startIdx);
+					} else {
+						NdTypeId typeId = type.getDeclaringType();
+
+						if (typeId != null) {
+							this.enclosingType = typeId.getBinaryName();
+						}
+					}
+
+					this.modifiers = type.getModifiers();
+					this.isAnonymous = type.isAnonymous();
+					this.isLocal = type.isLocal();
+					this.isMember = type.isMember();
+					this.tagBits = type.getTagBits();
+
+					NdTypeSignature superclass = type.getSuperclass();
+					if (superclass != null) {
+						this.superclassName = superclass.getRawType().getBinaryName();
+					} else {
+						this.superclassName = null;
+					}
+
+					this.binaryTypeName = JavaNames.fieldDescriptorToBinaryName(type.getFieldDescriptor().getChars());
+				} else {
+					this.binaryTypeName = JavaNames.fieldDescriptorToBinaryName(this.typeRef.getFieldDescriptor());
+				}
+			}
+		}
+	}
+
+	private static Object unpackValue(NdConstant value) {
+		if (value == null) {
+			return null;
+		}
+		if (value instanceof NdConstantAnnotation) {
+			NdConstantAnnotation annotation = (NdConstantAnnotation) value;
+
+			return createBinaryAnnotation(annotation.getValue());
+		}
+		if (value instanceof NdConstantArray) {
+			NdConstantArray array = (NdConstantArray) value;
+
+			List<NdConstant> arrayContents = array.getValue();
+
+			Object[] result = new Object[arrayContents.size()];
+			for (int idx = 0; idx < arrayContents.size(); idx++) {
+				result[idx] = unpackValue(arrayContents.get(idx));
+			}
+			return result;
+		}
+		if (value instanceof NdConstantEnum) {
+			NdConstantEnum ndConstantEnum = (NdConstantEnum) value;
+
+			NdTypeSignature signature = ndConstantEnum.getType();
+
+			return new EnumConstantSignature(signature.getRawType().getBinaryName(), ndConstantEnum.getValue());
+		}
+		if (value instanceof NdConstantClass) {
+			NdConstantClass constant = (NdConstantClass) value;
+
+			return new ClassSignature(constant.getValue().getRawType().getBinaryName());
+		}
+
+		return value.getConstant();
+	}
+
+	@Override
+	public ExternalAnnotationStatus getExternalAnnotationStatus() {
+		return ExternalAnnotationStatus.NOT_EEA_CONFIGURED;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryTypeAnnotation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryTypeAnnotation.java
new file mode 100644
index 0000000..521e17b
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryTypeAnnotation.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * 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.java.model;
+
+import org.eclipse.jdt.internal.compiler.classfmt.BinaryTypeFormatter;
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+
+public class IndexBinaryTypeAnnotation implements IBinaryTypeAnnotation {
+	private int targetType;
+
+	// info is used in different ways:
+	// TargetType 0x00: CLASS_TYPE_PARAMETER: type parameter index
+	// TargetType 0x01: METHOD_TYPE_PARAMETER: type parameter index
+	// TargetType 0x10: CLASS_EXTENDS: supertype index (-1 = superclass, 0..N superinterface)
+	// TargetType 0x11: CLASS_TYPE_PARAMETER_BOUND: type parameter index
+	// TargetType 0x12: METHOD_TYPE_PARAMETER_BOUND: type parameter index
+	// TargetType 0x16: METHOD_FORMAL_PARAMETER: method formal parameter index
+	// TargetType 0x17: THROWS: throws type index
+	private int info;
+
+	// TargetType 0x11: CLASS_TYPE_PARAMETER_BOUND: bound index
+	// TargetType 0x12: METHOD_TYPE_PARAMETER_BOUND: bound index
+	private int info2;
+
+
+	private int[] typePath;
+	private IBinaryAnnotation annotation;
+
+	public IndexBinaryTypeAnnotation(int targetType, int info, int info2, int[] typePath, IBinaryAnnotation annotation) {
+		this.targetType = targetType;
+		this.info = info;
+		this.info2 = info2;
+		this.typePath = typePath;
+		this.annotation = annotation;
+	}
+
+	@Override
+	public IBinaryAnnotation getAnnotation() {
+		return this.annotation;
+	}
+
+	@Override
+	public int getTargetType() {
+		return this.targetType;
+	}
+
+	@Override
+	public int[] getTypePath() {
+		return this.typePath;
+	}
+
+	@Override
+	public int getSupertypeIndex() {
+		return this.info;
+	}
+
+	@Override
+	public int getTypeParameterIndex() {
+		return this.info;
+}
+
+	@Override
+	public int getBoundIndex() {
+		return this.info2;
+	}
+
+	@Override
+	public int getMethodFormalParameterIndex() {
+		return this.info;
+	}
+
+	@Override
+	public int getThrowsTypeIndex() {
+		return this.info;
+	}
+
+	@Override
+	public String toString() {
+		return BinaryTypeFormatter.annotationToString(this);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/TypeAnnotationBuilder.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/TypeAnnotationBuilder.java
new file mode 100644
index 0000000..0b8d7d4
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/TypeAnnotationBuilder.java
@@ -0,0 +1,150 @@
+/*******************************************************************************
+ * 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.java.model;
+
+import org.eclipse.jdt.internal.compiler.codegen.AnnotationTargetTypeConstants;
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+
+public class TypeAnnotationBuilder implements ITypeAnnotationBuilder {
+	TypeAnnotationBuilder parent;
+	int kind;
+	int index;
+	int length;
+	int target;
+	int targetParameter;
+	int targetParameter2;
+
+	private TypeAnnotationBuilder(TypeAnnotationBuilder parent, int kind, int index,
+			int length, int nextTarget, int nextTargetParameter, int nextTargetParameter2) {
+		super();
+		this.parent = parent;
+		this.kind = kind;
+		this.index = index;
+		this.length = length;
+		this.target = nextTarget;
+		this.targetParameter = nextTargetParameter;
+		this.targetParameter2 = nextTargetParameter2;
+	}
+
+	public static TypeAnnotationBuilder create() {
+		return new TypeAnnotationBuilder(null, 0, 0, 0, -1, -1, -1);
+	}
+
+	private TypeAnnotationBuilder walk(int nextKind, int nextIndex) {
+		return new TypeAnnotationBuilder(this, nextKind, nextIndex, this.length+1, this.target, this.targetParameter, this.targetParameter2);
+	}
+
+	private TypeAnnotationBuilder toTarget(int newTarget) {
+		return new TypeAnnotationBuilder(this.parent, this.kind, this.index, this.length, newTarget, this.targetParameter, this.targetParameter2);
+	}
+
+	private TypeAnnotationBuilder toTarget(int newTarget, int parameter) {
+		return new TypeAnnotationBuilder(this.parent, this.kind, this.index, this.length, newTarget, parameter, this.targetParameter2);
+	}
+
+	private TypeAnnotationBuilder toTarget2(int parameter) {
+		return new TypeAnnotationBuilder(this.parent, this.kind, this.index, this.length, this.target, this.targetParameter, parameter);
+	}
+
+	@Override
+	public ITypeAnnotationBuilder toField() {
+		return toTarget(AnnotationTargetTypeConstants.FIELD);
+	}
+
+	@Override
+	public ITypeAnnotationBuilder toMethodReturn() {
+		return toTarget(AnnotationTargetTypeConstants.METHOD_RETURN);
+	}
+
+	@Override
+	public ITypeAnnotationBuilder toReceiver() {
+		return toTarget(AnnotationTargetTypeConstants.METHOD_RECEIVER);
+	}
+
+	@Override
+	public ITypeAnnotationBuilder toTypeParameter(boolean isClassTypeParameter, int rank) {
+		int targetType = isClassTypeParameter ? AnnotationTargetTypeConstants.CLASS_TYPE_PARAMETER
+				: AnnotationTargetTypeConstants.METHOD_TYPE_PARAMETER;
+		return toTarget(targetType, rank);
+	}
+
+	@Override
+	public ITypeAnnotationBuilder toTypeParameterBounds(boolean isClassTypeParameter, int parameterRank) {
+		int targetType = isClassTypeParameter ?
+				AnnotationTargetTypeConstants.CLASS_TYPE_PARAMETER_BOUND : AnnotationTargetTypeConstants.METHOD_TYPE_PARAMETER_BOUND;
+
+		return toTarget(targetType, parameterRank);
+	}
+
+	@Override
+	public ITypeAnnotationBuilder toTypeBound(short boundIndex) {
+		return toTarget2(boundIndex);
+	}
+
+	@Override
+	public ITypeAnnotationBuilder toSupertype(short superTypeIndex) {
+		return toTarget(AnnotationTargetTypeConstants.CLASS_EXTENDS, superTypeIndex);
+	}
+
+	@Override
+	public ITypeAnnotationBuilder toMethodParameter(short parameterIndex) {
+		return toTarget(AnnotationTargetTypeConstants.METHOD_FORMAL_PARAMETER, parameterIndex);
+	}
+
+	@Override
+	public ITypeAnnotationBuilder toThrows(int rank) {
+		return toTarget(AnnotationTargetTypeConstants.THROWS, rank);
+	}
+
+	@Override
+	public ITypeAnnotationBuilder toTypeArgument(int rank) {
+		return walk(AnnotationTargetTypeConstants.TYPE_ARGUMENT, rank);
+	}
+
+	@Override
+	public ITypeAnnotationBuilder toWildcardBound() {
+		return walk(AnnotationTargetTypeConstants.WILDCARD_BOUND, 0);
+	}
+
+	@Override
+	public ITypeAnnotationBuilder toNextArrayDimension() {
+		return walk(AnnotationTargetTypeConstants.NEXT_ARRAY_DIMENSION, 0);
+	}
+
+	@Override
+	public ITypeAnnotationBuilder toNextNestedType() {
+		return walk(AnnotationTargetTypeConstants.NEXT_NESTED_TYPE, 0);
+	}
+
+	@Override
+	public IBinaryTypeAnnotation build(IBinaryAnnotation annotation) {
+		return new IndexBinaryTypeAnnotation(this.target, this.targetParameter, this.targetParameter2, getTypePath(), annotation);
+	}
+
+	private int[] getTypePath() {
+		if (this.length == 0) {
+			return IBinaryTypeAnnotation.NO_TYPE_PATH;
+		}
+
+		int[] result = new int[this.length * 2];
+
+		TypeAnnotationBuilder next = this;
+		while (next != null && next.length > 0) {
+			int writeIdx = (next.length - 1) * 2;
+			result[writeIdx] = next.kind;
+			result[writeIdx + 1] = next.index;
+			next = next.parent;
+		}
+
+		return result;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/CharArrayMap.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/CharArrayMap.java
new file mode 100644
index 0000000..eed0a5a
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/CharArrayMap.java
@@ -0,0 +1,312 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 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
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+
+/**
+ * Provides functionality similar to a Map, with the feature that char arrays
+ * and sections of char arrays (known as slices) may be used as keys.
+ *
+ * This class is useful because small pieces of an existing large char[] buffer
+ * can be directly used as map keys. This avoids the need to create many String
+ * objects as would normally be needed as keys in a standard java.util.Map.
+ * Thus performance is improved in the CDT core.
+ *
+ * Most methods are overloaded with two versions, one that uses a
+ * section of a char[] as the key (a slice), and one that uses
+ * the entire char[] as the key.
+ *
+ * This class is intended as a replacement for CharArrayObjectMap.
+ *
+ * ex:
+ * char[] key = "one two three".toCharArray();
+ * map.put(key, 4, 3, new Integer(99));
+ * map.get(key, 4, 3); // returns 99
+ * map.get("two".toCharArray()); // returns 99
+ *
+ * @author Mike Kucera
+ *
+ * @param <V>
+ */
+public final class CharArrayMap<V> {
+
+	/**
+	 * Wrapper class used as keys in the map. The purpose
+	 * of this class is to provide implementations of
+	 * equals() and hashCode() that operate on array slices.
+	 *
+	 * This class is private so it is assumed that the arguments
+	 * passed to the constructor are legal.
+	 */
+    private static final class Key implements Comparable<Key>{
+        final char[] buffer;
+        final int start;
+        final int length;
+
+        public Key(char[] buffer, int start, int length) {
+            this.buffer = buffer;
+            this.length = length;
+            this.start = start;
+        }
+
+        /**
+         * @throws NullPointerException if buffer is null
+         */
+        public Key(char[] buffer) {
+        	this.buffer = buffer;
+        	this.length = buffer.length; // throws NPE
+        	this.start = 0;
+        }
+
+        @Override
+        public boolean equals(Object x) {
+        	if(this == x)
+        		return true;
+        	if(!(x instanceof Key))
+        		return false;
+
+            Key k = (Key) x;
+            if(this.length != k.length)
+            	return false;
+
+            for(int i = this.start, j = k.start; i < this.length; i++, j++) {
+            	if(this.buffer[i] != k.buffer[j]) {
+            		return false;
+            	}
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 17;
+            for(int i = this.start; i < this.start+this.length; i++) {
+            	result = 37 * result + this.buffer[i];
+            }
+            return result;
+        }
+
+        @SuppressWarnings("nls")
+		@Override
+        public String toString() {
+        	String slice = new String(this.buffer, this.start, this.length);
+        	return "'" + slice + "'@(" + this.start + "," + this.length + ")";
+        }
+
+
+        @Override
+		public int compareTo(Key other) {
+        	char[] b1 = this.buffer, b2 = other.buffer;
+
+        	for(int i = this.start, j = other.start; i < b1.length && j < b2.length; i++, j++) {
+        		if(b1[i] != b2[j])
+        			return b1[i] < b2[j] ? -1 : 1;
+        	}
+        	return b1.length - b2.length;
+        }
+    }
+
+
+    /**
+     * Used to enforce preconditions.
+     * Note that the NPE thrown by mutator methods is thrown from the Key constructor.
+     *
+     * @throws IndexOutOfBoundsException if boundaries are wrong in any way
+     */
+    private static void checkBoundaries(char[] chars, int start, int length) {
+    	if(start < 0 || length < 0 || start >= chars.length || start + length > chars.length)
+    		throw new IndexOutOfBoundsException("Buffer length: " + chars.length + //$NON-NLS-1$
+    				                          ", Start index: " + start + //$NON-NLS-1$
+    				                          ", Length: " + length); //$NON-NLS-1$
+    }
+
+
+    private final Map<Key,V> map;
+
+
+    /**
+     * Constructs an empty CharArrayMap with default initial capacity.
+     */
+    public CharArrayMap() {
+    	this.map = new HashMap<Key,V>();
+    }
+
+
+    /**
+     * Static factory method that constructs an empty CharArrayMap with default initial capacity,
+     * and the map will be kept in ascending key order.
+     *
+     * Characters are compared using a strictly numerical comparison; it is not locale-dependent.
+     */
+    public static <V> CharArrayMap<V> createOrderedMap() {
+    	// TreeMap does not have a constructor that takes an initial capacity
+    	return new CharArrayMap<V>(new TreeMap<Key, V>());
+    }
+
+
+    private CharArrayMap(Map<Key, V> map) {
+    	assert map != null;
+    	this.map = map;
+    }
+
+
+    /**
+     * Constructs an empty CharArrayMap with the given initial capacity.
+     * @throws IllegalArgumentException if the initial capacity is negative
+     */
+    public CharArrayMap(int initialCapacity) {
+    	this.map = new HashMap<Key,V>(initialCapacity);
+    }
+
+    /**
+	 * Creates a new mapping in this map, uses the given array slice as the key.
+	 * If the map previously contained a mapping for this key, the old value is replaced.
+	 * @throws NullPointerException if chars is null
+	 * @throws IndexOutOfBoundsException if the boundaries specified by start and length are out of range
+	 */
+    public void put(char[] chars, int start, int length, V value) {
+    	checkBoundaries(chars, start, length);
+        this.map.put(new Key(chars, start, length), value);
+    }
+
+    /**
+	 * Creates a new mapping in this map, uses all of the given array as the key.
+	 * If the map previously contained a mapping for this key, the old value is replaced.
+	 * @throws NullPointerException if chars is null
+	 */
+    public void put(char[] chars, V value) {
+        this.map.put(new Key(chars), value);
+    }
+
+    /**
+	 * Returns the value to which the specified array slice is mapped in this map,
+	 * or null if the map contains no mapping for this key.
+	 * @throws NullPointerException if chars is null
+	 * @throws IndexOutOfBoundsException if the boundaries specified by start and length are out of range
+	 */
+    public V get(char[] chars, int start, int length) {
+    	checkBoundaries(chars, start, length);
+        return this.map.get(new Key(chars, start, length));
+    }
+
+    /**
+	 * Returns the value to which the specified array is mapped in this map,
+	 * or null if the map contains no mapping for this key.
+	 * @throws NullPointerException if chars is null
+	 */
+    public V get(char[] chars) {
+        return this.map.get(new Key(chars));
+    }
+
+    /**
+	 * Removes the mapping for the given array slice if present.
+	 * Returns the value object that corresponded to the key
+	 * or null if the key was not in the map.
+	 * @throws NullPointerException if chars is null
+	 * @throws IndexOutOfBoundsException if the boundaries specified by start and length are out of range
+	 */
+    public V remove(char[] chars, int start, int length) {
+    	checkBoundaries(chars, start, length);
+    	return this.map.remove(new Key(chars, start, length));
+    }
+
+    /**
+	 * Removes the mapping for the given array if present.
+	 * Returns the value object that corresponded to the key
+	 * or null if the key was not in the map.
+	 * @throws NullPointerException if chars is null
+	 */
+    public V remove(char[] chars) {
+    	return this.map.remove(new Key(chars));
+    }
+
+    /**
+	 * Returns true if the given key has a value associated with it in the map.
+	 * @throws NullPointerException if chars is null
+	 * @throws IndexOutOfBoundsException if the boundaries specified by start and length are out of range
+	 */
+    public boolean containsKey(char[] chars, int start, int length) {
+    	checkBoundaries(chars, start, length);
+    	return this.map.containsKey(new Key(chars, start, length));
+    }
+
+    /**
+	 * Returns true if the given key has a value associated with it in the map.
+	 * @throws NullPointerException if chars is null
+	 */
+    public boolean containsKey(char[] chars) {
+    	return this.map.containsKey(new Key(chars));
+    }
+
+    /**
+	 * Returns true if the given value is contained in the map.
+	 */
+    public boolean containsValue(V value) {
+    	return this.map.containsValue(value);
+    }
+
+    /**
+	 * Use this in a foreach loop.
+	 */
+    public Collection<V> values() {
+        return this.map.values();
+    }
+
+    /**
+	 * Returns the keys stored in the map.
+	 */
+    public Collection<char[]> keys() {
+    	Set<Key> keys= this.map.keySet();
+    	ArrayList<char[]> r= new ArrayList<char[]>(keys.size());
+    	for (Key key : keys) {
+    		r.add(CharArrayUtils.extract(key.buffer, key.start, key.length));
+		}
+        return r;
+    }
+
+    /**
+	 * Removes all mappings from the map.
+	 */
+    public void clear() {
+    	this.map.clear();
+    }
+
+    /**
+	 * Returns the number of mappings.
+	 */
+    public int size() {
+    	return this.map.size();
+    }
+
+    /**
+	 * Returns true if the map is empty.
+	 */
+    public boolean isEmpty() {
+    	return this.map.isEmpty();
+    }
+
+
+    /**
+     * Returns a String representation of the map.
+     */
+    @Override
+    public String toString() {
+    	return this.map.toString();
+    }
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/CharArrayUtils.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/CharArrayUtils.java
new file mode 100644
index 0000000..1a5791e
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/CharArrayUtils.java
@@ -0,0 +1,522 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *     Andrew Ferguson (Symbian)
+ *     Markus Schorn (Wind River Systems)
+ *     Sergey Prigogin (Google)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.util;
+
+import java.util.Arrays;
+
+/**
+ * A static utility class for char arrays.
+ */
+public class CharArrayUtils {
+	/** @since 5.4 */
+	public static final char[] EMPTY_CHAR_ARRAY = {};
+	public static final char[] EMPTY = EMPTY_CHAR_ARRAY;
+	/** @since 5.7 */
+	public static final char[][] EMPTY_ARRAY_OF_CHAR_ARRAYS = {};
+
+	private CharArrayUtils() {}
+
+	public static final int hash(char[] str, int start, int length) {
+		int h = 0;
+		int end = start + length;
+
+		for (int curr = start; curr < end; ++curr) {
+			h = 31 * h + str[curr];
+		}
+
+		return h;
+	}
+
+	public static final int hash(char[] str) {
+		return hash(str, 0, str.length);
+	}
+
+	public static final boolean equals(char[] str1, char[] str2) {
+		return Arrays.equals(str1, str2);
+	}
+
+	public static final boolean equals(char[][] strarr1, char[][] strarr2) {
+		if (strarr1 == strarr2) {
+			return true;
+		}
+		if (strarr1 == null || strarr2 == null) {
+			return false;
+		}
+		if (strarr1.length != strarr2.length) {
+			return false;
+		}
+		for (int i = 0; i < strarr2.length; i++) {
+			if (!Arrays.equals(strarr1[i], strarr2[i])) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Returns {@code true} if the contents of a character array are the same as contents
+	 * of a string.
+	 * @since 5.4
+	 */
+	public static final boolean equals(char[] str1, String str2) {
+        int length = str1.length;
+        if (str2.length() != length)
+            return false;
+
+        for (int i = 0; i < length; i++) {
+            if (str1[i] != str2.charAt(i))
+                return false;
+        }
+        return true;
+	}
+
+	/**
+	 * Returns true iff the given array contains the given char at the given position
+	 */
+	public static final boolean hasCharAt(char toLookFor, int position, char[] toSearch) {
+		if (toSearch.length <= position) {
+			return false;
+		}
+
+		return toSearch[position] == toLookFor;
+	}
+
+	/**
+	 * Returns {@code true} if the contents of a section of a character array are the same as contents of a string.
+	 * 
+	 * @since 5.5
+	 */
+	public static final boolean equals(char[] str1, int start1, int length1, String str2) {
+		if (length1 != str2.length() || str1.length < length1 + start1)
+			return false;
+		for (int i = 0; i < length1; ++i) {
+			if (str1[start1++] != str2.charAt(i))
+				return false;
+		}
+		return true;
+	}
+
+	/**
+	 * Returns {@code true} if a prefix of the character array is the same as contents
+	 * of a string.
+	 * @since 5.4
+	 */
+	public static final boolean startsWith(char[] str1, String str2) {
+		int len = str2.length();
+		if (str1.length < len)
+			return false;
+		for (int i = 0; i < len; i++) {
+            if (str1[i] != str2.charAt(i)) {
+                return false;
+            }
+        }
+        return true;
+	}
+
+	/**
+	 * Implements a lexicographical comparator for char arrays. Comparison is done
+	 * on a per char basis, not a code-point basis.
+	 *
+	 * @param str1 the first of the two char arrays to compare
+	 * @param str2 the second of the two char arrays to compare
+	 * @return  0 if str1==str2, -1 if str1 &lt; str2 and 1 if str1 &gt; str2
+	 */
+	/*
+	 * aftodo - we should think about using the Character codepoint static methods
+	 * if we move to Java 5
+	 */
+	public static final int compare(char[] str1, char[] str2) {
+		if (str1 == str2)
+			return 0;
+
+		int end= Math.min(str1.length, str2.length);
+		for (int i = 0; i < end; ++i) {
+			int diff= str1[i] - str2[i];
+			if (diff != 0)
+				return diff;
+		}
+
+		return str1.length - str2.length;
+	}
+
+	/**
+	 * Returns {@code true} if the contents of a section of a character array are the same as
+	 * contents of another character array.
+	 */
+	public static final boolean equals(char[] str1, int start1, int length1, char[] str2) {
+		if (length1 != str2.length || str1.length < length1 + start1)
+			return false;
+		if (str1 == str2 && start1 == 0)
+		    return true;
+		for (int i = 0; i < length1; ++i) {
+			if (str1[start1++] != str2[i])
+				return false;
+		}
+
+		return true;
+	}
+
+	public static final boolean equals(char[] str1, int start1, int length1, char[] str2, boolean ignoreCase) {
+	    if (!ignoreCase)
+	        return equals(str1, start1, length1, str2);
+
+		if (length1 != str2.length || str1.length < start1 + length1)
+			return false;
+
+		for (int i = 0; i < length1; ++i) {
+			if (Character.toLowerCase(str1[start1++]) != Character.toLowerCase(str2[i]))
+				return false;
+		}
+		return true;
+	}
+
+	public static final char[] extract(char[] str, int start, int length) {
+		if (start == 0 && length == str.length)
+			return str;
+
+		char[] copy = new char[length];
+		System.arraycopy(str, start, copy, 0, length);
+		return copy;
+	}
+
+	public static final char[] concat(char[] first, char[] second) {
+		if (first == null)
+			return second;
+		if (second == null)
+			return first;
+
+		int length1 = first.length;
+		int length2 = second.length;
+		char[] result = new char[length1 + length2];
+		System.arraycopy(first, 0, result, 0, length1);
+		System.arraycopy(second, 0, result, length1, length2);
+		return result;
+	}
+
+	public static final char[] concat(char[] first, char[] second, char[] third) {
+		if (first == null)
+			return concat(second, third);
+		if (second == null)
+			return concat(first, third);
+		if (third == null)
+			return concat(first, second);
+
+		int length1 = first.length;
+		int length2 = second.length;
+		int length3 = third.length;
+		char[] result = new char[length1 + length2 + length3];
+		System.arraycopy(first, 0, result, 0, length1);
+		System.arraycopy(second, 0, result, length1, length2);
+		System.arraycopy(third, 0, result, length1 + length2, length3);
+		return result;
+	}
+
+	public static final char[] concat(char[] first, char[] second, char[] third, char[] fourth) {
+		if (first == null)
+			return concat(second, third, fourth);
+		if (second == null)
+			return concat(first, third, fourth);
+		if (third == null)
+			return concat(first, second, fourth);
+		if (fourth == null)
+			return concat(first, second, third);
+
+		int length1 = first.length;
+		int length2 = second.length;
+		int length3 = third.length;
+		int length4 = fourth.length;
+		char[] result = new char[length1 + length2 + length3 + length4];
+		System.arraycopy(first, 0, result, 0, length1);
+		System.arraycopy(second, 0, result, length1, length2);
+		System.arraycopy(third, 0, result, length1 + length2, length3);
+		System.arraycopy(fourth, 0, result, length1 + length2 + length3, length4);
+		return result;
+	}
+
+	/**
+	 * Answers a new array which is the concatenation of all the given arrays.
+	 *
+	 * @param toCatenate
+	 * @since 3.12
+	 */
+	public static char[] concat(char[]... toCatenate) {
+		int totalSize = 0;
+		for (char[] next: toCatenate) {
+			totalSize += next.length;
+		}
+
+		char[] result = new char[totalSize];
+		int writeIndex = 0;
+		for (char[] next: toCatenate) {
+			if (next == null) {
+				continue;
+			}
+			System.arraycopy(next, 0, result, writeIndex, next.length);
+			writeIndex += next.length;
+		}
+		return result;
+	}
+
+	public static final char[] replace(char[] array, char[] toBeReplaced, char[] replacementChars) {
+		int max = array.length;
+		int replacedLength = toBeReplaced.length;
+		int replacementLength = replacementChars.length;
+
+		int[] starts = new int[5];
+		int occurrenceCount = 0;
+
+		if (!equals(toBeReplaced, replacementChars)) {
+			next: for (int i = 0; i < max; i++) {
+				int j = 0;
+				while (j < replacedLength) {
+					if (i + j == max)
+						continue next;
+					if (array[i + j] != toBeReplaced[j++])
+						continue next;
+				}
+				if (occurrenceCount == starts.length) {
+					System.arraycopy(starts, 0, starts = new int[occurrenceCount * 2], 0,
+							occurrenceCount);
+				}
+				starts[occurrenceCount++] = i;
+			}
+		}
+		if (occurrenceCount == 0)
+			return array;
+		char[] result = new char[max + occurrenceCount * (replacementLength - replacedLength)];
+		int inStart = 0, outStart = 0;
+		for (int i = 0; i < occurrenceCount; i++) {
+			int offset = starts[i] - inStart;
+			System.arraycopy(array, inStart, result, outStart, offset);
+			inStart += offset;
+			outStart += offset;
+			System.arraycopy(
+				replacementChars,
+				0,
+				result,
+				outStart,
+				replacementLength);
+			inStart += replacedLength;
+			outStart += replacementLength;
+		}
+		System.arraycopy(array, inStart, result, outStart, max - inStart);
+		return result;
+	}
+
+	public static final char[][] subarray(char[][] array, int start, int end) {
+		if (end == -1)
+			end = array.length;
+		if (start > end)
+			return null;
+		if (start < 0)
+			return null;
+		if (end > array.length)
+			return null;
+
+		char[][] result = new char[end - start][];
+		System.arraycopy(array, start, result, 0, end - start);
+		return result;
+	}
+
+	public static final char[] subarray(char[] array, int start, int end) {
+		if (end == -1)
+			end = array.length;
+		if (start > end)
+			return null;
+		if (start < 0)
+			return null;
+		if (end > array.length)
+			return null;
+
+		char[] result = new char[end - start];
+		System.arraycopy(array, start, result, 0, end - start);
+		return result;
+	}
+
+	public static final int indexOf(char toBeFound, char[] array) {
+		for (int i = 0; i < array.length; i++) {
+			if (toBeFound == array[i])
+				return i;
+		}
+		return -1;
+	}
+
+    public static int indexOf(char toBeFound, char[] buffer, int start, int end) {
+        if (start < 0 || start > buffer.length || end > buffer.length)
+            return -1;
+
+        for (int i = start; i < end; i++) {
+			if (toBeFound == buffer[i])
+				return i;
+        }
+		return -1;
+    }
+
+	public static final int indexOf(char[] toBeFound, char[] array) {
+	    if (toBeFound.length > array.length)
+	        return -1;
+
+	    int j = 0;
+	    for (int i = 0; i < array.length; i++) {
+	        if (toBeFound[j] == array[i]) {
+	            if (++j == toBeFound.length)
+	                return i - j + 1;
+	        } else {
+	        	j = 0;
+	        }
+	    }
+	    return -1;
+	}
+
+	public static final int lastIndexOf(char[] toBeFound, char[] array) {
+		return lastIndexOf(toBeFound, array, 0);
+	}
+
+	/**
+	 * @since 5.11
+	 */
+	public static int lastIndexOf(char toBeFound, char[] array) {
+		return lastIndexOf(toBeFound, array, 0);
+	}
+
+	/**
+	 * @since 5.11
+	 */
+	public static int lastIndexOf(char toBeFound, char[] array, int fromIndex) {
+		for (int i = array.length; --i >= fromIndex;) {
+			if (array[i] == toBeFound) {
+				return i;
+			}
+		}
+		return -1;
+	}
+
+	/**
+	 * @since 5.11
+	 */
+	public static int lastIndexOf(char[] toBeFound, char[] array, int fromIndex) {
+		int i = array.length;
+		int j = toBeFound.length;
+		while (true) {
+			if (--j < 0)
+				return i;
+			if (--i < fromIndex)
+				return -1;
+			if (toBeFound[j] != array[i]) {
+				i += toBeFound.length - j - 1;
+				j = toBeFound.length;
+			}
+		}
+	}
+
+	static final public char[] trim(char[] chars) {
+		if (chars == null)
+			return null;
+
+		int length = chars.length;
+		int start = 0;
+		while (start < length && chars[start] == ' ') {
+			start++;
+		}
+		if (start == length)
+			return EMPTY_CHAR_ARRAY;
+
+		int end = length;
+		while (--end > start && chars[end] == ' ') {
+			// Nothing to do
+		}
+		end++;
+		if (start == 0 && end == length)
+			return chars;
+		return subarray(chars, start, end);
+	}
+
+	static final public char[] lastSegment(char[] array, char[] separator) {
+		int pos = lastIndexOf(separator, array);
+		if (pos < 0)
+			return array;
+		return subarray(array, pos + separator.length, array.length);
+	}
+
+    /**
+     * @param buff
+     * @param i
+     * @param charImage
+     */
+    public static void overWrite(char[] buff, int i, char[] charImage) {
+        if (buff.length < i + charImage.length)
+            return;
+        for (int j = 0; j < charImage.length; j++) {
+            buff[i + j] = charImage[j];
+        }
+    }
+
+    /**
+     * Finds an array of chars in an array of arrays of chars.
+     *
+     * @return offset where the array was found or {@code -1}
+     */
+    public static int indexOf(final char[] searchFor, final char[][] searchIn) {
+		for (int i = 0; i < searchIn.length; i++) {
+			if (equals(searchIn[i], searchFor)) {
+				return i;
+			}
+		}
+		return -1;
+    }
+
+    /**
+     * Converts a {@link StringBuilder} to a character array.
+     * @since 5.5
+     */
+	public static char[] extractChars(StringBuilder buf) {
+		final int len = buf.length();
+		if (len == 0)
+			return EMPTY_CHAR_ARRAY;
+		char[] result= new char[len];
+		buf.getChars(0, len, result, 0);
+		return result;
+	}
+
+	public static char[] subarray(char[] inputString, int index) {
+		if (inputString.length <= index) {
+			return EMPTY_CHAR_ARRAY;
+		}
+
+		char[] result = new char[inputString.length - index];
+		System.arraycopy(inputString, index, result, 0, result.length);
+		return result;
+	}
+
+	public static boolean startsWith(char[] fieldDescriptor, char c) {
+		return fieldDescriptor.length > 0 && fieldDescriptor[0] == c;
+	}
+
+	/**
+	 * If the given array is null, returns the empty array. Otherwise, returns the argument.
+	 */
+	public static char[] notNull(char[] contents) {
+		if (contents == null) {
+			return EMPTY_CHAR_ARRAY;
+		}
+		return contents;
+	}
+
+	public static boolean endsWith(char[] fieldDescriptor, char c) {
+		if (fieldDescriptor.length == 0) {
+			return false;
+		}
+		return fieldDescriptor[fieldDescriptor.length - 1] == c;
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/PathMap.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/PathMap.java
new file mode 100644
index 0000000..f328fe0
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/PathMap.java
@@ -0,0 +1,225 @@
+/*******************************************************************************
+ * 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.util;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Maps IPath keys onto values.
+ */
+public class PathMap<T> {
+	private static class Node<T> {
+		int depth;
+		boolean exists;
+		T value;
+		Map<String, Node<T>> children;
+
+		Node(int depth) {
+			this.depth = depth;
+		}
+
+		String getSegment(IPath key) {
+			return key.segment(this.depth);
+		}
+
+		Node<T> createNode(IPath key) {
+			if (this.depth == key.segmentCount()) {
+				this.exists = true;
+				return this;
+			}
+
+			String nextSegment = getSegment(key);
+			Node<T> next = createChild(nextSegment);
+			return next.createNode(key);
+		}
+
+		public Node<T> createChild(String nextSegment) {
+			if (this.children == null) {
+				this.children = new HashMap<>();
+			}
+			Node<T> next = this.children.get(nextSegment);
+			if (next == null) {
+				next = new Node<>(this.depth + 1);
+				this.children.put(nextSegment, next);
+			}
+			return next;
+		}
+
+		public Node<T> getMostSpecificNode(IPath key) {
+			if (this.depth == key.segmentCount()) {
+				return this;
+			}
+			String nextSegment = getSegment(key);
+
+			Node<T> child = getChild(nextSegment);
+			if (child == null) {
+				return this;
+			}
+			Node<T> result = child.getMostSpecificNode(key);
+			if (result.exists) {
+				return result;
+			} else {
+				return this;
+			}
+		}
+
+		Node<T> getChild(String nextSegment) {
+			if (this.children == null) {
+				return null;
+			}
+			return this.children.get(nextSegment);
+		}
+
+	    public void addAllKeys(Set<IPath> result, IPath parent) {
+	    	if (this.exists) {
+	    		result.add(parent);
+	    	}
+
+	    	if (this.children == null) {
+	    		return;
+	    	}
+	
+	    	for (Entry<String, Node<T>> next : this.children.entrySet()) {
+	    		String key = next.getKey();
+	    		IPath nextPath = buildChildPath(parent, key);
+	    		next.getValue().addAllKeys(result, nextPath);
+	    	}
+	    }
+
+	    IPath buildChildPath(IPath parent, String key) {
+	      IPath nextPath = parent.append(key);
+	      return nextPath;
+	    }
+		
+	    public void toString(StringBuilder builder, IPath parentPath) {
+		    if (this.exists) {
+		    	builder.append("["); //$NON-NLS-1$
+		    	builder.append(parentPath);
+		    	builder.append("] = "); //$NON-NLS-1$
+		    	builder.append(this.value);
+		    	builder.append("\n"); //$NON-NLS-1$
+		    }
+		    if (this.children != null) { 
+		    	for (Entry<String, Node<T>> next : this.children.entrySet()) {
+		    		String key = next.getKey();
+		    		IPath nextPath = buildChildPath(parentPath, key);
+		    		next.getValue().toString(builder, nextPath);
+		    	}
+		    }
+		}
+	}
+
+	private static class DeviceNode<T> extends Node<T> {
+		Node<T> noDevice = new Node<>(0);
+
+		DeviceNode() {
+			super(-1);
+		}
+
+		@Override
+		String getSegment(IPath key) {
+			return key.getDevice();
+		}
+
+		@Override
+		public Node<T> createChild(String nextSegment) {
+			if (nextSegment == null) {
+				return this.noDevice;
+			}
+			return super.createChild(nextSegment);
+		}
+
+		Node<T> getChild(String nextSegment) {
+			if (nextSegment == null) {
+				return this.noDevice;
+			}
+			return super.getChild(nextSegment);
+		}
+
+		@Override
+		IPath buildChildPath(IPath parent, String key) {
+    		IPath nextPath = Path.EMPTY.append(parent);
+    		nextPath.setDevice(key);
+    		return nextPath;
+		}
+		
+		@Override
+		public void toString(StringBuilder builder, IPath parentPath) {
+			this.noDevice.toString(builder, parentPath);
+			super.toString(builder,parentPath);
+		}
+	}
+
+	private Node<T> root = new DeviceNode<T>();
+
+	/**
+	 * Inserts the given key into the map.
+	 */
+	public T put(IPath key, T value) {
+		Node<T> node = this.root.createNode(key);
+		T result = node.value;
+		node.value = value;
+		return result;
+	}
+
+	/**
+	 * Returns the value associated with the given key
+	 */
+	public T get(IPath key) {
+		Node<T> node = this.root.getMostSpecificNode(key);
+		if (!node.exists || node.depth < key.segmentCount()) {
+			return null;
+		}
+		return node.value;
+	}
+
+	/**
+	 * Returns the value associated with the longest prefix of the given key
+	 * that can be found in the map.
+	 */
+	public T getMostSpecific(IPath key) {
+		Node<T> node = this.root.getMostSpecificNode(key);
+		if (!node.exists) {
+			return null;
+		}
+		return node.value;
+	}
+
+	/**
+	 * Returns true iff any key in this map is a prefix of the given path.
+	 */
+	public boolean containsPrefixOf(IPath path) {
+		Node<T> node = this.root.getMostSpecificNode(path);
+		return node.exists;
+	}
+
+	public Set<IPath> keySet() {
+		Set<IPath> result = new HashSet<>();
+
+		this.root.addAllKeys(result, Path.EMPTY);
+		return result;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder builder = new StringBuilder();
+		
+		this.root.toString(builder, Path.EMPTY);
+		return builder.toString();
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/UnindexedSearchScope.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/UnindexedSearchScope.java
new file mode 100644
index 0000000..15ab020
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/UnindexedSearchScope.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * 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.search;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaElementDelta;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.search.IJavaSearchScope;
+
+public class UnindexedSearchScope extends AbstractSearchScope {
+	private IJavaSearchScope searchScope;
+	
+	private UnindexedSearchScope(IJavaSearchScope scope) {
+		this.searchScope = scope;
+	}
+
+	public static IJavaSearchScope filterEntriesCoveredByTheNewIndex(IJavaSearchScope scope) {
+		return new UnindexedSearchScope(scope);
+	}
+	
+	@Override
+	public boolean encloses(String resourcePathString) {
+		int separatorIndex = resourcePathString.indexOf(JAR_FILE_ENTRY_SEPARATOR);
+		if (separatorIndex != -1) {
+			// Files within jar files would have been indexed
+			return false;
+		}
+
+		// Jar files themselves would have been indexed
+		if (isJarFile(resourcePathString)) {
+			return false;
+		}
+
+		// Consult with the search scope
+		return this.searchScope.encloses(resourcePathString);
+	}
+
+	private boolean isJarFile(String possibleJarFile) {
+		if (possibleJarFile == null) {
+			return false;
+		}
+		return (possibleJarFile.endsWith(".jar") || possibleJarFile.endsWith(".JAR")); //$NON-NLS-1$//$NON-NLS-2$
+	}
+
+	@Override
+	public boolean encloses(IJavaElement element) {
+		try {
+			IResource underlyingResource = element.getUnderlyingResource();
+
+			if (underlyingResource != null && isJarFile(underlyingResource.getName())) {
+				return false;
+			}
+		} catch (JavaModelException e) {
+			JavaCore.getPlugin().getLog().log(e.getStatus());
+		}
+		return this.searchScope.encloses(element);
+	}
+
+	@Override
+	public IPath[] enclosingProjectsAndJars() {
+		IPath[] unfiltered = this.searchScope.enclosingProjectsAndJars();
+		
+		List<IPath> result = new ArrayList<>();
+		
+		for (IPath next : unfiltered) {
+			if (isJarFile(next.lastSegment())) {
+				continue;
+			}
+			result.add(next);
+		}
+		return result.toArray(new IPath[result.size()]);
+	}
+
+	@Override
+	public void processDelta(IJavaElementDelta delta, int eventType) {
+		if (this.searchScope instanceof AbstractSearchScope) {
+			AbstractSearchScope inner = (AbstractSearchScope) this.searchScope;
+			
+			inner.processDelta(delta, eventType);
+		}
+	}
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/IndexManager.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/IndexManager.java
index 1f53509..7ce53fa 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/IndexManager.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/IndexManager.java
@@ -10,27 +10,48 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.core.search.indexing;
 
-import java.io.*;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
 import java.net.URL;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.Map;
 import java.util.zip.CRC32;
 
-import org.eclipse.core.resources.*;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.OperationCanceledException;
 import org.eclipse.core.runtime.Path;
-import org.eclipse.jdt.core.*;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
 import org.eclipse.jdt.core.compiler.CharOperation;
-import org.eclipse.jdt.core.search.*;
+import org.eclipse.jdt.core.search.IJavaSearchScope;
+import org.eclipse.jdt.core.search.SearchDocument;
+import org.eclipse.jdt.core.search.SearchEngine;
+import org.eclipse.jdt.core.search.SearchParticipant;
 import org.eclipse.jdt.internal.compiler.ISourceElementRequestor;
 import org.eclipse.jdt.internal.compiler.SourceElementParser;
 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
 import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
 import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable;
 import org.eclipse.jdt.internal.compiler.util.SimpleSet;
-import org.eclipse.jdt.internal.core.*;
-import org.eclipse.jdt.internal.core.index.*;
+import org.eclipse.jdt.internal.core.ClasspathEntry;
+import org.eclipse.jdt.internal.core.JavaModel;
+import org.eclipse.jdt.internal.core.JavaModelManager;
+import org.eclipse.jdt.internal.core.JavaProject;
+import org.eclipse.jdt.internal.core.index.DiskIndex;
+import org.eclipse.jdt.internal.core.index.FileIndexLocation;
+import org.eclipse.jdt.internal.core.index.Index;
+import org.eclipse.jdt.internal.core.index.IndexLocation;
+import org.eclipse.jdt.internal.core.nd.indexer.Indexer;
 import org.eclipse.jdt.internal.core.search.BasicSearchEngine;
 import org.eclipse.jdt.internal.core.search.PatternSearchJob;
 import org.eclipse.jdt.internal.core.search.processing.IJob;
@@ -77,6 +98,8 @@
 
 
 public synchronized void aboutToUpdateIndex(IPath containerPath, Integer newIndexState) {
+	// TODO(sxenos): Find a more appropriate and more specific place to trigger re-indexing
+	Indexer.getInstance().rescanAll();
 	// newIndexState is either UPDATING_STATE or REBUILDING_STATE
 	// must tag the index as inconsistent, in case we exit before the update job is started
 	IndexLocation indexLocation = computeIndexLocation(containerPath);
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java
index 58e3f0e..451231e 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.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
@@ -10,10 +10,13 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.core.search.indexing;
 
+import java.util.Collections;
+
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.Path;
+import org.eclipse.jdt.core.IJavaProject;
 import org.eclipse.jdt.core.IType;
 import org.eclipse.jdt.core.JavaCore;
 import org.eclipse.jdt.core.compiler.CharOperation;
@@ -49,7 +52,7 @@
 import org.eclipse.jdt.internal.core.JavaProject;
 import org.eclipse.jdt.internal.core.SourceTypeElementInfo;
 import org.eclipse.jdt.internal.core.jdom.CompilationUnit;
-import org.eclipse.jdt.internal.core.search.matching.JavaSearchNameEnvironment;
+import org.eclipse.jdt.internal.core.search.matching.IndexBasedJavaSearchEnvironment;
 import org.eclipse.jdt.internal.core.search.matching.MethodPattern;
 import org.eclipse.jdt.internal.core.search.processing.JobManager;
 import org.eclipse.objectteams.otdt.internal.core.compiler.control.Dependencies;
@@ -165,7 +168,7 @@
 			this.cud = this.basicParser.parse(this.compilationUnit, new CompilationResult(this.compilationUnit, 0, 0, this.options.maxProblemsPerUnit));
 
 			// Use a non model name environment to avoid locks, monitors and such.
-			INameEnvironment nameEnvironment = new JavaSearchNameEnvironment(javaProject, JavaModelManager.getJavaModelManager().getWorkingCopies(DefaultWorkingCopyOwner.PRIMARY, true/*add primary WCs*/));
+			INameEnvironment nameEnvironment = IndexBasedJavaSearchEnvironment.create(Collections.singletonList((IJavaProject)javaProject), JavaModelManager.getJavaModelManager().getWorkingCopies(DefaultWorkingCopyOwner.PRIMARY, true/*add primary WCs*/));
 			this.lookupEnvironment = new LookupEnvironment(this, this.options, problemReporter, nameEnvironment);
 			reduceParseTree(this.cud);
 //{ObjectTeams: need Dependencies configured:
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/ClassFileMatchLocator.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/ClassFileMatchLocator.java
index 6e8e133..48e2819 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/ClassFileMatchLocator.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/ClassFileMatchLocator.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2012 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,10 +18,9 @@
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.core.search.*;
 import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
-import org.eclipse.jdt.internal.compiler.classfmt.FieldInfo;
-import org.eclipse.jdt.internal.compiler.classfmt.MethodInfo;
 import org.eclipse.jdt.internal.compiler.env.*;
 import org.eclipse.jdt.internal.compiler.lookup.*;
+import org.eclipse.jdt.internal.core.BinaryType;
 import org.eclipse.jdt.internal.core.*;
 import org.eclipse.jdt.internal.core.search.indexing.IIndexConstants;
 
@@ -352,10 +351,10 @@
 	}
 
 	// Look for references in methods annotations
-	MethodInfo[] methods = (MethodInfo[]) binaryType.getMethods();
+	IBinaryMethod[] methods = binaryType.getMethods();
 	if (methods != null) {
 		for (int i = 0, max = methods.length; i < max; i++) {
-			MethodInfo method = methods[i];
+			IBinaryMethod method = methods[i];
 			if (checkAnnotations(typeReferencePattern, method.getAnnotations(), method.getTagBits())) {
 					binaryTypeBinding = locator.cacheBinaryType(classFileBinaryType, binaryType);
 					IMethod methodHandle = classFileBinaryType.getMethod(
@@ -370,10 +369,10 @@
 	}
 	
 	// Look for references in fields annotations
-	FieldInfo[] fields = (FieldInfo[]) binaryType.getFields();
+	IBinaryField[] fields = binaryType.getFields();
 	if (fields != null) {
 		for (int i = 0, max = fields.length; i < max; i++) {
-			FieldInfo field = fields[i];
+			IBinaryField field = fields[i];
 			if (checkAnnotations(typeReferencePattern, field.getAnnotations(), field.getTagBits())) {
 					IField fieldHandle = classFileBinaryType.getField(new String(field.getName()));
 					TypeReferenceMatch match = new TypeReferenceMatch(fieldHandle, SearchMatch.A_ACCURATE, -1, 0, false, locator.getParticipant(), locator.currentPossibleMatch.resource);
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/IndexBasedJavaSearchEnvironment.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/IndexBasedJavaSearchEnvironment.java
new file mode 100644
index 0000000..9077075
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/IndexBasedJavaSearchEnvironment.java
@@ -0,0 +1,333 @@
+/*******************************************************************************
+ * 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.search.matching;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
+import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
+import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
+import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
+import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
+import org.eclipse.jdt.internal.core.ClasspathEntry;
+import org.eclipse.jdt.internal.core.JavaModel;
+import org.eclipse.jdt.internal.core.JavaProject;
+import org.eclipse.jdt.internal.core.PackageFragmentRoot;
+import org.eclipse.jdt.internal.core.builder.ClasspathLocation;
+import org.eclipse.jdt.internal.core.nd.IReader;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchIndex;
+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.NdType;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeId;
+import org.eclipse.jdt.internal.core.nd.java.TypeRef;
+import org.eclipse.jdt.internal.core.nd.java.model.IndexBinaryType;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+import org.eclipse.jdt.internal.core.nd.util.PathMap;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class IndexBasedJavaSearchEnvironment implements INameEnvironment, SuffixConstants {
+
+	private Map<String, ICompilationUnit> workingCopies;
+	private PathMap<Integer> mapPathsToRoots = new PathMap<>();
+	private IPackageFragmentRoot[] roots;
+	private int sourceEntryPosition;
+	private List<ClasspathLocation> unindexedEntries = new ArrayList<>();
+
+	public IndexBasedJavaSearchEnvironment(List<IJavaProject> javaProject, org.eclipse.jdt.core.ICompilationUnit[] copies) {
+		this.workingCopies = JavaSearchNameEnvironment.getWorkingCopyMap(copies);
+
+		try {
+			List<IPackageFragmentRoot> localRoots = new ArrayList<>();
+
+			for (IJavaProject next : javaProject) {
+				for (IPackageFragmentRoot nextRoot : next.getAllPackageFragmentRoots()) {
+					IPath path = nextRoot.getPath();
+					if (!nextRoot.isArchive()) {
+						Object target = JavaModel.getTarget(path, true);
+						if (target != null) {
+							ClasspathLocation cp;
+							if (nextRoot.getKind() == IPackageFragmentRoot.K_SOURCE) {
+								PackageFragmentRoot root = (PackageFragmentRoot)nextRoot;
+								cp = new ClasspathSourceDirectory((IContainer)target, root.fullExclusionPatternChars(), root.fullInclusionPatternChars());
+								this.unindexedEntries.add(cp);
+							}
+						}
+					}
+
+					localRoots.add(nextRoot);
+				}
+			}
+
+			this.roots = localRoots.toArray(new IPackageFragmentRoot[0]);
+		} catch (JavaModelException e) {
+			this.roots = new IPackageFragmentRoot[0];
+			// project doesn't exist
+		}
+
+		// Build the map of paths onto root indices
+		int length = this.roots.length;
+		for (int i = 0; i < length; i++) {
+			IPath nextPath = JavaIndex.getLocationForElement(this.roots[i]);
+			this.mapPathsToRoots.put(nextPath, i);
+		}
+
+		// Locate the position of the first source entry
+		this.sourceEntryPosition = Integer.MAX_VALUE;
+		for (int i = 0; i < length; i++) {
+			IPackageFragmentRoot nextRoot = this.roots[i];
+			try {
+				if (nextRoot.getKind() == IPackageFragmentRoot.K_SOURCE) {
+					this.sourceEntryPosition = i;
+					break;
+				}
+			} catch (JavaModelException e) {
+				// project doesn't exist
+			}
+		}
+	}
+
+	public static boolean isEnabled() {
+		return Platform.getPreferencesService().getBoolean(JavaCore.PLUGIN_ID, "useIndexBasedSearchEnvironment", false, //$NON-NLS-1$
+				null);
+	}
+
+	@Override
+	public NameEnvironmentAnswer findType(char[][] compoundTypeName) {
+		char[] binaryName = CharOperation.concatWith(compoundTypeName, '/');
+
+		int bestEntryPosition = Integer.MAX_VALUE;
+		NameEnvironmentAnswer result = findClassInUnindexedLocations(new String(binaryName), compoundTypeName[compoundTypeName.length - 1]);
+		if (result != null) {
+			bestEntryPosition = this.sourceEntryPosition;
+		}
+
+		char[] fieldDescriptor = JavaNames.binaryNameToFieldDescriptor(binaryName);
+		JavaIndex index = JavaIndex.getIndex();
+		Nd nd = index.getNd();
+		try (IReader lock = nd.acquireReadLock()) {
+			NdTypeId typeId = index.findType(fieldDescriptor);
+
+			if (typeId != null) {
+				List<NdType> types = typeId.getTypes();
+				for (NdType next : types) {
+					NdResourceFile resource = next.getFile();
+
+					IPath path = resource.getPath();
+					Integer nextRoot = this.mapPathsToRoots.getMostSpecific(path);
+					if (nextRoot != null) {
+						IPackageFragmentRoot root = this.roots[nextRoot];
+
+						ClasspathEntry classpathEntry = (ClasspathEntry)root.getRawClasspathEntry();
+						AccessRuleSet ruleSet = classpathEntry.getAccessRuleSet();
+						AccessRestriction accessRestriction = ruleSet == null? null : ruleSet.getViolatedRestriction(binaryName);
+						TypeRef typeRef = TypeRef.create(next);
+						String fileName = new String(binaryName) + ".class"; //$NON-NLS-1$
+						IBinaryType binaryType = new IndexBinaryType(typeRef, fileName.toCharArray()); 
+						NameEnvironmentAnswer nextAnswer = new NameEnvironmentAnswer(binaryType, accessRestriction);
+
+						boolean useNewAnswer = isBetter(result, bestEntryPosition, nextAnswer, nextRoot);
+
+						if (useNewAnswer) {
+							bestEntryPosition = nextRoot;
+							result = nextAnswer;
+						}
+					}
+				}
+			}
+		} catch (JavaModelException e) {
+			// project doesn't exist
+		}
+
+		return result;
+	}
+
+	/**
+	 * Search unindexed locations on the classpath for the given class
+	 */
+	private NameEnvironmentAnswer findClassInUnindexedLocations(String qualifiedTypeName, char[] typeName) {
+		String
+			binaryFileName = null, qBinaryFileName = null,
+			sourceFileName = null, qSourceFileName = null,
+			qPackageName = null;
+		NameEnvironmentAnswer suggestedAnswer = null;
+		Iterator <ClasspathLocation> iter = this.unindexedEntries.iterator();
+		while (iter.hasNext()) {
+			ClasspathLocation location = iter.next();
+			NameEnvironmentAnswer answer;
+			if (location instanceof ClasspathSourceDirectory) {
+				if (sourceFileName == null) {
+					qSourceFileName = qualifiedTypeName; // doesn't include the file extension
+					sourceFileName = qSourceFileName;
+					qPackageName =  ""; //$NON-NLS-1$
+					if (qualifiedTypeName.length() > typeName.length) {
+						int typeNameStart = qSourceFileName.length() - typeName.length;
+						qPackageName =  qSourceFileName.substring(0, typeNameStart - 1);
+						sourceFileName = qSourceFileName.substring(typeNameStart);
+					}
+				}
+				org.eclipse.jdt.internal.compiler.env.ICompilationUnit workingCopy = (org.eclipse.jdt.internal.compiler.env.ICompilationUnit) this.workingCopies.get(qualifiedTypeName);
+				if (workingCopy != null) {
+					answer = new NameEnvironmentAnswer(workingCopy, null /*no access restriction*/);
+				} else {
+					answer = location.findClass(
+						sourceFileName, // doesn't include the file extension
+						qPackageName,
+						qSourceFileName);  // doesn't include the file extension
+				}
+			} else {
+				if (binaryFileName == null) {
+					qBinaryFileName = qualifiedTypeName + SUFFIX_STRING_class;
+					binaryFileName = qBinaryFileName;
+					qPackageName =  ""; //$NON-NLS-1$
+					if (qualifiedTypeName.length() > typeName.length) {
+						int typeNameStart = qBinaryFileName.length() - typeName.length - 6; // size of ".class"
+						qPackageName =  qBinaryFileName.substring(0, typeNameStart - 1);
+						binaryFileName = qBinaryFileName.substring(typeNameStart);
+					}
+				}
+				answer =
+					location.findClass(
+						binaryFileName,
+						qPackageName,
+						qBinaryFileName);
+			}
+			if (answer != null) {
+				if (!answer.ignoreIfBetter()) {
+					if (answer.isBetter(suggestedAnswer))
+						return answer;
+				} else if (answer.isBetter(suggestedAnswer))
+					// remember suggestion and keep looking
+					suggestedAnswer = answer;
+			}
+		}
+		if (suggestedAnswer != null)
+			// no better answer was found
+			return suggestedAnswer;
+		return null;
+	}
+
+	public boolean isBetter(NameEnvironmentAnswer currentBest, int currentBestClasspathPosition,
+			NameEnvironmentAnswer toTest, int toTestClasspathPosition) {
+		boolean useNewAnswer = false;
+
+		if (currentBest == null) {
+			useNewAnswer = true;
+		} else {
+			if (toTest.isBetter(currentBest)) {
+				useNewAnswer = true;
+			} else {
+				// If neither one is better, use the one with the earlier classpath position
+				if (!currentBest.isBetter(toTest)) {
+					useNewAnswer = (toTestClasspathPosition < currentBestClasspathPosition);
+				}
+			}
+		}
+		return useNewAnswer;
+	}
+
+	@Override
+	public NameEnvironmentAnswer findType(char[] typeName, char[][] packageName) {
+		char[][] newArray = new char[packageName.length + 1][];
+		for (int idx = 0; idx < packageName.length; idx++) {
+			newArray[idx] = packageName[idx];
+		}
+		newArray[packageName.length] = typeName;
+		return findType(newArray);
+	}
+
+	@Override
+	public boolean isPackage(char[][] parentPackageName, char[] packageName) {
+		char[] binaryPackageName = CharOperation.concatWith(parentPackageName, '/');
+		final char[] fieldDescriptorPrefix;
+		
+		if (parentPackageName == null || parentPackageName.length == 0) {
+			fieldDescriptorPrefix = CharArrayUtils.concat(JavaNames.FIELD_DESCRIPTOR_PREFIX, packageName, 
+					new char[] { '/' });
+		} else {
+			fieldDescriptorPrefix = CharArrayUtils.concat(JavaNames.FIELD_DESCRIPTOR_PREFIX, binaryPackageName,
+					new char[] { '/' }, packageName, new char[] { '/' });
+		}
+
+		// Search all the types that are a subpackage of the given package name. Return if we find any one of them on
+		// the classpath of this project.
+		JavaIndex index = JavaIndex.getIndex();
+		Nd nd = index.getNd();
+		try (IReader lock = nd.acquireReadLock()) {
+			return !index.visitFieldDescriptorsStartingWith(fieldDescriptorPrefix,
+					new FieldSearchIndex.Visitor<NdTypeId>() {
+						@Override
+						public boolean visit(NdTypeId typeId) {
+							//String fd = typeId.getFieldDescriptor().getString();
+							// If this is an exact match for the field descriptor prefix we're looking for then
+							// this class can't be part of the package we're searching for (and, most likely, the
+							// "package" we're searching for is actually a class name - not a package).
+							if (typeId.getFieldDescriptor().length() <= fieldDescriptorPrefix.length + 1) {
+								return true;
+							}
+							List<NdType> types = typeId.getTypes();
+							for (NdType next : types) {
+								if (next.isMember() || next.isLocal() || next.isAnonymous()) {
+									continue;
+								}
+								NdResourceFile resource = next.getFile();
+
+								IPath path = resource.getPath();
+
+								if (containsPrefixOf(path)) {
+									// Terminate the search -- we've found a class belonging to the package
+									// we're searching for.
+									return false;
+								}
+							}
+							return true;
+						}
+					});
+		}
+	}
+
+	boolean containsPrefixOf(IPath path) {
+		return this.mapPathsToRoots.containsPrefixOf(path);
+	}
+
+	@Override
+	public void cleanup() {
+		// No explicit cleanup required for this class
+	}
+	
+	public static INameEnvironment create(List<IJavaProject> javaProjects, org.eclipse.jdt.core.ICompilationUnit[] copies) {
+		if (JavaIndex.isEnabled() && isEnabled()) {
+			return new IndexBasedJavaSearchEnvironment(javaProjects, copies);
+		} else {
+			Iterator<IJavaProject> next = javaProjects.iterator();
+			JavaSearchNameEnvironment result = new JavaSearchNameEnvironment(next.next(), copies);
+
+			while (next.hasNext()) {
+				result.addProjectClassPath((JavaProject)next.next());
+			}
+			return result;
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/JavaSearchNameEnvironment.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/JavaSearchNameEnvironment.java
index 9a11572..0c7de15 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/JavaSearchNameEnvironment.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/JavaSearchNameEnvironment.java
@@ -15,11 +15,15 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
+import java.util.Map;
 
 import org.eclipse.core.resources.IContainer;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
-import org.eclipse.jdt.core.*;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageDeclaration;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaModelException;
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
 import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
@@ -39,19 +43,24 @@
  */
 @SuppressWarnings({"rawtypes", "unchecked"})
 public class JavaSearchNameEnvironment implements INameEnvironment, SuffixConstants {
-	
+
 	LinkedHashSet<ClasspathLocation> locationSet;
 	
 	/*
 	 * A map from the fully qualified slash-separated name of the main type (String) to the working copy
 	 */
-	HashMap workingCopies;
-	
+	Map<String, org.eclipse.jdt.core.ICompilationUnit> workingCopies;
+
 public JavaSearchNameEnvironment(IJavaProject javaProject, org.eclipse.jdt.core.ICompilationUnit[] copies) {
 	this.locationSet = computeClasspathLocations((JavaProject) javaProject);
+	this.workingCopies = getWorkingCopyMap(copies);
+}
+
+public static Map<String, org.eclipse.jdt.core.ICompilationUnit> getWorkingCopyMap(
+		org.eclipse.jdt.core.ICompilationUnit[] copies) {
+	int length = copies == null ? 0 : copies.length;
+	HashMap<String, org.eclipse.jdt.core.ICompilationUnit> result = new HashMap<>(length);
 	try {
-		int length = copies == null ? 0 : copies.length;
-		this.workingCopies = new HashMap(length);
 		if (copies != null) {
 			for (int i = 0; i < length; i++) {
 				org.eclipse.jdt.core.ICompilationUnit workingCopy = copies[i];
@@ -60,12 +69,13 @@
 				String cuName = workingCopy.getElementName();
 				String mainTypeName = Util.getNameWithoutJavaLikeExtension(cuName);
 				String qualifiedMainTypeName = pkg.length() == 0 ? mainTypeName : pkg.replace('.', '/') + '/' + mainTypeName;
-				this.workingCopies.put(qualifiedMainTypeName, workingCopy);
+				result.put(qualifiedMainTypeName, workingCopy);
 			}
 		}
 	} catch (JavaModelException e) {
 		// working copy doesn't exist: cannot happen
 	}
+	return result;
 }
 
 public void cleanup() {
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocator.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocator.java
index 48094d3..1630f54 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocator.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocator.java
@@ -294,11 +294,11 @@
 	return result;
 }
 
-public static ClassFileReader classFileReader(IType type) {
+public static IBinaryType classFileReader(IType type) {
 	IClassFile classFile = type.getClassFile();
 	JavaModelManager manager = JavaModelManager.getJavaModelManager();
 	if (classFile.isOpen())
-		return (ClassFileReader) manager.getInfo(type);
+		return (IBinaryType)manager.getInfo(type);
 
 	PackageFragment pkg = (PackageFragment) type.getPackageFragment();
 	IPackageFragmentRoot root = (IPackageFragmentRoot) pkg.getParent();
@@ -549,7 +549,7 @@
 	if (type.isBinary()) {
 		// don't cache the methods of the binary type
 		// fall thru if its a constructor with a synthetic argument... find it the slower way
-		ClassFileReader reader = classFileReader(type);
+		IBinaryType reader = classFileReader(type);
 		if (reader != null) {
 			// build arguments names
 			boolean firstIsSynthetic = false;
@@ -619,7 +619,7 @@
  * Create binary method handle
  */
 IMethod createBinaryMethodHandle(IType type, char[] methodSelector, char[][] argumentTypeNames) {
-	ClassFileReader reader = MatchLocator.classFileReader(type);
+	IBinaryType reader = MatchLocator.classFileReader(type);
 	if (reader != null) {
 		IBinaryMethod[] methods = reader.getMethods();
 		if (methods != null) {
@@ -1271,9 +1271,15 @@
 
 	SearchableEnvironment searchableEnvironment = project.newSearchableNameEnvironment(this.workingCopies);
 
-	this.nameEnvironment = new JavaSearchNameEnvironment(project, this.workingCopies);
-	if (this.pattern.focus != null)  
-		((JavaSearchNameEnvironment) this.nameEnvironment).addProjectClassPath((JavaProject) this.pattern.focus.getJavaProject());
+	List<IJavaProject> projects = new ArrayList<>();
+	projects.add(project);
+	if (this.pattern.focus != null) {
+		IJavaProject focusProject = this.pattern.focus.getJavaProject();
+		if (focusProject != project) {
+			projects.add(focusProject);
+		}
+	}
+	this.nameEnvironment = IndexBasedJavaSearchEnvironment.create(projects, this.workingCopies);
 
 	// create lookup environment
 	Map map = project.getOptions(true);
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/PossibleMatch.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/PossibleMatch.java
index 3cd748c..88f084a 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/PossibleMatch.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/PossibleMatch.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2012 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
@@ -15,7 +15,7 @@
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.core.search.SearchDocument;
 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
-import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
 import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
 import org.eclipse.jdt.internal.core.*;
 import org.eclipse.jdt.internal.core.util.Util;
@@ -135,7 +135,7 @@
 	this.sourceFileName = NO_SOURCE_FILE_NAME; 
 	if (this.openable.getSourceMapper() != null) {
 		BinaryType type = (BinaryType) ((ClassFile) this.openable).getType();
-		ClassFileReader reader = MatchLocator.classFileReader(type);
+		IBinaryType reader = MatchLocator.classFileReader(type);
 		if (reader != null) {
 			String fileName = type.sourceFileName(reader);
 			this.sourceFileName = fileName == null ? NO_SOURCE_FILE_NAME : fileName;
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/processing/JobManager.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/processing/JobManager.java
index 6351839..71bc061 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/processing/JobManager.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/processing/JobManager.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2011 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
@@ -201,7 +201,7 @@
 
 					case IJob.WaitUntilReady :
 						int totalWork = 1000;
-						SubMonitor subProgress = subMonitor.setWorkRemaining(10).split(8).setWorkRemaining(totalWork);
+						SubMonitor waitMonitor = subMonitor.setWorkRemaining(10).split(8).setWorkRemaining(totalWork);
 						// use local variable to avoid potential NPE (see bug 20435 NPE when searching java method
 						// and bug 42760 NullPointerException in JobManager when searching)
 						Thread t = this.processingThread;
@@ -218,30 +218,28 @@
 							float lastWorked = 0;
 							float totalWorked = 0;
 							while ((awaitingJobsCount = awaitingJobsCount()) > 0) {
-								if (subProgress.isCanceled() || this.processingThread == null)
+								if (waitMonitor.isCanceled() || this.processingThread == null)
 									throw new OperationCanceledException();
 								IJob currentJob = currentJob();
 								// currentJob can be null when jobs have been added to the queue but job manager is not enabled
 								if (currentJob != null && currentJob != previousJob) {
 									if (VERBOSE)
 										Util.verbose("-> NOT READY - waiting until ready - " + searchJob);//$NON-NLS-1$
-									if (subProgress != null) {
-										String indexing = Messages.bind(Messages.jobmanager_filesToIndex, currentJob.getJobFamily(), Integer.toString(awaitingJobsCount));
-										subProgress.subTask(indexing);
-										// ratio of the amount of work relative to the total work
-										float ratio = awaitingJobsCount < totalWork ? 1 : ((float) totalWork) / awaitingJobsCount;
-										if (lastJobsCount > awaitingJobsCount) {
-											totalWorked += (lastJobsCount - awaitingJobsCount) * ratio;
-										} else {
-											// more jobs were added, just increment by the ratio
-											totalWorked += ratio;
-										}
-										if (totalWorked - lastWorked >= 1) {
-											subProgress.worked((int) (totalWorked - lastWorked));
-											lastWorked = totalWorked;
-										}
-										lastJobsCount = awaitingJobsCount;
+									String indexing = Messages.bind(Messages.jobmanager_filesToIndex, currentJob.getJobFamily(), Integer.toString(awaitingJobsCount));
+									waitMonitor.subTask(indexing);
+									// ratio of the amount of work relative to the total work
+									float ratio = awaitingJobsCount < totalWork ? 1 : ((float) totalWork) / awaitingJobsCount;
+									if (lastJobsCount > awaitingJobsCount) {
+										totalWorked += (lastJobsCount - awaitingJobsCount) * ratio;
+									} else {
+										// more jobs were added, just increment by the ratio
+										totalWorked += ratio;
 									}
+									if (totalWorked - lastWorked >= 1) {
+										waitMonitor.worked((int) (totalWorked - lastWorked));
+										lastWorked = totalWorked;
+									}
+									lastJobsCount = awaitingJobsCount;
 									previousJob = currentJob;
 								}
 								try {