Bug 509397: [null] Enable EEA for generated sources

Change-Id: Ibe1ad400bde67b14b1c55d10788048ea1a83fa15
Also-by: Frank Benoit <frank.rene.benoit@gmail.com>
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations18Test.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations18Test.java
index 06ff023..5024a9e 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations18Test.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations18Test.java
@@ -315,6 +315,13 @@
 		this.project.setRawClasspath(rawClasspath, new NullProgressMonitor());
 	}
 
+	protected void addSourceFolderWithExternalAnnotations(IJavaProject javaProject, String sourceFolder, String outputFolder, String externalAnnotationPath) throws JavaModelException {
+		IClasspathAttribute[] extraAttributes = new IClasspathAttribute[] { new ClasspathAttribute(IClasspathAttribute.EXTERNAL_ANNOTATION_PATH, externalAnnotationPath) };
+		IClasspathEntry entry = JavaCore.newSourceEntry(new Path(sourceFolder), null, null,
+				outputFolder != null ? new Path(outputFolder) : null, extraAttributes);
+		addClasspathEntry(javaProject, entry);
+	}
+
 	protected void createFileInProject(String projectRelativeFolder, String fileName, String content) throws CoreException {
 		String folderPath = this.project.getProject().getName()+'/'+projectRelativeFolder;
 		createFolder(folderPath);
@@ -2666,4 +2673,154 @@
 			deleteProject("Bug500024");
 		}
 	}
