Upgrade to neon.2 75dbfad0
diff --git a/org.eclipse.jdt.core/antadapter/org/eclipse/jdt/core/BuildJarIndex.java b/org.eclipse.jdt.core/antadapter/org/eclipse/jdt/core/BuildJarIndex.java
index cd7238a..fcb9685 100644
--- a/org.eclipse.jdt.core/antadapter/org/eclipse/jdt/core/BuildJarIndex.java
+++ b/org.eclipse.jdt.core/antadapter/org/eclipse/jdt/core/BuildJarIndex.java
@@ -48,7 +48,7 @@
 		try {
 			JavaIndexer.generateIndexForJar(this.jarPath, this.indexPath);
 		} catch (IOException e) {
-			throw new BuildException(AntAdapterMessages.getString("buildJarIndex.ioexception.occured", e.getLocalizedMessage())); //$NON-NLS-1$
+			throw new BuildException(AntAdapterMessages.getString("buildJarIndex.ioexception.occured", e.getLocalizedMessage()), e); //$NON-NLS-1$
 		}
 	}
 
diff --git a/org.eclipse.jdt.core/antadapter/org/eclipse/jdt/core/CheckDebugAttributes.java b/org.eclipse.jdt.core/antadapter/org/eclipse/jdt/core/CheckDebugAttributes.java
index b6617d7..28488d8 100644
--- a/org.eclipse.jdt.core/antadapter/org/eclipse/jdt/core/CheckDebugAttributes.java
+++ b/org.eclipse.jdt.core/antadapter/org/eclipse/jdt/core/CheckDebugAttributes.java
@@ -60,7 +60,7 @@
 				try {
 					jarFile = new ZipFile(this.file);
 				} catch (ZipException e) {
-					throw new BuildException(AntAdapterMessages.getString("checkDebugAttributes.file.argument.must.be.a.classfile.or.a.jarfile")); //$NON-NLS-1$
+					throw new BuildException(AntAdapterMessages.getString("checkDebugAttributes.file.argument.must.be.a.classfile.or.a.jarfile"), e); //$NON-NLS-1$
 				} finally {
 					if (jarFile != null) {
 						jarFile.close();
@@ -78,7 +78,7 @@
 				getProject().setUserProperty(this.property, "has debug"); //$NON-NLS-1$
 			}
 		} catch (IOException e) {
-			throw new BuildException(AntAdapterMessages.getString("checkDebugAttributes.ioexception.occured") + this.file); //$NON-NLS-1$
+			throw new BuildException(AntAdapterMessages.getString("checkDebugAttributes.ioexception.occured") + this.file, e); //$NON-NLS-1$
 		}
 	}
 
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 c2bdf71..507f55d 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
@@ -84,7 +84,7 @@
 			}
 			return resultValue;
 		} catch (ClassNotFoundException cnfe) {
-			throw new BuildException(AntAdapterMessages.getString("ant.jdtadapter.error.cannotFindJDTCompiler")); //$NON-NLS-1$
+			throw new BuildException(AntAdapterMessages.getString("ant.jdtadapter.error.cannotFindJDTCompiler"), cnfe); //$NON-NLS-1$
 		} catch (Exception ex) {
 			throw new BuildException(ex);
 		}
diff --git a/org.eclipse.jdt.core/apt/org/eclipse/jdt/internal/compiler/apt/model/TypesImpl.java b/org.eclipse.jdt.core/apt/org/eclipse/jdt/internal/compiler/apt/model/TypesImpl.java
index 6f5c2a5..b0ac625 100644
--- a/org.eclipse.jdt.core/apt/org/eclipse/jdt/internal/compiler/apt/model/TypesImpl.java
+++ b/org.eclipse.jdt.core/apt/org/eclipse/jdt/internal/compiler/apt/model/TypesImpl.java
@@ -8,6 +8,7 @@
  * Contributors:
  *    Walter Harley - initial API and implementation
  *    IBM Corporation - fix for 342598, 382590
+ *    Jean-Marie Henaff <jmhenaff@google.com> (Google) - Bug 481555
  *******************************************************************************/
 
 package org.eclipse.jdt.internal.compiler.apt.model;
@@ -74,62 +75,124 @@
     }
 
     @Override
-    public TypeMirror asMemberOf(DeclaredType containing, Element element) {
-//        throw new UnsupportedOperationException("NYI: TypesImpl.asMemberOf(" + containing + ", " + element + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-    	ElementImpl elementImpl = (ElementImpl) element;
-    	DeclaredTypeImpl declaredTypeImpl = (DeclaredTypeImpl) containing;
-    	ReferenceBinding referenceBinding = (ReferenceBinding) declaredTypeImpl._binding;
-    	switch(element.getKind()) {
-    		case CONSTRUCTOR :
-    		case METHOD :
-    			MethodBinding methodBinding = (MethodBinding) elementImpl._binding;
-    			while (referenceBinding != null) {
-                    for (MethodBinding method : referenceBinding.methods()) {
-                        if (CharOperation.equals(method.selector, methodBinding.selector) &&
-                                (method.original() == methodBinding || method.areParameterErasuresEqual(methodBinding))) {
-                            return this._env.getFactory().newTypeMirror(method);
-                        }
-                    }
-                    referenceBinding = referenceBinding.superclass();
-                }
-    			break;
-    		case FIELD :
-    		case ENUM_CONSTANT:
-    			FieldBinding fieldBinding = (FieldBinding) elementImpl._binding;
-                while (referenceBinding != null) {
-                    for (FieldBinding field : referenceBinding.fields()) {
-                        if (CharOperation.equals(field.name, fieldBinding.name)) {
-                            return this._env.getFactory().newTypeMirror(field);
-                        }
-                    }
-                    referenceBinding = referenceBinding.superclass();
-                }
-    			break;
-    		case ENUM :
-    		case ANNOTATION_TYPE :
-    		case INTERFACE :
-    		case CLASS :
-    			ReferenceBinding elementBinding = (ReferenceBinding) elementImpl._binding;
-                while (referenceBinding != null) {
-                    // If referenceBinding is a ParameterizedTypeBinding, this will return only ParameterizedTypeBindings
-                    // for member types, even if the member happens to be a static nested class. That's probably a bug;
-                    // static nested classes are not parameterized by their outer class.
-                    for (ReferenceBinding memberReferenceBinding : referenceBinding.memberTypes()) {
-                        if (CharOperation.equals(elementBinding.compoundName, memberReferenceBinding.compoundName)) {
-                            return this._env.getFactory().newTypeMirror(memberReferenceBinding);
-                        }
-                    }
-                    referenceBinding = referenceBinding.superclass();
-                }
-    			break;
-    		default:
-                throw new IllegalArgumentException("element " + element + //$NON-NLS-1$
-                        " has unrecognized element kind " + element.getKind()); //$NON-NLS-1$
-            }
-            throw new IllegalArgumentException("element " + element + //$NON-NLS-1$
-                    " is not a member of the containing type " + containing +  //$NON-NLS-1$
-                    " nor any of its superclasses"); //$NON-NLS-1$
-    }
+	public TypeMirror asMemberOf(DeclaredType containing, Element element) {
+		// throw new UnsupportedOperationException("NYI: TypesImpl.asMemberOf("
+		// + containing + ", " + element + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+		// //$NON-NLS-3$
+		ElementImpl elementImpl = (ElementImpl) element;
+		DeclaredTypeImpl declaredTypeImpl = (DeclaredTypeImpl) containing;
+		ReferenceBinding referenceBinding = (ReferenceBinding) declaredTypeImpl._binding;
+		TypeMirror typeMirror;
+
+		switch (element.getKind()) {
+		case CONSTRUCTOR:
+		case METHOD:
+			typeMirror = findMemberInHierarchy(referenceBinding, elementImpl._binding, new MemberInTypeFinder() {
+				@Override
+				public TypeMirror find(ReferenceBinding typeBinding, Binding memberBinding) {
+					MethodBinding methodBinding = ((MethodBinding) memberBinding);
+					for (MethodBinding method : typeBinding.methods()) {
+						if (CharOperation.equals(method.selector, methodBinding.selector)
+								&& (method.original() == methodBinding
+										|| method.areParameterErasuresEqual(methodBinding))) {
+							return TypesImpl.this._env.getFactory().newTypeMirror(method);
+						}
+					}
+					return null;
+				}
+			});
+
+			if (typeMirror != null) {
+				return typeMirror;
+			}
+			break;
+		case FIELD:
+		case ENUM_CONSTANT:
+			typeMirror = findMemberInHierarchy(referenceBinding, elementImpl._binding, new MemberInTypeFinder() {
+				@Override
+				public TypeMirror find(ReferenceBinding typeBinding, Binding memberBinding) {
+					FieldBinding fieldBinding = (FieldBinding) memberBinding;
+					for (FieldBinding field : typeBinding.fields()) {
+						if (CharOperation.equals(field.name, fieldBinding.name)) {
+							return TypesImpl.this._env.getFactory().newTypeMirror(field);
+						}
+					}
+					return null;
+				}
+			});
+
+			if (typeMirror != null) {
+				return typeMirror;
+			}
+			break;
+		case ENUM:
+		case ANNOTATION_TYPE:
+		case INTERFACE:
+		case CLASS:
+
+			typeMirror = findMemberInHierarchy(referenceBinding, elementImpl._binding, new MemberInTypeFinder() {
+				@Override
+				public TypeMirror find(ReferenceBinding typeBinding, Binding memberBinding) {
+					ReferenceBinding elementBinding = (ReferenceBinding) memberBinding;
+					// If referenceBinding is a ParameterizedTypeBinding, this
+					// will return only ParameterizedTypeBindings
+					// for member types, even if the member happens to be a
+					// static nested class. That's probably a bug;
+					// static nested classes are not parameterized by their
+					// outer class.
+					for (ReferenceBinding memberReferenceBinding : typeBinding.memberTypes()) {
+						if (CharOperation.equals(elementBinding.compoundName, memberReferenceBinding.compoundName)) {
+							return TypesImpl.this._env.getFactory().newTypeMirror(memberReferenceBinding);
+						}
+					}
+					return null;
+				}
+			});
+
+			if (typeMirror != null) {
+				return typeMirror;
+			}
+			break;
+		default:
+			throw new IllegalArgumentException("element " + element + //$NON-NLS-1$
+					" has unrecognized element kind " + element.getKind()); //$NON-NLS-1$
+		}
+		throw new IllegalArgumentException("element " + element + //$NON-NLS-1$
+				" is not a member of the containing type " + containing + //$NON-NLS-1$
+				" nor any of its superclasses"); //$NON-NLS-1$
+	}
+
+	private static interface MemberInTypeFinder {
+		TypeMirror find(ReferenceBinding typeBinding, Binding memberBinding);
+	}
+
+	private TypeMirror findMemberInHierarchy(ReferenceBinding typeBinding, Binding memberBinding,
+			MemberInTypeFinder finder) {
+		TypeMirror result = null;
+
+		if (typeBinding == null) {
+			return null;
+		}
+
+		result = finder.find(typeBinding, memberBinding);
+		if (result != null) {
+			return result;
+		}
+
+		result = findMemberInHierarchy(typeBinding.superclass(), memberBinding, finder);
+		if (result != null) {
+			return result;
+		}
+
+		for (ReferenceBinding superInterface : typeBinding.superInterfaces()) {
+			result = findMemberInHierarchy(superInterface, memberBinding, finder);
+			if (result != null) {
+				return result;
+			}
+		}
+
+		return null;
+	}
 
     @Override
     public TypeElement boxedClass(PrimitiveType p) {
diff --git a/org.eclipse.jdt.core/apt/org/eclipse/jdt/internal/compiler/apt/util/EclipseFileManager.java b/org.eclipse.jdt.core/apt/org/eclipse/jdt/internal/compiler/apt/util/EclipseFileManager.java
index 96bd353..2319696 100644
--- a/org.eclipse.jdt.core/apt/org/eclipse/jdt/internal/compiler/apt/util/EclipseFileManager.java
+++ b/org.eclipse.jdt.core/apt/org/eclipse/jdt/internal/compiler/apt/util/EclipseFileManager.java
@@ -452,7 +452,7 @@
 					}
 					uri2 = new URI(uri.getScheme(), uri.getHost(), path, uri.getFragment());
 				} catch (URISyntaxException e) {
-					throw new IllegalArgumentException("invalid sibling");//$NON-NLS-1$
+					throw new IllegalArgumentException("invalid sibling", e);//$NON-NLS-1$
 				}
 				return new EclipseFileObject(className, uri2, kind, this.charset);
 			} else {
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 e1d1283..2bfd704 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
@@ -1223,9 +1223,9 @@
 					this.log.println("# " + dateFormat.format(date));//$NON-NLS-1$
 				}
 			} catch (FileNotFoundException e) {
-				throw new IllegalArgumentException(this.main.bind("configure.cannotOpenLog", logFileName)); //$NON-NLS-1$
+				throw new IllegalArgumentException(this.main.bind("configure.cannotOpenLog", logFileName), e); //$NON-NLS-1$
 			} catch (UnsupportedEncodingException e) {
-				throw new IllegalArgumentException(this.main.bind("configure.cannotOpenLogInvalidEncoding", logFileName)); //$NON-NLS-1$
+				throw new IllegalArgumentException(this.main.bind("configure.cannotOpenLogInvalidEncoding", logFileName), e); //$NON-NLS-1$
 			}
 		}
 		private void startLoggingExtraProblems(int count) {
@@ -1955,7 +1955,7 @@
 								new InputStreamReader(new ByteArrayInputStream(new byte[0]), customEncoding);
 							} catch (UnsupportedEncodingException e) {
 								throw new IllegalArgumentException(
-									this.bind("configure.unsupportedEncoding", customEncoding)); //$NON-NLS-1$
+									this.bind("configure.unsupportedEncoding", customEncoding), e); //$NON-NLS-1$
 							}
 						}
 						currentArg = currentArg.substring(0, encodingStart - 1);
@@ -2547,7 +2547,7 @@
 						throw new IllegalArgumentException(this.bind("configure.repetition", currentArg)); //$NON-NLS-1$
 					}
 				} catch (NumberFormatException e) {
-					throw new IllegalArgumentException(this.bind("configure.repetition", currentArg)); //$NON-NLS-1$
+					throw new IllegalArgumentException(this.bind("configure.repetition", currentArg), e); //$NON-NLS-1$
 				}
 				mode = DEFAULT;
 				continue;
@@ -2559,7 +2559,7 @@
 					}
 					this.options.put(CompilerOptions.OPTION_MaxProblemPerUnit, currentArg);
 				} catch (NumberFormatException e) {
-					throw new IllegalArgumentException(this.bind("configure.maxProblems", currentArg)); //$NON-NLS-1$
+					throw new IllegalArgumentException(this.bind("configure.maxProblems", currentArg), e); //$NON-NLS-1$
 				}
 				mode = DEFAULT;
 				continue;
@@ -2609,7 +2609,7 @@
 					new InputStreamReader(new ByteArrayInputStream(new byte[0]), currentArg);
 				} catch (UnsupportedEncodingException e) {
 					throw new IllegalArgumentException(
-						this.bind("configure.unsupportedEncoding", currentArg)); //$NON-NLS-1$
+						this.bind("configure.unsupportedEncoding", currentArg), e); //$NON-NLS-1$
 				}
 				specifiedEncodings.add(currentArg);
 				this.options.put(CompilerOptions.OPTION_Encoding, currentArg);
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/CharOperation.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/CharOperation.java
index 7eafeee..23b3817 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/CharOperation.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/CharOperation.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
@@ -9,9 +9,12 @@
  *     IBM Corporation - initial API and implementation
  *     Luiz-Otavio Zorzella <zorzella at gmail dot com> - Improve CamelCase algorithm
  *     Gábor Kövesdán - Contribution for Bug 350000 - [content assist] Include non-prefix matches in auto-complete suggestions
+ *     Stefan Xenos <sxenos@gmail.com> (Google) - Bug 501283 - Lots of hash collisions during indexing
  *******************************************************************************/
 package org.eclipse.jdt.core.compiler;
 
+import java.util.Arrays;
+
 import org.eclipse.jdt.internal.compiler.parser.ScannerHelper;
 
 /**
@@ -2281,19 +2284,9 @@
  *
  * @param array the array for which a hashcode is required
  * @return the hashcode
- * @throws NullPointerException if array is null
  */
 public static final int hashCode(char[] array) {
-	int length = array.length;
-	int hash = length == 0 ? 31 : array[0];
-	if (length < 8) {
-		for (int i = length; --i > 0;)
-			hash = (hash * 31) + array[i];
-	} else {
-		// 8 characters is enough to compute a decent hash code, don't waste time examining every character
-		for (int i = length - 1, last = i > 16 ? i - 16 : 0; i > last; i -= 2)
-			hash = (hash * 31) + array[i];
-	}
+	int hash = Arrays.hashCode(array);
 	return hash & 0x7FFFFFFF;
 }
 
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java
index 671edcf..c0d8ebd 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java
@@ -1894,6 +1894,12 @@
 	int IllegalDefaultModifierSpecification = MethodRelated + 1058;
 	/** @since 3.13 */
 	int CannotInferInvocationType = TypeRelated + 1059;
+	
+	/** @since 3.13 */
+	int TypeAnnotationAtQualifiedName = Internal + Syntax + 1060;
+
+	/** @since 3.13 */
+	int NullAnnotationAtQualifyingType = Internal + Syntax + 1061;
 
 	/** @since 3.10 */
 	int GenericInferenceError = 1100; 	// FIXME: This is just a stop-gap measure, be more specific via https://bugs.eclipse.org/404675
@@ -1901,4 +1907,6 @@
 	/** @deprecated - problem is no longer generated (implementation issue has been resolved)
 	 * @since 3.10 */
 	int LambdaShapeComputationError = 1101;
+	/** @since 3.13 */
+	int ProblemNotAnalysed = 1102;
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java
index 12639ff..5d4fb85 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java
@@ -695,8 +695,11 @@
 				} else {
 					updatedArgumentType = argument.resolveType(scope);
 				}
-				if (updatedArgumentType != null && updatedArgumentType.kind() != Binding.POLY_TYPE)
+				if (updatedArgumentType != null && updatedArgumentType.kind() != Binding.POLY_TYPE) {
 					argumentTypes[i] = updatedArgumentType;
+					if (candidateMethod.isPolymorphic())
+						candidateMethod.parameters[i] = updatedArgumentType;
+				}
 			}
 		}
 	}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java
