Merge remote-tracking branch 'origin/master' into BETA_JAVA9

Conflicts:
	org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties
	org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRoot.java
diff --git a/org.eclipse.jdt.apt.pluggable.tests/plugin.xml b/org.eclipse.jdt.apt.pluggable.tests/plugin.xml
index cab9cf2..3fb6eb8 100644
--- a/org.eclipse.jdt.apt.pluggable.tests/plugin.xml
+++ b/org.eclipse.jdt.apt.pluggable.tests/plugin.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <?eclipse version="3.0"?>
 <!--
-    Copyright (c) 2005, 2014 IBM Corporation and others.
+    Copyright (c) 2005, 2017 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
@@ -49,6 +49,9 @@
          <java6processor
                class="org.eclipse.jdt.apt.pluggable.tests.processors.genclass6.Bug419769Proc">
          </java6processor>
+         <java6processor
+               class="org.eclipse.jdt.apt.pluggable.tests.processors.buildertester.Bug510118Processor">
+         </java6processor>
       </java6processors>
    </extension>
 </plugin>
diff --git a/org.eclipse.jdt.apt.pluggable.tests/resources/targets/bug510118/A.java b/org.eclipse.jdt.apt.pluggable.tests/resources/targets/bug510118/A.java
new file mode 100644
index 0000000..7f24ec7
--- /dev/null
+++ b/org.eclipse.jdt.apt.pluggable.tests/resources/targets/bug510118/A.java
@@ -0,0 +1,4 @@
+package targets.bug510118;
+
+@Annotation510118
+public class A implements I<TypeParam>{}
diff --git a/org.eclipse.jdt.apt.pluggable.tests/resources/targets/bug510118/Annotation510118.java b/org.eclipse.jdt.apt.pluggable.tests/resources/targets/bug510118/Annotation510118.java
new file mode 100644
index 0000000..0aa48c6
--- /dev/null
+++ b/org.eclipse.jdt.apt.pluggable.tests/resources/targets/bug510118/Annotation510118.java
@@ -0,0 +1,6 @@
+package targets.bug510118;
+import java.lang.annotation.Inherited;
+
+@Inherited
+public @interface Annotation510118 {
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.apt.pluggable.tests/resources/targets/bug510118/I.java b/org.eclipse.jdt.apt.pluggable.tests/resources/targets/bug510118/I.java
new file mode 100644
index 0000000..58ce6bd
--- /dev/null
+++ b/org.eclipse.jdt.apt.pluggable.tests/resources/targets/bug510118/I.java
@@ -0,0 +1,3 @@
+package targets.bug510118;
+
+public interface I <T extends Object> {}
diff --git a/org.eclipse.jdt.apt.pluggable.tests/resources/targets/bug510118/TypeParam.java b/org.eclipse.jdt.apt.pluggable.tests/resources/targets/bug510118/TypeParam.java
new file mode 100644
index 0000000..6f2134c
--- /dev/null
+++ b/org.eclipse.jdt.apt.pluggable.tests/resources/targets/bug510118/TypeParam.java
@@ -0,0 +1,4 @@
+package targets.bug510118;
+
+public class TypeParam {}
+
diff --git a/org.eclipse.jdt.apt.pluggable.tests/src/org/eclipse/jdt/apt/pluggable/tests/BuilderTests.java b/org.eclipse.jdt.apt.pluggable.tests/src/org/eclipse/jdt/apt/pluggable/tests/BuilderTests.java
index b645759..848e55f 100644
--- a/org.eclipse.jdt.apt.pluggable.tests/src/org/eclipse/jdt/apt/pluggable/tests/BuilderTests.java
+++ b/org.eclipse.jdt.apt.pluggable.tests/src/org/eclipse/jdt/apt/pluggable/tests/BuilderTests.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2008 - 2015 Walter Harley and others
+ * Copyright (c) 2008 - 2017 Walter Harley and others
  * All rights reserved. This program and 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,6 +19,7 @@
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.jdt.apt.core.util.AptConfig;
 import org.eclipse.jdt.apt.pluggable.tests.processors.buildertester.Bug468893Processor;
+import org.eclipse.jdt.apt.pluggable.tests.processors.buildertester.Bug510118Processor;
 import org.eclipse.jdt.apt.pluggable.tests.processors.buildertester.BugsProc;
 import org.eclipse.jdt.apt.pluggable.tests.processors.buildertester.InheritedAnnoProc;
 import org.eclipse.jdt.apt.pluggable.tests.processors.buildertester.TestFinalRoundProc;
@@ -350,4 +351,16 @@
 			
 		}
 	}
+	public void testBugbug510118() throws Throwable {
+		ProcessorTestStatus.reset();
+		IJavaProject jproj = createJavaProject(_projectName);
+		disableJava5Factories(jproj);
+		IProject proj = jproj.getProject();
+		IdeTestUtils.copyResources(proj, "targets/bug510118", "src/targets/bug510118");
+
+		AptConfig.setEnabled(jproj, true);
+		fullBuild();
+		expectingNoProblems();
+		assertTrue("Incorrect status received from annotation processor", Bug510118Processor.status());
+	}
 }
diff --git a/org.eclipse.jdt.apt.pluggable.tests/src/org/eclipse/jdt/apt/pluggable/tests/processors/buildertester/Bug510118Processor.java b/org.eclipse.jdt.apt.pluggable.tests/src/org/eclipse/jdt/apt/pluggable/tests/processors/buildertester/Bug510118Processor.java
new file mode 100644
index 0000000..9255b63
--- /dev/null
+++ b/org.eclipse.jdt.apt.pluggable.tests/src/org/eclipse/jdt/apt/pluggable/tests/processors/buildertester/Bug510118Processor.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2017 IBM Corporation.
+ * All rights reserved. This program and 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.apt.pluggable.tests.processors.buildertester;
+
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleTypeVisitor6;
+import javax.tools.Diagnostic;
+
+@SupportedAnnotationTypes("targets.bug510118.Annotation510118")
+@SupportedSourceVersion(SourceVersion.RELEASE_6)
+public class Bug510118Processor extends AbstractProcessor {
+
+	private static final String INTERFACE_NAME = "targets.bug510118.I";
+	private static final String CLASS_NAME = "targets.bug510118.A";
+	private static final String TYPE_ARGUMENT_NAME =  "targets.bug510118.TypeParam";
+	private static boolean status = false;
+
+	private static final class DeclaredTypeVisitor extends CastingTypeVisitor<DeclaredType> {
+		private static final DeclaredTypeVisitor INSTANCE = new DeclaredTypeVisitor();
+
+		DeclaredTypeVisitor() {
+			super("declared type");
+		}
+
+		@Override
+		public DeclaredType visitDeclared(DeclaredType type, Void ignore) {
+			return type;
+		}
+	}
+
+	private abstract static class CastingTypeVisitor<T> extends SimpleTypeVisitor6<T, Void> {
+		private final String label;
+
+		CastingTypeVisitor(String label) {
+			this.label = label;
+		}
+
+		@Override
+		protected T defaultAction(TypeMirror e, Void v) {
+			throw new IllegalArgumentException(e + " does not represent a " + label);
+		}
+	}
+
+	public Bug510118Processor() {
+	}
+
+	@Override
+	public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+		if (!roundEnv.processingOver()) {
+			TypeElement testClass = null;
+			Messager messager = processingEnv.getMessager();
+			try {
+				Elements elements = processingEnv.getElementUtils();
+				testClass = elements.getTypeElement(CLASS_NAME);
+
+				TypeElement typeAdapterElement = elements.getTypeElement(INTERFACE_NAME);
+				TypeParameterElement param = typeAdapterElement.getTypeParameters().get(0);
+				DeclaredType declaredType = testClass.asType().accept(DeclaredTypeVisitor.INSTANCE, null);
+				TypeMirror tm = processingEnv.getTypeUtils().asMemberOf(declaredType, param);
+				if (tm != null) {
+					testClass = elements.getTypeElement(TYPE_ARGUMENT_NAME);
+					if (processingEnv.getTypeUtils().isSameType(tm, testClass.asType())) {
+						status = true;
+					}
+				}
+			} catch (Exception e) {
+				messager.printMessage(Diagnostic.Kind.ERROR, ("Exception: " + e), testClass);
+			}
+		}
+		return false;
+	}
+	public static boolean status() {
+		return status;
+	}
+
+}
diff --git a/org.eclipse.jdt.compiler.apt/src/org/eclipse/jdt/internal/compiler/apt/model/TypesImpl.java b/org.eclipse.jdt.compiler.apt/src/org/eclipse/jdt/internal/compiler/apt/model/TypesImpl.java
index b0ac625..13f27db 100644
--- a/org.eclipse.jdt.compiler.apt/src/org/eclipse/jdt/internal/compiler/apt/model/TypesImpl.java
+++ b/org.eclipse.jdt.compiler.apt/src/org/eclipse/jdt/internal/compiler/apt/model/TypesImpl.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007 - 2015 BEA Systems, Inc. and others
+ * Copyright (c) 2007 - 2017 BEA 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
@@ -106,6 +106,33 @@
 				return typeMirror;
 			}
 			break;