+
+	// reconcile client of a "generated" source+eea
+    @SuppressWarnings("deprecation")
+	public void testSourceFolder1() throws CoreException {
+		myCreateJavaProject("Bug509397");
+		addSourceFolderWithExternalAnnotations(this.project, "/Bug509397/src-gen", "/Bug509397/bin-gen", "/Bug509397/annot-gen");
+
+		createFileInProject("annot-gen/pgen", "CGen.eea",
+				"class pgen/CGen\n" +
+				"\n" +
+				"get\n" +
+				" (Ljava/lang/String;)Ljava/lang/String;\n" +
+				" (L1java/lang/String;)L1java/lang/String;\n");
+
+		createFileInProject("src-gen/pgen", "CGen.java",
+				"package pgen;\n" +
+				"public class CGen {\n" +
+				"	public String get(String in) { return in; }\n" +
+				"}\n");
+
+		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("p", true, null);
+		ICompilationUnit unit = fragment.createCompilationUnit("Use.java",
+				"package p;\n" +
+				"import pgen.CGen;\n" +
+				"import org.eclipse.jdt.annotation.NonNull;\n" +
+				"public class Use {\n" +
+				"	public @NonNull String test(CGen c) {\n" +
+				"		String s = c.get(null);\n" + // problem here (6)
+				"		return s;\n" + // no problem here
+				"	}\n" +
+				"}\n",
+				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
+		CompilationUnit reconciled = unit.reconcile(AST.JLS8, true, null, new NullProgressMonitor());
+		IProblem[] problems = reconciled.getProblems();
+		assertProblems(problems, new String[] {
+				"Pb(910) Null type mismatch: required '@NonNull String' but the provided value is null"
+			}, new int[] { 6 });
+	}
+
+	// reconcile client of a "generated" source+eea
+    // single merged output folder
+    @SuppressWarnings("deprecation")
+	public void testSourceFolder1a() throws CoreException {
+		myCreateJavaProject("Bug509397");
+		addSourceFolderWithExternalAnnotations(this.project, "/Bug509397/src-gen", null, "/Bug509397/annot-gen");
+
+		createFileInProject("annot-gen/pgen", "CGen.eea",
+				"class pgen/CGen\n" +
+				"\n" +
+				"get\n" +
+				" (Ljava/lang/String;)Ljava/lang/String;\n" +
+				" (L1java/lang/String;)L1java/lang/String;\n");
+
+		createFileInProject("src-gen/pgen", "CGen.java",
+				"package pgen;\n" +
+				"public class CGen {\n" +
+				"	public String get(String in) { return in; }\n" +
+				"}\n");
+		this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
+
+		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("p", true, null);
+		ICompilationUnit unit = fragment.createCompilationUnit("Use.java",
+				"package p;\n" +
+				"import pgen.CGen;\n" +
+				"import org.eclipse.jdt.annotation.NonNull;\n" +
+				"public class Use {\n" +
+				"	public @NonNull String test(CGen c) {\n" +
+				"		String s = c.get(null);\n" + // problem here (6)
+				"		return s;\n" + // no problem here
+				"	}\n" +
+				"}\n",
+				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
+		CompilationUnit reconciled = unit.reconcile(AST.JLS8, true, null, new NullProgressMonitor());
+		IProblem[] problems = reconciled.getProblems();
+		assertProblems(problems, new String[] {
+				"Pb(910) Null type mismatch: required '@NonNull String' but the provided value is null"
+			}, new int[] { 6 });
+	}
+
+    // full build of a project with src-gen & annot-gen
+	public void testSourceFolder2() throws CoreException {
+		myCreateJavaProject("Bug509397");
+		addSourceFolderWithExternalAnnotations(this.project, "/Bug509397/src-gen", "/Bug509397/bin-gen", "/Bug509397/annot-gen");
+
+		createFileInProject("annot-gen/pgen", "CGen.eea",
+				"class pgen/CGen\n" +
+				"\n" +
+				"get\n" +
+				" (Ljava/lang/String;)Ljava/lang/String;\n" +
+				" (L1java/lang/String;)L1java/lang/String;\n");
+
+		createFileInProject("src-gen/pgen", "CGen.java",
+				"package pgen;\n" +
+				"public class CGen {\n" +
+				"	public String get(String in) { return in; }\n" +
+				"}\n");
+
+		createFileInProject("src/p", "Use.java",
+				"package p;\n" +
+				"import pgen.CGen;\n" +
+				"import org.eclipse.jdt.annotation.NonNull;\n" +
+				"public class Use {\n" +
+				"	public @NonNull String test(CGen c) {\n" +
+				"		String s = c.get(null);\n" + // problem here (6)
+				"		return s;\n" + // no problem here
+				"	}\n" +
+				"}\n");
+		this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
+		IMarker[] markers = this.project.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
+		assertMarkers("Unexpected markers",
+				"Null type mismatch: required '@NonNull String' but the provided value is null",
+				markers);
+	}
+
+    // full build of a project with src-gen & annot-gen
+    // single merged output folder
+	public void testSourceFolder2a() throws CoreException {
+		myCreateJavaProject("Bug509397");
+		addSourceFolderWithExternalAnnotations(this.project, "/Bug509397/src-gen", null, "/Bug509397/annot-gen");
+
+		createFileInProject("annot-gen/pgen", "CGen.eea",
+				"class pgen/CGen\n" +
+				"\n" +
+				"get\n" +
+				" (Ljava/lang/String;)Ljava/lang/String;\n" +
+				" (L1java/lang/String;)L1java/lang/String;\n");
+
+		createFileInProject("src-gen/pgen", "CGen.java",
+				"package pgen;\n" +
+				"public class CGen {\n" +
+				"	public String get(String in) { return in; }\n" +
+				"}\n");
+
+		createFileInProject("src/p", "Use.java",
+				"package p;\n" +
+				"import pgen.CGen;\n" +
+				"import org.eclipse.jdt.annotation.NonNull;\n" +
+				"public class Use {\n" +
+				"	public @NonNull String test(CGen c) {\n" +
+				"		String s = c.get(null);\n" + // problem here (6)
+				"		return s;\n" + // no problem here
+				"	}\n" +
+				"}\n");
+		this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
+		IMarker[] markers = this.project.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
+		assertMarkers("Unexpected markers",
+				"Null type mismatch: required '@NonNull String' but the provided value is null",
+				markers);
+	}
+
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/env/ICompilationUnit.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/env/ICompilationUnit.java
index 24bd52d..5e2cdfa 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/env/ICompilationUnit.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/env/ICompilationUnit.java
@@ -74,4 +74,10 @@
 default String getDestinationPath() {
 	return null;
 }
+/**
+ * Answers a path for external annotations that has been configured for
+ * the providing classpath entry, or <code>null</code>.
+ */
+default String getExternalAnnotationPath() { return null; }
+
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java
index fafd05d..a2affaf 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java
@@ -52,6 +52,7 @@
 import org.eclipse.jdt.internal.compiler.ast.TypeReference;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
 import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
+import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
 import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
 import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
 import org.eclipse.jdt.internal.compiler.util.HashtableOfObject;
@@ -522,6 +523,13 @@
 		SourceTypeBinding sourceType = this.referenceContext.binding;
 		sourceType.module = module();
 		environment().setAccessRestriction(sourceType, accessRestriction);
+		ICompilationUnit compilationUnit = this.referenceContext.compilationResult.getCompilationUnit();
+		if (compilationUnit != null && compilerOptions().isAnnotationBasedNullAnalysisEnabled) {
+			String externalAnnotationPath = compilationUnit.getExternalAnnotationPath();
+			if (externalAnnotationPath != null) {
+				ExternalAnnotationSuperimposer.apply(sourceType, externalAnnotationPath);
+			}
+		}
 
 		TypeParameter[] typeParameters = this.referenceContext.typeParameters;
 		sourceType.typeVariables = typeParameters == null || typeParameters.length == 0 ? Binding.NO_TYPE_VARIABLES : null;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ExternalAnnotationSuperimposer.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ExternalAnnotationSuperimposer.java
index 9fd2eba..ee946d3 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ExternalAnnotationSuperimposer.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ExternalAnnotationSuperimposer.java
@@ -21,6 +21,7 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
+import org.eclipse.jdt.internal.compiler.ast.Argument;
 import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationProvider;
 import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
 import org.eclipse.jdt.internal.compiler.env.ITypeAnnotationWalker;
@@ -101,7 +102,7 @@
 			field.type = visitor.superimpose(field.type, TypeBinding.class);
 	}
 
-	public static void annotateMethodBinding(MethodBinding method, ExternalAnnotationProvider provider, LookupEnvironment environment) {
+	public static void annotateMethodBinding(MethodBinding method, Argument[] arguments, ExternalAnnotationProvider provider, LookupEnvironment environment) {
 		char[] methodSignature = method.genericSignature();
 		if (methodSignature == null)
 			methodSignature = method.signature();
@@ -119,8 +120,11 @@
 			}
 			TypeBinding[] parameters = method.parameters;
 			for (short i = 0; i < parameters.length; i++) {
-				if (visitor.go(walker.toMethodParameter(i)))
+				if (visitor.go(walker.toMethodParameter(i))) {
 					parameters[i] = visitor.superimpose(parameters[i], TypeBinding.class);
+					if (arguments != null && i < arguments.length)
+						arguments[i].binding.type = parameters[i];
+				}
 			}
 		}
 	}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java