index 9606e2b..9f06279 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java
@@ -644,7 +644,7 @@
 			// no need to check annotation usage if missing
 			return;
 		}
-		if (! isAnnotationTargetAllowed(repeatingAnnotation, scope, containerAnnotationType, repeatingAnnotation.recipient.kind())) {
+		if (isAnnotationTargetAllowed(repeatingAnnotation, scope, containerAnnotationType, repeatingAnnotation.recipient.kind()) != AnnotationTargetAllowed.YES) {
 			scope.problemReporter().disallowedTargetForContainerAnnotation(repeatingAnnotation, containerAnnotationType);
 		}
 	}
@@ -961,63 +961,71 @@
 		return this.resolvedType;
 	}
 
-	private static boolean isAnnotationTargetAllowed(Binding recipient, BlockScope scope, TypeBinding annotationType, int kind, long metaTagBits) {
+	public enum AnnotationTargetAllowed {
+		YES, TYPE_ANNOTATION_ON_QUALIFIED_NAME, NO;
+	}
+	
+	private static AnnotationTargetAllowed isAnnotationTargetAllowed(Binding recipient, BlockScope scope, TypeBinding annotationType, int kind, long metaTagBits) {
 		switch (kind) {
 			case Binding.PACKAGE :
 				if ((metaTagBits & TagBits.AnnotationForPackage) != 0)
-					return true;
+					return AnnotationTargetAllowed.YES;
 				else if (scope.compilerOptions().sourceLevel <= ClassFileConstants.JDK1_6) {
 					SourceTypeBinding sourceType = (SourceTypeBinding) recipient;
 					if (CharOperation.equals(sourceType.sourceName, TypeConstants.PACKAGE_INFO_NAME))
-						return true;
+						return AnnotationTargetAllowed.YES;
 				}
 				break;
 			case Binding.TYPE_USE :
 				if ((metaTagBits & TagBits.AnnotationForTypeUse) != 0) {
 					// jsr 308
-					return true;
+					return AnnotationTargetAllowed.YES;
 				}
 				if (scope.compilerOptions().sourceLevel < ClassFileConstants.JDK1_8) {
 					// already reported as syntax error; don't report secondary problems
-					return true;
+					return AnnotationTargetAllowed.YES;
 				}
 				break;
 			case Binding.TYPE :
 			case Binding.GENERIC_TYPE :
 				if (((ReferenceBinding)recipient).isAnnotationType()) {
 					if ((metaTagBits & (TagBits.AnnotationForAnnotationType | TagBits.AnnotationForType | TagBits.AnnotationForTypeUse)) != 0)
-					return true;
+					return AnnotationTargetAllowed.YES;
 				} else if ((metaTagBits & (TagBits.AnnotationForType | TagBits.AnnotationForTypeUse)) != 0) {
-					return true;
+					return AnnotationTargetAllowed.YES;
 				} else if ((metaTagBits & TagBits.AnnotationForPackage) != 0) {
 					if (CharOperation.equals(((ReferenceBinding) recipient).sourceName, TypeConstants.PACKAGE_INFO_NAME))
-						return true;
+						return AnnotationTargetAllowed.YES;
 				}
 				break;
 			case Binding.METHOD :
 				MethodBinding methodBinding = (MethodBinding) recipient;
 				if (methodBinding.isConstructor()) {
 					if ((metaTagBits & (TagBits.AnnotationForConstructor | TagBits.AnnotationForTypeUse)) != 0)
-						return true;
+						return AnnotationTargetAllowed.YES;
 				} else if ((metaTagBits & TagBits.AnnotationForMethod) != 0) {
-					return true;
+					return AnnotationTargetAllowed.YES;
 				} else if ((metaTagBits & TagBits.AnnotationForTypeUse) != 0) {
 					SourceTypeBinding sourceType = (SourceTypeBinding) methodBinding.declaringClass;
 					MethodDeclaration methodDecl = (MethodDeclaration) sourceType.scope.referenceContext.declarationOf(methodBinding);
 					if (isTypeUseCompatible(methodDecl.returnType, scope)) {
-						return true;
+						return AnnotationTargetAllowed.YES;
+					} else {
+						return AnnotationTargetAllowed.TYPE_ANNOTATION_ON_QUALIFIED_NAME;
 					}
 				}
 				break;
 			case Binding.FIELD :
 				if ((metaTagBits & TagBits.AnnotationForField) != 0) {
-					return true;
+					return AnnotationTargetAllowed.YES;
 				} else if ((metaTagBits & TagBits.AnnotationForTypeUse) != 0) {
 					FieldBinding sourceField = (FieldBinding) recipient;
 					SourceTypeBinding sourceType = (SourceTypeBinding) sourceField.declaringClass;
 					FieldDeclaration fieldDeclaration = sourceType.scope.referenceContext.declarationOf(sourceField);
 					if (isTypeUseCompatible(fieldDeclaration.type, scope)) {
-						return true;
+						return AnnotationTargetAllowed.YES;
+					} else {
+						return AnnotationTargetAllowed.TYPE_ANNOTATION_ON_QUALIFIED_NAME;
 					}
 				}
 				break;
@@ -1025,27 +1033,31 @@
 				LocalVariableBinding localVariableBinding = (LocalVariableBinding) recipient;
 				if ((localVariableBinding.tagBits & TagBits.IsArgument) != 0) {
 					if ((metaTagBits & TagBits.AnnotationForParameter) != 0) {
-						return true;
+						return AnnotationTargetAllowed.YES;
 					} else if ((metaTagBits & TagBits.AnnotationForTypeUse) != 0) {
 						if (isTypeUseCompatible(localVariableBinding.declaration.type, scope)) {
-							return true;
+							return AnnotationTargetAllowed.YES;
+						} else {
+							return AnnotationTargetAllowed.TYPE_ANNOTATION_ON_QUALIFIED_NAME;
 						}
 					}
 				} else if ((annotationType.tagBits & TagBits.AnnotationForLocalVariable) != 0) {
-					return true;
+					return AnnotationTargetAllowed.YES;
 				} else if ((metaTagBits & TagBits.AnnotationForTypeUse) != 0) {
 					if (isTypeUseCompatible(localVariableBinding.declaration.type, scope)) {
-						return true;
+						return AnnotationTargetAllowed.YES;
+					} else {
+						return AnnotationTargetAllowed.TYPE_ANNOTATION_ON_QUALIFIED_NAME;
 					}
 				}
 				break;
 			case Binding.TYPE_PARAMETER : // jsr308
 				// https://bugs.eclipse.org/bugs/show_bug.cgi?id=391196
 				if ((metaTagBits & (TagBits.AnnotationForTypeParameter | TagBits.AnnotationForTypeUse)) != 0) {
-					return true;
+					return AnnotationTargetAllowed.YES;
 				}
 		}
-		return false;
+		return AnnotationTargetAllowed.NO;
 	}
 
 	public static boolean isAnnotationTargetAllowed(BlockScope scope, TypeBinding annotationType, Binding recipient) {
@@ -1053,10 +1065,10 @@
 		if ((metaTagBits & TagBits.AnnotationTargetMASK) == 0) {
 			return true;
 		}
-		return isAnnotationTargetAllowed(recipient, scope, annotationType, recipient.kind(), metaTagBits);
+		return isAnnotationTargetAllowed(recipient, scope, annotationType, recipient.kind(), metaTagBits)==AnnotationTargetAllowed.YES;
 	}
 
-	static boolean isAnnotationTargetAllowed(Annotation annotation, BlockScope scope, TypeBinding annotationType, int kind) {
+	static AnnotationTargetAllowed isAnnotationTargetAllowed(Annotation annotation, BlockScope scope, TypeBinding annotationType, int kind) {
 
 		long metaTagBits = annotationType.getAnnotationTagBits(); // could be forward reference
 		if ((metaTagBits & TagBits.AnnotationTargetMASK) == 0) {
@@ -1064,7 +1076,7 @@
 			if (kind == Binding.TYPE_PARAMETER || kind == Binding.TYPE_USE) {
 				scope.problemReporter().explitAnnotationTargetRequired(annotation);
 			}
-			return true;
+			return AnnotationTargetAllowed.YES;
 		}
 
 		// https://bugs.eclipse.org/bugs/show_bug.cgi?id=391201
@@ -1091,8 +1103,13 @@
 			// no need to check annotation usage if missing
 			return;
 		}
-		if (! isAnnotationTargetAllowed(annotation, scope, annotationType, kind)) {
+	AnnotationTargetAllowed annotationTargetAllowed = isAnnotationTargetAllowed(annotation, scope, annotationType, kind);
+	if (annotationTargetAllowed != AnnotationTargetAllowed.YES) {
+		if(annotationTargetAllowed == AnnotationTargetAllowed.TYPE_ANNOTATION_ON_QUALIFIED_NAME) {
+			scope.problemReporter().typeAnnotationAtQualifiedName(annotation);			
+		} else {
 			scope.problemReporter().disallowedTargetForAnnotation(annotation);
+		}
 			if (recipient instanceof TypeBinding)
 				((TypeBinding)recipient).tagBits &= ~tagBitsToRevert;
 		}
@@ -1161,7 +1178,7 @@
 							continue nextAnnotation;
 						} else {
 							if (annotation.hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation)) {
-								scope.problemReporter().nullAnnotationUnsupportedLocation(annotation);
+								scope.problemReporter().nullAnnotationAtQualifyingType(annotation);
 								continue nextAnnotation;
 							}
 						}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CastExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CastExpression.java
index 00b8bea..1c7dafb 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CastExpression.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CastExpression.java
@@ -267,6 +267,9 @@
 }
 
 public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) {
+	if((this.resolvedType.tagBits & TagBits.AnnotationNonNull) != 0) {
+		return true;
+	}
 	checkNPEbyUnboxing(scope, flowContext, flowInfo);
 	return this.expression.checkNPE(scope, flowContext, flowInfo, ttlForFieldCheck);
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java
index 895ea19..acb079b 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java
@@ -299,22 +299,28 @@
 										Constant cst = inits[iToken].constant;
 										if (cst != Constant.NotAConstant && cst.typeID() == TypeIds.T_JavaLangString) {
 											IrritantSet tokenIrritants = CompilerOptions.warningTokenToIrritants(cst.stringValue());
-											if (tokenIrritants != null
-													&& !tokenIrritants.areAllSet() // no complaint against @SuppressWarnings("all")
-													&& options.isAnyEnabled(tokenIrritants) // if irritant is effectively enabled
-													&& (foundIrritants[iSuppress] == null || !foundIrritants[iSuppress].isAnySet(tokenIrritants))) { // if irritant had no matching problem
-												if (unusedWarningTokenIsWarning) {
-													int start = value.sourceStart, end = value.sourceEnd;
-													nextSuppress: for (int jSuppress = iSuppress - 1; jSuppress >= 0; jSuppress--) {
-														long position = this.suppressWarningScopePositions[jSuppress];
-														int startSuppress = (int) (position >>> 32);
-														int endSuppress = (int) position;
-														if (start < startSuppress) continue nextSuppress;
-														if (end > endSuppress) continue nextSuppress;
-														if (this.suppressWarningIrritants[jSuppress].areAllSet()) break pairLoop; // suppress all?
+											if (tokenIrritants != null) {
+												if (!tokenIrritants.areAllSet() // no complaint against @SuppressWarnings("all")
+														&& (foundIrritants[iSuppress] == null || !foundIrritants[iSuppress].isAnySet(tokenIrritants))) { // if irritant had no matching problem
+													if (unusedWarningTokenIsWarning) {
+														int start = value.sourceStart, end = value.sourceEnd;
+														nextSuppress: for (int jSuppress = iSuppress - 1; jSuppress >= 0; jSuppress--) {
+															long position = this.suppressWarningScopePositions[jSuppress];
+															int startSuppress = (int) (position >>> 32);
+															int endSuppress = (int) position;
+															if (start < startSuppress) continue nextSuppress;
+															if (end > endSuppress) continue nextSuppress;
+															if (this.suppressWarningIrritants[jSuppress].areAllSet()) break pairLoop; // suppress all?
+														}
+													}
+													int id = options.getIgnoredIrritant(tokenIrritants);
+													if (id > 0) {
+														String key = CompilerOptions.optionKeyFromIrritant(id);
+														this.scope.problemReporter().problemNotAnalysed(inits[iToken], key);
+													} else {
+														this.scope.problemReporter().unusedWarningToken(inits[iToken]);														
 													}
 												}
-												this.scope.problemReporter().unusedWarningToken(inits[iToken]);
 											}
 										}
 									}
@@ -323,22 +329,28 @@
 								Constant cst = value.constant;
 								if (cst != Constant.NotAConstant && cst.typeID() == T_JavaLangString) {
 									IrritantSet tokenIrritants = CompilerOptions.warningTokenToIrritants(cst.stringValue());
-									if (tokenIrritants != null
-											&& !tokenIrritants.areAllSet() // no complaint against @SuppressWarnings("all")
-											&& options.isAnyEnabled(tokenIrritants) // if irritant is effectively enabled
-											&& (foundIrritants[iSuppress] == null || !foundIrritants[iSuppress].isAnySet(tokenIrritants))) { // if irritant had no matching problem
-										if (unusedWarningTokenIsWarning) {
-											int start = value.sourceStart, end = value.sourceEnd;
-											nextSuppress: for (int jSuppress = iSuppress - 1; jSuppress >= 0; jSuppress--) {
-												long position = this.suppressWarningScopePositions[jSuppress];
-												int startSuppress = (int) (position >>> 32);
-												int endSuppress = (int) position;
-												if (start < startSuppress) continue nextSuppress;
-												if (end > endSuppress) continue nextSuppress;
-												if (this.suppressWarningIrritants[jSuppress].areAllSet()) break pairLoop; // suppress all?
+									if (tokenIrritants != null) {
+										if (!tokenIrritants.areAllSet() // no complaint against @SuppressWarnings("all")
+												&& (foundIrritants[iSuppress] == null || !foundIrritants[iSuppress].isAnySet(tokenIrritants))) { // if irritant had no matching problem
+											if (unusedWarningTokenIsWarning) {
+												int start = value.sourceStart, end = value.sourceEnd;
+												nextSuppress: for (int jSuppress = iSuppress - 1; jSuppress >= 0; jSuppress--) {
+													long position = this.suppressWarningScopePositions[jSuppress];
+													int startSuppress = (int) (position >>> 32);
+													int endSuppress = (int) position;
+													if (start < startSuppress) continue nextSuppress;
+													if (end > endSuppress) continue nextSuppress;
+													if (this.suppressWarningIrritants[jSuppress].areAllSet()) break pairLoop; // suppress all?
+												}
+											}
+											int id = options.getIgnoredIrritant(tokenIrritants);
+											if (id > 0) {
+												String key = CompilerOptions.optionKeyFromIrritant(id);
+												this.scope.problemReporter().problemNotAnalysed(value, key);
+											} else {
+												this.scope.problemReporter().unusedWarningToken(value);
 											}
 										}
-										this.scope.problemReporter().unusedWarningToken(value);
 									}
 								}
 							}
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 ff0850e..789c72d 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
@@ -670,13 +670,13 @@
 	// Answer the signature return type, answers PolyTypeBinding if a poly expression and there is no target type  
 	// Base type promotion
 	if (this.constant != Constant.NotAConstant) {
-	this.constant = Constant.NotAConstant;
-	long sourceLevel = scope.compilerOptions().sourceLevel;
-	boolean receiverCast = false;
-	if (this.receiver instanceof CastExpression) {
-		this.receiver.bits |= ASTNode.DisableUnnecessaryCastCheck; // will check later on
-		receiverCast = true;
-	}
+		this.constant = Constant.NotAConstant;
+		long sourceLevel = scope.compilerOptions().sourceLevel;
+		boolean receiverCast = false;
+		if (this.receiver instanceof CastExpression) {
+			this.receiver.bits |= ASTNode.DisableUnnecessaryCastCheck; // will check later on
+			receiverCast = true;
+		}
 	// AspectJ Extension: commenting this out for now. An InterTypeScope has been observed
 	// to have an already resolved receiver
 //	if (this.receiver.resolvedType != null)
@@ -689,8 +689,11 @@
 	this.receiverIsType = this.receiver instanceof NameReference && (((NameReference) this.receiver).bits & Binding.TYPE) != 0;
 	if (receiverCast && this.actualReceiverType != null) {
 		 // due to change of declaring class with receiver type, only identity cast should be notified
-		if (TypeBinding.equalsEquals(((CastExpression)this.receiver).expression.resolvedType, this.actualReceiverType)) {
-			scope.problemReporter().unnecessaryCast((CastExpression)this.receiver);
+			TypeBinding resolvedType2 = ((CastExpression)this.receiver).expression.resolvedType;
+			if (TypeBinding.equalsEquals(resolvedType2, this.actualReceiverType)) {
+				if (!scope.environment().usesNullTypeAnnotations() || !NullAnnotationMatching.analyse(this.actualReceiverType, resolvedType2, -1).isAnyMismatch()) {
+				scope.problemReporter().unnecessaryCast((CastExpression)this.receiver);
+			}
 		}
 	}
 	// resolve type arguments (for generic constructor call)
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedTypeReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedTypeReference.java
index 537bcbe..b10f83e 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedTypeReference.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedTypeReference.java
@@ -75,8 +75,15 @@
 		for (int j = 0; j < i; j++) {
 			Annotation[] qualifierAnnot = this.annotations[j];
 			if (qualifierAnnot != null && qualifierAnnot.length > 0) {
-				scope.problemReporter().misplacedTypeAnnotations(qualifierAnnot[0], qualifierAnnot[qualifierAnnot.length - 1]);
-				this.annotations[j] = null;
+				if (j == 0) {
+					for (int k = 0; k < qualifierAnnot.length; k++) {
+						scope.problemReporter().typeAnnotationAtQualifiedName(qualifierAnnot[k]);
+					}
+				} else {
+					scope.problemReporter().misplacedTypeAnnotations(qualifierAnnot[0],
+							qualifierAnnot[qualifierAnnot.length - 1]);
+					this.annotations[j] = null;
+				}
 			}
 		}
 	}
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 004ddaf..3a2af2c 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
@@ -239,7 +239,7 @@
 		}
 		
 		// Process the lambda, taking care not to double report diagnostics. Don't expect any from resolve, Any from code generation should surface, but not those from flow analysis.
-		implicitLambda.resolve(currentScope);
+		implicitLambda.resolveType(currentScope, true);
 		IErrorHandlingPolicy oldPolicy = currentScope.problemReporter().switchErrorHandlingPolicy(silentErrorHandlingPolicy);
 		try {
 			implicitLambda.analyseCode(currentScope, 
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 cf7b55c..fd6da1b 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
@@ -586,7 +586,8 @@
 				if (an == NULLABLE || an == NONNULL)
 					start++;
 			}
-			int end = wrapperWithStart(start).computeEnd();
+			SignatureWrapper wrapper1 = wrapperWithStart(start);
+			int end = wrapper1.skipAngleContents(wrapper1.computeEnd());
 			return end;
 		}
 		
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 30dbbfc..6e1f66d 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
@@ -2730,7 +2730,7 @@
 				invoke(Opcodes.OPC_invokevirtual, 1, 1,
 						ConstantPool.JavaLangInvokeSerializedLambdaConstantPoolName,
 					ConstantPool.GetCapturedArg, ConstantPool.GetCapturedArgSignature);
-				checkcast(isLambda ? mb.declaringClass : ((ReferenceExpression)funcEx).receiverType);
+				checkcast(mb.declaringClass);
 			sig.append(mb.declaringClass.signature());
 		}
 			for (int p = 0, max = outerLocalVariables == null ? 0 : outerLocalVariables.length; p < max; p++) {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/ConditionalFlowInfo.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/ConditionalFlowInfo.java
index 288fb8f..2d5fd51 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/ConditionalFlowInfo.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/ConditionalFlowInfo.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
@@ -214,7 +214,9 @@
 public UnconditionalFlowInfo mergedWith(UnconditionalFlowInfo otherInits) {
 	return unconditionalInits().mergedWith(otherInits);
 }
-
+public UnconditionalFlowInfo mergeDefiniteInitsWith(UnconditionalFlowInfo otherInits) {
+	return unconditionalInits().mergeDefiniteInitsWith(otherInits);
+}
 public UnconditionalFlowInfo nullInfoLessUnconditionalCopy() {
 	return unconditionalInitsWithoutSideEffect().
 		nullInfoLessUnconditionalCopy();
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowInfo.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowInfo.java
index ad63440..67194d5 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowInfo.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowInfo.java
@@ -538,7 +538,8 @@
 			unconditionalInits();
 		// if a variable is only initialized in one branch and not initialized in the other,
 		// then we need to cast a doubt on its initialization in the merged info
-		mergedInfo.definiteInits &= initsWhenFalse.unconditionalCopy().definiteInits;
+		mergedInfo.mergeDefiniteInitsWith(initsWhenFalse.unconditionalCopy());
+		
 		// https://bugs.eclipse.org/bugs/show_bug.cgi?id=415997, classify unreachability precisely, IsElseStatementUnreachable could be due to null analysis
 		if ((mergedInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 0 && (initsWhenFalse.tagBits & FlowInfo.UNREACHABLE) == FlowInfo.UNREACHABLE_BY_NULLANALYSIS) {
 			mergedInfo.tagBits &= ~UNREACHABLE_OR_DEAD;
@@ -558,7 +559,7 @@
 			unconditionalInits();
 		// if a variable is only initialized in one branch and not initialized in the other,
 		// then we need to cast a doubt on its initialization in the merged info
-		mergedInfo.definiteInits &= initsWhenTrue.unconditionalCopy().definiteInits;
+		mergedInfo.mergeDefiniteInitsWith(initsWhenTrue.unconditionalCopy());
 		// https://bugs.eclipse.org/bugs/show_bug.cgi?id=415997, classify unreachability precisely, IsThenStatementUnreachable could be due to null analysis
 		if ((mergedInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 0 && (initsWhenTrue.tagBits & FlowInfo.UNREACHABLE) == FlowInfo.UNREACHABLE_BY_NULLANALYSIS) {
 			mergedInfo.tagBits &= ~UNREACHABLE_OR_DEAD;
@@ -615,6 +616,8 @@
 abstract public UnconditionalFlowInfo mergedWith(
 		UnconditionalFlowInfo otherInits);
 
+abstract public UnconditionalFlowInfo mergeDefiniteInitsWith(UnconditionalFlowInfo otherInits);
+
 /**
  * Return a copy of this unconditional flow info, deprived from its null
  * info. {@link #DEAD_END DEAD_END} is returned unmodified.
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/UnconditionalFlowInfo.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/UnconditionalFlowInfo.java
index 584b45d..916a590 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/UnconditionalFlowInfo.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/UnconditionalFlowInfo.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
@@ -2183,7 +2183,54 @@
 public UnconditionalFlowInfo unconditionalInitsWithoutSideEffect() {
 	return this;
 }
+public UnconditionalFlowInfo mergeDefiniteInitsWith(UnconditionalFlowInfo otherInits) {
+	if ((otherInits.tagBits & UNREACHABLE_OR_DEAD) != 0 && this != DEAD_END) {
+		return this;
+	}
+	if ((this.tagBits & UNREACHABLE_OR_DEAD) != 0) {
+		return (UnconditionalFlowInfo) otherInits.copy(); // make sure otherInits won't be affected
+	}
 
+	// intersection of definitely assigned variables,
+	this.definiteInits &= otherInits.definiteInits;
+	if (this.extra != null) {
+		if (otherInits.extra != null) {
+			// both sides have extra storage
+			int i = 0, length, otherLength;
+			if ((length = this.extra[0].length) < (otherLength = otherInits.extra[0].length)) {
+				// current storage is shorter -> grow current
+				for (int j = 0; j < extraLength; j++) {
+					System.arraycopy(this.extra[j], 0,
+						(this.extra[j] = new long[otherLength]), 0, length);
+				}
+				for (; i < length; i++) {
+					this.extra[0][i] &= otherInits.extra[0][i];
+				}
+				for (; i < otherLength; i++) {
+					this.extra[0][i] = otherInits.extra[0][i];
+				}
+			}
+			else {
+				// current storage is longer
+				for (; i < otherLength; i++) {
+					this.extra[0][i] &= otherInits.extra[0][i];
+				}
+			}
+		} else {
+			for (int i = 0; i < this.extra[0].length; i++) {
+				this.extra[0][i] = 0;
+			}
+		}
+	}
+	else if (otherInits.extra != null) {
+		// no storage here, but other has extra storage.
+		int otherLength = otherInits.extra[0].length;
+		createExtraSpace(otherLength);
+		System.arraycopy(otherInits.extra[0], 0, this.extra[0], 0,
+				otherLength);
+	}
+	return this;
+}
 public void resetAssignmentInfo(LocalVariableBinding local) {
 	resetAssignmentInfo(local.id + this.maxFieldCount);
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java
index 967230b..3a25dea 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java
@@ -614,7 +614,7 @@
 			case UnqualifiedFieldAccess :
 				return OPTION_ReportUnqualifiedFieldAccess;
 			case UnusedDeclaredThrownException :
-				return OPTION_ReportUnusedDeclaredThrownExceptionWhenOverriding;
+				return OPTION_ReportUnusedDeclaredThrownException;
 			case FinallyBlockNotCompleting :
 				return OPTION_ReportFinallyBlockNotCompletingNormally;
 			case InvalidJavadoc :
@@ -1277,6 +1277,27 @@
 		return this.warningThreshold.isAnySet(irritants) || this.errorThreshold.isAnySet(irritants)
 					|| this.infoThreshold.isAnySet(irritants);
 	}
+	/*
+	 * Just return the first irritant id that is set to 'ignored'.
+	 */
+	public int getIgnoredIrritant(IrritantSet irritants) {
+		int[] bits = irritants.getBits();
+		for (int i = 0; i < IrritantSet.GROUP_MAX; i++) {
+			int bit = bits[i];
+			for (int b = 0; b < IrritantSet.GROUP_SHIFT; b++) {
+				int single = bit & (1 << b);
+				if (single > 0) {
+					single |= (i << IrritantSet.GROUP_SHIFT);
+					if (single == MissingNonNullByDefaultAnnotation)
+						continue;
+					if (!(this.warningThreshold.isSet(single) || this.errorThreshold.isSet(single) || this.infoThreshold.isSet(single))) {
+						return single;
+					}
+				}
+			}
+		}
+		return 0;
+	}
 
 	protected void resetDefaults() {
 		// problem default severities defined on IrritantSet
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java
index 54f524d..280a502 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java
@@ -259,7 +259,9 @@
 		int group = (singleGroupIrritants & GROUP_MASK) >> GROUP_SHIFT;
 		return (this.bits[group] & singleGroupIrritants) != 0;
 	}
-
+	public int[] getBits() {
+		return this.bits;
+	}
 	public IrritantSet set(int singleGroupIrritants) {
 		int group = (singleGroupIrritants & GROUP_MASK) >> GROUP_SHIFT;
 		this.bits[group] |= (singleGroupIrritants & ~GROUP_MASK); // erase the group bits
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java
index 9b7b03c..17312a2 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java
@@ -15,6 +15,8 @@
 package org.eclipse.jdt.internal.compiler.lookup;
 
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.ast.*;
@@ -60,6 +62,8 @@
 	
 	boolean connectingHierarchy;
 	private ArrayList<Invocation> inferredInvocations;
+	/** Cache of interned inference variables. Access only via {@link InferenceVariable#get(TypeBinding, int, InvocationSite, Scope, ReferenceBinding)}. */
+	Map<InferenceVariable.InferenceVarKey, InferenceVariable> uniqueInferenceVariables = new HashMap<>();
 
 public CompilationUnitScope(CompilationUnitDeclaration unit, LookupEnvironment environment) {
 	super(COMPILATION_UNIT_SCOPE, null);
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 f359f9c..2e127a7 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
@@ -292,7 +292,7 @@
 																	this.environment.getNonNullAnnotationName());
 						break returnType;
 					} else {
-						scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod, useTypeAnnotations);
+						scope.problemReporter().cannotImplementIncompatibleNullness(scope.referenceContext(), currentMethod, inheritedMethod, useTypeAnnotations);
 						return;
 					}
 				}
@@ -308,7 +308,7 @@
 							scope.problemReporter().illegalReturnRedefinition(srcMethod, inheritedMethod,
 																	this.environment.getNonNullAnnotationName());
 						else
-							scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod, useTypeAnnotations);
+							scope.problemReporter().cannotImplementIncompatibleNullness(scope.referenceContext(), currentMethod, inheritedMethod, useTypeAnnotations);
 						return;
 					}
 				}
@@ -400,7 +400,7 @@
 								inheritedMethod.declaringClass,
 								(inheritedNonNullNess == null) ? null : this.environment.getNullableAnnotationName());
 					} else {
-						scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod, false);
+						scope.problemReporter().cannotImplementIncompatibleNullness(scope.referenceContext(), currentMethod, inheritedMethod, false);
 					}
 					continue;
 				} else if (currentNonNullNess == null) 
@@ -413,7 +413,7 @@
 									inheritedMethod.declaringClass,
 									annotationName);
 						} else {
-							scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod, false);
+							scope.problemReporter().cannotImplementIncompatibleNullness(scope.referenceContext(), currentMethod, inheritedMethod, false);
 						}
 						continue;
 					} else if (inheritedNonNullNess == Boolean.TRUE) {
@@ -438,7 +438,7 @@
 						if (currentArgument != null)
 							scope.problemReporter().illegalParameterRedefinition(currentArgument, inheritedMethod.declaringClass, inheritedParameter);
 						else
-							scope.problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod, false);
+							scope.problemReporter().cannotImplementIncompatibleNullness(scope.referenceContext(), currentMethod, inheritedMethod, false);
 					}
 				}
 			}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java
