Bug 440687 - [compiler][batch][null] improve command line option for
external annotations


Change-Id: I710ec106a8dae968d03fb8ce4375c23de76b0672
Signed-off-by: Stephan Herrmann <stephan.herrmann@berlin.de>
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java
index 72c3430..d47c9cc 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -25,6 +25,7 @@
  *								bug 383368 - [compiler][null] syntactic null analysis for field references
  *								Bug 392099 - [1.8][compiler][null] Apply null annotation on types for null analysis 
  *								Bug 440477 - [null] Infrastructure for feeding external annotations into compilation
+ *								Bug 440687 - [compiler][batch][null] improve command line option for external annotations
  *     Jesper Steen Moller - Contributions for
  *								bug 404146 - [1.7][compiler] nested try-catch-finally-blocks leads to unrunnable Java byte code
  *								bug 407297 - [1.8][compiler] Control generation of parameter names by option
@@ -111,6 +112,14 @@
 			"@Target({ ElementType.TYPE_USE })\n" +
 			"public @interface NonNull{\n" +
 			"}\n";
+	private static final String NULLABLE_ANNOTATION_18_CONTENT = "package org.eclipse.jdt.annotation;\n" +
+			"import java.lang.annotation.ElementType;\n" +
+			"import java.lang.annotation.*;\n" +
+			"@Documented\n" +
+			"@Retention(RetentionPolicy.CLASS)\n" +
+			"@Target({ ElementType.TYPE_USE })\n" +
+			"public @interface Nullable{\n" +
+			"}\n";
 	private static final String NONNULL_BY_DEFAULT_ANNOTATION_18_CONTENT = "package org.eclipse.jdt.annotation;\n" +
 			"import java.lang.annotation.ElementType;\n" + 
 			"import static org.eclipse.jdt.annotation.DefaultLocation.*;\n" + 
@@ -1704,9 +1713,11 @@
         "    -Xemacs            used to enable emacs-style output in the console.\n" +
         "                       It does not affect the xml log output\n" +
         "    -missingNullDefault  report missing default nullness annotation\n" +
-        "    -annotationpath <path>\n" + 
-        "                       Path to a base directory or zip file holding external\n" + 
-        "                       annotations to support annotation-based null analysis\n" + 
+        "    -annotationpath <directories and ZIP archives separated by " + File.pathSeparator + ">\n" + 
+        "                       specify locations where to find external annotations\n" + 
+        "                       to support annotation-based null analysis.\n" + 
+        "                       The special name CLASSPATH will cause lookup of\n" + 
+        "                       external annotations from the classpath and sourcepath.\n" + 
         " \n" + 
         "    -? -help           print this help message\n" +
         "    -v -version        print compiler version\n" +
@@ -14105,18 +14116,7 @@
 	new File(annots_java_util).mkdirs();
 	Util.createFile(
 			annots_java_util + File.separator + "Map.eea", 
-			"class java/util/Map\n" +
-			" <K:V:>\n" + 
-			"\n" + 
-			"get\n" + 
-			" (Ljava/lang/Object;)TV;\n" + 
-			" (Ljava/lang/Object;)T0V;\n" + 
-			"put\n" + 
-			" (TK;TV;)TV;\n" + 
-			" (TK;TV;)T0V;\n" + 
-			"remove\n" + 
-			" (Ljava/lang/Object;)TV;\n" + 
-			" (Ljava/lang/Object;)T0V;\n");
+			TEST_440687_MAP_EEA_CONTENT);
 
 	String o_e_j_annotation_dir = OUTPUT_DIR + File.separator +
 			"org" + File.separator + "eclipse" + File.separator + "jdt" + File.separator + "annotation";
@@ -14128,6 +14128,8 @@
 			ELEMENT_TYPE_18_CONTENT,
 			"org/eclipse/jdt/annotation/NonNull.java",
 			NONNULL_ANNOTATION_18_CONTENT,