+		case TYPE_PARAMETER:
+			typeMirror = findMemberInHierarchy(referenceBinding, elementImpl._binding, new MemberInTypeFinder() {
+				@Override
+				public TypeMirror find(ReferenceBinding typeBinding, Binding memberBinding) {
+					if (typeBinding instanceof ParameterizedTypeBinding) {
+						TypeVariableBinding variableBinding = ((TypeVariableBinding) memberBinding);
+						ReferenceBinding binding = ((ParameterizedTypeBinding) typeBinding).genericType();
+						if (variableBinding.declaringElement == binding) { // check in advance avoid looking into type parameters unnecessarily.
+							TypeVariableBinding[] typeVariables = binding.typeVariables();
+							TypeBinding[] typeArguments = ((ParameterizedTypeBinding) typeBinding).typeArguments();
+							if (typeVariables.length == typeArguments.length) {
+								for(int i = 0; i < typeVariables.length; i++) {
+									if (typeVariables[i] == memberBinding) {
+										return TypesImpl.this._env.getFactory().newTypeMirror(typeArguments[i]); 
+									}
+								}
+							}
+						}
+					}
+					return null;
+				}
+			});
+
+			if (typeMirror != null) {
+				return typeMirror;
+			}
+			break;
 		case FIELD:
 		case ENUM_CONSTANT:
 			typeMirror = findMemberInHierarchy(referenceBinding, elementImpl._binding, new MemberInTypeFinder() {
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java
index f66196b..b357c85 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2016 IBM Corporation and others.
+ * Copyright (c) 2000, 2017 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
@@ -2111,7 +2111,7 @@
 			"	<classpaths>NORMALIZED SECTION</classpaths>\n" + 
 			"	<sources>\n" + 
 			"		<source output=\"---OUTPUT_DIR_PLACEHOLDER---\" path=\"---OUTPUT_DIR_PLACEHOLDER---" + File.separator + "X.java\">\n" + 
-			"			<problems errors=\"1\" problems=\"1\" warnings=\"0\">\n" + 
+			"			<problems errors=\"1\" infos=\"0\" problems=\"1\" warnings=\"0\">\n" + 
 			"				<problem categoryID=\"40\" charEnd=\"28\" charStart=\"25\" id=\"UndefinedType\" line=\"3\" problemID=\"16777218\" severity=\"ERROR\">\n" + 
 			"					<message value=\"Zork cannot be resolved to a type\"/>\n" + 
 			"					<source_context sourceEnd=\"3\" sourceStart=\"0\" value=\"Zork z;\"/>\n" + 
@@ -2124,7 +2124,7 @@
 			"		</source>\n" + 
 			"	</sources>\n" + 
 			"	<stats>\n" + 
-			"		<problem_summary errors=\"1\" problems=\"1\" tasks=\"0\" warnings=\"0\"/>\n" + 
+			"		<problem_summary errors=\"1\" infos=\"0\" problems=\"1\" tasks=\"0\" warnings=\"0\"/>\n" + 
 			"	</stats>\n" + 
 			"</compiler>\n";
 		String normalizedExpectedLogContents =
@@ -11678,7 +11678,7 @@
 		+ " -proceedOnError -err:+unused -d \"" + OUTPUT_DIR + "\"",
 		"",
 		"----------\n" + 
-		"1. WARNING in ---OUTPUT_DIR_PLACEHOLDER---/X.java (at line 2)\n" + 
+		"1. INFO in ---OUTPUT_DIR_PLACEHOLDER---/X.java (at line 2)\n" + 
 		"	@SuppressWarnings(\"unused\")\n" + 
 		"	                  ^^^^^^^^\n" + 
 		"At least one of the problems in category 'unused' is not analysed due to a compiler option being ignored\n" + 
@@ -11688,7 +11688,7 @@
 		"	            ^\n" + 
 		"The value of the field X.i is not used\n" + 
 		"----------\n" + 
-		"2 problems (1 error, 1 warning)\n",
+		"2 problems (1 error, 0 warnings, 1 info)\n",
 		true);
 }
 //https://bugs.eclipse.org/bugs/show_bug.cgi?id=295551
@@ -11769,7 +11769,7 @@
 		+ " -proceedOnError -warn:-suppress -err:+suppress,unused -warn:+suppress -d \"" + OUTPUT_DIR + "\"",
 		"",
 		"----------\n" + 
-		"1. WARNING in ---OUTPUT_DIR_PLACEHOLDER---/X.java (at line 2)\n" + 
+		"1. INFO in ---OUTPUT_DIR_PLACEHOLDER---/X.java (at line 2)\n" + 
 		"	@SuppressWarnings(\"unused\")\n" + 
 		"	                  ^^^^^^^^\n" + 
 		"At least one of the problems in category 'unused' is not analysed due to a compiler option being ignored\n" + 
@@ -11779,7 +11779,7 @@
 		"	            ^\n" + 
 		"The value of the field X.i is not used\n" + 
 		"----------\n" + 
-		"2 problems (1 error, 1 warning)\n",
+		"2 problems (1 error, 0 warnings, 1 info)\n",
 		true);
 }
 //https://bugs.eclipse.org/bugs/show_bug.cgi?id=295551
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/TypeHierarchyTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/TypeHierarchyTests.java
index cde5db5..bf8bd15 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/TypeHierarchyTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/TypeHierarchyTests.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2016 IBM Corporation and others.
+ * Copyright (c) 2000, 2017 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
@@ -2919,4 +2919,98 @@
 			deleteProject(prj);
 	}
 }
+public void testBug507954_0001() throws JavaModelException, CoreException {
+	IJavaProject javaProject = null; 
+	try {
+		String projectName = "507954";
+		javaProject = createJavaProject(projectName, new String[] {"src"}, "bin");
+		String packA = "/" + projectName + "/src/a/";
+		createFolder(packA);
+		String fileA = 				"package a;\n" +
+				"public abstract class A {\n"+
+				"  protected abstract void foo2();\n" +
+				"  public void baz2() {\n" +
+				"    foo2();\n" +
+			"}";
+		createFile(packA + "A.java", fileA);
+		createFile(
+				packA + "B.java",
+				"package a;\n" +
+				"public class B {\n" +
+				"  public void a() {\n" +
+				"    new A() {\n" +
+				"      @Override\n" +
+				"      protected void foo2() {}\n" +
+				"    }.baz2();\n" +
+				"  }\n" +
+				"}"
+				);
+		createFile(
+				packA + "C.java",
+				"package a;\n" +
+				"public abstract class C {\n" +
+				"  protected abstract void foo1();\n" +
+				"  public void baz1() {\n" +
+				"    foo1();\n" +
+				"  }\n" +
+				"}"
+				);
+		String packD = "/" + projectName + "/src/b/";
+		createFolder(packD);
+
+		String fileD = "package d;\n" +
+				"import a.A;\n" +
+				"import a.C;\n" +
+				"public final class D {\n" +
+				"\n" +
+				"	protected void c() {\n" +
+				"		new C() {\n" +
+				"			@Override\n" +
+				"			protected void foo1() {\n" +
+				"				a();\n" +
+				"				b();\n" +
+				"			}\n" +
+				"			private void a() {\n" +
+				"				new A() {\n" +
+				"					@Override\n" +
+				"					protected void foo2() {\n" +
+				"					}\n" +
+				"				}.baz2();\n" +
+				"			}\n" +
+				"			private void b() {\n" +
+				"				new A() {\n" +
+				"					@Override\n" +
+				"					protected void foo2() {\n" +
+				"					}\n" +
+				"				}.baz2();\n" +
+				"			}\n" +
+				"		}.baz1();\n" +
+				"	}\n" +
+				"\n" +
+				"}\n";
+
+		createFile(packA + "D.java", fileD);
+		
+		String classA = "A";
+		int start = fileA.indexOf(classA);
+		this.workingCopies = new ICompilationUnit[1];
+		this.workingCopies[0] = getWorkingCopy(packA + "A.java", fileA);
+
+		IJavaElement[] elements = this.workingCopies[0].codeSelect(start, classA.length());
+		assertEquals("Incorrect elements", 1, elements.length);
+		IType focus = (IType) elements[0];
+		ITypeHierarchy hierarchy = focus.newTypeHierarchy(this.workingCopies, null);
+		IType[] allSubTypes = hierarchy.getAllSubtypes(focus);
+		assertTypesEqual("Incorrect hierarchy",
+				"a.B$1\n" + 
+				"a.D$1$1\n" + 
+				"a.D$1$1\n",
+				allSubTypes,
+				false);
+	}
+	finally{
+		if (javaProject != null) deleteProject(javaProject);
+	}
+}
+
 }
diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/Main.java b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/Main.java
index 4834d4c..1467162 100644
--- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/Main.java
+++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/Main.java
@@ -146,6 +146,7 @@
 		private static final String NUMBER_OF_PROBLEMS = "problems"; //$NON-NLS-1$
 		private static final String NUMBER_OF_TASKS = "tasks"; //$NON-NLS-1$
 		private static final String NUMBER_OF_WARNINGS = "warnings"; //$NON-NLS-1$
+		private static final String NUMBER_OF_INFOS = "infos"; //$NON-NLS-1$
 		private static final String OPTION = "option"; //$NON-NLS-1$
 		private static final String OPTIONS = "options"; //$NON-NLS-1$
 		private static final String OUTPUT = "output"; //$NON-NLS-1$