index 07851ae..f30d941 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java
@@ -130,7 +130,6 @@
 	
 	/** The inference variables for which as solution is sought. */
 	InferenceVariable[] inferenceVariables;
-	int nextVarId;
 
 	/** Constraints that have not yet been reduced and incorporated. */
 	ConstraintFormula[] initialConstraints;
@@ -164,43 +163,12 @@
 	/** Not per JLS: signal when current is ready to directly merge all bounds from inner. */
 	private boolean directlyAcceptingInnerBounds = false;
 	
-	// InferenceVariable interning:
-	private InferenceVariable[] internedVariables;
-	
-	private InferenceVariable getInferenceVariable(TypeBinding typeParameter, int rank, InvocationSite site) {
-		InferenceContext18 outermostContext = this.environment.currentInferenceContext;
-		if (outermostContext == null)
-			outermostContext = this;
-		int i = 0;
-		InferenceVariable[] interned = outermostContext.internedVariables;
-		if (interned == null) {
-			outermostContext.internedVariables = new InferenceVariable[10];
-		} else {
-			int len = interned.length;
-			for (i = 0; i < len; i++) {
-				InferenceVariable var = interned[i];
-				if (var == null)
-					break;
-				if (var.typeParameter == typeParameter && var.rank == rank && isSameSite(var.site, site)) //$IDENTITY-COMPARISON$
-					return var;
-			}
-			if (i >= len)
-				System.arraycopy(interned, 0, outermostContext.internedVariables = new InferenceVariable[len+10], 0, len);
-		}
-		boolean differentContext = outermostContext != this;
-		int id = differentContext ? Math.max(this.nextVarId, outermostContext.nextVarId) : this.nextVarId;
-		this.nextVarId = id + 1;
-		if (differentContext)
-			outermostContext.nextVarId = this.nextVarId;
-		return outermostContext.internedVariables[i] = new InferenceVariable(typeParameter, rank, id, site, this.environment, this.object);
-	}
-	
-	boolean isSameSite(InvocationSite site1, InvocationSite site2) {
+	public static boolean isSameSite(InvocationSite site1, InvocationSite site2) {
 		if (site1 == site2)
 			return true;
 		if (site1 == null || site2 == null)
 			return false;
-		if (site1.sourceStart() == site2.sourceStart() && site1.sourceEnd() == site2.sourceEnd() && site1.toString().equals(site2.toString()))
+		if (site1.sourceStart() == site2.sourceStart() && site1.sourceEnd() == site2.sourceEnd())
 			return true;
 		return false;
 	}
@@ -336,7 +304,7 @@
 		}
 		InferenceVariable[] newVariables = new InferenceVariable[len];
 		for (int i = 0; i < len; i++)
-			newVariables[i] = getInferenceVariable(typeVariables[i], i, this.currentInvocation);
+			newVariables[i] = InferenceVariable.get(typeVariables[i], i, this.currentInvocation, this.scope, this.object);
 		addInferenceVariables(newVariables);
 		return newVariables;
 	}
@@ -364,7 +332,7 @@
 				newVariables[i] = (InferenceVariable) typeVariables[i]; // prevent double substitution of an already-substituted inferenceVariable
 			else
 				toAdd[numToAdd++] =