+			"org/eclipse/jdt/annotation/Nullable.java",
+			NULLABLE_ANNOTATION_18_CONTENT,
 			"org/eclipse/jdt/annotation/DefaultLocation.java",				
 			DEFAULT_LOCATION_CONTENT,
 			"org/eclipse/jdt/annotation/NonNullByDefault.java",				
@@ -14159,6 +14161,160 @@
 			"",
 			true);
 }
+// file content for tests below:
+private static final String TEST_440687_MAP_EEA_CONTENT =
+			"class java/util/Map\n" +
+			" <K:V:>\n" + 
+			"\n" + 
+			"get\n" + 
+			" (Ljava/lang/Object;)TV;\n" + 
+			" (Ljava/lang/Object;)T0V;\n" + 
+			"put\n" + 
+			" (TK;TV;)TV;\n" + 
+			" (TK;TV;)T0V;\n" + 
+			"remove\n" + 
+			" (Ljava/lang/Object;)TV;\n" + 
+			" (Ljava/lang/Object;)T0V;\n";
+private static final String TEST_440687_OBJECT_EEA_CONTENT =
+			"class java/lang/Object\n" +
+			"\n" + 
+			"equals\n" + 
+			" (Ljava/lang/Object;)Z\n" + 
+			" (L0java/lang/Object;)Z\n";
+// Bug 440687 - [compiler][batch][null] improve command line option for external annotations
+// work horse for tests below
+void runTest440687(String compilerPathArgs, String extraSourcePaths, String expectedCompilerMessage, boolean isSuccess) {
+
+	String[] testFiles = new String[] {
+				"java/lang/annotation/ElementType.java",
+				ELEMENT_TYPE_18_CONTENT,
+				"org/eclipse/jdt/annotation/NonNull.java",
+				NONNULL_ANNOTATION_18_CONTENT,
+				"org/eclipse/jdt/annotation/Nullable.java",
+				NULLABLE_ANNOTATION_18_CONTENT,
+				"org/eclipse/jdt/annotation/DefaultLocation.java",				
+				DEFAULT_LOCATION_CONTENT,
+				"org/eclipse/jdt/annotation/NonNullByDefault.java",				
+				NONNULL_BY_DEFAULT_ANNOTATION_18_CONTENT,
+				"test1/Test1.java",
+				"package test1;\n" + 
+				"\n" + 
+				"import java.util.Map;\n" + 
+				"import org.eclipse.jdt.annotation.*;\n" + 
+				"\n" + 
+				"@NonNullByDefault\n" + 
+				"public class Test1 {\n" + 
+				"	void test(Map<String,Test1> map, String key) {\n" + 
+				"		Test1 v = map.get(key);\n" + 
+				"		if (v == null)\n" + 
+				"			throw new RuntimeException(); // should not be reported as dead code, although V is a '@NonNull Test1'\n" + 
+				"	}\n" +
+				"	public boolean equals(@NonNull Object other) { return false; }\n" + 
+				"}\n"
+			};
+
+	String o_e_j_annotation_dir = OUTPUT_DIR + File.separator +
+			"org" + File.separator + "eclipse" + File.separator + "jdt" + File.separator + "annotation";
+	String j_l_annotation_dir = OUTPUT_DIR +  File.separator +
+			"java" + File.separator + "lang" + File.separator + "annotation";
+	
+	String commandLine = " -1.8 -proc:none -d none -warn:+nullAnnot " + compilerPathArgs +
+			" -sourcepath \"" + OUTPUT_DIR + extraSourcePaths + "\" " +
+			// explicitly mention all files to ensure a good order, cannot pull in source of NNBD on demand
+			"\"" + j_l_annotation_dir   +  File.separator + "ElementType.java\" " +
+			"\"" + o_e_j_annotation_dir +  File.separator + "NonNull.java\" " +
+			"\"" + o_e_j_annotation_dir +  File.separator + "DefaultLocation.java\" " +
+			"\"" + o_e_j_annotation_dir +  File.separator + "NonNullByDefault.java\" " +
+			"\"" + OUTPUT_DIR +  File.separator + "test1" + File.separator + "Test1.java\"";
+	
+	if (expectedCompilerMessage == null)
+		expectedCompilerMessage =
+				"----------\n" + 
+				"1. WARNING in ---OUTPUT_DIR_PLACEHOLDER---/test1/Test1.java (at line 13)\n" + 
+				"	public boolean equals(@NonNull Object other) { return false; }\n" + 
+				"	                      ^^^^^^^^^^^^^^^\n" + 
+				"Illegal redefinition of parameter other, inherited method from Object declares this parameter as @Nullable\n" + 
+				"----------\n" + 
+				"1 problem (1 warning)\n";
+	
+	if (isSuccess)
+		this.runConformTest(testFiles, commandLine, "", expectedCompilerMessage, true);
+	else
+		this.runNegativeTest(testFiles, commandLine, "", expectedCompilerMessage, true);
+}
+// Bug 440687 - [compiler][batch][null] improve command line option for external annotations
+// - two external annotation directories as part of the sourcepath/classpath
+public void test440687a() throws IOException {
+
+	String annots_dir1 = Util.getOutputDirectory() + File.separator + "annots1";
+	String annots_java_util = annots_dir1 + File.separator + "java/util";
+	new File(annots_java_util).mkdirs();
+	Util.createFile(annots_java_util + File.separator + "Map.eea",
+			TEST_440687_MAP_EEA_CONTENT);
+
+	String annots_dir2 = Util.getOutputDirectory() + File.separator + "annots2";
+	String annots_java_lang = annots_dir2 + File.separator + "java/lang";
+	new File(annots_java_lang).mkdirs();
+	Util.createFile(annots_java_lang + File.separator + "Object.eea",
+			TEST_440687_OBJECT_EEA_CONTENT);
+
+	runTest440687("-annotationpath CLASSPATH -classpath \"" + annots_dir2 + "\"", 
+			File.pathSeparator + annots_dir1, // extra source path 
+			null, // expect normal error
+			true);
+}
+// Bug 440687 - [compiler][batch][null] improve command line option for external annotations
+// - two external annotation directories specifically configured.
+public void test440687b() throws IOException {
+	
+	String annots_dir = Util.getOutputDirectory() + File.separator + "annots1";
+	String annots_java_util = annots_dir + File.separator + "java/util";
+	new File(annots_java_util).mkdirs();
+	Util.createFile(
+			annots_java_util + File.separator + "Map.eea", 
+			TEST_440687_MAP_EEA_CONTENT);
+
+	String annots_dir2 = Util.getOutputDirectory() + File.separator + "annots2";
+	String annots_java_lang = annots_dir2 + File.separator + "java/lang";
+	new File(annots_java_lang).mkdirs();
+	Util.createFile(
+			annots_java_lang + File.separator + "Object.eea", 
+			TEST_440687_OBJECT_EEA_CONTENT);
+	
+	runTest440687("-annotationpath \"" + annots_dir + File.pathSeparator + annots_dir2 + "\" ",
+				"", // no extra source path
+				null, // expect normal error
+				true);
+}
+// Bug 440687 - [compiler][batch][null] improve command line option for external annotations
+// - single external annotation zip with 2 entries
+public void test440687c() throws IOException {
+	
+	String annots_dir = Util.getOutputDirectory() + File.separator + "annots";
+	new File(annots_dir).mkdirs();
+	String annotsZipFile = annots_dir+ File.separator + "jre-annots.zip";
+	Util.createSourceZip(
+		new String[] {
+			"java/util/Map.eea",
+			TEST_440687_MAP_EEA_CONTENT,
+			"java/lang/Object.eea", 
+			TEST_440687_OBJECT_EEA_CONTENT
+		},
+		annotsZipFile);
+
+	runTest440687("-annotationpath CLASSPATH -classpath \"" + annotsZipFile + "\"",
+					"", // no extra source path
+					null, // expect normal error
+					true);
+}
+// Bug 440687 - [compiler][batch][null] improve command line option for external annotations
+// - missing argument after -annotationpath
+public void test440687d() throws IOException {
+	runTest440687("-annotationpath", // missing argument 
+					"",
+					"Missing argument to -annotationpath at \'-sourcepath\'\n",
+					false);
+}
 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=439750
 public void test439750() {
 	this.runConformTest(
diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathDirectory.java b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathDirectory.java
index af49bb5..27c067b 100644
--- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathDirectory.java
+++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathDirectory.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2013 IBM Corporation and others.
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -7,6 +7,8 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Stephan Herrmann - Contribution for
+ *								Bug 440687 - [compiler][batch][null] improve command line option for external annotations
  *******************************************************************************/
 package org.eclipse.jdt.internal.compiler.batch;
 
@@ -19,6 +21,7 @@
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationProvider;
 import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
 import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
 import org.eclipse.jdt.internal.compiler.parser.ScannerHelper;
@@ -135,6 +138,15 @@
 	}
 	return null;
 }