@@ -179,6 +180,8 @@
 		private static final String TIME = "time"; //$NON-NLS-1$
 		private static final String VALUE = "value"; //$NON-NLS-1$
 		private static final String WARNING = "WARNING"; //$NON-NLS-1$
+		private static final String INFO = "INFO"; //$NON-NLS-1$
+
 		public static final int XML = 1;
 		private static final String XML_DTD_DECLARATION = "<!DOCTYPE compiler PUBLIC \"-//Eclipse.org//DTD Eclipse JDT 3.2.004 Compiler//EN\" \"http://www.eclipse.org/jdt/core/compiler_32_004.dtd\">"; //$NON-NLS-1$
 		static {
@@ -602,26 +605,24 @@
 			char[] originatingFileName = problem.getOriginatingFileName();
 			if (originatingFileName == null) {
 				// simplified message output
-				if (problem.isError()) {
-					printErr(this.main.bind(
-								"requestor.extraerror", //$NON-NLS-1$
+				String severity = problem.isError() ? "requestor.extraerror" //$NON-NLS-1$
+						: problem.isInfo() ? "requestor.extrainfo" : "requestor.extrawarning"; //$NON-NLS-1$ //$NON-NLS-2$
+				printErr(this.main.bind(
+								severity,
 								Integer.toString(globalErrorCount)));
-				} else {
-					// warning / mandatory warning / other
-					printErr(this.main.bind(
-							"requestor.extrawarning", //$NON-NLS-1$
-							Integer.toString(globalErrorCount)));
-				}
 				printErr(" "); //$NON-NLS-1$
 				this.printlnErr(problem.getMessage());
 			} else {
 				String fileName = new String(originatingFileName);
 				if ((this.tagBits & Logger.EMACS) != 0) {
+					String severity = problem.isError() ? "output.emacs.error" : //$NON-NLS-1$
+										problem.isInfo() ? "output.emacs.info" //$NON-NLS-1$
+													: "output.emacs.warning"; //$NON-NLS-1$
 					String result = fileName
 							+ ":" //$NON-NLS-1$
 							+ problem.getSourceLineNumber()
 							+ ": " //$NON-NLS-1$
-							+ (problem.isError() ? this.main.bind("output.emacs.error") : this.main.bind("output.emacs.warning")) //$NON-NLS-1$ //$NON-NLS-2$
+							+ this.main.bind(severity)
 							+ ": " //$NON-NLS-1$
 							+ problem.getMessage();
 					this.printlnErr(result);
@@ -631,15 +632,12 @@
 					if (localErrorCount == 0) {
 						this.printlnErr("----------"); //$NON-NLS-1$
 					}
-					printErr(problem.isError() ?
-							this.main.bind(
-									"requestor.error", //$NON-NLS-1$
-									Integer.toString(globalErrorCount),
-									new String(fileName))
-									: this.main.bind(
-											"requestor.warning", //$NON-NLS-1$
-											Integer.toString(globalErrorCount),
-											new String(fileName)));
+					String severity = problem.isError() ? "requestor.error" //$NON-NLS-1$
+							: problem.isInfo() ? "requestor.info" : "requestor.warning"; //$NON-NLS-1$ //$NON-NLS-2$
+					printErr(this.main.bind(
+								severity,
+								Integer.toString(globalErrorCount),
+								new String(fileName)));
 					final String errorReportSource = errorReportSource(problem, null, 0);
 					this.printlnErr(errorReportSource);
 					this.printlnErr(problem.getMessage());
@@ -655,6 +653,7 @@
 			if (count != 0) {
 				int errors = 0;
 				int warnings = 0;
+				int infos = 0;
 				for (int i = 0; i < count; i++) {
 					CategorizedProblem problem = (CategorizedProblem) problems.get(i);
 					if (problem != null) {
@@ -664,14 +663,17 @@
 						if (problem.isError()) {
 							errors++;
 							currentMain.globalErrorsCount++;
-						} else if (problem.isWarning()) {
+						} else if (problem.isInfo()) {
+							currentMain.globalInfoCount++;
+							infos++;
+						} else {
 							currentMain.globalWarningsCount++;
 							warnings++;
 						}
 					}
 				}
 				if ((this.tagBits & Logger.XML) != 0) {
-					if ((errors + warnings) != 0) {
+					if ((errors + warnings + infos) != 0) {
 						startLoggingExtraProblems(count);
 						for (int i = 0; i < count; i++) {
 							CategorizedProblem problem = (CategorizedProblem) problems.get(i);
@@ -791,11 +793,14 @@
 		private void logProblem(CategorizedProblem problem, int localErrorCount,
 			int globalErrorCount, char[] unitSource) {
 			if ((this.tagBits & Logger.EMACS) != 0) {
+				String severity = problem.isError() ? "output.emacs.error" : //$NON-NLS-1$
+									problem.isInfo() ? "output.emacs.info" //$NON-NLS-1$
+											: "output.emacs.warning"; //$NON-NLS-1$
 				String result = (new String(problem.getOriginatingFileName())
 						+ ":" //$NON-NLS-1$
 						+ problem.getSourceLineNumber()
 						+ ": " //$NON-NLS-1$
-						+ (problem.isError() ? this.main.bind("output.emacs.error") : this.main.bind("output.emacs.warning")) //$NON-NLS-1$ //$NON-NLS-2$
+						+ (this.main.bind(severity))
 						+ ": " //$NON-NLS-1$
 						+ problem.getMessage());
 				this.printlnErr(result);
@@ -805,13 +810,8 @@
 				if (localErrorCount == 0) {
 					this.printlnErr("----------"); //$NON-NLS-1$
 				}
-				printErr(problem.isError() ?
-						this.main.bind(
-								"requestor.error", //$NON-NLS-1$
-								Integer.toString(globalErrorCount),
-								new String(problem.getOriginatingFileName()))
-								: this.main.bind(
-										"requestor.warning", //$NON-NLS-1$
+				String severity = problem.isError() ? "requestor.error" : problem.isInfo() ? "requestor.info" : "requestor.warning";  //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
+				printErr(this.main.bind(severity,
 										Integer.toString(globalErrorCount),
 										new String(problem.getOriginatingFileName())));
 				try {
@@ -833,6 +833,7 @@
 			if (count != 0) {
 				int errors = 0;
 				int warnings = 0;
+				int infos = 0;
 				int tasks = 0;
 				for (int i = 0; i < count; i++) {
 					CategorizedProblem problem = problems[i];
@@ -847,6 +848,9 @@
 						} else if (problem.getID() == IProblem.Task) {
 							currentMain.globalTasksCount++;
 							tasks++;
+						} else if (problem.isInfo()) {
+							currentMain.globalInfoCount++;
+							infos++;
 						} else {
 							currentMain.globalWarningsCount++;
 							warnings++;
@@ -854,8 +858,8 @@
 					}
 				}
 				if ((this.tagBits & Logger.XML) != 0) {
-					if ((errors + warnings) != 0) {
-						startLoggingProblems(errors, warnings);
+					if ((errors + warnings + infos) != 0) {
+						startLoggingProblems(errors, warnings, infos);
 						for (int i = 0; i < count; i++) {
 							CategorizedProblem problem = problems[i];
 							if (problem!= null) {
@@ -889,12 +893,13 @@
 		 * @param globalWarningsCount
 		 */
 		public void logProblemsSummary(int globalProblemsCount,
-			int globalErrorsCount, int globalWarningsCount, int globalTasksCount) {
+			int globalErrorsCount, int globalWarningsCount, int globalInfoCount, int globalTasksCount) {
 			if ((this.tagBits & Logger.XML) != 0) {
 				// generate xml
 				this.parameters.put(Logger.NUMBER_OF_PROBLEMS, Integer.valueOf(globalProblemsCount));
 				this.parameters.put(Logger.NUMBER_OF_ERRORS, Integer.valueOf(globalErrorsCount));
 				this.parameters.put(Logger.NUMBER_OF_WARNINGS, Integer.valueOf(globalWarningsCount));
+				this.parameters.put(Logger.NUMBER_OF_INFOS, Integer.valueOf(globalInfoCount));
 				this.parameters.put(Logger.NUMBER_OF_TASKS, Integer.valueOf(globalTasksCount));
 				printTag(Logger.PROBLEM_SUMMARY, this.parameters, true, true);
 			}
@@ -902,6 +907,8 @@
 				String message = null;
 				if (globalErrorsCount == 1) {
 					message = this.main.bind("compile.oneError"); //$NON-NLS-1$
+				} else if (globalInfoCount == 1) {
+					message = this.main.bind("compile.oneInfo"); //$NON-NLS-1$
 				} else {
 					message = this.main.bind("compile.oneWarning"); //$NON-NLS-1$
 				}
@@ -909,6 +916,7 @@
 			} else {
 				String errorMessage = null;
 				String warningMessage = null;
+				String infoMessage = null;
 				if (globalErrorsCount > 0) {
 					if (globalErrorsCount == 1) {
 						errorMessage = this.main.bind("compile.oneError"); //$NON-NLS-1$
@@ -924,26 +932,42 @@
 						warningMessage = this.main.bind("compile.severalWarnings", String.valueOf(warningsNumber)); //$NON-NLS-1$
 					}
 				}
-				if (errorMessage == null || warningMessage == null) {
-					if (errorMessage == null) {
-						printErr(this.main.bind(
-							"compile.severalProblemsErrorsOrWarnings", //$NON-NLS-1$
-							String.valueOf(globalProblemsCount),
-							warningMessage));
-					} else {
-						printErr(this.main.bind(
-							"compile.severalProblemsErrorsOrWarnings", //$NON-NLS-1$
-							String.valueOf(globalProblemsCount),
-							errorMessage));
-					}
-				} else {
+				if (globalInfoCount == 1) {
+					infoMessage = this.main.bind("compile.oneInfo"); //$NON-NLS-1$
+				} else if (globalInfoCount > 1) {
+					infoMessage = this.main.bind("compile.severalInfos", String.valueOf(warningsNumber)); //$NON-NLS-1$					
+				}
+				if (globalProblemsCount == globalInfoCount || globalProblemsCount == globalErrorsCount || globalProblemsCount == globalWarningsCount) {
+					String msg = errorMessage != null ? errorMessage : warningMessage != null ? warningMessage : infoMessage;
 					printErr(this.main.bind(
-						"compile.severalProblemsErrorsAndWarnings", //$NON-NLS-1$
-						new String[] {
-							String.valueOf(globalProblemsCount),
-							errorMessage,
-							warningMessage
-						}));
+						"compile.severalProblemsErrorsOrWarnings", //$NON-NLS-1$
+						String.valueOf(globalProblemsCount),
+						msg));
+				} else {
+					if (globalInfoCount == 0) {
+						printErr(this.main.bind(
+								"compile.severalProblemsErrorsAndWarnings", //$NON-NLS-1$
+								new String[] {
+									String.valueOf(globalProblemsCount),
+									errorMessage,
+									warningMessage
+								}));
+					} else {
+						if (errorMessage == null) {
+							errorMessage  = this.main.bind("compile.severalErrors", String.valueOf(globalErrorsCount)); //$NON-NLS-1$
+						}
+						if (warningMessage == null) {
+							warningMessage  = this.main.bind("compile.severalWarnings", String.valueOf(warningsNumber)); //$NON-NLS-1$
+						}
+						printErr(this.main.bind(
+								"compile.severalProblems", //$NON-NLS-1$
+								new String[] {
+									String.valueOf(globalProblemsCount),
+									errorMessage,
+									warningMessage,
+									infoMessage
+								}));
+					}
 				}
 			}
 			if ((this.tagBits & Logger.XML) == 0) {
@@ -1064,7 +1088,7 @@
 			final int sourceStart = problem.getSourceStart();
 			final int sourceEnd = problem.getSourceEnd();
 			boolean isError = problem.isError();
-			this.parameters.put(Logger.PROBLEM_SEVERITY, isError ? Logger.ERROR : Logger.WARNING);
+			this.parameters.put(Logger.PROBLEM_SEVERITY, isError ? Logger.ERROR : (problem.isInfo() ? Logger.INFO : Logger.WARNING));
 			this.parameters.put(Logger.PROBLEM_LINE, Integer.valueOf(problem.getSourceLineNumber()));
 			this.parameters.put(Logger.PROBLEM_SOURCE_START, Integer.valueOf(sourceStart));
 			this.parameters.put(Logger.PROBLEM_SOURCE_END, Integer.valueOf(sourceEnd));
@@ -1088,7 +1112,7 @@
 			this.parameters.put(Logger.PROBLEM_ID, Integer.valueOf(id)); // ID as numeric value
 			boolean isError = problem.isError();
 			int severity = isError ? ProblemSeverities.Error : ProblemSeverities.Warning;
-			this.parameters.put(Logger.PROBLEM_SEVERITY, isError ? Logger.ERROR : Logger.WARNING);
+			this.parameters.put(Logger.PROBLEM_SEVERITY, isError ? Logger.ERROR : (problem.isInfo() ? Logger.INFO : Logger.WARNING));
 			this.parameters.put(Logger.PROBLEM_LINE, Integer.valueOf(problem.getSourceLineNumber()));
 			this.parameters.put(Logger.PROBLEM_SOURCE_START, Integer.valueOf(sourceStart));
 			this.parameters.put(Logger.PROBLEM_SOURCE_END, Integer.valueOf(sourceEnd));
@@ -1186,7 +1210,8 @@
 				logTiming(compilerStats);
 			}
 			if (this.main.globalProblemsCount > 0) {
-				logProblemsSummary(this.main.globalProblemsCount, this.main.globalErrorsCount, this.main.globalWarningsCount, this.main.globalTasksCount);
+				logProblemsSummary(this.main.globalProblemsCount, this.main.globalErrorsCount, this.main.globalWarningsCount, 
+						this.main.globalInfoCount, this.main.globalTasksCount);
 			}
 			if (this.main.exportedClassFilesCounter != 0
 					&& (this.main.showProgress || isTimed || this.main.verbose)) {
@@ -1246,10 +1271,11 @@
 		 * Used to start logging problems.
 		 * Only use in xml mode.
 		 */
-		private void startLoggingProblems(int errors, int warnings) {
+		private void startLoggingProblems(int errors, int warnings, int infos) {
 			this.parameters.put(Logger.NUMBER_OF_PROBLEMS, Integer.valueOf(errors + warnings));
 			this.parameters.put(Logger.NUMBER_OF_ERRORS, Integer.valueOf(errors));
 			this.parameters.put(Logger.NUMBER_OF_WARNINGS, Integer.valueOf(warnings));
+			this.parameters.put(Logger.NUMBER_OF_INFOS, Integer.valueOf(infos));
 			printTag(Logger.PROBLEMS, this.parameters, true, false);
 		}
 
@@ -1361,6 +1387,7 @@
 	public int globalProblemsCount;
 	public int globalTasksCount;
 	public int globalWarningsCount;
+	public int globalInfoCount;
 
 	private File javaHomeCache;
 
@@ -1726,6 +1753,7 @@
 				this.globalProblemsCount = 0;
 				this.globalErrorsCount = 0;
 				this.globalWarningsCount = 0;
+				this.globalInfoCount = 0;
 				this.globalTasksCount = 0;
 				this.exportedClassFilesCounter = 0;
 
diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties
index 5554499..7bcf1c6 100644
--- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties
+++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties
@@ -1,5 +1,5 @@
 ###############################################################################
-# Copyright (c) 2000, 2016 IBM Corporation and others.
+# Copyright (c) 2000, 2017 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
@@ -46,10 +46,13 @@
 compile.oneProblem = 1 problem ({0})
 compile.severalProblemsErrorsOrWarnings = {0} problems ({1})
 compile.severalProblemsErrorsAndWarnings = {0} problems ({1}, {2})
+compile.severalProblems = {0} problems ({1}, {2}, {3})
 compile.oneError = 1 error
 compile.severalErrors = {0} errors
 compile.oneWarning = 1 warning
 compile.severalWarnings = {0} warnings
+compile.oneInfo = 1 info
+compile.severalInfos = {0} info
 compile.oneClassFileGenerated = [1 .class file generated]
 compile.severalClassFilesGenerated = [{0} .class files generated]
 
@@ -131,14 +134,17 @@
 ### requestor
 requestor.error = {0}. ERROR in {1}
 requestor.warning = {0}. WARNING in {1}
+requestor.info = {0}. INFO in {1}
 requestor.extraerror = {0}. ERROR:
 requestor.extrawarning = {0}. WARNING:
+requestor.extrainfo = {0}. INFO:
 requestor.notRetrieveErrorMessage = Cannot retrieve the error message for {0}
 requestor.noFileNameSpecified = (original file name is not available)
 
 ### EMACS STYLE
 output.emacs.error=error
 output.emacs.warning=warning
+output.emacs.info=info
 
 ### unit
 unit.more = File {0} is specified more than once
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ReadManager.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ReadManager.java
index c5d0e4f..d3f8567 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ReadManager.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ReadManager.java
@@ -75,7 +75,8 @@
 }
 
 public char[] getContents(ICompilationUnit unit) throws Error {
-	if (this.readingThreads == null || this.units.length == 0) {
+	Thread[] rThreads = this.readingThreads;
+	if (rThreads == null || this.units.length == 0) {
 		if (this.caughtException != null) {
 			// rethrow the caught exception from the readingThreads in the main compiler thread
 			if (this.caughtException instanceof Error)
@@ -85,7 +86,7 @@
 		return unit.getContents();
 	}
 
-	boolean yield = false;
+	boolean yield = this.sleepingThreadCount == rThreads.length;
 	char[] result = null;
 	synchronized (this) {
 		if (unit == this.filesRead[this.readyToReadPosition]) {
@@ -115,7 +116,6 @@
 				//System.out.print('+');
 				//System.out.print(this.nextFileToRead);
 				notify();
-				yield = this.sleepingThreadCount == this.readingThreads.length;
 			}
 		} else {
 			// must make sure we're reading ahead of the unit
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationProvider.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationProvider.java
index fd6da1b..6d5f0d7 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationProvider.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationProvider.java
@@ -118,7 +118,7 @@
 				}
 				if (rawSig == null || annotSig == null) {
 					if (errLine == -1) errLine = reader.getLineNumber();
-					throw new IOException("Illegal format for annotation file at line "+errLine); //$NON-NLS-1$
+					throw new IOException("Illegal format in annotation file for "+this.typeName+" at line "+errLine); //$NON-NLS-1$ //$NON-NLS-2$
 				}
 				// discard optional meta data (separated by whitespace):
 				annotSig = trimTail(annotSig);
@@ -146,7 +146,7 @@
 		if (line != null && line.startsWith(CLASS_PREFIX)) {
 			line = line.substring(CLASS_PREFIX.length());
 		} else {
-			throw new IOException("missing class header in annotation file"); //$NON-NLS-1$
+			throw new IOException("missing class header in annotation file for "+typeName); //$NON-NLS-1$
 		}
 		if (!trimTail(line).equals(typeName)) {
 			throw new IOException("mismatching class name in annotation file, expected "+typeName+", but header said "+line); //$NON-NLS-1$ //$NON-NLS-2$
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/ManifestAnalyzer.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/ManifestAnalyzer.java
index ee3754c..c9817a6 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/ManifestAnalyzer.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/ManifestAnalyzer.java
@@ -31,7 +31,7 @@
 	private ArrayList calledFilesNames;
 	
 	/**
-	 * Analyze the manifest contents. The given input stream is read using a UTF-8 encoded reader.
+	 * Analyzes the manifest contents. The given input stream is read using a UTF-8 encoded reader.
 	 * If the contents of the input stream is not encoded using a UTF-8 encoding, the analysis will fail.
 	 * 
 	 * @param inputStream the given input stream.
@@ -41,6 +41,17 @@
 	 */
 	public boolean analyzeManifestContents(InputStream inputStream) throws IOException {
 		char[] chars = Util.getInputStreamAsCharArray(inputStream, -1, Util.UTF_8);
+		return analyzeManifestContents(chars);
+	}
+
+	/**
+	 * Analyzes the manifest contents.
+	 * 
+	 * @param chars the content of the manifest
+	 * 
+	 * @return <code>true</code> if the analysis is successful, <code>false</code> otherwise.
+	 */
+	public boolean analyzeManifestContents(char[] chars) {
 		int state = START, substate = 0;
 		StringBuffer currentJarToken = new StringBuffer();
 		int currentChar;
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClasspathEntry.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClasspathEntry.java
index ae0a018..a962d66 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClasspathEntry.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClasspathEntry.java
@@ -20,6 +20,9 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.core;
 
+import static org.eclipse.jdt.internal.compiler.util.Util.UTF_8;
+import static org.eclipse.jdt.internal.compiler.util.Util.getInputStreamAsCharArray;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
@@ -66,6 +69,9 @@
 import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
 import org.eclipse.jdt.internal.compiler.util.ManifestAnalyzer;
+import org.eclipse.jdt.internal.core.nd.IReader;
+import org.eclipse.jdt.internal.core.nd.java.JavaIndex;
+import org.eclipse.jdt.internal.core.nd.java.NdResourceFile;
 import org.eclipse.jdt.internal.core.util.Messages;
 import org.eclipse.jdt.internal.core.util.Util;
 import org.w3c.dom.DOMException;
@@ -969,23 +975,60 @@
 		}
 	}
 
+	private static char[] getManifestContents(IPath jarPath) throws CoreException, IOException {
+		// Try to read a cached manifest from the index
+		if (JavaIndex.isEnabled()) {
+			JavaIndex index = JavaIndex.getIndex();
+			String location = JavaModelManager.getLocalFile(jarPath).getAbsolutePath();
+			try (IReader reader = index.getNd().acquireReadLock()) {
+				NdResourceFile resourceFile = index.getResourceFile(location.toCharArray());
+				if (index.isUpToDate(resourceFile)) {
+					char[] manifestContent = resourceFile.getManifestContent().getChars();
+					if (manifestContent.length == 0) {
+						return null;
+					}
+					return manifestContent;
+				}
+			}
+		}
+
+		ZipFile zip = null;
+		InputStream inputStream = null;
+		JavaModelManager manager = JavaModelManager.getJavaModelManager();
+		try {
+			zip = manager.getZipFile(jarPath);
+			ZipEntry manifest = zip.getEntry("META-INF/MANIFEST.MF"); //$NON-NLS-1$
+			if (manifest == null) {
+				return null;
+			}
+			inputStream = zip.getInputStream(manifest);
+			char[] chars = getInputStreamAsCharArray(inputStream, -1, UTF_8);
+			return chars;
+		} finally {
+			if (inputStream != null) {
+				try {
+					inputStream.close();
+				} catch (IOException e) {
+					// best effort
+				}
+			}
+			manager.closeZipFile(zip);
+		}
+	}
+
 	private static List getCalledFileNames(IPath jarPath) {
 		Object target = JavaModel.getTarget(jarPath, true/*check existence, otherwise the manifest cannot be read*/);
 		if (!(target instanceof IFile || target instanceof File))
 			return null;
-		JavaModelManager manager = JavaModelManager.getJavaModelManager();
-		ZipFile zip = null;
-		InputStream inputStream = null;
+
 		List calledFileNames = null;
 		try {
-			zip = manager.getZipFile(jarPath);
-			ZipEntry manifest = zip.getEntry("META-INF/MANIFEST.MF"); //$NON-NLS-1$
-			if (manifest == null) 
+			char[] manifestContents = getManifestContents(jarPath);
+			if (manifestContents == null) 
 				return null;
 			// non-null implies regular file
 			ManifestAnalyzer analyzer = new ManifestAnalyzer();
-			inputStream = zip.getInputStream(manifest);
-			boolean success = analyzer.analyzeManifestContents(inputStream);
+			boolean success = analyzer.analyzeManifestContents(manifestContents);
 			calledFileNames = analyzer.getCalledFileNames();
 			if (!success || analyzer.getClasspathSectionsCount() == 1 && calledFileNames == null) {
 				if (JavaModelManager.CP_RESOLVE_VERBOSE_FAILURE) {
@@ -1010,19 +1053,10 @@
 				Util.verbose("Could not read Class-Path header in manifest of jar file: " + jarPath.toOSString()); //$NON-NLS-1$
 				e.printStackTrace();
 			}
-		} finally {
-			if (inputStream != null) {
-				try {
-					inputStream.close();
-				} catch (IOException e) {
-					// best effort
-				}
-			}
-			manager.closeZipFile(zip);
 		}
 		return calledFileNames;
 	}
-	
+
 	/*
 	 * Resolves the ".." in the given path. Returns the given path if it contains no ".." segment.
 	 */
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRoot.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRoot.java
index d662c9c..8d78557 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRoot.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRoot.java
@@ -33,7 +33,6 @@
 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
 import org.eclipse.jdt.internal.core.nd.IReader;
 import org.eclipse.jdt.internal.core.nd.java.JavaIndex;
-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.NdZipEntry;
@@ -96,22 +95,21 @@
 			if (JavaIndex.isEnabled()) {
 				JavaIndex index = JavaIndex.getIndex();
 				try (IReader reader = index.getNd().acquireReadLock()) {
-					NdResourceFile resourceFile = index.getResourceFile(getPath().toString().toCharArray());
-					if (index.isUpToDate(resourceFile)) {
-						usedIndex = true;
-						long level = resourceFile.getJdkLevel();
-						String compliance = CompilerOptions.versionFromJdkLevel(level);
-						// Locate all the non-classfile entries
-						for (NdZipEntry next : resourceFile.getZipEntries()) {
-							String filename = next.getFileName().getString();
-							initRawPackageInfo(rawPackageInfo, filename, filename.endsWith("/"), compliance); //$NON-NLS-1$
-						}
-
-						// Locate all the classfile entries
-						for (NdBinding binding : resourceFile.getBindings()) {
-							if (binding instanceof NdType) {
-								NdType type = (NdType) binding;
-
+					IPath resourcePath = JavaIndex.getLocationForElement(this); 
+					if (!resourcePath.isEmpty()) {
+						NdResourceFile resourceFile = index.getResourceFile(resourcePath.toString().toCharArray());
+						if (index.isUpToDate(resourceFile)) {
+							usedIndex = true;
+							long level = resourceFile.getJdkLevel();
+							String compliance = CompilerOptions.versionFromJdkLevel(level);
+							// Locate all the non-classfile entries
+							for (NdZipEntry next : resourceFile.getZipEntries()) {
+								String filename = next.getFileName().getString();
+								initRawPackageInfo(rawPackageInfo, filename, filename.endsWith("/"), compliance); //$NON-NLS-1$
+							}
+	
+							// Locate all the classfile entries
+							for (NdType type : resourceFile.getTypes()) {	
 								String path = new String(type.getTypeId().getBinaryName()) + ".class"; //$NON-NLS-1$
 								initRawPackageInfo(rawPackageInfo, path, false, compliance);
 							}
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 e536b23..309194a 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
@@ -136,8 +136,11 @@
 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.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.NdResourceFile;
 import org.eclipse.jdt.internal.core.search.AbstractSearchScope;
 import org.eclipse.jdt.internal.core.search.BasicSearchEngine;
 import org.eclipse.jdt.internal.core.search.IRestrictedAccessTypeRequestor;
@@ -2796,6 +2799,24 @@
 			return;
 		}
 		throwExceptionIfArchiveInvalid(path);
+		// Check if we can determine the archive's validity by examining the index
+		if (JavaIndex.isEnabled()) {
+			JavaIndex index = JavaIndex.getIndex();
+			String location = JavaModelManager.getLocalFile(path).getAbsolutePath();
+			try (IReader reader = index.getNd().acquireReadLock()) {
+				NdResourceFile resourceFile = index.getResourceFile(location.toCharArray());
+				if (index.isUpToDate(resourceFile)) {
+					// We have this file in the index and the index is up-to-date, so we can determine the file's
+					// validity without touching the filesystem.
+					if (resourceFile.isCorruptedZipFile()) {
+						throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1,
+								Messages.status_IOException, new ZipException()));
+					}
+					return;
+				}
+			}
+		}
+
 		ZipFile file = getZipFile(path);
 		closeZipFile(file);
 	}
diff --git a/org.eclipse.jdt.core/schema/compiler.dtd b/org.eclipse.jdt.core/schema/compiler.dtd
index 7d989a3..e83ee0a 100644
--- a/org.eclipse.jdt.core/schema/compiler.dtd
+++ b/org.eclipse.jdt.core/schema/compiler.dtd
@@ -39,6 +39,7 @@
 <!ATTLIST problems problems CDATA #REQUIRED
 				   errors   CDATA #REQUIRED
 				   warnings CDATA #REQUIRED
+				   infos CDATA #REQUIRED
 >
 <!ATTLIST tasks tasks CDATA #REQUIRED>
 <!ATTLIST problem charEnd    CDATA #REQUIRED
@@ -73,6 +74,7 @@
 <!ATTLIST problem_summary problems CDATA #REQUIRED
                           errors   CDATA #REQUIRED
                           warnings CDATA #REQUIRED
+                          infos CDATA #REQUIRED
                           tasks    CDATA #REQUIRED
 >
 <!ATTLIST number_of_classfiles value CDATA #REQUIRED>
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/index/DiskIndex.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/index/DiskIndex.java
index d7c78b7..b13ab07 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/index/DiskIndex.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/index/DiskIndex.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2016 IBM Corporation and others.
+ * Copyright (c) 2000, 2017 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
@@ -46,7 +46,7 @@
 private int streamEnd; // used when writing data from the streamBuffer to the file
 char separator = Index.DEFAULT_SEPARATOR;
 
-public static final String SIGNATURE= "INDEX VERSION 1.129"; //$NON-NLS-1$
+public static final String SIGNATURE= "INDEX VERSION 1.130"; //$NON-NLS-1$
 private static final char[] SIGNATURE_CHARS = SIGNATURE.toCharArray();
 public static boolean DEBUG = false;
 
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNode.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNode.java
index 67d039b..2fc229c 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNode.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNode.java
@@ -100,7 +100,7 @@
 		return this.nd.getDB();
 	}
 
-	public Nd getNd() {
+	public final Nd getNd() {
 		return this.nd;
 	}
 
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
index 64c7da0..8847cc4 100644
--- 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
@@ -23,6 +23,8 @@
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.FileChannel;
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
 
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.OperationCanceledException;
@@ -109,6 +111,7 @@
 	public static final int WRITE_NUMBER_OFFSET = FREE_BLOCK_OFFSET + PTR_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;
+	private static final int NUM_HEADER_CHUNKS = 1;
 
 	// 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. */
@@ -132,7 +135,16 @@
 
 	private int fVersion;
 	private final Chunk fHeaderChunk;
+	/**
+	 * Stores the {@link Chunk} associated with each page number or null if the chunk isn't loaded. Synchronize on
+	 * {@link #fCache} before accessing.
+	 */
 	private Chunk[] fChunks;
+
+	/**
+	 * Holds all the non-null entries from {@link #fChunks}. Synchronize on {@link #fCache} before accessing.
+	 */
+	private HashSet<Chunk> allocatedChunks = new HashSet<>();
 	private int fChunksUsed;
 	private ChunkCache fCache;
 
@@ -328,11 +340,12 @@
 
 	private void removeChunksFromCache() {
 		synchronized (this.fCache) {
-			for (int i= 1; i < this.fChunks.length; i++) {
-				Chunk chunk= this.fChunks[i];
-				if (chunk != null) {
+			for (Iterator<Chunk> next = this.allocatedChunks.iterator(); next.hasNext();) {
+				Chunk chunk = next.next();
+				if (chunk.fSequenceNumber >= NUM_HEADER_CHUNKS) {
 					this.fCache.remove(chunk);
-					this.fChunks[i]= null;
+					this.fChunks[chunk.fSequenceNumber] = null;
+					next.remove();
 				}
 			}
 		}
@@ -362,6 +375,7 @@
 				chunk = new Chunk(this, index);
 				chunk.read();
 				this.fChunks[index] = chunk;
+				this.allocatedChunks.add(chunk);
 			} else {
 				this.cacheHits++;
 			}
@@ -831,6 +845,7 @@
 
 			this.fChunksUsed = lastChunkIndex + 1;
 			this.fChunks[lastChunkIndex] = lastChunk;
+			this.allocatedChunks.add(lastChunk);
 			this.fCache.add(lastChunk, true);
 			long result = (long) firstChunkIndex * CHUNK_SIZE;
 
@@ -1182,6 +1197,7 @@
 	void releaseChunk(final Chunk chunk) {
 		if (!chunk.fLocked) {
 			this.fChunks[chunk.fSequenceNumber]= null;
+			this.allocatedChunks.remove(chunk);
 		}
 	}
 
@@ -1212,16 +1228,17 @@
 			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) {
+					for (Iterator<Chunk> iter = this.allocatedChunks.iterator(); iter.hasNext();) {
+						Chunk chunk = iter.next();
+						if (chunk.fSequenceNumber >= NUM_HEADER_CHUNKS) {
 							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;
+									this.fChunks[chunk.fSequenceNumber]= null;
+									iter.remove();
 								}
 							} else if (chunk.fLocked) {
 								// Locked chunk, still in cache.
@@ -1238,6 +1255,7 @@
 						}
 					}
 				}
+				sortBySequenceNumber(dirtyChunks);
 				// Also handles header chunk.
 				wasInterrupted = flushAndUnlockChunks(dirtyChunks, flush) || wasInterrupted;
 			} finally {
@@ -1247,6 +1265,10 @@
 		return wasInterrupted;
 	}
 
+	private void sortBySequenceNumber(ArrayList<Chunk> dirtyChunks) {
+		dirtyChunks.sort((a, b) -> {return a.fSequenceNumber - b.fSequenceNumber;});
+	}
+
 	public boolean flush() throws IndexException {
 		boolean wasInterrupted = false;
 		assert this.fLocked;
@@ -1262,14 +1284,15 @@
 		// 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) {
+			for (Chunk chunk : this.allocatedChunks) {
+				if (chunk.fSequenceNumber >= 1 && chunk.fDirty) {
 					dirtyChunks.add(chunk);
 				}
 			}
 		}
 
+		sortBySequenceNumber(dirtyChunks);
+
 		// Also handles header chunk.
 		return flushAndUnlockChunks(dirtyChunks, true) || wasInterrupted;
 	}
@@ -1301,6 +1324,7 @@
 						chunk.fLocked= false;
 						if (chunk.fCacheIndex < 0) {
 							this.fChunks[chunk.fSequenceNumber]= null;
+							this.allocatedChunks.remove(chunk);
 						}
 					}
 				}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Indexer.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Indexer.java
index e630ee4..5ac0308 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Indexer.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Indexer.java
@@ -10,8 +10,12 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.core.nd.indexer;
 
+import static org.eclipse.jdt.internal.compiler.util.Util.UTF_8;
+import static org.eclipse.jdt.internal.compiler.util.Util.getInputStreamAsCharArray;
+
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -68,7 +72,6 @@
 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;
@@ -471,19 +474,16 @@
 					break;
 				}
 		
-				int numChildren = toDelete.getBindingCount();
+				int numChildren = toDelete.getTypeCount();
 				deletionMonitor.setWorkRemaining(numChildren + 1);
 				if (numChildren == 0) {
 					break;
 				}
 
-				NdBinding nextDeletion = toDelete.getBinding(numChildren - 1);
+				NdType nextDeletion = toDelete.getType(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$
-					}
+					Package.logInfo("Deleting " + nextDeletion.getTypeId().getFieldDescriptor().getString() + " from "  //$NON-NLS-1$//$NON-NLS-2$
+							+ new String(toDelete.getLocation().getString()) + " " + toDelete.address); //$NON-NLS-1$
 				}
 				nextDeletion.delete();
 			} finally {
@@ -693,6 +693,14 @@
 											+ resourceFile.getLocation().getString() + " " + resourceFile.address); //$NON-NLS-1$
 								}
 								new NdZipEntry(resourceFile, fileName);
+
+								if (fileName.equals("META-INF/MANIFEST.MF")) { //$NON-NLS-1$
+									try (InputStream inputStream = zipFile.getInputStream(member)) {
+										char[] chars = getInputStreamAsCharArray(inputStream, -1, UTF_8);
+
+										resourceFile.setManifestContent(chars);
+									}
+								}
 							}
 						} finally {
 							this.nd.releaseWriteLock();
@@ -729,6 +737,14 @@
 			} 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.
+				this.nd.acquireWriteLock(null);
+				try {
+					if (resourceFile.isInIndex()) {
+						resourceFile.setFlags(NdResourceFile.FLG_CORRUPT_ZIP_FILE);
+					}
+				} finally {
+					this.nd.releaseWriteLock();
+				}
 			} catch (FileNotFoundException e) {
 				throw e;
 			} catch (IOException ioException) {
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaIndex.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaIndex.java
index 9d8f411..dc0d730 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaIndex.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaIndex.java
@@ -34,9 +34,9 @@
 
 public class JavaIndex {
 	// Version constants
-	static final int CURRENT_VERSION = Nd.version(1, 41);
-	static final int MAX_SUPPORTED_VERSION = Nd.version(1, 41);
-	static final int MIN_SUPPORTED_VERSION = Nd.version(1, 41);
+	static final int CURRENT_VERSION = Nd.version(1, 46);
+	static final int MAX_SUPPORTED_VERSION = Nd.version(1, 46);
+	static final int MIN_SUPPORTED_VERSION = Nd.version(1, 46);
 
 	// Fields for the search header
 	public static final FieldSearchIndex<NdResourceFile> FILES;
@@ -282,6 +282,7 @@
 		registry.register(0x00F0, NdConstantShort.type.getFactory());
 		registry.register(0x0100, NdConstantString.type.getFactory());
 		registry.register(0x0110, NdMethod.type.getFactory());
+		registry.register(0x0118, NdMethodAnnotationData.type.getFactory());
 		registry.register(0x0120, NdMethodException.type.getFactory());
 		registry.register(0x0130, NdMethodId.type.getFactory());
 		registry.register(0x0140, NdMethodParameter.type.getFactory());
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
index e7e48eb..c1b524e 100644
--- 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
@@ -15,14 +15,14 @@
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
 
 public class NdAnnotationInMethod extends NdAnnotation {
-	public static final FieldManyToOne<NdMethod> OWNER;
+	public static final FieldManyToOne<NdMethodAnnotationData> OWNER;
 
 	@SuppressWarnings("hiding")
 	public static final StructDef<NdAnnotationInMethod> type;
 
 	static {
 		type = StructDef.create(NdAnnotationInMethod.class, NdAnnotation.type);
-		OWNER = FieldManyToOne.createOwner(type, NdMethod.ANNOTATIONS);
+		OWNER = FieldManyToOne.createOwner(type, NdMethodAnnotationData.ANNOTATIONS);
 		type.done();
 	}
 
@@ -33,7 +33,7 @@
 	public NdAnnotationInMethod(Nd nd, NdMethod owner) {
 		super(nd);
 
-		OWNER.put(getNd(), this.address, owner);
+		OWNER.put(getNd(), this.address, owner.createAnnotationData());
 	}
 
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdBinding.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdBinding.java
index 25938a1..3c48fc9 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdBinding.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdBinding.java
@@ -17,7 +17,6 @@
 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;
@@ -28,7 +27,6 @@
 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")
@@ -38,7 +36,6 @@
 		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();
 	}
@@ -47,10 +44,8 @@
 		super(nd, address);
 	}
 
-	public NdBinding(Nd nd, NdResourceFile resource) {
+	public NdBinding(Nd nd) {
 		super(nd);
-
-		FILE.put(nd, this.address, resource);
 	}
 
 	public List<NdVariable> getVariables() {
@@ -88,14 +83,6 @@
 		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()][];
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethod.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethod.java
index 77991be..e3a5730 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethod.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethod.java
@@ -10,10 +10,10 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.core.nd.java;
 
+import java.util.Collections;
 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;
@@ -31,9 +31,7 @@
 	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;
+	public static final FieldOneToOne<NdMethodAnnotationData> ANNOTATION_DATA;
 
 	@SuppressWarnings("hiding")
 	public static final StructDef<NdMethod> type;
@@ -42,15 +40,13 @@
 		type = StructDef.create(NdMethod.class, NdBinding.type);
 		METHOD_ID = FieldManyToOne.create(type, NdMethodId.METHODS);
 		METHOD_FLAGS = type.addShort();
-		PARENT = FieldManyToOne.create(type, NdType.METHODS);
+		PARENT = FieldManyToOne.createOwner(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);
+		ANNOTATION_DATA = FieldOneToOne.create(type, NdMethodAnnotationData.class, NdMethodAnnotationData.METHOD);
 		type.done();
 	}
 
@@ -62,7 +58,7 @@
 	}
 
 	public NdMethod(NdType parent) {
-		super(parent.getNd(), parent.getFile());
+		super(parent.getNd());
 
 		PARENT.put(getNd(), this.address, parent);
 	}
@@ -95,7 +91,11 @@
 	}
 
 	public List<NdAnnotationInMethod> getAnnotations() {
-		return ANNOTATIONS.asList(getNd(), this.address);
+		NdMethodAnnotationData annotationData = getAnnotationData();
+		if (annotationData != null) {
+			return annotationData.getAnnotations();
+		}
+		return Collections.emptyList();
 	}
 
 	public void setDefaultValue(NdConstant value) {
@@ -115,7 +115,11 @@
 	}
 
 	public List<NdTypeAnnotationInMethod> getTypeAnnotations() {
-		return TYPE_ANNOTATIONS.asList(getNd(), this.address);
+		NdMethodAnnotationData annotationData = getAnnotationData();
+		if (annotationData != null) {
+			return annotationData.getTypeAnnotations();
+		}
+		return Collections.emptyList();
 	}
 
 	public List<NdMethodException> getExceptions() {
@@ -144,11 +148,22 @@
 	}
 
 	public void setTagBits(long bits) {
-		TAG_BITS.put(getNd(), this.address, bits);
+		if (bits != 0) {
+			createAnnotationData().setTagBits(bits);
+		} else {
+			NdMethodAnnotationData annotationData = getAnnotationData();
+			if (annotationData != null) {
+				annotationData.setTagBits(bits);
+			}
+		}
 	}
 
 	public long getTagBits() {
-		return TAG_BITS.get(getNd(), this.address);
+		NdMethodAnnotationData annotations = getAnnotationData();
+		if (annotations == null) {
+			return 0;
+		}
+		return annotations.getTagBits();
 	}
 
 	public String toString() {
@@ -189,4 +204,20 @@
 			}
 		}
 	}
+
+	/**
+	 * Creates the {@link NdMethodAnnotationData} struct for this method if it does not already exist. Returns
+	 * the existing or newly-created struct. 
+	 */
+	public NdMethodAnnotationData createAnnotationData() {
+		NdMethodAnnotationData result = getAnnotationData();
+		if (result == null) {
+			result = new NdMethodAnnotationData(this);
+		}
+		return result;
+	}
+
+	private NdMethodAnnotationData getAnnotationData() {
+		return ANNOTATION_DATA.get(getNd(), getAddress());
+	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodAnnotationData.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodAnnotationData.java
new file mode 100644
index 0000000..913eaad
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodAnnotationData.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.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.FieldLong;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+/**
+ * Stores all annotation-related information for a single method. This is stored separately from the rest of the
+ * {@link NdMethod} structure in order to save space in the common case where a method has no annotations.
+ */
+public class NdMethodAnnotationData extends NdNode {
+	public static final FieldOneToOne<NdMethod> METHOD;
+	public static final FieldLong TAG_BITS;
+	public static final FieldOneToMany<NdAnnotationInMethod> ANNOTATIONS;
+	public static final FieldOneToMany<NdTypeAnnotationInMethod> TYPE_ANNOTATIONS;
+
+	@SuppressWarnings("hiding")
+	public static final StructDef<NdMethodAnnotationData> type;
+
+	static {
+		type = StructDef.create(NdMethodAnnotationData.class, NdNode.type);
+		METHOD = FieldOneToOne.createOwner(type, NdMethod.class, NdMethod.ANNOTATION_DATA);
+		TAG_BITS = type.addLong();
+		ANNOTATIONS = FieldOneToMany.create(type, NdAnnotationInMethod.OWNER);
+		TYPE_ANNOTATIONS = FieldOneToMany.create(type, NdTypeAnnotationInMethod.OWNER);
+		type.done();
+	}
+
+	public NdMethodAnnotationData(Nd nd, long address) {
+		super(nd, address);
+	}
+
+	public NdMethodAnnotationData(NdMethod method) {
+		super(method.getNd());
+
+		METHOD.put(getNd(), this.address, method);
+	}
+
+	public void setTagBits(long bits) {
+		TAG_BITS.put(getNd(), this.address, bits);
+	}
+
+	public long getTagBits() {
+		return TAG_BITS.get(getNd(), this.address);
+	}
+
+	public List<NdTypeAnnotationInMethod> getTypeAnnotations() {
+		return TYPE_ANNOTATIONS.asList(getNd(), this.address);
+	}
+
+	public List<NdAnnotationInMethod> getAnnotations() {
+		return ANNOTATIONS.asList(getNd(), this.address);
+	}
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdResourceFile.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdResourceFile.java
index 286180f..925d717 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdResourceFile.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdResourceFile.java
@@ -27,6 +27,7 @@
 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.FieldShort;
 import org.eclipse.jdt.internal.core.nd.field.FieldString;
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
 
@@ -35,7 +36,7 @@
  */
 public class NdResourceFile extends NdTreeNode {
 	public static final FieldSearchKey<JavaIndex> FILENAME;
-	public static final FieldOneToMany<NdBinding> ALL_NODES;
+	public static final FieldOneToMany<NdType> TYPES;
 	public static final FieldLong TIME_LAST_USED;
 	public static final FieldLong TIME_LAST_SCANNED;
 	public static final FieldLong SIZE_LAST_SCANNED;
@@ -44,6 +45,13 @@
 	public static final FieldString JAVA_ROOT;
 	public static final FieldLong JDK_LEVEL;
 	public static final FieldOneToMany<NdZipEntry> ZIP_ENTRIES;
+	public static final FieldString MANIFEST_CONTENT;
+	public static final FieldShort FILE_FLAGS;
+
+	/**
+	 * Flag indicating that this is a corrupted zip file.
+	 */
+	public static final int FLG_CORRUPT_ZIP_FILE = 0x0001;
 
 	@SuppressWarnings("hiding")
 	public static final StructDef<NdResourceFile> type;
@@ -51,7 +59,7 @@
 	static {
 		type = StructDef.create(NdResourceFile.class, NdTreeNode.type);
 		FILENAME = FieldSearchKey.create(type, JavaIndex.FILES);
-		ALL_NODES = FieldOneToMany.create(type, NdBinding.FILE, 16);
+		TYPES = FieldOneToMany.create(type, NdType.FILE, 16);
 		TIME_LAST_USED = type.addLong();
 		TIME_LAST_SCANNED = type.addLong();
 		SIZE_LAST_SCANNED = type.addLong();
@@ -60,6 +68,9 @@
 		JAVA_ROOT = type.addString();
 		JDK_LEVEL = type.addLong();
 		ZIP_ENTRIES = FieldOneToMany.create(type, NdZipEntry.JAR_FILE);
+		MANIFEST_CONTENT = type.addString();
+		FILE_FLAGS = type.addShort();
+
 		type.done();
 	}
 
@@ -73,6 +84,24 @@
 		super(nd, null);
 	}
 
+	public boolean isCorruptedZipFile() {
+		return hasAllFlags(FLG_CORRUPT_ZIP_FILE);
+	}
+
+	public int getFlags() {
+		return FILE_FLAGS.get(getNd(), this.address);
+	}
+
+	public boolean hasAllFlags(int flags) {
+		int ourFlags = getFlags();
+
+		return (ourFlags & flags) == flags;
+	}
+
+	public void setFlags(int flags) {
+		FILE_FLAGS.put(getNd(), this.address, (short) (getFlags() | flags));
+	}
+
 	/**
 	 * Returns the set of all leaf zip entries that are not .class files. Does not include non-empty directories
 	 * or .class files, but will contain all other zip entries from the original jar file. Returns the empty list
@@ -82,6 +111,22 @@
 		return ZIP_ENTRIES.asList(getNd(), getAddress());
 	}
 
+	/**
+	 * Returns the content of the JAR's MANIFEST.MF file, or null if either this isn't a .JAR file or it didn't contain
+	 * a MANIFEST.MF file.
+	 */
+	public IString getManifestContent() {
+		return MANIFEST_CONTENT.get(getNd(), getAddress());
+	}
+
+	/**
+	 * Stores the content of the JAR's MANIFEST.MF file. This should only be invoked on resources that correspond to JAR
+	 * files.
+	 */
+	public void setManifestContent(char[] newContent) {
+		MANIFEST_CONTENT.put(getNd(), getAddress(), newContent);
+	}
+
 	public long getJdkLevel() {
 		if (this.jdkLevel == 0) {
 			this.jdkLevel = JDK_LEVEL.get(getNd(), this.address);
@@ -264,16 +309,16 @@
 		TIME_LAST_SCANNED.put(getNd(), this.address, 0);
 	}
 
-	public int getBindingCount() {
-		return ALL_NODES.size(getNd(), this.address);
+	public int getTypeCount() {
+		return TYPES.size(getNd(), this.address);
 	}
 
-	public List<NdBinding> getBindings() {
-		return ALL_NODES.asList(getNd(), this.address);
+	public List<NdType> getTypes() {
+		return TYPES.asList(getNd(), this.address);
 	}
 
-	public NdBinding getBinding(int index) {
-		return ALL_NODES.get(getNd(), this.address, index);
+	public NdType getType(int index) {
+		return TYPES.get(getNd(), this.address, index);
 	}
 
 	public String toString() {
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdType.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdType.java
index 5d28363..d83f37a 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdType.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdType.java
@@ -25,6 +25,7 @@
 import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
 
 public class NdType extends NdBinding {
+	public static final FieldManyToOne<NdResourceFile> FILE;
 	public static final FieldManyToOne<NdTypeId> TYPENAME;
 	public static final FieldManyToOne<NdTypeSignature> SUPERCLASS;
 	public static final FieldOneToMany<NdTypeInterface> INTERFACES;
@@ -50,6 +51,7 @@
 
 	static {
 		type = StructDef.create(NdType.class, NdBinding.type);
+		FILE = FieldManyToOne.createOwner(type, NdResourceFile.TYPES);
 		TYPENAME = FieldManyToOne.create(type, NdTypeId.TYPES);
 		DECLARING_TYPE = FieldManyToOne.create(type, NdTypeId.DECLARED_TYPES);
 		INTERFACES = FieldOneToMany.create(type, NdTypeInterface.APPLIES_TO);
@@ -77,7 +79,9 @@
 	}
 
 	public NdType(Nd nd, NdResourceFile resource) {
-		super(nd, resource);
+		super(nd);
+
+		FILE.put(nd, this.address, resource);
 	}
 
 	/**
@@ -95,6 +99,14 @@
 		TYPENAME.put(getNd(), this.address, typeId);
 	}
 
+	public void setFile(NdResourceFile file) {
+		FILE.put(getNd(), this.address, file);
+	}
+
+	public NdResourceFile getFile() {
+		return FILE.get(getNd(), this.address);
+	}
+
 	/**
 	 * Sets the source name for this type.
 	 */
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
index 894a1d5..06dcce3 100644
--- 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
@@ -15,14 +15,14 @@
 import org.eclipse.jdt.internal.core.nd.field.StructDef;
 
 public class NdTypeAnnotationInMethod extends NdTypeAnnotation {
-	public static final FieldManyToOne<NdMethod> OWNER;
+	public static final FieldManyToOne<NdMethodAnnotationData> OWNER;
 
 	@SuppressWarnings("hiding")
 	public static final StructDef<NdTypeAnnotationInMethod> type;
 
 	static {
 		type = StructDef.create(NdTypeAnnotationInMethod.class, NdTypeAnnotation.type);
-		OWNER = FieldManyToOne.createOwner(type, NdMethod.TYPE_ANNOTATIONS);
+		OWNER = FieldManyToOne.createOwner(type, NdMethodAnnotationData.TYPE_ANNOTATIONS);
 		type.done();
 	}
 
@@ -30,10 +30,10 @@
 		super(nd, address);
 	}
 
-	public NdTypeAnnotationInMethod(Nd nd, NdMethod variable) {
+	public NdTypeAnnotationInMethod(Nd nd, NdMethod method) {
 		super(nd);
 
-		OWNER.put(getNd(), this.address, variable);
+		OWNER.put(getNd(), this.address, method.createAnnotationData());
 	}
 
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdVariable.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdVariable.java
index e85f805..fabc0e8 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdVariable.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdVariable.java
@@ -45,7 +45,7 @@
 		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);
+		PARENT = FieldManyToOne.createOwner(type, NdBinding.VARIABLES);
 		NAME = type.addString();
 		CONSTANT = FieldOneToOne.create(type, NdConstant.class, NdConstant.PARENT_VARIABLE);
 		TAG_BITS = type.addLong();
@@ -60,7 +60,7 @@
 	}
 
 	public NdVariable(NdBinding parent) {
-		super(parent.getNd(), parent.getFile());
+		super(parent.getNd());
 
 		PARENT.put(getNd(), this.address, parent);
 	}
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
index 631c384..f46fd1f 100644
--- 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
@@ -57,8 +57,6 @@
 		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
@@ -201,54 +199,49 @@
 	 * 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();
+		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 (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
+					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();
+						NdResourceFile resourceFile = index.getResourceFile(descriptor.location);
 						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;
+							return null;
 						}
 						throw new NotInIndexException();
-					} catch (CoreException e) {
-						throw new JavaModelException(e);
 					}
-				} catch (IndexException e) {
-					Package.log("Index corruption detected. Rebuilding index.", e); //$NON-NLS-1$
-					Indexer.getInstance().requestRebuildIndex();
+					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) {
+				Package.log("Index corruption detected. Rebuilding index.", e); //$NON-NLS-1$
+				Indexer.getInstance().requestRebuildIndex();
 			}
 		}
-		
+
 		throw new NotInIndexException();
 	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexerRequestor.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexerRequestor.java
index 384fe31..27941fd 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexerRequestor.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexerRequestor.java
@@ -328,7 +328,10 @@
 	this.indexer.addMethodDeclaration(methodInfo.name, methodInfo.parameterTypes, methodInfo.returnType, methodInfo.exceptionTypes);
 	int argCount = methodInfo.parameterTypes == null ? 0 : methodInfo.parameterTypes.length;
 	char[] typeName = methodInfo.enclosingType != null ? methodInfo.enclosingType.name : null;
-	if (typeName == null || typeName.length == 0) return;
+	if (typeName == null || typeName.length == 0)  {
+		this.methodDepth++;
+		return;
+	}
 	this.indexer.addMethodDeclaration(
 			typeName,
 			getDeclaringQualification(methodInfo.enclosingType),