-					newVariables[i] = getInferenceVariable(typeVariables[i], i, this.currentInvocation);
+					newVariables[i] = InferenceVariable.get(typeVariables[i], i, this.currentInvocation, this.scope, this.object);
 		}
 		if (numToAdd > 0) {
 			int start = 0;
@@ -1565,18 +1533,6 @@
 		if (!isSameSite(innerCtx.currentInvocation, this.currentInvocation))
 			innerCtx.outerContext = this;
 		this.usesUncheckedConversion = innerCtx.usesUncheckedConversion;
-		for (InferenceVariable variable : this.inferenceVariables)
-			if (!isInterned(variable))
-				variable.updateSourceName(this.nextVarId++);
-	}
-	
-	boolean isInterned(InferenceVariable iv) {
-		if (this.internedVariables != null)
-			for (int i = 0; i < this.internedVariables.length; i++) {
-				if (this.internedVariables[i] == iv) //$IDENTITY-COMPARISON$
-					return true;
-			}
-		return false;
 	}
 
 	public void resumeSuspendedInference(SuspendedInferenceRecord record) {
@@ -1589,8 +1545,6 @@
 			// move to back, add previous to front:
 			System.arraycopy(this.inferenceVariables, 0, this.inferenceVariables=new InferenceVariable[l1+l2], l2, l1);
 			System.arraycopy(record.inferenceVariables, 0, this.inferenceVariables, 0, l2);
-			for (int i=l1;i<l1+l2;i++)
-				this.inferenceVariables[i].updateSourceName(this.nextVarId++);
 		}
 
 		// replace invocation site & arguments:
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceSubstitution.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceSubstitution.java
index 180f770..d482c7a 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceSubstitution.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceSubstitution.java
@@ -36,7 +36,7 @@
 	public TypeBinding substitute(Substitution substitution, TypeBinding originalType) {
 		for (int i = 0; i < this.variables.length; i++) {
 			InferenceVariable variable = this.variables[i];
-			if (this.site == variable.site && TypeBinding.equalsEquals(getP(i), originalType)) {
+			if (InferenceContext18.isSameSite(this.site, variable.site) && TypeBinding.equalsEquals(getP(i), originalType)) {
 				if (this.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled && originalType.hasNullTypeAnnotations())
 					return this.environment.createAnnotatedType(variable.withoutToplevelNullAnnotation(), originalType.getTypeAnnotations());
 				return variable;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceVariable.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceVariable.java
index b999b85..6b78a5f 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceVariable.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceVariable.java
@@ -10,6 +10,7 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.compiler.lookup;
 
+import java.util.Map;
 import java.util.Set;
 
 import org.eclipse.jdt.core.compiler.CharOperation;
@@ -18,14 +19,74 @@
  * Implementation of 18.1.1 in JLS8
  */
 public class InferenceVariable extends TypeVariableBinding {
+	
+	/** Structured key for interning. */
+	static class InferenceVarKey {
+		/*@NonNull*/ TypeBinding typeParameter;
+		long position;
+		int rank;
+		InferenceVarKey(TypeBinding typeParameter, InvocationSite site, int rank) {
+			this.typeParameter = typeParameter;
+			this.position = ((long) site.sourceStart() << 32) + site.sourceEnd();
+			this.rank = rank;
+		}
+		@Override
+		public int hashCode() {
+			final int prime = 31;
+			int result = 1;
+			result = prime * result + (int) (this.position ^ (this.position >>> 32));
+			result = prime * result + this.rank;
+			result = prime * result + this.typeParameter.id;
+			return result;
+		}
+		@Override
+		public boolean equals(Object obj) {
+			if (this == obj)
+				return true;
+			if (obj == null)
+				return false;
+			if (!(obj instanceof InferenceVarKey))
+				return false;
+			InferenceVarKey other = (InferenceVarKey) obj;
+			if (this.position != other.position)
+				return false;
+			if (this.rank != other.rank)
+				return false;
+			if (TypeBinding.notEquals(this.typeParameter, other.typeParameter))
+				return false;
+			return true;
+		}	
+	}
+
+	/**
+	 * Create or retrieve the inference variable representing the given typeParameter.
+	 * Inference variables are interned to avoid duplication due to lambda copying.
+	 */
+	public static InferenceVariable get(TypeBinding typeParameter, int rank, InvocationSite site, Scope scope, ReferenceBinding object) {
+		Map<InferenceVarKey, InferenceVariable> uniqueInferenceVariables = scope.compilationUnitScope().uniqueInferenceVariables;
+		InferenceVariable var = null;
+		InferenceVarKey key = null;
+		if (site != null && typeParameter != null) {
+			key = new InferenceVarKey(typeParameter, site, rank);
+			var = uniqueInferenceVariables.get(key);
+		}
+		if (var == null) {
+			int newVarId = uniqueInferenceVariables.size();
+			var = new InferenceVariable(typeParameter, rank, newVarId, site, scope.environment(), object);
+			if (key != null)
+				uniqueInferenceVariables.put(key, var);
+		}
+		return var;
+	}
+
 
 	InvocationSite site;
 	TypeBinding typeParameter;
 	long nullHints; // one of TagBits.{AnnotationNonNull,AnnotationNullable} may steer inference into inferring nullness as well; set both bits to request avoidance.
 	private InferenceVariable prototype;
-	int varId; // this is used for constructing a source name like T#0. NB: varId and sourceName are mutable, to be updated when two InferenceContext18 are integrated.
+	int varId; // this is used for constructing a source name like T#0.
 	
-	public InferenceVariable(TypeBinding typeParameter, int parameterRank, int iVarId, InvocationSite site, LookupEnvironment environment, ReferenceBinding object) {
+	private InferenceVariable(TypeBinding typeParameter, int parameterRank, int iVarId, InvocationSite site, LookupEnvironment environment, ReferenceBinding object) {
 		this(typeParameter, parameterRank, site,
 				CharOperation.concat(typeParameter.shortReadableName(), Integer.toString(iVarId).toCharArray(), '#'),
 				environment, object);
@@ -49,15 +110,7 @@
 		this.superclass = object;
 		this.prototype = this;
 	}
-	void updateSourceName(int newId) {
-		int hashPos = CharOperation.indexOf('#', this.sourceName);
-		this.varId = newId;
-		this.sourceName = CharOperation.concat(
-				CharOperation.subarray(this.sourceName, 0, hashPos),
-				Integer.toString(this.varId).toCharArray(),
-				'#');
-	}
-	
+
 	@Override
 	public TypeBinding clone(TypeBinding enclosingType) {
 		InferenceVariable clone = new InferenceVariable(this.typeParameter, this.rank, this.site, this.sourceName, this.environment, this.superclass);
@@ -131,17 +184,18 @@
 	
 	public int hashCode() {
 		int code = this.typeParameter.hashCode() + 17 * this.rank;
-		if (this.site != null)
-			return 31 * code + this.site.hashCode();
-		else
-			return code;
+		if (this.site != null) {
+			code = 31 * code + this.site.sourceStart();
+			code = 31 * code + this.site.sourceEnd();
+		}
+		return code;
 	}
 	public boolean equals(Object obj) {
 		if (!(obj instanceof InferenceVariable))
 			return false;
 		InferenceVariable other = (InferenceVariable) obj;
 		return this.rank == other.rank
-				&& this.site == other.site
+				&& InferenceContext18.isSameSite(this.site, other.site)
 				&& TypeBinding.equalsEquals(this.typeParameter, other.typeParameter);
 	}
 
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 67faad1..55cc090 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
@@ -569,12 +569,16 @@
 
 //pre: null annotation analysis is enabled
 protected void fillInDefaultNonNullness18(AbstractMethodDeclaration sourceMethod, LookupEnvironment env) {
+	MethodBinding original = original();
+	if(original == null) {
+		return;
+	}
 	if (hasNonNullDefaultFor(DefaultLocationParameter, true)) {
 		boolean added = false;
 		int length = this.parameters.length;
 		for (int i = 0; i < length; i++) {
 			TypeBinding parameter = this.parameters[i];
-			if (!parameter.acceptsNonNullDefault())
+			if (!original.parameters[i].acceptsNonNullDefault())
 				continue;
 			long existing = parameter.tagBits & TagBits.AnnotationNullMASK;
 			if (existing == 0L) {
@@ -592,7 +596,7 @@
 		if (added)
 			this.tagBits |= TagBits.HasParameterAnnotations;
 	}
-	if (this.returnType != null && hasNonNullDefaultFor(DefaultLocationReturnType, true) && this.returnType.acceptsNonNullDefault()) {
+	if (original.returnType != null && hasNonNullDefaultFor(DefaultLocationReturnType, true) && original.returnType.acceptsNonNullDefault()) {
 		if ((this.returnType.tagBits & TagBits.AnnotationNullMASK) == 0) {
 			this.returnType = env.createAnnotatedType(this.returnType, new AnnotationBinding[]{env.getNonNullAnnotation()});
 		} else if (sourceMethod instanceof MethodDeclaration && (this.returnType.tagBits & TagBits.AnnotationNonNull) != 0 
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java
index aea2423..bbd02d3 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java
@@ -60,6 +60,12 @@
 	 */
 	public static MethodBinding computeCompatibleMethod(MethodBinding originalMethod, TypeBinding[] arguments, Scope scope,	InvocationSite invocationSite)
 	{
+		LookupEnvironment environment = scope.environment();
+		if ((originalMethod.tagBits & TagBits.IsNullnessKnown) == 0) {
+			// ensure nullness of originalMethod is known (but we are not interested in reporting problems against originalMethod)
+			new ImplicitNullAnnotationVerifier(environment, environment.globalOptions.inheritNullAnnotations)
+					.checkImplicitNullAnnotations(originalMethod, null/*srcMethod*/, false, scope);
+		}
 		ParameterizedGenericMethodBinding methodSubstitute;
 		TypeVariableBinding[] typeVariables = originalMethod.typeVariables;
 		TypeBinding[] substitutes = invocationSite.genericTypeArguments();
@@ -72,7 +78,7 @@
 			        // incompatible due to wrong arity
 			        return new ProblemMethodBinding(originalMethod, originalMethod.selector, substitutes, ProblemReasons.TypeParameterArityMismatch);
 				}
-				methodSubstitute = scope.environment().createParameterizedGenericMethod(originalMethod, substitutes);
+				methodSubstitute = environment.createParameterizedGenericMethod(originalMethod, substitutes);
 				break computeSubstitutes;
 			}
 			// perform type argument inference (15.12.2.7)
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java
index 40cae44..20639ff 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java
@@ -1149,7 +1149,7 @@
 			else // WildcardBinding, CaptureBinding have no sourceName
 				nameBuffer.append(this.type.readableName());
 		}
-		if (this.arguments != null && this.arguments.length > 0) { // empty arguments array happens when PTB has been created just to capture type annotations
+		if (this.arguments != null && this.arguments.length > 0 && !isRawType()) { // empty arguments array happens when PTB has been created just to capture type annotations
 			nameBuffer.append('<');
 		    for (int i = 0, length = this.arguments.length; i < length; i++) {
 		        if (i > 0) nameBuffer.append(',');
@@ -1177,7 +1177,7 @@
 			else // WildcardBinding, CaptureBinding have no sourceName
 				nameBuffer.append(this.type.shortReadableName());
 		}
-		if (this.arguments != null && this.arguments.length > 0) { // empty arguments array happens when PTB has been created just to capture type annotations
+		if (this.arguments != null && this.arguments.length > 0 && !isRawType()) { // empty arguments array happens when PTB has been created just to capture type annotations
 			nameBuffer.append('<');
 		    for (int i = 0, length = this.arguments.length; i < length; i++) {
 		        if (i > 0) nameBuffer.append(',');
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeSystem.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeSystem.java
index 8c7c2c7..965f055 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeSystem.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeSystem.java
@@ -18,7 +18,6 @@
 
 import java.util.HashMap;
 
-import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.ast.ASTNode;
 import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable;
 import org.eclipse.jdt.internal.compiler.util.Util;
@@ -168,19 +167,7 @@
 			urb = (UnresolvedReferenceBinding) type;
 			ReferenceBinding resolvedType = urb.resolvedType;
 			if (resolvedType != null) {
-				if(CharOperation.indexOf('$', type.sourceName()) > 0) {
-					type = this.environment.convertToRawType(resolvedType, false);
-				} else {
-					type = resolvedType;
-				}
-			} else if (CharOperation.indexOf('$', type.sourceName()) > 0) {
-				boolean mayTolerateMissingType = this.environment.mayTolerateMissingType;
-				this.environment.mayTolerateMissingType = true;
-				try {
-					type = BinaryTypeBinding.resolveType(type, this.environment, true); // to ensure unique id assignment (when enclosing type is parameterized, inner type is also) 
-				} finally {
-					this.environment.mayTolerateMissingType = mayTolerateMissingType;
-				}
+				type = resolvedType;
 			}
 		}
 		try {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java
index 0fe0324..9386b62 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java
@@ -984,7 +984,6 @@
 			TypeBinding [] annotatedTypes = getDerivedTypesForDeferredInitialization();
 			for (int i = 0, length = annotatedTypes == null ? 0 : annotatedTypes.length; i < length; i++) {
 				TypeVariableBinding annotatedType = (TypeVariableBinding) annotatedTypes[i];
-				if (annotatedType.firstBound == null)
 					annotatedType.firstBound = firstBound;
 			}
 		}
@@ -1001,7 +1000,6 @@
 			TypeBinding [] annotatedTypes = getDerivedTypesForDeferredInitialization();
 			for (int i = 0, length = annotatedTypes == null ? 0 : annotatedTypes.length; i < length; i++) {
 				TypeVariableBinding annotatedType = (TypeVariableBinding) annotatedTypes[i];
-				if (annotatedType.superclass == null)
 					annotatedType.superclass = superclass;
 			}
 		}
@@ -1016,7 +1014,6 @@
 			TypeBinding [] annotatedTypes = getDerivedTypesForDeferredInitialization();
 			for (int i = 0, length = annotatedTypes == null ? 0 : annotatedTypes.length; i < length; i++) {
 				TypeVariableBinding annotatedType = (TypeVariableBinding) annotatedTypes[i];
-				if (annotatedType.superInterfaces == null)
 					annotatedType.superInterfaces = superInterfaces;
 			}
 		}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/AbstractCommentParser.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/AbstractCommentParser.java
index 5fecec4..2a1fd28 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/AbstractCommentParser.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/parser/AbstractCommentParser.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2013 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
@@ -172,7 +172,13 @@
 			}
 			this.lineEnd = (this.linePtr == this.lastLinePtr) ? this.javadocEnd: this.scanner.getLineEnd(this.linePtr) - 1;
 			this.javadocTextEnd = this.javadocEnd - 2; // supposed text end, it will be refined later...
-
+						// https://bugs.eclipse.org/bugs/show_bug.cgi?id=206345
+						// when parsing tags such as @code and @literal,
+						// any tag should be discarded and considered as plain text until
+						// properly closed with closing brace
+						boolean considerTagAsPlainText = false;
+						// internal counter for opening braces
+						int openingBraces = 0;
 			// Loop on each comment character
 			int textEndPosition = -1;
 			while (!this.abort && this.index < this.javadocEnd) {
@@ -208,7 +214,20 @@
 				switch (nextCharacter) {
 					case '@' :
 						// Start tag parsing only if we are on line beginning or at inline tag beginning
-						if ((!this.lineStarted || previousChar == '{')) {
+						// https://bugs.eclipse.org/bugs/show_bug.cgi?id=206345: ignore all tags when inside @literal or @code tags
+						if (considerTagAsPlainText) {
+							// new tag found
+							if (!this.lineStarted) {
+								// we may want to report invalid syntax when no closing brace found,
+								// or when incoherent number of closing braces found
+								if (openingBraces > 0 && this.reportProblems) {
+									this.sourceParser.problemReporter().javadocUnterminatedInlineTag(this.inlineTagStart, invalidInlineTagLineEnd);
+								}
+								considerTagAsPlainText = false;
+								this.inlineTagStarted = false;
+								openingBraces = 0;
+							}
+						} else if ((!this.lineStarted || previousChar == '{')) {
 							if (this.inlineTagStarted) {
 								setInlineTagStarted(false);
 								// bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
@@ -252,6 +271,12 @@
 									invalidTagLineEnd  = this.lineEnd;
 									textEndPosition = this.index;
 								}
+								// https://bugs.eclipse.org/bugs/show_bug.cgi?id=206345
+								// dealing with @literal or @code tags: ignore next tags
+								if (!isFormatterParser && (this.tagValue == TAG_LITERAL_VALUE || this.tagValue == TAG_CODE_VALUE)) {
+									considerTagAsPlainText = true;
+									openingBraces++;
+								}
 							} catch (InvalidInputException e) {
 								consumeToken();
 							}
@@ -284,13 +309,24 @@
 						if (verifText && this.tagValue == TAG_RETURN_VALUE && this.returnStatement != null) {
 							refreshReturnStatement();
 						}
+						// https://bugs.eclipse.org/bugs/show_bug.cgi?id=206345: when ignoring tags, only decrement the opening braces counter
+						if (considerTagAsPlainText) {
+							invalidInlineTagLineEnd = this.lineEnd;
+							if (--openingBraces == 0) {
+								considerTagAsPlainText = false; // re-enable tag validation
+							}
+						}
 						if (this.inlineTagStarted) {
 							textEndPosition = this.index - 1;
-							if (this.lineStarted && this.textStart != -1 && this.textStart < textEndPosition) {
-								pushText(this.textStart, textEndPosition);
+							// https://bugs.eclipse.org/bugs/show_bug.cgi?id=206345: do not push text yet if ignoring tags
+							if (!considerTagAsPlainText) {
+								if (this.lineStarted && this.textStart != -1 && this.textStart < textEndPosition) {
+									pushText(this.textStart, textEndPosition);
+								}
+								refreshInlineTagPosition(previousPosition);
 							}
-							refreshInlineTagPosition(previousPosition);
-							if (!isFormatterParser) this.textStart = this.index;
+							if (!isFormatterParser && !considerTagAsPlainText) 
+								this.textStart = this.index;
 							setInlineTagStarted(false);
 						} else {
 							if (!this.lineStarted) {
@@ -304,7 +340,10 @@
 						if (verifText && this.tagValue == TAG_RETURN_VALUE && this.returnStatement != null) {
 							refreshReturnStatement();
 						}
-						if (this.inlineTagStarted) {
+												// https://bugs.eclipse.org/bugs/show_bug.cgi?id=206345: count opening braces when ignoring tags
+						if (considerTagAsPlainText) {
+							openingBraces++;
+						} else if (this.inlineTagStarted) {
 							setInlineTagStarted(false);
 							// bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
 							// Cannot have opening brace in inline comment
@@ -325,7 +364,8 @@
 							this.textStart = previousPosition;
 						}
 						this.lineStarted = true;
-						this.inlineTagStart = previousPosition;
+						// https://bugs.eclipse.org/bugs/show_bug.cgi?id=206345: do not update tag start position when ignoring tags
+						if (!considerTagAsPlainText) this.inlineTagStart = previousPosition;
 						break;
 					case '*' :
 						// Store the star position as text start while formatting
@@ -395,7 +435,8 @@
 
 			// bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53279
 			// Cannot leave comment inside inline comment
-			if (this.inlineTagStarted) {
+			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=206345: handle unterminated @code or @literal tag
+			if (this.inlineTagStarted || considerTagAsPlainText) {
 				if (this.reportProblems) {
 					int end = this.javadocTextEnd<invalidInlineTagLineEnd ? this.javadocTextEnd : invalidInlineTagLineEnd;
 					if (this.index >= this.javadocEnd) end = invalidInlineTagLineEnd;
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 12822b2..28f8c44 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
@@ -1594,6 +1594,8 @@
 			return ProblemSeverities.Warning;
 		case IProblem.IllegalUseOfUnderscoreAsAnIdentifier:
 			return this.underScoreIsLambdaParameter ? ProblemSeverities.Error : ProblemSeverities.Warning;
+		case IProblem.ProblemNotAnalysed:
+			return ProblemSeverities.Info; // Not configurable
 	}
 	int irritant = getIrritant(problemID);
 	if (irritant != 0) {
@@ -5861,6 +5863,19 @@
 			severity,
 			annotation.sourceStart, annotation.sourceEnd);
 }
+public void nullAnnotationAtQualifyingType(Annotation annotation) {
+	String[] arguments = new String[] {
+		String.valueOf(annotation.resolvedType.readableName())
+	};
+	String[] shortArguments = new String[] {
+		String.valueOf(annotation.resolvedType.shortReadableName())
+	};
+	int severity = ProblemSeverities.Error | ProblemSeverities.Fatal;
+	handle(IProblem.NullAnnotationAtQualifyingType,
+			arguments, shortArguments,
+			severity,
+			annotation.sourceStart, annotation.sourceEnd);
+}
 public void nullAnnotationUnsupportedLocation(TypeReference type) {
 	int sourceEnd = type.sourceEnd;
 	if (type instanceof ParameterizedSingleTypeReference) {
@@ -8952,6 +8967,14 @@
 		token.sourceStart,
 		token.sourceEnd);
 }
+public void problemNotAnalysed(Expression token, String optionKey) {
+	this.handle(
+		IProblem.ProblemNotAnalysed,
+		optionKey != null ? new String[]{optionKey} : new String[]{},
+		new String[] { token.constant.stringValue() },
+		token.sourceStart,
+		token.sourceEnd);
+}
 public void useAssertAsAnIdentifier(int sourceStart, int sourceEnd) {
 	this.handle(
 		IProblem.UseAssertAsAnIdentifier,
@@ -9643,11 +9666,11 @@
 		location.sourceEnd);
 }
 
-public void cannotImplementIncompatibleNullness(MethodBinding currentMethod, MethodBinding inheritedMethod, boolean showReturn) {
+public void cannotImplementIncompatibleNullness(ReferenceContext context, MethodBinding currentMethod, MethodBinding inheritedMethod, boolean showReturn) {
 	int sourceStart = 0, sourceEnd = 0;
-	if (this.referenceContext instanceof TypeDeclaration) {
-		sourceStart = ((TypeDeclaration) this.referenceContext).sourceStart;
-		sourceEnd =   ((TypeDeclaration) this.referenceContext).sourceEnd;
+	if (context instanceof TypeDeclaration) {
+		sourceStart = ((TypeDeclaration) context).sourceStart;
+		sourceEnd =   ((TypeDeclaration) context).sourceEnd;
 	}
 	String[] problemArguments = {
 			showReturn 
@@ -10416,6 +10439,10 @@
 		annotation.sourceStart,
 		annotation.sourceEnd);
 }
+public void typeAnnotationAtQualifiedName(Annotation annotation) {
+		this.handle(IProblem.TypeAnnotationAtQualifiedName, NoArgument, NoArgument, annotation.sourceStart,
+				annotation.sourceEnd);
+	}
 public void genericInferenceError(String message, InvocationSite invocationSite) {
 	genericInferenceProblem(message, invocationSite, ProblemSeverities.Error);
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties
index 4746474..d07d1ee 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties
@@ -862,9 +862,12 @@
 1057 = strictfp is not permitted for abstract interface method {0}
 1058 = Default methods are allowed only in interfaces.
 1059 = Cannot infer type argument(s) for <{0}> {1}
+1060 = Illegally placed annotation: type annotations must directly precede the simple name of the type they are meant to affect (or the [] for arrays)
+1061 = The nullness annotation ''{0}'' is not applicable at this location, it must be placed directly before the nested type name.
 
 1100 = Problem detected during type inference: {0}
-
+#1101 is already used up but deprecated
+1102 = At least one of the problems in category ''{0}'' is not analysed due to a compiler option being ignored
 ### ELABORATIONS
 ## Access restrictions
 78592 = The type ''{1}'' is not API (restriction on classpath entry ''{0}'')
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/Util.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/Util.java
index e033637..818bb0b 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/Util.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/Util.java
@@ -1198,7 +1198,7 @@
 				count++;
 			}
 		} catch (ArrayIndexOutOfBoundsException e) {
-			throw new IllegalArgumentException();
+			throw new IllegalArgumentException(e);
 		}
 	}
 
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/AST.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/AST.java
index 7757fed..aae7faa 100644
--- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/AST.java
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/AST.java
@@ -503,7 +503,7 @@
 			return (CompilationUnit) result;
 		} catch (IllegalStateException e) {
 			// convert ASTParser's complaints into old form
-			throw new IllegalArgumentException();
+			throw new IllegalArgumentException(e);
 		}
 	}
 
@@ -577,7 +577,7 @@
 			return (CompilationUnit) result;
 		} catch (IllegalStateException e) {
 			// convert ASTParser's complaints into old form
-			throw new IllegalArgumentException();
+			throw new IllegalArgumentException(e);
 		}
 	}
 
@@ -815,21 +815,19 @@
 		} catch (NoSuchMethodException e) {
 			// all AST node classes have a Foo(AST) constructor
 			// therefore nodeClass is not legit
-			throw new IllegalArgumentException();
+			throw new IllegalArgumentException(e);
 		} catch (InstantiationException e) {
 			// all concrete AST node classes can be instantiated
 			// therefore nodeClass is not legit
-			throw new IllegalArgumentException();
+			throw new IllegalArgumentException(e);
 		} catch (IllegalAccessException e) {
 			// all AST node classes have an accessible Foo(AST) constructor
 			// therefore nodeClass is not legit
-			throw new IllegalArgumentException();
+			throw new IllegalArgumentException(e);
 		} catch (InvocationTargetException e) {
 			// concrete AST node classes do not die in the constructor
 			// therefore nodeClass is not legit
-			IllegalArgumentException iae = new IllegalArgumentException();
-			iae.initCause(e.getCause());
-			throw iae;
+			throw new IllegalArgumentException(e.getCause());
 		}
 	}
 
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTParser.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTParser.java
index fa4e478..4452ea6 100644
--- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTParser.java
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTParser.java
@@ -299,7 +299,7 @@
 				throw new IllegalStateException("invalid environment settings"); //$NON-NLS-1$
 			}
 		} catch (IllegalArgumentException e) {
-			throw new IllegalStateException("invalid environment settings"); //$NON-NLS-1$
+			throw new IllegalStateException("invalid environment settings", e); //$NON-NLS-1$
 		}
 		return allClasspaths;
 	}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CharacterLiteral.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CharacterLiteral.java
index b4b1da6..512a8d0 100644
--- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CharacterLiteral.java
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CharacterLiteral.java
@@ -286,7 +286,7 @@
 							throw new IllegalArgumentException("illegal character literal");//$NON-NLS-1$
 						}
 					} catch (InvalidInputException e) {
-						throw new IllegalArgumentException("illegal character literal");//$NON-NLS-1$
+						throw new IllegalArgumentException("illegal character literal", e);//$NON-NLS-1$
 					}
 			}
 			nextChar = scanner.getNextChar();
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java
index 15ca187..bbb9ab2 100644
--- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java
@@ -769,7 +769,7 @@
 						throw new IllegalArgumentException(element + " has an unexpected type"); //$NON-NLS-1$
 					binaryElementPositions.put(key, i);
 				} catch (JavaModelException e) {
-					throw new IllegalArgumentException(element + " does not exist"); //$NON-NLS-1$
+					throw new IllegalArgumentException(element + " does not exist", e); //$NON-NLS-1$
 				}
 			}
 		}
@@ -792,7 +792,7 @@
 					try {
 						finder.search();
 					} catch (JavaModelException e) {
-						throw new IllegalArgumentException(element + " does not exist"); //$NON-NLS-1$
+						throw new IllegalArgumentException(element + " does not exist", e); //$NON-NLS-1$
 					}
 					this.bindings[index] = finder.foundBinding;
 				}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/DocCommentParser.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/DocCommentParser.java
index 1136b34..6bb5390 100644
--- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/DocCommentParser.java
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/DocCommentParser.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2004, 2013 IBM Corporation and others.
+ * 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
@@ -427,6 +427,9 @@
 						if (length == TAG_CATEGORY_LENGTH && CharOperation.equals(TAG_CATEGORY, tagName)) {
 							this.tagValue = TAG_CATEGORY_VALUE;
 							valid = parseIdentifierTag(false); // TODO (frederic) reconsider parameter value when @category will be significant in spec
+						} else if (length == TAG_CODE_LENGTH && CharOperation.equals(TAG_CODE, tagName)) {
+							this.tagValue = TAG_CODE_VALUE;
+							createTag();
 						} else {
 							this.tagValue = TAG_OTHERS_VALUE;
 							createTag();
@@ -490,8 +493,11 @@
 							this.tagValue = TAG_LINK_VALUE;
 						} else if (length == TAG_LINKPLAIN_LENGTH && CharOperation.equals(TAG_LINKPLAIN, tagName)) {
 							this.tagValue = TAG_LINKPLAIN_VALUE;
+						} else if (length == TAG_LITERAL_LENGTH && CharOperation.equals(TAG_LITERAL, tagName)) {
+							this.tagValue = TAG_LITERAL_VALUE;
 						}
-						if (this.tagValue != NO_TAG_VALUE)  {
+						
+						if (this.tagValue != NO_TAG_VALUE && this.tagValue != TAG_LITERAL_VALUE)  {
 							if (this.inlineTagStarted) {
 								valid = parseReference();
 							} else {
@@ -500,7 +506,7 @@
 								valid = false;
 							}
 						} else {
-							this.tagValue = TAG_OTHERS_VALUE;
+							if (this.tagValue == NO_TAG_VALUE) this.tagValue = TAG_OTHERS_VALUE;
 							createTag();
 						}
 					break;
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/Javadoc.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/Javadoc.java
index 85ae7ff..4d4abe5 100644
--- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/Javadoc.java
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/Javadoc.java
@@ -264,7 +264,7 @@
 				throw new IllegalArgumentException();
 			}
 		} catch (InvalidInputException e) {
-			throw new IllegalArgumentException();
+			throw new IllegalArgumentException(e);
 		}
 		preValueChange(COMMENT_PROPERTY);
 		this.comment = docComment;
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/SimpleName.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/SimpleName.java
index 60dab87..fda05fb 100644
--- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/SimpleName.java
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/SimpleName.java
@@ -199,9 +199,7 @@
 					throw new IllegalArgumentException("Invalid identifier : >" + identifier + "<");  //$NON-NLS-1$//$NON-NLS-2$
 				}
 			} catch (InvalidInputException e) {
-				IllegalArgumentException iae = new IllegalArgumentException("Invalid identifier : >" + identifier + "<"); //$NON-NLS-1$//$NON-NLS-2$
-				iae.initCause(e);
-				throw iae; 
+				throw new IllegalArgumentException("Invalid identifier : >" + identifier + "<", e); //$NON-NLS-1$//$NON-NLS-2$
 			}
 		} finally {
 			this.ast.scanner.sourceLevel = sourceLevel;
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/Statement.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/Statement.java
index dffc912..60ba518 100644
--- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/Statement.java
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/Statement.java
@@ -155,7 +155,7 @@
 					throw new IllegalArgumentException();
 				}
 			} catch (InvalidInputException e) {
-				throw new IllegalArgumentException();
+				throw new IllegalArgumentException(e);
 			}
 		}
 		// we do not consider the obsolete comment as a structureal property
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteAnalyzer.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteAnalyzer.java
index fb0571a..7ecfa08 100644
--- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteAnalyzer.java
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteAnalyzer.java
@@ -4346,8 +4346,6 @@
 	}
 
 	final void handleException(Throwable e) {
-		IllegalArgumentException runtimeException= new IllegalArgumentException("Document does not match the AST"); //$NON-NLS-1$
-		runtimeException.initCause(e);
-		throw runtimeException;
+		throw new IllegalArgumentException("Document does not match the AST", e); //$NON-NLS-1$
 	}
 }
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteFormatter.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteFormatter.java
index 2bd730c..efb8893 100644
--- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteFormatter.java
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteFormatter.java
@@ -429,7 +429,7 @@
 					try {
 						doc.addPosition(POS_CATEGORY, positions[i]);
 					} catch (BadLocationException e) {
-						throw new IllegalArgumentException("Position outside of string. offset: " + positions[i].offset + ", length: " + positions[i].length + ", string size: " + string.length());   //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
+						throw new IllegalArgumentException("Position outside of string. offset: " + positions[i].offset + ", length: " + positions[i].length + ", string size: " + string.length(), e);   //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
 					}
 				}
 			}
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java
index adb1566..987a744 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java
@@ -4408,7 +4408,7 @@
 			int existingValue = Integer.parseInt(value);
 			return (existingValue & Alignment.M_FORCE) != 0;
 		} catch (NumberFormatException e) {
-			throw new IllegalArgumentException("Alignment value is not an integer: " + value); //$NON-NLS-1$
+			throw new IllegalArgumentException("Alignment value is not an integer: " + value, e); //$NON-NLS-1$
 		}
 	}
 
