Bug 522401 - [9][null] make external annotations work with modules

- also make sure projects get deleted in testBug522503, and only execute
if on jre9

Change-Id: I9401bafbd4ef29e5836e04d1d7f3b43df980f72b
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java
index de8f39a..b7b8c48 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java
@@ -208,6 +208,7 @@
 		NullAnnotationModelTests.class,
 		ExternalAnnotations17Test.class,
 		ExternalAnnotations18Test.class,
+		ExternalAnnotations9Test.class,
 
 		// Java model changes related to Java 8
 		JavaElement8Tests.class,
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations9Test.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations9Test.java
new file mode 100644
index 0000000..e3b20f1
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations9Test.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Till Brychcy and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Till Brychcy - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.core.tests.model;
+
+import java.util.Hashtable;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.jdt.core.IJavaModelMarker;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.tests.util.AbstractCompilerTest;
+
+import junit.framework.Test;
+
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class ExternalAnnotations9Test extends ExternalAnnotations18Test {
+
+	public ExternalAnnotations9Test(String name) {
+		super(name, "9", "JCL19_LIB");
+	}
+	
+	// Static initializer to specify tests subset using TESTS_* static variables
+	// All specified tests which do not belong to the class are skipped...
+	static {
+//		TESTS_NAMES = new String[] {"testBug522401"};
+	}
+
+	public static Test suite() {
+		return buildModelTestSuite(ExternalAnnotations9Test.class, BYTECODE_DECLARATION_ORDER);
+	}
+
+	public String getSourceWorkspacePath() {
+		// we read individual projects from within this folder:
+		return super.getSourceWorkspacePathBase()+"/ExternalAnnotations9";
+	}
+
+	protected boolean hasJRE19() {
+		return ((AbstractCompilerTest.getPossibleComplianceLevels() & AbstractCompilerTest.F_9) != 0);
+	}
+
+	/** Project with real JRE. */
+	public void testBug522401() throws Exception {
+		if (!hasJRE19()) {
+			System.out.println("Skipping ExternalAnnotations9Test.testBug522401(), needs JRE9");
+			return;
+		}
+		Hashtable options = JavaCore.getOptions();
+		try {
+			setupJavaProject("Test2");
+			this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
+			IMarker[] markers = this.project.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
+			assertNoMarkers(markers);
+		} finally {
+			JavaCore.setOptions(options);
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ModuleBuilderTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ModuleBuilderTests.java
index c43096f..1bacd96 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ModuleBuilderTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ModuleBuilderTests.java
@@ -5720,42 +5720,48 @@
 	}
 
 	public void testBug522503() throws Exception {
-		IJavaProject p1 = setupModuleProject("mod.one",
-			new String[] {
+		if (!isJRE9) return;
+		try {
+			IJavaProject p1 = setupModuleProject("mod.one",
+				new String[] {
+					"src/module-info.java",
+					"module mod.one {\n" +
+					"	exports p1;\n" +
+					"}\n",
+					"src/p1/API.java",
+					"package p1;\n" +
+					"public class API {}\n"
+				});
+			IClasspathAttribute[] attr = { JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true") };
+			IClasspathEntry[] deps = { JavaCore.newLibraryEntry(p1.getOutputLocation(), null, null, null, attr, false) };
+			String[] sources2 = new String[] {
 				"src/module-info.java",
-				"module mod.one {\n" +
-				"	exports p1;\n" +
+				"module mod.two {\n" +
+				"	requires mod.one;\n" +
 				"}\n",
-				"src/p1/API.java",
-				"package p1;\n" +
-				"public class API {}\n"
-			});
-		IClasspathAttribute[] attr = { JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true") };
-		IClasspathEntry[] deps = { JavaCore.newLibraryEntry(p1.getOutputLocation(), null, null, null, attr, false) };
-		String[] sources2 = new String[] {
-			"src/module-info.java",
-			"module mod.two {\n" +
-			"	requires mod.one;\n" +
-			"}\n",
-			"src/client/Client.java",
-			"package client;\n" +
-			"import p1.API;\n" +
-			"public class Client {\n" +
-			"	API api;\n" +
-			"}\n"
-		};
-		IJavaProject p2 = setupModuleProject("mod.two", sources2, deps);
-		p2.getProject().getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, null);
-		assertNoErrors();
-
-		this.problemRequestor.reset();
-		ICompilationUnit cu = getCompilationUnit("/mod.two/src/client/Client.java");
-		cu.getWorkingCopy(this.wcOwner, null);
-		assertProblems(
-			"Unexpected problems",
-			"----------\n" + 
-			"----------\n",
-			this.problemRequestor);
+				"src/client/Client.java",
+				"package client;\n" +
+				"import p1.API;\n" +
+				"public class Client {\n" +
+				"	API api;\n" +
+				"}\n"
+			};
+			IJavaProject p2 = setupModuleProject("mod.two", sources2, deps);
+			p2.getProject().getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, null);
+			assertNoErrors();
+	
+			this.problemRequestor.reset();
+			ICompilationUnit cu = getCompilationUnit("/mod.two/src/client/Client.java");
+			cu.getWorkingCopy(this.wcOwner, null);
+			assertProblems(
+				"Unexpected problems",
+				"----------\n" + 
+				"----------\n",
+				this.problemRequestor);
+		} finally {
+			deleteProject("mod.one");			
+			deleteProject("mod.two");			
+		}
 	}
 
 	protected void assertNoErrors() throws CoreException {
diff --git a/org.eclipse.jdt.core.tests.model/workspace/ExternalAnnotations9/Test2/.classpath b/org.eclipse.jdt.core.tests.model/workspace/ExternalAnnotations9/Test2/.classpath
new file mode 100644
index 0000000..611bff8
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/workspace/ExternalAnnotations9/Test2/.classpath
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.core.tests.model.TEST_CONTAINER">
+		<attributes>
+			<attribute name="annotationpath" value="annots"/>
+		</attributes>
+	</classpathentry> 
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.eclipse.jdt.core.tests.model/workspace/ExternalAnnotations9/Test2/.project b/org.eclipse.jdt.core.tests.model/workspace/ExternalAnnotations9/Test2/.project
new file mode 100644
index 0000000..1b369ff
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/workspace/ExternalAnnotations9/Test2/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>Test2</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/org.eclipse.jdt.core.tests.model/workspace/ExternalAnnotations9/Test2/annots/java/util/Map.eea b/org.eclipse.jdt.core.tests.model/workspace/ExternalAnnotations9/Test2/annots/java/util/Map.eea
new file mode 100644
index 0000000..bf24983
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/workspace/ExternalAnnotations9/Test2/annots/java/util/Map.eea
@@ -0,0 +1,4 @@
+class java/util/Map
+get
+ (Ljava/lang/Object;)TV;
+ (Ljava/lang/Object;)T0V;
diff --git a/org.eclipse.jdt.core.tests.model/workspace/ExternalAnnotations9/Test2/src/test1/Test1.java b/org.eclipse.jdt.core.tests.model/workspace/ExternalAnnotations9/Test2/src/test1/Test1.java
new file mode 100644
index 0000000..a2da2b3
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/workspace/ExternalAnnotations9/Test2/src/test1/Test1.java
@@ -0,0 +1,13 @@
+package test1;
+
+import java.util.Map;
+import org.eclipse.jdt.annotation.NonNullByDefault; // note: if org.eclipse.jdt.annotation.* was used, no problem appeared
+
+@NonNullByDefault
+public class Test1 {
+	void test(Map<String,Test1> map, String key) {
+		Test1 v = map.get(key);
+		if (v == null)
+			throw new RuntimeException(); // should not be reported as dead code, although V is a '@NonNull Test1'
+	}
+}
\ No newline at end of file
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 4dffadf..5787889 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
@@ -213,6 +213,10 @@
 		public IBinaryElementValuePair[] getElementValuePairs() {
 			return ElementValuePairInfo.NoMembers;
 		}
+		@Override
+		public boolean isExternalAnnotation() {
+			return true;
+		}
 		protected char[] getBinaryTypeName(char[][] name) {
 			return CharOperation.concat('L', CharOperation.concatWith(name, '/'), ';');
 		}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/env/IBinaryAnnotation.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/env/IBinaryAnnotation.java
index 7388428..36b1a17 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/env/IBinaryAnnotation.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/env/IBinaryAnnotation.java
@@ -24,4 +24,12 @@
  * @return the list of element value pairs of the annotation
  */
 IBinaryElementValuePair[] getElementValuePairs();
+
+/**
+ * @return true, if this an external annotation
+ */
+default boolean isExternalAnnotation() {
+	return false;
 }
+}
+
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java
index 13dc33f..da599ef 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java
@@ -170,8 +170,10 @@
 		pairs[i] = new ElementValuePair(binaryPairs[i].getName(), convertMemberValue(binaryPairs[i].getValue(), env, missingTypeNames, false), null);
 
 	char[] typeName = annotationInfo.getTypeName();
-	ReferenceBinding annotationType = env.getTypeFromConstantPoolName(typeName, 1, typeName.length - 1, false, missingTypeNames);
-	return env.createUnresolvedAnnotation(annotationType, pairs);
+	LookupEnvironment env2 = annotationInfo.isExternalAnnotation() ? env.root : env;
+	ReferenceBinding annotationType = env2.getTypeFromConstantPoolName(typeName, 1, typeName.length - 1, false,
+			missingTypeNames);
+	return env2.createUnresolvedAnnotation(annotationType, pairs);
 }
 
 public static AnnotationBinding[] createAnnotations(IBinaryAnnotation[] annotationInfos, LookupEnvironment env, char[][][] missingTypeNames) {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java
index 22e32e4..0d7e0b3 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java
@@ -1429,6 +1429,9 @@
 public AnnotationBinding getNullableAnnotation() {
 	if (this.nullableAnnotation != null)
 		return this.nullableAnnotation;
+	if (this.root != this) {
+		return this.nullableAnnotation = this.root.getNullableAnnotation();
+	}
 	ReferenceBinding nullable = getResolvedType(this.globalOptions.nullableAnnotationName, null);
 	return this.nullableAnnotation = this.typeSystem.getAnnotationType(nullable, true);
 }
@@ -1440,6 +1443,9 @@
 public AnnotationBinding getNonNullAnnotation() {
 	if (this.nonNullAnnotation != null) 
 		return this.nonNullAnnotation;
+	if (this.root != this) {
+		return this.nonNullAnnotation = this.root.getNonNullAnnotation();
+	}
 	ReferenceBinding nonNull = getResolvedType(this.globalOptions.nonNullAnnotationName, null);
 	return this.nonNullAnnotation = this.typeSystem.getAnnotationType(nonNull, true);
 }
@@ -1482,6 +1488,9 @@
 }
 
 public boolean usesNullTypeAnnotations() {
+	if(this.root != this) {
+		return this.root.usesNullTypeAnnotations();
+	}
 	if (this.globalOptions.useNullTypeAnnotations != null)
 		return this.globalOptions.useNullTypeAnnotations;