+@Override
+public boolean hasAnnotationFileFor(String qualifiedTypeName) {
+	int pos = qualifiedTypeName.lastIndexOf('/');
+	if (pos != -1 && (pos + 1 < qualifiedTypeName.length())) {
+		String fileName = qualifiedTypeName.substring(pos + 1) + '.' + ExternalAnnotationProvider.ANNOTION_FILE_EXTENSION;
+		return doesFileExist(fileName, qualifiedTypeName.substring(0, pos));
+	}
+	return false;
+}
 public char[][][] findTypeNames(String qualifiedPackageName) {
 	if (!isPackage(qualifiedPackageName)) {
 		return null; // most common case
diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java
index 181ad0e..8d56c0a 100644
--- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java
+++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java
@@ -9,6 +9,7 @@
  *     IBM Corporation - initial API and implementation
  *     Stephan Herrmann - Contribution for
  *								Bug 440477 - [null] Infrastructure for feeding external annotations into compilation
+ *								Bug 440687 - [compiler][batch][null] improve command line option for external annotations
  *******************************************************************************/
 package org.eclipse.jdt.internal.compiler.batch;
 
@@ -26,6 +27,7 @@
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationProvider;
 import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
 import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
 import org.eclipse.jdt.internal.compiler.util.ManifestAnalyzer;
@@ -40,7 +42,7 @@
 protected ZipFile annotationZipFile;
 protected boolean closeZipFileAtEnd;
 protected Hashtable packageCache;
-protected String annotationPath;
+protected List<String> annotationPaths;
 
 public ClasspathJar(File file, boolean closeZipFileAtEnd,
 		AccessRuleSet accessRuleSet, String destinationPath) {
@@ -102,12 +104,16 @@
 	try {
 		ClassFileReader reader = ClassFileReader.read(this.zipFile, qualifiedBinaryFileName);
 		if (reader != null) {
-			if (this.annotationPath != null) {
+			if (this.annotationPaths != null) {
 				String qualifiedClassName = qualifiedBinaryFileName.substring(0, qualifiedBinaryFileName.length()-SuffixConstants.EXTENSION_CLASS.length()-1);
-				try {
-					this.annotationZipFile = reader.setExternalAnnotationProvider(this.annotationPath, qualifiedClassName, this.annotationZipFile, null);
-				} catch (IOException e) {
-					// don't let error on annotations fail class reading
+				for (String annotationPath : this.annotationPaths) {
+					try {
+						this.annotationZipFile = reader.setExternalAnnotationProvider(annotationPath, qualifiedClassName, this.annotationZipFile, null);
+						if (reader.hasAnnotationProvider())
+							break;
+					} catch (IOException e) {
+						// don't let error on annotations fail class reading
+					}
 				}
 			}
 			return new NameEnvironmentAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName));
@@ -119,6 +125,10 @@
 	}
 	return null;
 }
+@Override
+public boolean hasAnnotationFileFor(String qualifiedTypeName) {
+	return this.zipFile.getEntry(qualifiedTypeName+'.'+ExternalAnnotationProvider.ANNOTION_FILE_EXTENSION) != null; 
+}
 public char[][][] findTypeNames(String qualifiedPackageName) {
 	if (!isPackage(qualifiedPackageName))
 		return null; // most common case
diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/FileSystem.java b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/FileSystem.java
index 69f26d2..bcc420b 100644
--- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/FileSystem.java
+++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/FileSystem.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2013 IBM Corporation and others.
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -7,6 +7,8 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Stephan Herrmann - Contribution for
+ *								Bug 440687 - [compiler][batch][null] improve command line option for external annotations
  *******************************************************************************/
 package org.eclipse.jdt.internal.compiler.batch;
 
@@ -17,8 +19,10 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import java.util.zip.ZipFile;
 
 import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
 import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
 import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
 import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
@@ -68,6 +72,11 @@
 		 * Initialize the entry
 		 */
 		void initialize() throws IOException;
+		/**
+		 * Can the current location provide an external annotation file for the given type?
+		 * @param qualifiedTypeName type name in qualified /-separated notation.
+		 */
+		boolean hasAnnotationFileFor(String qualifiedTypeName);
 	}
 	public interface ClasspathSectionProblemReporter {
 		void invalidClasspathSection(String jarFilePath);
@@ -103,6 +112,7 @@
 
 	protected Classpath[] classpaths;
 	Set knownFileNames;
+	protected boolean annotationsFromClasspath; // should annotation files be read from the classpath (vs. explicit separate path)?
 
 /*
 	classPathNames is a collection is Strings representing the full path of each class path
@@ -126,7 +136,7 @@
 	}
 	initializeKnownFileNames(initialFileNames);
 }
-protected FileSystem(Classpath[] paths, String[] initialFileNames) {
+protected FileSystem(Classpath[] paths, String[] initialFileNames, boolean annotationsFromClasspath) {
 	final int length = paths.length;
 	int counter = 0;
 	this.classpaths = new FileSystem.Classpath[length];
@@ -144,6 +154,7 @@
 		System.arraycopy(this.classpaths, 0, (this.classpaths = new FileSystem.Classpath[counter]), 0, counter);
 	}
 	initializeKnownFileNames(initialFileNames);
+	this.annotationsFromClasspath = annotationsFromClasspath;
 }
 public static Classpath getClasspath(String classpathName, String encoding, AccessRuleSet accessRuleSet) {
 	return getClasspath(classpathName, encoding, false, accessRuleSet, null);
@@ -248,6 +259,24 @@
 		 : path.replace('/', '\\');
 }
 private NameEnvironmentAnswer findClass(String qualifiedTypeName, char[] typeName, boolean asBinaryOnly){
+	NameEnvironmentAnswer answer = internalFindClass(qualifiedTypeName, typeName, asBinaryOnly);
+	if (this.annotationsFromClasspath && answer != null && answer.getBinaryType() instanceof ClassFileReader) {
+		for (int i = 0, length = this.classpaths.length; i < length; i++) {
+			Classpath classpathEntry = this.classpaths[i];
+			if (classpathEntry.hasAnnotationFileFor(qualifiedTypeName)) {
+				ZipFile zip = classpathEntry instanceof ClasspathJar ? ((ClasspathJar) classpathEntry).zipFile : null;
+				try {
+					((ClassFileReader) answer.getBinaryType()).setExternalAnnotationProvider(classpathEntry.getPath(), qualifiedTypeName, zip, null);
+					break;
+				} catch (IOException e) {
+					// ignore broken entry, keep searching
+				}
+			}
+		}
+	}
+	return answer;
+}
+private NameEnvironmentAnswer internalFindClass(String qualifiedTypeName, char[] typeName, boolean asBinaryOnly){
 	if (this.knownFileNames.contains(qualifiedTypeName)) return null; // looking for a file which we know was provided at the beginning of the compilation
 
 	String qualifiedBinaryFileName = qualifiedTypeName + SUFFIX_STRING_class;
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 93554e9..5abfda0 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
@@ -20,6 +20,7 @@
  *								bug 388281 - [compiler][null] inheritance of null annotations as an option
  *								bug 381443 - [compiler][null] Allow parameter widening from @NonNull to unannotated
  *								Bug 440477 - [null] Infrastructure for feeding external annotations into compilation
+ *								Bug 440687 - [compiler][batch][null] improve command line option for external annotations
  *     Jesper S Moller   - Contributions for
  *								bug 407297 - [1.8][compiler] Control generation of parameter names by option
  *    Mat Booth - Contribution for bug 405176 
@@ -1287,6 +1288,10 @@
 			return bundle;
 		}
 	}
+
+	// used with -annotationpath to declare that annotations should be read from the classpath:
+	private static final String ANNOTATION_SOURCE_CLASSPATH = "CLASSPATH"; //$NON-NLS-1$
+
 	// javadoc analysis tuning
 	boolean enableJavadocOn;
 
@@ -1297,8 +1302,9 @@
 	/* Bundle containing messages */
 	public ResourceBundle bundle;
 	protected FileSystem.Classpath[] checkedClasspaths;
-	// path to external annotations:
-	protected String annotationPath;
+	// paths to external annotations:
+	protected List<String> annotationPaths;
+	protected boolean annotationsFromClasspath;
 
 	public Locale compilerLocale;
 	public CompilerOptions compilerOptions; // read-only
@@ -1769,7 +1775,8 @@
 	ArrayList classpaths = new ArrayList(DEFAULT_SIZE_CLASSPATH);
 	ArrayList extdirsClasspaths = null;
 	ArrayList endorsedDirClasspaths = null;
-	this.annotationPath = null;
+	this.annotationPaths = null;
+	this.annotationsFromClasspath = false;
 
 	int index = -1;
 	int filesCount = 0;
@@ -2660,7 +2667,17 @@
 				continue;
 			case INSIDE_ANNOTATIONPATH_start:
 				mode = DEFAULT;
-				this.annotationPath = currentArg;
+				if (currentArg.isEmpty() || currentArg.charAt(0) == '-')
+					throw new IllegalArgumentException(this.bind("configure.missingAnnotationPath", currentArg)); //$NON-NLS-1$
+				if (ANNOTATION_SOURCE_CLASSPATH.equals(currentArg)) {
+					this.annotationsFromClasspath = true;
+				} else {
+					if (this.annotationPaths == null)
+						this.annotationPaths = new ArrayList<String>();
+					StringTokenizer tokens = new StringTokenizer(currentArg, File.pathSeparator);
+					while (tokens.hasMoreTokens())
+						this.annotationPaths.add(tokens.nextToken());
+				}
 				continue;
 		}
 
@@ -3060,7 +3077,8 @@
 }
 
 public FileSystem getLibraryAccess() {
-	return new FileSystem(this.checkedClasspaths, this.filenames);
+	return new FileSystem(this.checkedClasspaths, this.filenames, 
+					this.annotationsFromClasspath && CompilerOptions.ENABLED.equals(this.options.get(CompilerOptions.OPTION_AnnotationBasedNullAnalysis)));
 }
 
 /*
@@ -4572,10 +4590,10 @@
 	classpaths.toArray(this.checkedClasspaths);
 	this.logger.logClasspath(this.checkedClasspaths);
 
-	if (this.annotationPath != null) {
+	if (this.annotationPaths != null && CompilerOptions.ENABLED.equals(this.options.get(CompilerOptions.OPTION_AnnotationBasedNullAnalysis))) {
 		for (FileSystem.Classpath cp : this.checkedClasspaths) {
 			if (cp instanceof ClasspathJar)
-				((ClasspathJar) cp).annotationPath = this.annotationPath;
+				((ClasspathJar) cp).annotationPaths = this.annotationPaths;
 		}
 	}
 }
diff --git a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties
index 905f39f..5f6a1d9 100644
--- a/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties
+++ b/org.eclipse.jdt.core/batch/org/eclipse/jdt/internal/compiler/batch/messages.properties
@@ -15,6 +15,7 @@
 #								bug 365208 - [compiler][batch] command line options for annotation based null analysis
 #								bug 374605 - Unreasonable warning for enum-based switch statements
 #								bug 388281 - [compiler][null] inheritance of null annotations as an option
+#								bug 440687 - [compiler][batch][null] improve command line option for external annotations
 #		Alan Moraes <alan@kelon.org> - Contribution for bug 383644
 #		Jesper S Moller - Contribution for bug 407297 - [1.8][compiler] Control generation of parameter names by option
 ###############################################################################
@@ -111,6 +112,7 @@
 
 ### null annotations
 configure.invalidNullAnnot = Token {0} is not in the expected format "nullAnnot(<non null annotation name> | <nullable annotation name> | <non-null by default annotation name>)"
+configure.missingAnnotationPath = Missing argument to -annotationpath at ''{0}''
 
 ### requestor
 requestor.error = {0}. ERROR in {1}
@@ -263,9 +265,11 @@
 \    -Xemacs            used to enable emacs-style output in the console.\n\
 \                       It does not affect the xml log output\n\
 \    -missingNullDefault  report missing default nullness annotation\n\
-\    -annotationpath <path>\n\
-\                       Path to a base directory or zip file holding external\n\
-\                       annotations to support annotation-based null analysis\n\
+\    -annotationpath <directories and ZIP archives separated by {0}>\n\
+\                       specify locations where to find external annotations\n\
+\                       to support annotation-based null analysis.\n\
+\                       The special name CLASSPATH will cause lookup of\n\
+\                       external annotations from the classpath and sourcepath.\n\
 \ \n\
 \    -? -help           print this help message\n\
 \    -v -version        print compiler version\n\
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java
index 9fbcb34..0e7bb06 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java
@@ -10,6 +10,7 @@
  *     Stephan Herrmann - Contribution for
  *								Bug 365992 - [builder] [null] Change of nullness for a parameter doesn't trigger a build for the files that call the method
  *								Bug 440477 - [null] Infrastructure for feeding external annotations into compilation
+ *								Bug 440687 - [compiler][batch][null] improve command line option for external annotations
  *     Andy Clement (GoPivotal, Inc) aclement@gopivotal.com - Contributions for
  *         						bug 407191 - [1.8] Binary access support for type annotations
  *******************************************************************************/
@@ -446,6 +447,9 @@
 		this.annotationProvider = new ExternalAnnotationProvider(zipFile.getInputStream(entry), String.valueOf(getName()));
 	return zipFile;
 }
+public boolean hasAnnotationProvider() {
+	return this.annotationProvider != null;
+}
 
 /**
  * Conditionally add external annotations to the mix.
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/NameEnvironmentWithProgress.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/NameEnvironmentWithProgress.java
index f5bc4e8..e907680 100644
--- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/NameEnvironmentWithProgress.java
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/NameEnvironmentWithProgress.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2010 IBM Corporation and others.
+ * Copyright (c) 2010, 2015 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -7,6 +7,8 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Stephan Herrmann - Contribution for
+ *								Bug 440687 - [compiler][batch][null] improve command line option for external annotations
  *******************************************************************************/
 package org.eclipse.jdt.core.dom;
 
@@ -26,7 +28,7 @@
 	IProgressMonitor monitor;
 	
 	public NameEnvironmentWithProgress(Classpath[] paths, String[] initialFileNames, IProgressMonitor monitor) {
-		super(paths, initialFileNames);
+		super(paths, initialFileNames, false);
 		setMonitor(monitor);
 	}
 	private void checkCanceled() {