@@ -4435,7 +4435,7 @@
 				return INDENT_DEFAULT;
 			}
 		} catch (NumberFormatException e) {
-			throw new IllegalArgumentException("Alignment value is not an integer: " + value); //$NON-NLS-1$
+			throw new IllegalArgumentException("Alignment value is not an integer: " + value, e); //$NON-NLS-1$
 		}
 	}
 
@@ -4478,7 +4478,7 @@
 					return WRAP_NO_SPLIT;
 			}
 		} catch (NumberFormatException e) {
-			throw new IllegalArgumentException("Alignment value is not an integer: " + value); //$NON-NLS-1$
+			throw new IllegalArgumentException("Alignment value is not an integer: " + value, e); //$NON-NLS-1$
 		}
 	}
 	/**
@@ -4504,7 +4504,7 @@
 			}
 			return String.valueOf(existingValue);
 		} catch (NumberFormatException e) {
-			throw new IllegalArgumentException("Alignment value is not an integer: " + value); //$NON-NLS-1$
+			throw new IllegalArgumentException("Alignment value is not an integer: " + value, e); //$NON-NLS-1$
 		}
 	}
 
@@ -4547,7 +4547,7 @@
 			}
 			return String.valueOf(existingValue);
 		} catch (NumberFormatException e) {
-			throw new IllegalArgumentException("Alignment value is not an integer: " + value); //$NON-NLS-1$
+			throw new IllegalArgumentException("Alignment value is not an integer: " + value, e); //$NON-NLS-1$
 		}
 	}
 	/**
@@ -4605,7 +4605,7 @@
 			}
 			return String.valueOf(existingValue);
 		} catch (NumberFormatException e) {
-			throw new IllegalArgumentException("Alignment value is not an integer: " + value); //$NON-NLS-1$
+			throw new IllegalArgumentException("Alignment value is not an integer: " + value, e); //$NON-NLS-1$
 		}
 	}
 }
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 e186a14..6e5aa19 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
@@ -114,19 +114,6 @@
 import java.util.Hashtable;
 import java.util.Map;
 
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IConfigurationElement;
-import org.eclipse.core.runtime.IExtension;
-import org.eclipse.core.runtime.IExtensionPoint;
-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.Plugin;
-import org.eclipse.core.runtime.QualifiedName;
-import org.eclipse.core.runtime.SubMonitor;
-import org.eclipse.core.runtime.jobs.ISchedulingRule;
 import org.eclipse.core.resources.IContainer;
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IFolder;
@@ -140,6 +127,19 @@
 import org.eclipse.core.resources.IWorkspaceRoot;
 import org.eclipse.core.resources.IWorkspaceRunnable;
 import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtension;
+import org.eclipse.core.runtime.IExtensionPoint;
+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.Plugin;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.core.compiler.IProblem;
 import org.eclipse.jdt.core.search.IJavaSearchConstants;
@@ -150,10 +150,26 @@
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
 import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
-import org.eclipse.jdt.internal.core.*;
+import org.eclipse.jdt.internal.core.BatchOperation;
+import org.eclipse.jdt.internal.core.BufferManager;
+import org.eclipse.jdt.internal.core.ClasspathAccessRule;
+import org.eclipse.jdt.internal.core.ClasspathAttribute;
+import org.eclipse.jdt.internal.core.ClasspathEntry;
+import org.eclipse.jdt.internal.core.ClasspathValidation;
+import org.eclipse.jdt.internal.core.CreateTypeHierarchyOperation;
+import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner;
+import org.eclipse.jdt.internal.core.ExternalFoldersManager;
+import org.eclipse.jdt.internal.core.JavaCorePreferenceInitializer;
+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.Region;
+import org.eclipse.jdt.internal.core.SetContainerOperation;
+import org.eclipse.jdt.internal.core.SetVariablesOperation;
 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.search.indexing.IndexManager;
 import org.eclipse.jdt.internal.core.util.MementoTokenizer;
 import org.eclipse.jdt.internal.core.util.Messages;
 import org.eclipse.jdt.internal.core.util.Util;
@@ -4116,7 +4132,7 @@
 		// if factory is null, default factory must be used
 		if (factory == null) factory = BufferManager.getDefaultBufferManager().getDefaultBufferFactory();
 
-		return getWorkingCopies(BufferFactoryWrapper.create(factory));
+		return getWorkingCopies(org.eclipse.jdt.internal.core.BufferFactoryWrapper.create(factory));
 	}
 
 	/**
@@ -4181,14 +4197,14 @@
 		JavaModelManager manager = JavaModelManager.getJavaModelManager();
 		try {
 			SubMonitor subMonitor = mainMonitor.split(50).setWorkRemaining(100); // 50% of the time is spent in initializing containers and variables
-			subMonitor.worked(5); // give feedback to the user that something is happening
+			subMonitor.split(5); // give feedback to the user that something is happening
 			manager.batchContainerInitializationsProgress.initializeAfterLoadMonitor.set(subMonitor);
 			if (manager.forceBatchInitializations(true/*initAfterLoad*/)) { // if no other thread has started the batch container initializations
 				manager.getClasspathContainer(Path.EMPTY, null); // force the batch initialization
 			} else { // else wait for the batch initialization to finish
 				while (manager.batchContainerInitializations == JavaModelManager.BATCH_INITIALIZATION_IN_PROGRESS) {
 					subMonitor.subTask(manager.batchContainerInitializationsProgress.subTaskName);
-					subMonitor.worked(manager.batchContainerInitializationsProgress.getWorked());
+					subMonitor.split(manager.batchContainerInitializationsProgress.getWorked());
 					synchronized(manager) {
 						try {
 							manager.wait(100);
@@ -4264,38 +4280,8 @@
 
 		// dummy query for waiting until the indexes are ready
 		mainMonitor.subTask(Messages.javamodel_configuring_searchengine);
-		SearchEngine engine = new SearchEngine();
-		IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
-		try {
-			engine.searchAllTypeNames(
-				null,
-				SearchPattern.R_EXACT_MATCH,
-				"!@$#!@".toCharArray(), //$NON-NLS-1$
-				SearchPattern.R_PATTERN_MATCH | SearchPattern.R_CASE_SENSITIVE,
-				IJavaSearchConstants.CLASS,
-				scope,
-				new TypeNameRequestor() {
-					public void acceptType(
-						int modifiers,
-						char[] packageName,
-						char[] simpleTypeName,
-						char[][] enclosingTypeNames,
-						String path) {
-						// no type to accept
-					}
-				},
-				// will not activate index query caches if indexes are not ready, since it would take to long
-				// to wait until indexes are fully rebuild
-				IJavaSearchConstants.CANCEL_IF_NOT_READY_TO_SEARCH,
-				mainMonitor.split(47) // 47% of the time is spent in the dummy search
-			);
-		} catch (JavaModelException e) {
-			// /search failed: ignore
-		} catch (OperationCanceledException e) {
-			if (mainMonitor.isCanceled())
-				throw e;
-			// else indexes were not ready: catch the exception so that jars are still refreshed
-		}
+		// 47% of the time is spent in the dummy search
+		updateLegacyIndex(mainMonitor.split(47));
 
 		// check if the build state version number has changed since last session
 		// (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=98969)
@@ -4342,6 +4328,41 @@
 		}
 	}
 
+	private static void updateLegacyIndex(IProgressMonitor monitor) {
+		SearchEngine engine = new SearchEngine();
+		IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
+		try {
+			engine.searchAllTypeNames(
+				null,
+				SearchPattern.R_EXACT_MATCH,
+				"!@$#!@".toCharArray(), //$NON-NLS-1$
+				SearchPattern.R_PATTERN_MATCH | SearchPattern.R_CASE_SENSITIVE,
+				IJavaSearchConstants.CLASS,
+				scope,
+				new TypeNameRequestor() {
+					public void acceptType(
+						int modifiers,
+						char[] packageName,
+						char[] simpleTypeName,
+						char[][] enclosingTypeNames,
+						String path) {
+						// no type to accept
+					}
+				},
+				// will not activate index query caches if indexes are not ready, since it would take to long
+				// to wait until indexes are fully rebuild
+				IJavaSearchConstants.CANCEL_IF_NOT_READY_TO_SEARCH,
+				monitor
+			);
+		} catch (JavaModelException e) {
+			// /search failed: ignore
+		} catch (OperationCanceledException e) {
+			if (monitor.isCanceled())
+				throw e;
+			// else indexes were not ready: catch the exception so that jars are still refreshed
+		}
+	}
+
 	/**
 	 * Returns whether a given classpath variable is read-only or not.
 	 *
@@ -5439,6 +5460,22 @@
 		JavaModelManager.getDeltaState().removePreResourceChangedListener(listener);
 	}
 
+	/**
+	 * Deletes and rebuilds the java index.
+	 * 
+	 * @param monitor a progress monitor, or <code>null</code> if progress
+	 *    reporting and cancellation are not desired
+	 * @throws CoreException 
+	 * @since 3.13
+	 */
+	public static void rebuildIndex(IProgressMonitor monitor) throws CoreException {
+		SubMonitor subMonitor = SubMonitor.convert(monitor, 10);
+		IndexManager manager = JavaModelManager.getIndexManager();
+		manager.deleteIndexFiles(subMonitor.split(1));
+		manager.reset();
+		Indexer.getInstance().rebuildIndex(subMonitor.split(7));
+		updateLegacyIndex(subMonitor.split(2));
+	}
 
 
 	/**
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/Signature.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/Signature.java
index 1846d3f..c0ec076 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/Signature.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/Signature.java
@@ -1483,7 +1483,7 @@
 		}
 		return count;
 	} catch (ArrayIndexOutOfBoundsException e) { // signature is syntactically incorrect if last character is C_ARRAY
-		throw new IllegalArgumentException();
+		throw new IllegalArgumentException(e);
 	}
 }
 
@@ -1626,7 +1626,7 @@
 			count++;
 		}
 	} catch (ArrayIndexOutOfBoundsException e) {
-		throw new IllegalArgumentException();
+		throw new IllegalArgumentException(e);
 	}
 }
 
@@ -1680,7 +1680,7 @@
 			i = e + 1;
 		}
 	} catch (ArrayIndexOutOfBoundsException e) {
-		throw new IllegalArgumentException();
+		throw new IllegalArgumentException(e);
 	}
 }
 /**
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaElementInfo.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaElementInfo.java
index 0250d5d..4de81cb 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaElementInfo.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaElementInfo.java
@@ -28,7 +28,7 @@
 			return super.clone();
 		}
 		catch (CloneNotSupportedException e) {
-			throw new Error();
+			throw new Error(e);
 		}
 	}
 	public IJavaElement[] getChildren() {
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 b898a64..c250a8c 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
@@ -27,6 +27,7 @@
 import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.StringReader;
@@ -173,7 +174,7 @@
 	private static final String ASSUMED_EXTERNAL_FILES_CACHE = "assumedExternalFilesCache";  //$NON-NLS-1$
 
 	public static enum ArchiveValidity {
-		BAD_FORMAT, UNABLE_TO_READ, VALID;
+		BAD_FORMAT, UNABLE_TO_READ, FILE_NOT_FOUND, VALID;
 
 		public boolean isValid() {
 			return this == VALID;
@@ -2767,7 +2768,10 @@
 	 * zip/jar, or it must be an absolute workspace relative path if
 	 * representing a zip/jar inside the workspace.
 	 *
-	 * @exception CoreException If unable to create/open the ZipFile
+	 * @exception CoreException If unable to create/open the ZipFile. The
+	 * cause will be a {@link ZipException} if the file was corrupt, a
+	 * {@link FileNotFoundException} if the file does not exist, or a
+	 * {@link IOException} if we were unable to read the file.
 	 */
 	public ZipFile getZipFile(IPath path) throws CoreException {
 		return getZipFile(path, true);
@@ -2782,7 +2786,7 @@
 	 */
 	public static boolean throwIoExceptionsInGetZipFile = false;
 
-	private ZipFile getZipFile(IPath path, boolean checkInvalidArchiveCache) throws CoreException {
+	public ZipFile getZipFile(IPath path, boolean checkInvalidArchiveCache) throws CoreException {
 		if (checkInvalidArchiveCache) {
 			throwExceptionIfArchiveInvalid(path);
 		}
@@ -2807,7 +2811,15 @@
 			}
 			return zipFile;
 		} catch (IOException e) {
-			ArchiveValidity reason = (e instanceof ZipException) ? ArchiveValidity.BAD_FORMAT : ArchiveValidity.UNABLE_TO_READ;
+			ArchiveValidity reason; 
+			
+			if (e instanceof ZipException) {
+				reason = ArchiveValidity.BAD_FORMAT;
+			} else if (e instanceof FileNotFoundException) {
+				reason = ArchiveValidity.FILE_NOT_FOUND;
+			} else {
+				reason = ArchiveValidity.UNABLE_TO_READ;
+			}
 			addInvalidArchive(path, reason);
 			throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.status_IOException, e));
 		}
@@ -2835,13 +2847,14 @@
 
 	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();
-			}
+		IOException reason;
+		switch (validity) {
+			case BAD_FORMAT: reason = new ZipException(); break;
+			case FILE_NOT_FOUND: reason = new FileNotFoundException(); break;
+			case UNABLE_TO_READ: reason = new IOException(); break;
+			default: reason = null;
+		}
+		if (reason != null) {
 			throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.status_IOException, reason));
 		}
 	}
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaProject.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaProject.java
index 02e6460..65dd9b5 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaProject.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaProject.java
@@ -913,9 +913,9 @@
 			DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 			cpElement = parser.parse(new InputSource(reader)).getDocumentElement();
 		} catch (SAXException e) {
-			throw new IOException(Messages.file_badFormat);
+			throw new IOException(Messages.file_badFormat, e);
 		} catch (ParserConfigurationException e) {
-			throw new IOException(Messages.file_badFormat);
+			throw new IOException(Messages.file_badFormat, e);
 		} finally {
 			reader.close();
 		}
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/UserLibrary.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/UserLibrary.java
index 13a38c3..f205707 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/UserLibrary.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/UserLibrary.java
@@ -154,9 +154,9 @@
 			DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 			cpElement = parser.parse(new InputSource(reader)).getDocumentElement();
 		} catch (SAXException e) {
-			throw new IOException(Messages.file_badFormat);
+			throw new IOException(Messages.file_badFormat, e);
 		} catch (ParserConfigurationException e) {
-			throw new IOException(Messages.file_badFormat);
+			throw new IOException(Messages.file_badFormat, e);
 		} finally {
 			reader.close();
 		}
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/BatchImageBuilder.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/BatchImageBuilder.java
index 3097a43..26fb1c4 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/BatchImageBuilder.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/BatchImageBuilder.java
@@ -112,7 +112,11 @@
 								}
 							);
 						}