index 4a475f5..7a1b542 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java
@@ -2841,7 +2841,7 @@
 
 	method.modifiers &= ~ExtraCompilerModifiers.AccUnresolved;
 	if (this.externalAnnotationProvider != null) {
-		ExternalAnnotationSuperimposer.annotateMethodBinding(method, this.externalAnnotationProvider, this.environment);
+		ExternalAnnotationSuperimposer.annotateMethodBinding(method, arguments, this.externalAnnotationProvider, this.environment);
 	}
 	return method;
 }
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathLocation.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathLocation.java
index 4cdb930..44fc370 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathLocation.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathLocation.java
@@ -121,9 +121,8 @@
 		return true;
 	}
 	static ClasspathLocation forSourceFolder(IContainer sourceFolder, IContainer outputFolder,
-			char[][] inclusionPatterns, char[][] exclusionPatterns, boolean ignoreOptionalProblems) {
-		return new ClasspathMultiDirectory(sourceFolder, outputFolder, inclusionPatterns, exclusionPatterns,
-				ignoreOptionalProblems);
+			char[][] inclusionPatterns, char[][] exclusionPatterns, boolean ignoreOptionalProblems, IPath externalAnnotationPath) {
+		return new ClasspathMultiDirectory(sourceFolder, outputFolder, inclusionPatterns, exclusionPatterns, ignoreOptionalProblems, externalAnnotationPath);
 	}
 public static ClasspathLocation forBinaryFolder(IContainer binaryFolder, boolean isOutputFolder, AccessRuleSet accessRuleSet, IPath externalAnnotationPath, boolean autoModule) {
 	return new ClasspathDirectory(binaryFolder, isOutputFolder, accessRuleSet, externalAnnotationPath, autoModule);
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathMultiDirectory.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathMultiDirectory.java
index 96b26f2..e127fb7 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathMultiDirectory.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathMultiDirectory.java
@@ -15,6 +15,7 @@
 
 import org.eclipse.core.resources.*;
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.CompilationResult;
 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
@@ -32,8 +33,9 @@
 boolean hasIndependentOutputFolder; // if output folder is not equal to any of the source folders
 public boolean ignoreOptionalProblems;
 
-ClasspathMultiDirectory(IContainer sourceFolder, IContainer binaryFolder, char[][] inclusionPatterns, char[][] exclusionPatterns, boolean ignoreOptionalProblems) {
-	super(binaryFolder, true, null, null, false /* source never an automatic module*/);
+ClasspathMultiDirectory(IContainer sourceFolder, IContainer binaryFolder, char[][] inclusionPatterns, char[][] exclusionPatterns,
+		boolean ignoreOptionalProblems, IPath externalAnnotationPath) {
+	super(binaryFolder, true, null, externalAnnotationPath, false /* source never an automatic module*/);
 
 	this.sourceFolder = sourceFolder;
 	this.inclusionPatterns = inclusionPatterns;
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/NameEnvironment.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/NameEnvironment.java
index 577b3ee..837ebc1 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/NameEnvironment.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/NameEnvironment.java
@@ -181,7 +181,8 @@
 								outputFolder,
 								entry.fullInclusionPatternChars(),
 								entry.fullExclusionPatternChars(),
-								entry.ignoreOptionalProblems());
+								entry.ignoreOptionalProblems(),
+								externalAnnotationPath);
 					if (patchedModule != null) {
 						ModuleEntryProcessor.combinePatchIntoModuleEntry(sourceLocation, patchedModule, moduleEntries);
 					}
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/SourceFile.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/SourceFile.java
index 8425b8a..891a4ec 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/SourceFile.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/SourceFile.java
@@ -117,6 +117,10 @@
 public boolean ignoreOptionalProblems() {
 	return this.sourceLocation.ignoreOptionalProblems;
 }
+@Override
+public String getExternalAnnotationPath() {
+	return this.sourceLocation.externalAnnotationPath;
+}
 String typeLocator() {
 	return this.resource.getProjectRelativePath().toString();
 }
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/State.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/State.java
index eb188d4..e855d95 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/State.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/State.java
@@ -76,7 +76,7 @@
 private StringSet structurallyChangedTypes;
 public static int MaxStructurallyChangedTypes = 100; // keep track of ? structurally changed types, otherwise consider all to be changed
 
-public static final byte VERSION = 0x0023;
+public static final byte VERSION = 0x0024;
 
 static final byte SOURCE_FOLDER = 1;
 static final byte BINARY_FOLDER = 2;
@@ -338,7 +338,7 @@
 		if ((folderName = in.readUTF()).length() > 0) sourceFolder = project.getFolder(folderName);
 		if ((folderName = in.readUTF()).length() > 0) outputFolder = project.getFolder(folderName);
 		ClasspathMultiDirectory md =
-			(ClasspathMultiDirectory) ClasspathLocation.forSourceFolder(sourceFolder, outputFolder, readNames(in), readNames(in), in.readBoolean());
+			(ClasspathMultiDirectory) ClasspathLocation.forSourceFolder(sourceFolder, outputFolder, readNames(in), readNames(in), in.readBoolean(), readNullablePath(in));
 		if (in.readBoolean())
 			md.hasIndependentOutputFolder = true;
 		sourceLocations[i] = md;
@@ -429,6 +429,13 @@
 	return names;
 }
 