-						member.delete(IResource.FORCE, null);
+						try {
+							member.delete(IResource.FORCE, null);
+						} catch(CoreException e) {
+							Util.log(e, "Error occurred while deleting: " + member.getFullPath()); //$NON-NLS-1$
+						}
 					}
 				}
 				this.notifier.checkCancel();
@@ -139,7 +143,11 @@
 											return false;
 									if (!resource.isDerived())
 										resource.setDerived(true, null);
-									resource.delete(IResource.FORCE, null);
+									try {
+										resource.delete(IResource.FORCE, null);
+									} catch(CoreException e) {
+										Util.log(e, "Error occurred while deleting: " + resource.getFullPath()); //$NON-NLS-1$
+									}
 								}
 								return false;
 							}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Nd.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Nd.java
index fb02f13..f38304d 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Nd.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Nd.java
@@ -169,8 +169,8 @@
 
 		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$
+			Package.logInfo("Index database uses the unsupported version " + this.db.getVersion() //$NON-NLS-1$
+				+ ". Deleting and recreating."); //$NON-NLS-1$
 			this.db.close();
 			this.fPath.delete();
 			this.db = new Database(this.fPath, cache, getDefaultVersion(), isPermanentlyReadOnly());
@@ -599,4 +599,8 @@
 	public NdNodeTypeRegistry<NdNode> getTypeRegistry() {
 		return this.fNodeTypeRegistry;
 	}
+
+	public void clear(IProgressMonitor monitor) {
+		getDB().clear(getDefaultVersion());
+	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/RawGrowableArray.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/RawGrowableArray.java
index df7ca4e..2bd371c 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/RawGrowableArray.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/RawGrowableArray.java
@@ -126,7 +126,7 @@
 
 			GROWABLE_BLOCK_HEADER_BYTES = type.size();
 
-			MAX_GROWABLE_SIZE = (Database.MAX_MALLOC_SIZE - GROWABLE_BLOCK_HEADER_BYTES)
+			MAX_GROWABLE_SIZE = (Database.MAX_SINGLE_BLOCK_MALLOC_SIZE - GROWABLE_BLOCK_HEADER_BYTES)
 					/ Database.PTR_SIZE;
 		}
 	}
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 02060fe..8bb0ed0 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
@@ -46,8 +46,9 @@
  * 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
+ * FREE_BLOCK_OFFSET    | chunk number for the root of the large block free space trie
  * WRITE_NUMBER_OFFSET  | long integer which is incremented on every write
- * MALLOC_STATS_OFFSET  | memory usage statistics 
+ * 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
@@ -86,21 +87,26 @@
 	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
+
+	// Must be enough multiples of BLOCK_SIZE_DELTA in order to fit the free block header
+	public static final int MIN_BLOCK_DELTAS = (FREE_BLOCK_HEADER_SIZE + BLOCK_SIZE_DELTA - 1) / BLOCK_SIZE_DELTA;
+	public static final int MAX_BLOCK_DELTAS = (CHUNK_SIZE - LargeBlock.HEADER_SIZE - LargeBlock.FOOTER_SIZE)
+			/ BLOCK_SIZE_DELTA;
+	public static final int MAX_SINGLE_BLOCK_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 long MAX_MALLOC_SIZE = MAX_DB_SIZE - LargeBlock.HEADER_SIZE - LargeBlock.FOOTER_SIZE
+			- CHUNK_SIZE - BLOCK_HEADER_SIZE;
+
 	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
+	public static final int MALLOC_TABLE_OFFSET = VERSION_OFFSET + INT_SIZE;
+	public static final int FREE_BLOCK_OFFSET = MALLOC_TABLE_OFFSET
 			+ (CHUNK_SIZE / BLOCK_SIZE_DELTA - MIN_BLOCK_DELTAS + 1) * INT_SIZE;
+	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;
 
@@ -128,7 +134,6 @@
 	private final Chunk fHeaderChunk;
 	private Chunk[] fChunks;
 	private int fChunksUsed;
-	private int fChunksAllocated;
 	private ChunkCache fCache;
 
 	private long malloced;
@@ -159,12 +164,12 @@
 			if (nChunksOnDisk <= 0) {
 				this.fVersion= version;
 				this.fChunks= new Chunk[1];
-				this.fChunksUsed = this.fChunksAllocated = this.fChunks.length;
+				this.fChunksUsed = 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;
+				this.fChunksUsed = nChunksOnDisk;
 			}
 		} catch (IOException e) {
 			throw new IndexException(new DBStatus(e));
@@ -172,8 +177,8 @@
 		this.memoryUsage = new MemoryStats(this.fHeaderChunk, MALLOC_STATS_OFFSET);
 	}
 
-	private static int divideRoundingUp(int num, int den) {
-		return (num + den - 1) / den;
+	private static int divideRoundingUp(long num, int den) {
+		return (int) ((num + den - 1) / den);
 	}
 
 	private void openFile() throws FileNotFoundException {
@@ -292,7 +297,7 @@
 		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;
+		this.fChunksUsed = this.fChunks.length;
 		try {
 			wasCanceled = this.fHeaderChunk.flush() || wasCanceled; // Zero out header chunk.
 			wasCanceled = performUninterruptableWrite(() -> {
@@ -381,7 +386,7 @@
 	 */
 	public void memcpy(long dest, long source, int numBytes) {
 		assert numBytes >= 0;
-		assert numBytes <= MAX_MALLOC_SIZE;
+		assert numBytes <= MAX_SINGLE_BLOCK_MALLOC_SIZE;
 		// TODO: make use of lower-level System.arrayCopy
 		for (int count = 0; count < numBytes; count++) {
 			putByte(dest + count, getByte(source + count));
@@ -391,78 +396,442 @@
 	/**
 	 * Allocate a block out of the database.
 	 */
-	public long malloc(final int datasize, final short poolId) throws IndexException {
+	public long malloc(final long 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);
+		long result;
+		int usedSize;
+		if (datasize >= MAX_SINGLE_BLOCK_MALLOC_SIZE) {
+			int newChunkNum = createLargeBlock(datasize);
+			usedSize = Math.abs(getBlockHeaderForChunkNum(newChunkNum)) * CHUNK_SIZE;
+			result = newChunkNum * CHUNK_SIZE + LargeBlock.HEADER_SIZE;
+			// Note that we identify large blocks by setting their block size to 0.
+			clearRange(result, usedSize - LargeBlock.HEADER_SIZE - LargeBlock.FOOTER_SIZE);
+			result = result + BLOCK_HEADER_SIZE;
 		} else {
-			chunk = getChunk(freeblock);
-			removeBlock(chunk, useDeltas * BLOCK_SIZE_DELTA, freeblock);
+			long freeBlock = 0;
+			int needDeltas = divideRoundingUp(datasize + BLOCK_HEADER_SIZE, BLOCK_SIZE_DELTA);
+			if (needDeltas < MIN_BLOCK_DELTAS) {
+				needDeltas = MIN_BLOCK_DELTAS;
+			}
+
+			// Which block size.
+			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 = (long) (createLargeBlock(datasize)) * (long) CHUNK_SIZE + LargeBlock.HEADER_SIZE;
+				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.
+			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);
+			result = freeBlock + BLOCK_HEADER_SIZE;
 		}
 
-		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 {
+	/**
+	 * Clears all the bytes in the given range by setting them to zero.
+	 * 
+	 * @param startAddress first address to clear
+	 * @param bytesToClear number of addresses to clear
+	 */
+	public void clearRange(long startAddress, int bytesToClear) {
+		if (bytesToClear == 0) {
+			return;
+		}
+		long endAddress = startAddress + bytesToClear;
+		assert endAddress <= this.fChunksUsed * CHUNK_SIZE;
+		int blockNumber = (int) (startAddress / CHUNK_SIZE);
+		int firstBlockBytesToClear = Math.min((int) (((blockNumber + 1) * CHUNK_SIZE) - startAddress), bytesToClear);
+
+		Chunk firstBlock = getChunk(startAddress);
+		firstBlock.clear(startAddress, firstBlockBytesToClear);
+		startAddress += firstBlockBytesToClear;
+		bytesToClear -= firstBlockBytesToClear;
+		while (bytesToClear > CHUNK_SIZE) {
+			Chunk nextBlock = getChunk(startAddress);
+			nextBlock.clear(startAddress, CHUNK_SIZE);
+			startAddress += CHUNK_SIZE;
+			bytesToClear -= CHUNK_SIZE;
+		}
+
+		if (bytesToClear > 0) {
+			Chunk nextBlock = getChunk(startAddress);
+			nextBlock.clear(startAddress, bytesToClear);
+		}
+	}
+
+	/**
+	 * Obtains a new block that can fit the given number of bytes (at minimum). Returns the
+	 * chunk number.
+	 * 
+	 * @param datasize minimum number of bytes needed
+	 * @return the chunk number
+	 */
+	private int createLargeBlock(long datasize) {
+		final int neededChunks = getChunksNeededForBytes(datasize);
+		int freeBlockChunkNum = getFreeBlockFromTrie(neededChunks);
+		final int numChunks;
+
+		if (freeBlockChunkNum == 0) {
+			final int lastChunkNum = this.fChunksUsed;
+
+			numChunks = neededChunks;
+
+			// Check if the last block in the database is free. If so, unlink and expand it.
+			int lastBlockSize = getBlockFooterForChunkBefore(lastChunkNum);
+			if (lastBlockSize > 0) {
+				int startChunkNum = getFirstChunkOfBlockBefore(lastChunkNum);
+
+				unlinkFreeBlock(startChunkNum);
+				// Allocate additional new chunks such that the new chunk is large enough to
+				// handle this allocation.
+				createNewChunks(neededChunks - lastBlockSize);
+				freeBlockChunkNum = startChunkNum;
+			} else {
+				freeBlockChunkNum = createNewChunks(numChunks);
+			}
+		} else {
+			numChunks = getBlockHeaderForChunkNum(freeBlockChunkNum);
+
+			unlinkFreeBlock(freeBlockChunkNum);
+		}
+
+		final int resultChunkNum;
+		if (numChunks > neededChunks) {
+			// If the chunk we've selected is larger than necessary, split it. We have the
+			// choice of using either half of the block. In the interest of leaving more
+			// opportunities of merging large blocks, we leave the unused half of the block
+			// next to the larger adjacent block.
+			final long nextBlockChunkNum = freeBlockChunkNum + numChunks;
+
+			final int nextBlockSize = Math.abs(getBlockHeaderForChunkNum(nextBlockChunkNum));
+			final int prevBlockSize = Math.abs(getBlockFooterForChunkBefore(freeBlockChunkNum));
+
+			final int unusedChunks = numChunks - neededChunks;
+			if (nextBlockSize >= prevBlockSize) {
+				// Use the start of the block
+				resultChunkNum = freeBlockChunkNum;
+				// Return the last half of the block to the free block pool
+				linkFreeBlockToTrie(freeBlockChunkNum + neededChunks, unusedChunks);
+			} else {
+				// Use the end of the block
+				resultChunkNum = freeBlockChunkNum + neededChunks;
+				// Return the first half of the block to the free block pool
+				linkFreeBlockToTrie(freeBlockChunkNum, unusedChunks);
+			}
+		} else {
+			resultChunkNum = freeBlockChunkNum;
+		}
+
+		// Fill in the header and footer
+		setBlockHeader(resultChunkNum, -neededChunks);
+		return resultChunkNum;
+	}
+
+	/**
+	 * Unlinks a free block (which currently belongs to the free block trie) so that it may
+	 * be reused.
+	 * 
+	 * @param freeBlockChunkNum chunk number of the block to be unlinked
+	 */
+	private void unlinkFreeBlock(int freeBlockChunkNum) {
+		long freeBlockAddress = freeBlockChunkNum * CHUNK_SIZE;
+		int anotherBlockOfSameSize = 0;
+		int nextBlockChunkNum = getInt(freeBlockAddress + LargeBlock.NEXT_BLOCK_OFFSET);
+		int prevBlockChunkNum = getInt(freeBlockAddress + LargeBlock.PREV_BLOCK_OFFSET);
+		// Relink the linked list
+		if (nextBlockChunkNum != 0) {
+			anotherBlockOfSameSize = nextBlockChunkNum;
+			putInt(nextBlockChunkNum * CHUNK_SIZE + LargeBlock.PREV_BLOCK_OFFSET, prevBlockChunkNum);
+		}
+		if (prevBlockChunkNum != 0) {
+			anotherBlockOfSameSize = prevBlockChunkNum;
+			putInt(prevBlockChunkNum * CHUNK_SIZE + LargeBlock.NEXT_BLOCK_OFFSET, nextBlockChunkNum);
+		}
+
+		long root = getInt(FREE_BLOCK_OFFSET);
+		if (root == freeBlockChunkNum) {
+			putInt(FREE_BLOCK_OFFSET, 0);
+		}
+
+		int freeBlockSize = getBlockHeaderForChunkNum(freeBlockChunkNum);
+		int parentChunkNum = getInt(freeBlockAddress + LargeBlock.PARENT_OFFSET);
+		if (parentChunkNum != 0) {
+			int currentSize = getBlockHeaderForChunkNum(parentChunkNum);
+			int difference = currentSize ^ freeBlockSize;
+			if (difference != 0) {
+				int firstDifference = LargeBlock.SIZE_OF_SIZE_FIELD * 8 - Integer.numberOfLeadingZeros(difference) - 1;
+				long locationOfChildPointer = parentChunkNum * CHUNK_SIZE + LargeBlock.CHILD_TABLE_OFFSET
+						+ (firstDifference * INT_SIZE);
+				putInt(locationOfChildPointer, 0);
+			}
+		}
+
+		if (anotherBlockOfSameSize != 0) {
+			insertChild(parentChunkNum, anotherBlockOfSameSize);
+		}
+
+		int currentParent = parentChunkNum;
+		for (int childIdx = 0; childIdx < LargeBlock.ENTRIES_IN_CHILD_TABLE; childIdx++) {
+			int nextChildChunkNum = getInt(freeBlockAddress + LargeBlock.CHILD_TABLE_OFFSET + (childIdx * INT_SIZE));
+			if (nextChildChunkNum != 0) {
+				insertChild(currentParent, nextChildChunkNum);
+				// Parent all subsequent children under the child that was most similar to the old parent
+				if (currentParent == parentChunkNum) {
+					currentParent = nextChildChunkNum;
+				}
+			}
+		}
+
+	}
+
+	/**
+	 * Returns the chunk number of a free block that contains at least the given number of chunks, or
+	 * 0 if there is no existing contiguous free block containing at least the given number of chunks.
+	 * 
+	 * @param numChunks minumum number of chunks desired
+	 * @return the chunk number of a free block containing at least the given number of chunks or 0
+	 * if there is no existing free block containing that many chunks.
+	 */
+	private int getFreeBlockFromTrie(int numChunks) {
+		int currentChunkNum = getInt(FREE_BLOCK_OFFSET);
+
+		int resultChunkNum = getSmallestChildNoSmallerThan(currentChunkNum, numChunks);
+		if (resultChunkNum == 0) {
+			return 0;
+		}
+
+		// Try not to return the trie node itself if there is a linked list entry available, since unlinking
+		// something from the linked list is faster than unlinking a trie node.
+		int nextResultChunkNum = getInt(resultChunkNum * CHUNK_SIZE + LargeBlock.NEXT_BLOCK_OFFSET);
+		if (nextResultChunkNum != 0) {
+			return nextResultChunkNum;
+		}
+		return resultChunkNum;
+	}
+
+	/**
+	 * Given the chunk number of a block somewhere in the free space trie, this returns the smallest
+	 * child in the subtree that is no smaller than the given number of chunks.
+	 * 
+	 * @param trieNodeChunkNum chunk number of a block in the free space trie
+	 * @param numChunks desired number of chunks
+	 * @return the chunk number of the first chunk in a contiguous free block containing at least the
+	 * given number of chunks
+	 */
+	private int getSmallestChildNoSmallerThan(int trieNodeChunkNum, int numChunks) {
+		if (trieNodeChunkNum == 0) {
+			return 0;
+		}
+		int currentSize = getBlockHeaderForChunkNum(trieNodeChunkNum);
+		assert (currentSize >= 0);
+		int difference = currentSize ^ numChunks;
+		if (difference == 0) {
+			return trieNodeChunkNum;
+		}
+
+		int bitMask = Integer.highestOneBit(difference);
+		int firstDifference = LargeBlock.SIZE_OF_SIZE_FIELD * 8 - Integer.numberOfLeadingZeros(bitMask) - 1;
+		boolean lookingForSmallerChild = (currentSize > numChunks);
+		for (int testPosition = firstDifference; testPosition < LargeBlock.ENTRIES_IN_CHILD_TABLE; testPosition++) {
+			if (((currentSize & bitMask) != 0) == lookingForSmallerChild) {
+				int nextChildChunkNum = getInt(
+						trieNodeChunkNum * CHUNK_SIZE + LargeBlock.CHILD_TABLE_OFFSET + (testPosition * PTR_SIZE));
+				int childResultChunkNum = getSmallestChildNoSmallerThan(nextChildChunkNum, numChunks);
+				if (childResultChunkNum != 0) {
+					return childResultChunkNum;
+				}
+			}
+			bitMask <<= 1;
+		}
+
+		if (lookingForSmallerChild) {
+			return trieNodeChunkNum;
+		} else {
+			return 0;
+		}
+	}
+
+	/**
+	 * Link the given unused block into the free block tries. The block does not need to have
+	 * its header filled in already.
+	 * 
+	 * @param freeBlockChunkNum chunk number of the start of the block
+	 * @param numChunks number of chunks in the block
+	 */
+	private void linkFreeBlockToTrie(int freeBlockChunkNum, int numChunks) {
+		setBlockHeader(freeBlockChunkNum, numChunks);
+		long freeBlockAddress = freeBlockChunkNum * CHUNK_SIZE;
+		Chunk chunk = getChunk(freeBlockAddress);
+		chunk.clear(freeBlockAddress + LargeBlock.HEADER_SIZE,
+				LargeBlock.UNALLOCATED_HEADER_SIZE - LargeBlock.HEADER_SIZE);
+
+		insertChild(getInt(FREE_BLOCK_OFFSET), freeBlockChunkNum);
+	}
+
+	/**
+	 * Adds the given child block to the given parent subtree of the free space trie. Any existing
+	 * subtree under the given child block will be retained.
+	 * 
+	 * @param parentChunkNum root of the existing tree, or 0 if the child is going to be the new root
+	 * @param newChildChunkNum the new child to insert
+	 */
+	private void insertChild(int parentChunkNum, int newChildChunkNum) {
+		if (parentChunkNum == 0) {
+			putInt(newChildChunkNum * CHUNK_SIZE + LargeBlock.PARENT_OFFSET, parentChunkNum);
+			putInt(FREE_BLOCK_OFFSET, newChildChunkNum);
+			return;
+		}
+		int numChunks = getBlockHeaderForChunkNum(newChildChunkNum);
+		for (;;) {
+			int currentSize = getBlockHeaderForChunkNum(parentChunkNum);
+			int difference = currentSize ^ numChunks;
+			if (difference == 0) {
+				// The newly added item is exactly the same size as this trie node
+				insertFreeBlockAfter(parentChunkNum, newChildChunkNum);
+				return;
+			}
+
+			int firstDifference = LargeBlock.SIZE_OF_SIZE_FIELD * 8 - Integer.numberOfLeadingZeros(difference) - 1;
+			long locationOfChildPointer = parentChunkNum * CHUNK_SIZE + LargeBlock.CHILD_TABLE_OFFSET
+					+ (firstDifference * INT_SIZE);
+			int childChunkNum = getInt(locationOfChildPointer);
+			if (childChunkNum == 0) {
+				putInt(locationOfChildPointer, newChildChunkNum);
+				putInt(newChildChunkNum * CHUNK_SIZE + LargeBlock.PARENT_OFFSET, parentChunkNum);
+				return;
+			}
+			parentChunkNum = childChunkNum;
+		}
+	}
+
+	/**
+	 * Adds the given block to the linked list of equally-sized free chunks in the free space trie.
+	 * Both chunks must be unused, must be the same size, and the previous chunk must already
+	 * be linked into the free space trie. The newly-added chunk must not have any children.
+	 * 
+	 * @param prevChunkNum chunk number of previous block in the existing list
+	 * @param newChunkNum new chunk to be added to the list
+	 */
+	private void insertFreeBlockAfter(int prevChunkNum, int newChunkNum) {
+		long prevChunkAddress = (long) prevChunkNum * CHUNK_SIZE;
+		int nextChunkNum = getInt(prevChunkAddress + LargeBlock.NEXT_BLOCK_OFFSET);
+		long nextChunkAddress = (long) nextChunkNum * CHUNK_SIZE;
+		long newLockAddress = (long) newChunkNum * CHUNK_SIZE;
+
+		putInt(prevChunkAddress + LargeBlock.NEXT_BLOCK_OFFSET, newChunkNum);
+		if (nextChunkNum != 0) {
+			putInt(nextChunkAddress + LargeBlock.PREV_BLOCK_OFFSET, newChunkNum);
+		}
+		putInt(newLockAddress + LargeBlock.PREV_BLOCK_OFFSET, prevChunkNum);
+		putInt(newLockAddress + LargeBlock.NEXT_BLOCK_OFFSET, nextChunkNum);
+	}
+
+	/**
+	 * Returns the chunk number of the chunk at the start of a block, given the
+	 * chunk number of the chunk at the start of the following block.
+	 * 
+	 * @param chunkNum the chunk number of the chunk immediately following the
+	 * chunk being queried
+	 * @return the chunk number of the chunk at the start of the previous block
+	 */
+	private int getFirstChunkOfBlockBefore(int chunkNum) {
+		int blockChunks = Math.abs(getBlockFooterForChunkBefore(chunkNum));
+		return chunkNum - blockChunks;
+	}
+
+	/**
+	 * Sets the block header and footer for the given range of chunks which make
+	 * up a contiguous block.
+	 * 
+	 * @param firstChunkNum chunk number of the first chunk in the block
+	 * @param headerContent the content of the header. Its magnitude is the number of
+	 * chunks in the block. It is positive if the chunk is free and negative if
+	 * the chunk is in use.
+	 */
+	private void setBlockHeader(int firstChunkNum, int headerContent) {
+		assert headerContent != 0;
+		assert firstChunkNum < this.fChunksUsed;
+		int numBlocks = Math.abs(headerContent);
+		long firstChunkAddress = firstChunkNum * CHUNK_SIZE;
+		putInt(firstChunkAddress, headerContent);
+		putInt(firstChunkAddress + (numBlocks * CHUNK_SIZE) - LargeBlock.FOOTER_SIZE, headerContent);
+	}
+
+	/**
+	 * Returns the size of the block (in number of chunks) starting at the given address. The return value is positive
+	 * if the block is free and negative if the block is allocated.
+	 */
+	private int getBlockHeaderForChunkNum(long firstChunkNum) {
+		if (firstChunkNum >= this.fChunksUsed) {
+			return 0;
+		}
+		return getInt(firstChunkNum * CHUNK_SIZE);
+	}
+
+	/**
+	 * Returns the size of the block (in number of chunks), given the (non-inclusive) address that the block ends at.
+	 * The return value is positive if the block is free and negative if the block is allocated.
+	 */
+	private int getBlockFooterForChunkBefore(int chunkNum) {
+		if (chunkNum < 2) {
+			// Don't report the database header as a normal chunk.
+			return 0;
+		}
+		return getInt(chunkNum * CHUNK_SIZE - LargeBlock.FOOTER_SIZE);
+	}
+
+	private int createNewChunks(int numChunks) throws IndexException {
 		assert this.fExclusiveLock;
 		synchronized (this.fCache) {
-			final int newChunkIndex = this.fChunksUsed; // fChunks.length;
+			final int firstChunkIndex = this.fChunksUsed;
+			final int lastChunkIndex = firstChunkIndex + numChunks - 1;
 
-			final Chunk chunk = new Chunk(this, newChunkIndex);
-			chunk.fDirty = true;
+			final Chunk lastChunk = new Chunk(this, lastChunkIndex);
+			lastChunk.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;
+			if (lastChunkIndex >= this.fChunks.length) {
+				int increment = Math.max(1024, this.fChunks.length / 20);
+				int newNumChunks = Math.max(lastChunkIndex + 1, this.fChunks.length + increment);
+				Chunk[] newChunks = new Chunk[newNumChunks];
+				System.arraycopy(this.fChunks, 0, newChunks, 0, this.fChunks.length);
+				this.fChunks = newChunks;
 			}
-			this.fChunksUsed += 1;
-			this.fChunks[newChunkIndex] = chunk;
 
-			this.fCache.add(chunk, true);
-			long address = (long) newChunkIndex * CHUNK_SIZE;
+			this.fChunksUsed = lastChunkIndex + 1;
+			this.fChunks[lastChunkIndex] = lastChunk;
+			this.fCache.add(lastChunk, true);
+			long result = (long) firstChunkIndex * CHUNK_SIZE;
 
 			/*
 			 * Non-dense pointers are at most 31 bits dense pointers are at most 35 bits Check the sizes here and throw
@@ -470,33 +839,15 @@
 			 * 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) {
+			long endAddress = result + (numChunks * CHUNK_SIZE);
+			if (endAddress > 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));
+						NLS.bind("Database too large! Address = " + endAddress + ", max size = " + MAX_DB_SIZE, //$NON-NLS-1$ //$NON-NLS-2$
+								bindings), 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;
+			return firstChunkIndex;
 		}
 	}
 
@@ -544,26 +895,74 @@
 	/**
 	 * 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
+	 * @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 blockSize;
 		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$
+		blockSize = -chunk.getShort(block);
+		// We use a block size of 0 to indicate a large block that fills a range of chunks
+		if (blockSize == 0) {
+			int offsetIntoChunk = (int) (address % CHUNK_SIZE);
+			assert offsetIntoChunk == LargeBlock.HEADER_SIZE + BLOCK_HEADER_SIZE;
+			// Deallocating a large block
+			// This was a large block. It uses a sequence of full chunks.
+			int chunkNum = (int) (address / CHUNK_SIZE);
+			int numChunks = -getBlockHeaderForChunkNum(chunkNum);
+			if (numChunks < 0) {
+				// Already freed.
+				throw new IndexException(new Status(IStatus.ERROR, Package.PLUGIN_ID, 0,
+						"Already freed large block " + address, new Exception())); //$NON-NLS-1$
+			}
+			blockSize = numChunks * CHUNK_SIZE;
+			freeLargeChunk(chunkNum, numChunks);
+		} else {
+			// Deallocating a normal block
+			// TODO Look for opportunities to merge small blocks
+			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, (int) blockSize, block);
 		}
-		addBlock(chunk, blocksize, block);
-		this.freed += blocksize;
-		this.memoryUsage.recordFree(poolId, blocksize);
+
+		this.freed += blockSize;
+		this.memoryUsage.recordFree(poolId, blockSize);
+	}
+
+	private void freeLargeChunk(int chunkNum, int numChunks) {
+		assert chunkNum > 0;
+		assert numChunks > 0;
+		int prevBlockHeader = getBlockFooterForChunkBefore(chunkNum);
+		int nextBlockChunkNum = chunkNum + numChunks;
+		int nextBlockHeader = getBlockHeaderForChunkNum(nextBlockChunkNum);
+
+		// If the previous block is unused, merge with it
+		if (prevBlockHeader > 0) {
+			int prevBlockChunkNum = getFirstChunkOfBlockBefore(chunkNum);
+
+			unlinkFreeBlock(prevBlockChunkNum);
+			chunkNum = prevBlockChunkNum;
+			numChunks += prevBlockHeader;
+		}
+
+		// If the next block is unused, merge with it
+		if (nextBlockHeader > 0) {
+			unlinkFreeBlock(nextBlockChunkNum);
+			numChunks += nextBlockHeader;
+		}
+
+		// Block merging is done. Now reinsert the merged block into the free space trie
+		linkFreeBlockToTrie(chunkNum, numChunks);
 	}
 
 	public void putByte(long offset, byte value) throws IndexException {
@@ -745,7 +1144,7 @@
 		this.memoryUsage.refresh();
 		this.fHeaderChunk.fDirty= false;
 		this.fChunks= new Chunk[] { null };
-		this.fChunksUsed = this.fChunksAllocated = this.fChunks.length;
+		this.fChunksUsed = this.fChunks.length;
 		try {
 			this.fFile.close();
 		} catch (IOException e) {
@@ -931,6 +1330,10 @@
 		return this.fFile.length();
 	}
 
+	public int getChunkCount() {
+		return this.fChunksUsed;
+	}
+
 	/**
 	 * A Record Pointer is a pointer as returned by Database.malloc().
 	 * This is a pointer to a block + BLOCK_HEADER_SIZE.
@@ -953,4 +1356,19 @@
 	public MemoryStats getMemoryStats() {
 		return this.memoryUsage;
 	}
+
+	/**
+	 * Returns the number of bytes that can fit in the payload of the given number of chunks.
+	 */
+	public static long getBytesThatFitInChunks(int numChunks) {
+		return CHUNK_SIZE * numChunks - LargeBlock.HEADER_SIZE - LargeBlock.FOOTER_SIZE - BLOCK_HEADER_SIZE;
+	}
+
+	/**
+	 * Returns the number of chunks needed to fit the given number of bytes of payload.
+	 */
+	public static int getChunksNeededForBytes(long datasize) {
+		return divideRoundingUp(datasize + BLOCK_HEADER_SIZE + LargeBlock.HEADER_SIZE + LargeBlock.FOOTER_SIZE,
+				CHUNK_SIZE);
+	}
 }
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/LargeBlock.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/LargeBlock.java
new file mode 100644
index 0000000..26af53c
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/LargeBlock.java
@@ -0,0 +1,26 @@
+package org.eclipse.jdt.internal.core.nd.db;
+
+public class LargeBlock {
+	public static final int SIZE_OFFSET = 0;
+	public static final int SIZE_OF_SIZE_FIELD = Database.INT_SIZE;
+	/**
+	 * Size of the header for a large block. The header consists of a int which holds the number of chunks in the block.
+	 * It is negative for an allocated block and positive for an unallocated block. The header is located at the start
+	 * of the large block.
+	 */
+	public static final int HEADER_SIZE = Math.max(Database.INT_SIZE, Database.BLOCK_SIZE_DELTA);
+
+	public static final int ENTRIES_IN_CHILD_TABLE = SIZE_OF_SIZE_FIELD * 8;
+	public static final int CHILD_TABLE_OFFSET = HEADER_SIZE;
+	public static final int PARENT_OFFSET = CHILD_TABLE_OFFSET + (Database.INT_SIZE * ENTRIES_IN_CHILD_TABLE);
+	public static final int PREV_BLOCK_OFFSET = PARENT_OFFSET + Database.INT_SIZE;
+	public static final int NEXT_BLOCK_OFFSET = PREV_BLOCK_OFFSET + Database.INT_SIZE;
+
+	public static final int UNALLOCATED_HEADER_SIZE = NEXT_BLOCK_OFFSET + Database.INT_SIZE;
+
+	/**
+	 * The large block footer is located at the end of the last chunk in the large block. It is an exact copy of the
+	 * header.
+	 */
+	public static final int FOOTER_SIZE = HEADER_SIZE;
+}
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
index c78b7f9..eb0e48a 100644
--- 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
@@ -29,13 +29,13 @@
 	private static final int NEXT1 = 4;
 	private static final int CHARS1 = 8;
 
-	private static final int NUM_CHARS1 = (Database.MAX_MALLOC_SIZE - CHARS1) / 2;
+	private static final int NUM_CHARS1 = (Database.MAX_SINGLE_BLOCK_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;
+	private static final int NUM_CHARSN = (Database.MAX_SINGLE_BLOCK_MALLOC_SIZE - CHARSN) / 2;
 
 	public LongString(Database db, long record) {
 		this.db = db;
@@ -47,7 +47,7 @@
 		final int numCharsn = useBytes ? NUM_CHARSN * 2 : NUM_CHARSN;
 
 		this.db = db;
-		this.record = db.malloc(Database.MAX_MALLOC_SIZE, Database.POOL_STRING_LONG);
+		this.record = db.malloc(Database.MAX_SINGLE_BLOCK_MALLOC_SIZE, Database.POOL_STRING_LONG);
 
 		// Write the first record.
 		final int length = chars.length;
@@ -64,7 +64,7 @@
 		long lastNext = this.record + NEXT1;
 		int start = numChars1;
 		while (length - start > numCharsn) {
-			long nextRecord = db.malloc(Database.MAX_MALLOC_SIZE, Database.POOL_STRING_LONG);
+			long nextRecord = db.malloc(Database.MAX_SINGLE_BLOCK_MALLOC_SIZE, Database.POOL_STRING_LONG);
 			db.putRecPtr(lastNext, nextRecord);
 			chunk= db.getChunk(nextRecord);
 			if (useBytes) {
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
index 09992a5..eb56c6c 100644
--- 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
@@ -25,7 +25,7 @@
 	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 static final int MAX_BYTE_LENGTH = Database.MAX_SINGLE_BLOCK_MALLOC_SIZE - CHARS;
 
 	public ShortString(Database db, long offset) {
 		this.db = db;
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/ClassFileToIndexConverter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/ClassFileToIndexConverter.java
index afd740e..d52ce33 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/ClassFileToIndexConverter.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/ClassFileToIndexConverter.java
@@ -16,9 +16,7 @@
 
 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;
@@ -69,8 +67,6 @@
 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;
@@ -97,12 +93,6 @@
 		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.
 	 *
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 318a736..2380780 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,7 +10,7 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.core.nd.indexer;
 
-import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -43,6 +43,7 @@
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.SubMonitor;
 import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.core.runtime.jobs.JobGroup;
 import org.eclipse.jdt.core.IClassFile;
 import org.eclipse.jdt.core.IClasspathEntry;
 import org.eclipse.jdt.core.IJavaElement;
@@ -115,10 +116,16 @@
 	 */
 	private Set<Listener> listeners = Collections.newSetFromMap(new WeakHashMap<Listener, Boolean>());
 
+	private JobGroup group = new JobGroup(Messages.Indexer_updating_index_job_name, 1, 1);
+
 	private Job rescanJob = Job.create(Messages.Indexer_updating_index_job_name, monitor -> {
 		rescan(monitor);
 	});
 
+	private Job rebuildIndexJob = Job.create(Messages.Indexer_updating_index_job_name, monitor -> {
+		rebuildIndex(monitor);
+	});
+
 	public static interface Listener {
 		void consume(IndexerEvent event);
 	}
@@ -560,14 +567,6 @@
 		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));
@@ -589,9 +588,11 @@
 		if (DEBUG) {
 			Package.logInfo("rescanning " + thePath.toString() + ", " + fingerprint); //$NON-NLS-1$ //$NON-NLS-2$
 		}
-		int result;
+		int result = 0;
 		try {
-			result = addElement(resourceFile, element, subMonitor.split(50));
+			if (fingerprint.fileExists()) {
+				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$
@@ -611,6 +612,12 @@
 				Package.log("A RuntimeException occurred while indexing " + pathString, e); //$NON-NLS-1$
 			}
 			throw e;
+		} catch (FileNotFoundException e) {
+			fingerprint = FileFingerprint.getEmpty();
+		}
+
+		if (DEBUG && !fingerprint.fileExists()) {
+			Package.log("the file " + pathString + " was not indexed because it does not exist", null); //$NON-NLS-1$ //$NON-NLS-2$
 		}
 
 		List<NdResourceFile> allResourcesWithThisPath = Collections.emptyList();
@@ -648,9 +655,10 @@
 
 	/**
 	 * Adds an archive to the index, under the given NdResourceFile.
+	 * @throws FileNotFoundException if the file does not exist
 	 */
 	private int addElement(NdResourceFile resourceFile, IJavaElement element, IProgressMonitor monitor)
-			throws JavaModelException {
+			throws JavaModelException, FileNotFoundException {
 		SubMonitor subMonitor = SubMonitor.convert(monitor);
 
 		if (element instanceof JarPackageFragmentRoot) {
@@ -704,6 +712,8 @@
 			} 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 (FileNotFoundException e) {
+				throw e;
 			} catch (IOException ioException) {
 				throw new JavaModelException(ioException, IJavaModelStatusConstants.IO_EXCEPTION);
 			} catch (CoreException coreException) {
@@ -722,7 +732,7 @@
 
 			boolean indexed = false;
 			try {
-				ClassFileReader classFileReader = BinaryTypeFactory.rawReadType(descriptor, true);
+				ClassFileReader classFileReader = BinaryTypeFactory.rawReadTypeTestForExists(descriptor, true, false);
 				if (classFileReader != null) {
 					indexed = addClassToIndex(resourceFile, descriptor.fieldDescriptor, descriptor.indexPath,
 							classFileReader, iterationMonitor);
@@ -983,6 +993,10 @@
 	public Indexer(Nd toPopulate, IWorkspaceRoot workspaceRoot) {
 		this.nd = toPopulate;
 		this.root = workspaceRoot;
+		this.rescanJob.setSystem(true);
+		this.rescanJob.setJobGroup(this.group);
+		this.rebuildIndexJob.setSystem(true);
+		this.rebuildIndexJob.setJobGroup(this.group);
 	}
 
 	public void rescanAll() {
@@ -1070,4 +1084,20 @@
 			}
 		}
 	}
+
+	public void rebuildIndex(IProgressMonitor monitor) throws CoreException {
+		SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
+
+		this.nd.acquireWriteLock(subMonitor.split(1));
+		try {
+			this.nd.clear(subMonitor.split(2));
+		} finally {
+			this.nd.releaseWriteLock();
+		}
+		rescan(subMonitor.split(98));
+	}
+
+	public void requestRebuildIndex() {
+		this.rebuildIndexJob.schedule();
+	}
 }
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
index f1b0262..b5d6062 100644
--- 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
@@ -139,6 +139,15 @@
 	}
 
 	/**
+	 * Returns true iff the file existed at the time the fingerprint was computed.
+	 * 
+	 * @return true iff the file existed at the time the fingerprint was computed.
+	 */
+	public boolean fileExists() {
+		return !equals(EMPTY);
+	}
+
+	/**
 	 * Compares the given File with the receiver. If the fingerprint matches (ie: the file
 	 */
 	public FingerprintTestResult test(IPath path, IProgressMonitor monitor) throws CoreException {
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 006aeff..778d5da 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, 37);
-	static final int MAX_SUPPORTED_VERSION = Nd.version(1, 37);
-	static final int MIN_SUPPORTED_VERSION = Nd.version(1, 37);
+	static final int CURRENT_VERSION = Nd.version(1, 38);
+	static final int MAX_SUPPORTED_VERSION = Nd.version(1, 38);
+	static final int MIN_SUPPORTED_VERSION = Nd.version(1, 38);
 
 	// Fields for the search header
 	public static final FieldSearchIndex<NdResourceFile> FILES;
@@ -293,8 +293,4 @@
 		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/model/BinaryTypeFactory.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeFactory.java
index d6d3981..631c384 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
@@ -10,15 +10,19 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.core.nd.java.model;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResourceStatus;
 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.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.jdt.core.IClassFile;
 import org.eclipse.jdt.core.IJavaElement;
@@ -37,6 +41,7 @@
 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.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.NdResourceFile;
@@ -111,9 +116,8 @@
 	 * no such type exists.
 	 * @throws ClassFormatException 
 	 */
-	public static IBinaryType readType(BinaryTypeDescriptor descriptor, 
-			IProgressMonitor monitor) throws JavaModelException, ClassFormatException {
-		
+	public static IBinaryType readType(BinaryTypeDescriptor descriptor, IProgressMonitor monitor) throws JavaModelException, ClassFormatException {
+
 		if (JavaIndex.isEnabled()) {
 			try {
 				return readFromIndex(JavaIndex.getIndex(), descriptor, monitor);
@@ -125,6 +129,14 @@
 		return rawReadType(descriptor, true);
 	}
 
+	public static ClassFileReader rawReadType(BinaryTypeDescriptor descriptor, boolean fullyInitialize) throws JavaModelException, ClassFormatException {
+		try {
+			return rawReadTypeTestForExists(descriptor, fullyInitialize, true);
+		} catch (FileNotFoundException e) {
+			throw new JavaModelException(e, IJavaModelStatusConstants.IO_EXCEPTION);
+		}
+	}
+
 	/**
 	 * 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).
@@ -132,15 +144,18 @@
 	 * @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
+	 * @throws FileNotFoundException if the file does not exist
 	 */
-	public static ClassFileReader rawReadType(BinaryTypeDescriptor descriptor, boolean fullyInitialize) throws JavaModelException, ClassFormatException {
+	public static ClassFileReader rawReadTypeTestForExists(BinaryTypeDescriptor descriptor, boolean fullyInitialize,
+			boolean useInvalidArchiveCache) throws JavaModelException, ClassFormatException, FileNotFoundException {
 		if (descriptor == null) {
 			return null;
 		}
 		if (descriptor.isInJarFile()) {
 			ZipFile zip = null;
 			try {
-				zip = JavaModelManager.getJavaModelManager().getZipFile(new Path(new String(descriptor.workspacePath)));
+				zip = JavaModelManager.getJavaModelManager().getZipFile(new Path(new String(descriptor.workspacePath)),
+						useInvalidArchiveCache);
 				char[] entryNameCharArray = CharArrayUtils.concat(
 						JavaNames.fieldDescriptorToBinaryName(descriptor.fieldDescriptor), SuffixConstants.SUFFIX_class);
 				String entryName = new String(entryNameCharArray);
@@ -161,7 +176,18 @@
 			}
 		} else {
 			IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(new String(descriptor.workspacePath)));
-			byte[] contents = Util.getResourceContentsAsByteArray(file);
+			byte[] contents;
+			try (InputStream stream = file.getContents(true)) {
+				contents = org.eclipse.jdt.internal.compiler.util.Util.getInputStreamAsByteArray(stream, -1);
+			} catch (CoreException e) {
+				IStatus status = e.getStatus();
+				if (status.getCode() == IResourceStatus.RESOURCE_NOT_FOUND) {
+					throw new FileNotFoundException();
+				}
+				throw new JavaModelException(e);
+			} catch (IOException e) {
+				throw new JavaModelException(e, IJavaModelStatusConstants.IO_EXCEPTION);
+			}
 			return new ClassFileReader(contents, file.getFullPath().toString().toCharArray(), fullyInitialize);
 		}
 		return null;
@@ -217,8 +243,8 @@
 						throw new JavaModelException(e);
 					}
 				} catch (IndexException e) {
-					// Index corrupted. Rebuild it.
-					index.rebuildIndex();
+					Package.log("Index corruption detected. Rebuilding index.", e); //$NON-NLS-1$
+					Indexer.getInstance().requestRebuildIndex();
 				}
 			}
 		}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/Package.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/Package.java
new file mode 100644
index 0000000..1aad354
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/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.java.model;
+
+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/search/indexing/IndexManager.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/IndexManager.java
index 7ce53fa..dc72697 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
@@ -28,6 +28,7 @@
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.OperationCanceledException;
 import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.SubMonitor;
 import org.eclipse.jdt.core.IClasspathEntry;
 import org.eclipse.jdt.core.IJavaProject;
 import org.eclipse.jdt.core.JavaCore;
@@ -164,7 +165,7 @@
 		if (count > 0)
 			removeIndexesState(locations);
 	}
-	deleteIndexFiles(knownPaths);
+	deleteIndexFiles(knownPaths, null);
 }
 /**
  * Compute the pre-built index location for a specified URL
@@ -212,17 +213,25 @@
 	}
 	return indexLocation;
 }
-public void deleteIndexFiles() {
+/**
+ * Use {@link #deleteIndexFiles(IProgressMonitor)}
+ */
+public final void deleteIndexFiles() {
+	deleteIndexFiles(null);
+}
+public void deleteIndexFiles(IProgressMonitor monitor) {
 	if (DEBUG)
 		Util.verbose("Deleting index files"); //$NON-NLS-1$
 	this.savedIndexNamesFile.delete(); // forget saved indexes & delete each index file
-	deleteIndexFiles(null);
+	deleteIndexFiles(null, monitor);
 }
-private void deleteIndexFiles(SimpleSet pathsToKeep) {
+private void deleteIndexFiles(SimpleSet pathsToKeep, IProgressMonitor monitor) {
 	File[] indexesFiles = getSavedIndexesDirectory().listFiles();
 	if (indexesFiles == null) return;
 
+	SubMonitor subMonitor = SubMonitor.convert(monitor, indexesFiles.length);
 	for (int i = 0, l = indexesFiles.length; i < l; i++) {
+		subMonitor.split(1);
 		String fileName = indexesFiles[i].getAbsolutePath();
 		if (pathsToKeep != null && pathsToKeep.includes(new FileIndexLocation(indexesFiles[i]))) continue;
 		String suffix = ".index"; //$NON-NLS-1$
@@ -865,14 +874,16 @@
 /**
  * Flush current state
  */
-public synchronized void reset() {
+public void reset() {
 	super.reset();
-	if (this.indexes != null) {
-		this.indexes = new SimpleLookupTable();
-		this.indexStates = null;
+	synchronized (this) {
+		if (this.indexes != null) {
+			this.indexes = new SimpleLookupTable();
+			this.indexStates = null;
+		}
+		this.indexLocations = new SimpleLookupTable();
+		this.javaPluginLocation = null;
 	}
-	this.indexLocations = new SimpleLookupTable();
-	this.javaPluginLocation = null;
 }
 /**
  * Resets the index for a given path.
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 71bc061..7c07d0f 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
@@ -297,22 +297,29 @@
 	/**
 	 * Flush current state
 	 */
-	public synchronized void reset() {
+	public void reset() {
 		if (VERBOSE)
 			Util.verbose("Reset"); //$NON-NLS-1$
 
-		if (this.processingThread != null) {
+		Thread thread;
+		synchronized (this) {
+			thread = this.processingThread;
+		}
+
+		if (thread != null) {
 			discardJobs(null); // discard all jobs
 		} else {
-			/* initiate background processing */
-			this.processingThread = new Thread(this, processName());
-			this.processingThread.setDaemon(true);
-			// less prioritary by default, priority is raised if clients are actively waiting on it
-			this.processingThread.setPriority(Thread.NORM_PRIORITY-1);
-			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=296343
-			// set the context loader to avoid leaking the current context loader
-			this.processingThread.setContextClassLoader(this.getClass().getClassLoader());
-			this.processingThread.start();
+			synchronized (this) {
+				/* initiate background processing */
+				this.processingThread = new Thread(this, processName());
+				this.processingThread.setDaemon(true);
+				// less prioritary by default, priority is raised if clients are actively waiting on it
+				this.processingThread.setPriority(Thread.NORM_PRIORITY-1);
+				// https://bugs.eclipse.org/bugs/show_bug.cgi?id=296343
+				// set the context loader to avoid leaking the current context loader
+				this.processingThread.setContextClassLoader(this.getClass().getClassLoader());
+				this.processingThread.start();
+			}
 		}
 	}
 	/**