+private static IPath readNullablePath(DataInputStream in) throws IOException {
+	String path = in.readUTF();
+	if (!path.isEmpty())
+		return new Path(path);
+	return null;
+}
+
 private static AccessRuleSet readRestriction(DataInputStream in) throws IOException {
 	int length = in.readInt();
 	if (length == 0) return null; // no restriction specified
@@ -711,6 +718,7 @@
 		writeNames(md.inclusionPatterns, out);
 		writeNames(md.exclusionPatterns, out);
 		out.writeBoolean(md.ignoreOptionalProblems);
+		writeNullablePath(md.externalAnnotationPath, out);
 		out.writeBoolean(md.hasIndependentOutputFolder);
 	}
 }
@@ -741,7 +749,7 @@
 			out.writeUTF(cd.binaryFolder.getFullPath().toString());
 			out.writeBoolean(cd.isOutputFolder);
 			writeRestriction(cd.accessRuleSet, out);
-			out.writeUTF(cd.externalAnnotationPath != null ? cd.externalAnnotationPath : ""); //$NON-NLS-1$
+			writeNullablePath(cd.externalAnnotationPath, out);
 			out.writeBoolean(cd.isOnModulePath);
 		} else if (c instanceof ClasspathJar) {
 			ClasspathJar jar = (ClasspathJar) c;
@@ -754,7 +762,7 @@
 				out.writeUTF(jar.resource.getFullPath().toString());
 			}
 			writeRestriction(jar.accessRuleSet, out);
-			out.writeUTF(jar.externalAnnotationPath != null ? jar.externalAnnotationPath : ""); //$NON-NLS-1$
+			writeNullablePath(jar.externalAnnotationPath, out);
 			out.writeBoolean(jar.isOnModulePath);
 			out.writeUTF(jar.compliance == null ? "" : jar.compliance); //$NON-NLS-1$
 
@@ -763,7 +771,7 @@
 			out.writeByte(EXTERNAL_JAR);
 			out.writeUTF(jrt.zipFilename);
 			writeRestriction(jrt.accessRuleSet, out);
-			out.writeUTF(jrt.externalAnnotationPath != null ? jrt.externalAnnotationPath : ""); //$NON-NLS-1$
+			writeNullablePath(jrt.externalAnnotationPath, out);
 			if (jrt instanceof ClasspathJrtWithReleaseOption)
 				out.writeUTF(((ClasspathJrtWithReleaseOption) jrt).release);
 			else
@@ -835,6 +843,10 @@
 		writeName(names[i], out);
 }
 
+private void writeNullablePath(String path, DataOutputStream out) throws IOException {
+	out.writeUTF(path != null ? path : ""); //$NON-NLS-1$
+}
+
 private void writeRestriction(AccessRuleSet accessRuleSet, DataOutputStream out) throws IOException {
 	if (accessRuleSet == null) {
 		out.writeInt(0);