Bug 526011 - PDE should provide an abstraction above
annotation-configuration in .classpath

* introduce new manifest header Eclipse-ExportExternalAnnotations

* if set to true, resolved entries in Required-Plugins will define
  their annotationPath equal to the plugin location

This suffices to help JDT/Core to use these annotations for null
analysis, if .eea files are shipped with the plug-in jar.

We even have content assist for the new header :)

Change-Id: I84643bc57d3da9f5a8f50d61da4ea82f8cd9cac9
Signed-off-by: Stephan Herrmann <stephan.herrmann@berlin.de>
Reviewed-on: https://git.eclipse.org/r/c/pde/eclipse.pde.ui/+/175466
Tested-by: Vikas Chandra <Vikas.Chandra@in.ibm.com>
Reviewed-by: Vikas Chandra <Vikas.Chandra@in.ibm.com>
diff --git a/ui/org.eclipse.pde.core/.settings/.api_filters b/ui/org.eclipse.pde.core/.settings/.api_filters
index 5d17ac3..686677b 100644
--- a/ui/org.eclipse.pde.core/.settings/.api_filters
+++ b/ui/org.eclipse.pde.core/.settings/.api_filters
@@ -1,5 +1,13 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <component id="org.eclipse.pde.core" version="2">
+    <resource path="META-INF/MANIFEST.MF">
+        <filter comment="experimental addition of a manifest option" id="924844039">
+            <message_arguments>
+                <message_argument value="3.15.200"/>
+                <message_argument value="3.15.100"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="src/org/eclipse/pde/internal/core/project/BundleProjectService.java" type="org.eclipse.pde.internal.core.project.BundleProjectService">
         <filter comment="Platform Team allows use of bundle importers for PDE import from source repository" id="640712815">
             <message_arguments>
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/plugin/IPluginBase.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/plugin/IPluginBase.java
index 5f37e02..170f140 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/plugin/IPluginBase.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/plugin/IPluginBase.java
@@ -179,4 +179,16 @@
 	 */
 	void setSchemaVersion(String schemaVersion) throws CoreException;
 
+	/**
+	 * Returns whether this plugin exports its external annotations (.eea files)
+	 * to be considered by clients performing annotation based null analysis.
+	 * This is read from the manifest header
+	 * {@code Eclipse-ExportExternalAnnotations}.
+	 *
+	 * @since 3.15
+	 */
+	default boolean exportsExternalAnnotations() {
+		return false;
+	}
+
 }
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ICoreConstants.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ICoreConstants.java
index e28d771..d61f5e3 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ICoreConstants.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ICoreConstants.java
@@ -285,6 +285,7 @@
 	String ECLIPSE_SOURCE_REFERENCES = "Eclipse-SourceReferences"; //$NON-NLS-1$
 	String SERVICE_COMPONENT = "Service-Component"; //$NON-NLS-1$
 	String AUTOMATIC_MODULE_NAME = "Automatic-Module-Name"; //$NON-NLS-1$
+	String ECLIPSE_EXPORT_EXTERNAL_ANNOTATIONS = "Eclipse-ExportExternalAnnotations"; //$NON-NLS-1$
 
 	// Equinox-specific system properties
 	String OSGI_SYSTEM_BUNDLE = "osgi.system.bundle"; //$NON-NLS-1$
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEAuxiliaryState.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEAuxiliaryState.java
index d668d56..41996da 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEAuxiliaryState.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEAuxiliaryState.java
@@ -95,6 +95,7 @@
 		String project;
 		String localization;
 		String bundleSourceEntry;
+		boolean exportsExternalAnnotations;
 	}
 
 	/**
@@ -188,6 +189,11 @@
 		return info == null ? null : info.bundleSourceEntry;
 	}
 
+	public boolean exportsExternalAnnotations(long bundleID) {
+		PluginInfo info = fPluginInfos.get(Long.toString(bundleID));
+		return info == null ? false : info.exportsExternalAnnotations;
+	}
+
 	/**
 	 * Builds an xml document storing the auxiliary plugin info.
 	 * @param dir directory location to create the file
@@ -375,6 +381,8 @@
 		info.localization = manifest.get(Constants.BUNDLE_LOCALIZATION);
 		info.hasBundleStructure = hasBundleStructure;
 		info.bundleSourceEntry = manifest.get(ICoreConstants.ECLIPSE_SOURCE_BUNDLE);
+		info.exportsExternalAnnotations = "true" //$NON-NLS-1$
+				.equals(manifest.get(ICoreConstants.ECLIPSE_EXPORT_EXTERNAL_ANNOTATIONS));
 		fPluginInfos.put(Long.toString(desc.getBundleId()), info);
 	}
 
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEClasspathContainer.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEClasspathContainer.java
index a631457..1274af0 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEClasspathContainer.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEClasspathContainer.java
@@ -16,6 +16,7 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
@@ -24,6 +25,7 @@
 import org.eclipse.jdt.core.IClasspathAttribute;
 import org.eclipse.jdt.core.IClasspathEntry;
 import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
 import org.eclipse.osgi.service.resolver.BundleDescription;
 import org.eclipse.pde.core.plugin.IPluginLibrary;
 import org.eclipse.pde.core.plugin.IPluginModelBase;
@@ -54,21 +56,28 @@
 
 	private static final IAccessRule EXCLUDE_ALL_RULE = JavaCore.newAccessRule(new Path("**/*"), IAccessRule.K_NON_ACCESSIBLE | IAccessRule.IGNORE_IF_BETTER); //$NON-NLS-1$
 
-	protected void addProjectEntry(IProject project, Rule[] rules, ArrayList<IClasspathEntry> entries) throws CoreException {
+	protected void addProjectEntry(IProject project, Rule[] rules, boolean exportsExternalAnnotations,
+			ArrayList<IClasspathEntry> entries) throws CoreException {
 		if (project.hasNature(JavaCore.NATURE_ID)) {
-			IClasspathEntry entry = null;
-			if (rules != null) {
-				IAccessRule[] accessRules = getAccessRules(rules);
-				entry = JavaCore.newProjectEntry(project.getFullPath(), accessRules, true, new IClasspathAttribute[0], false);
-			} else {
-				entry = JavaCore.newProjectEntry(project.getFullPath());
-			}
+			IAccessRule[] accessRules = rules != null ? getAccessRules(rules) : null;
+			IClasspathAttribute[] extraAttribs = getClasspathAttributesForProject(project, exportsExternalAnnotations);
+			IClasspathEntry entry = JavaCore.newProjectEntry(project.getFullPath(), accessRules, true, extraAttribs, false);
 			if (!entries.contains(entry)) {
 				entries.add(entry);
 			}
 		}
 	}
 
+	private IClasspathAttribute[] getClasspathAttributesForProject(IProject project, boolean exportsExternalAnnotations)
+			throws JavaModelException {
+		if (exportsExternalAnnotations) {
+			String annotationPath = JavaCore.create(project).getOutputLocation().toString();
+			return new IClasspathAttribute[] {
+					JavaCore.newClasspathAttribute(IClasspathAttribute.EXTERNAL_ANNOTATION_PATH, annotationPath) };
+		}
+		return new IClasspathAttribute[0];
+	}
+
 	public static IClasspathEntry[] getExternalEntries(IPluginModelBase model) {
 		ArrayList<IClasspathEntry> entries = new ArrayList<>();
 		addExternalPlugin(model, new Rule[0], entries);
@@ -153,12 +162,20 @@
 	}
 
 	private static IClasspathAttribute[] getClasspathAttributes(IPluginModelBase model) {
+		List<IClasspathAttribute> attributes = new ArrayList<>();
 		JavadocLocationManager manager = PDECore.getDefault().getJavadocLocationManager();
 		String location = manager.getJavadocLocation(model);
-		if (location == null) {
-			return new IClasspathAttribute[0];
+		if (location != null) {
+			attributes
+					.add(JavaCore.newClasspathAttribute(IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, location));
 		}
-		return new IClasspathAttribute[] {JavaCore.newClasspathAttribute(IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, location)};
+		if (model.getPluginBase().exportsExternalAnnotations()) {
+			String installLocation = model.getInstallLocation();
+			attributes.add(
+					JavaCore.newClasspathAttribute(IClasspathAttribute.EXTERNAL_ANNOTATION_PATH,
+							installLocation));
+		}
+		return attributes.toArray(IClasspathAttribute[]::new);
 	}
 
 	private static synchronized IAccessRule getDiscouragedRule(IPath path) {
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEState.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEState.java
index 1a0cf66..c7ccc40 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEState.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEState.java
@@ -306,4 +306,8 @@
 		return fAuxiliaryState.getBundleSourceEntry(bundleID);
 	}
 
+	public boolean exportsExternalAnnotations(long bundleID) {
+		return fAuxiliaryState.exportsExternalAnnotations(bundleID);
+	}
+
 }
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/RequiredPluginsClasspathContainer.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/RequiredPluginsClasspathContainer.java
index 310abc4..3999a40 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/RequiredPluginsClasspathContainer.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/RequiredPluginsClasspathContainer.java
@@ -347,7 +347,7 @@
 		}
 
 		if (resource != null) {
-			addProjectEntry(resource.getProject(), rules, entries);
+			addProjectEntry(resource.getProject(), rules, model.getPluginBase().exportsExternalAnnotations(), entries);
 		} else {
 			addExternalPlugin(model, rules, entries);
 		}
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/WorkspacePluginModelManager.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/WorkspacePluginModelManager.java
index a726211..30dd5e9 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/WorkspacePluginModelManager.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/WorkspacePluginModelManager.java
@@ -84,7 +84,8 @@
 					Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT, //
 					IPDEBuildConstants.ECLIPSE_PLATFORM_FILTER, //
 					ICoreConstants.ECLIPSE_SYSTEM_BUNDLE, //
-					ICoreConstants.ECLIPSE_SOURCE_BUNDLE)));
+					ICoreConstants.ECLIPSE_SOURCE_BUNDLE, //
+					ICoreConstants.ECLIPSE_EXPORT_EXTERNAL_ANNOTATIONS)));
 
 	private final ArrayList<IExtensionDeltaListener> fExtensionListeners = new ArrayList<>();
 	private ArrayList<ModelChange> fChangedExtensions = null;
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bundle/BundlePlugin.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bundle/BundlePlugin.java
index 1b253ce..7965327 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bundle/BundlePlugin.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bundle/BundlePlugin.java
@@ -52,4 +52,9 @@
 		return "true".equals(getValue(ICoreConstants.EXTENSIBLE_API, false)); //$NON-NLS-1$
 	}
 
+	@Override
+	public boolean exportsExternalAnnotations() {
+		return "true".equals(getValue(ICoreConstants.ECLIPSE_EXPORT_EXTERNAL_ANNOTATIONS, false)); //$NON-NLS-1$
+	}
+
 }
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/plugin/PluginBase.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/plugin/PluginBase.java
index faf0a78..046b963 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/plugin/PluginBase.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/plugin/PluginBase.java
@@ -51,6 +51,7 @@
 	private String fVersion;
 	private boolean fHasBundleStructure;
 	private String fBundleSourceEntry;
+	private boolean fExportsExternalAnnotations;
 
 	public PluginBase(boolean readOnly) {
 		super(readOnly);
@@ -123,6 +124,7 @@
 		fProviderName = state.getProviderName(bundleDesc.getBundleId());
 		fHasBundleStructure = state.hasBundleStructure(bundleDesc.getBundleId());
 		fBundleSourceEntry = state.getBundleSourceEntry(bundleDesc.getBundleId());
+		fExportsExternalAnnotations = state.exportsExternalAnnotations(bundleDesc.getBundleId());
 		loadRuntime(bundleDesc, state);
 		loadImports(bundleDesc);
 	}
@@ -443,4 +445,8 @@
 		return fBundleSourceEntry;
 	}
 
+	public boolean exportsExternalAnnotations() {
+		return fExportsExternalAnnotations;
+	}
+
 }
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/contentassist/ManifestContentAssistProcessor.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/contentassist/ManifestContentAssistProcessor.java
index 806cede..ee4af33 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/contentassist/ManifestContentAssistProcessor.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/contentassist/ManifestContentAssistProcessor.java
@@ -48,7 +48,7 @@
 	private IJavaProject fJP;
 
 	// if we order the headers alphabetically in the array, there is no need to sort and we can save time
-	private static final String[] fHeader = { ICoreConstants.AUTOMATIC_MODULE_NAME, Constants.BUNDLE_ACTIVATIONPOLICY, Constants.BUNDLE_ACTIVATOR, Constants.BUNDLE_CATEGORY, Constants.BUNDLE_CLASSPATH, Constants.BUNDLE_CONTACTADDRESS, Constants.BUNDLE_COPYRIGHT, Constants.BUNDLE_DESCRIPTION, Constants.BUNDLE_DOCURL, Constants.BUNDLE_LOCALIZATION, Constants.BUNDLE_MANIFESTVERSION, Constants.BUNDLE_NAME, Constants.BUNDLE_NATIVECODE, Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT, Constants.BUNDLE_SYMBOLICNAME, Constants.BUNDLE_UPDATELOCATION, Constants.BUNDLE_VENDOR, Constants.BUNDLE_VERSION, Constants.DYNAMICIMPORT_PACKAGE, ICoreConstants.ECLIPSE_BUDDY_POLICY, ICoreConstants.ECLIPSE_BUNDLE_SHAPE, ICoreConstants.ECLIPSE_GENERIC_CAPABILITY, ICoreConstants.ECLIPSE_GENERIC_REQUIRED, ICoreConstants.ECLIPSE_LAZYSTART,
+	private static final String[] fHeader = { ICoreConstants.AUTOMATIC_MODULE_NAME, Constants.BUNDLE_ACTIVATIONPOLICY, Constants.BUNDLE_ACTIVATOR, Constants.BUNDLE_CATEGORY, Constants.BUNDLE_CLASSPATH, Constants.BUNDLE_CONTACTADDRESS, Constants.BUNDLE_COPYRIGHT, Constants.BUNDLE_DESCRIPTION, Constants.BUNDLE_DOCURL, Constants.BUNDLE_LOCALIZATION, Constants.BUNDLE_MANIFESTVERSION, Constants.BUNDLE_NAME, Constants.BUNDLE_NATIVECODE, Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT, Constants.BUNDLE_SYMBOLICNAME, Constants.BUNDLE_UPDATELOCATION, Constants.BUNDLE_VENDOR, Constants.BUNDLE_VERSION, Constants.DYNAMICIMPORT_PACKAGE, ICoreConstants.ECLIPSE_BUDDY_POLICY, ICoreConstants.ECLIPSE_BUNDLE_SHAPE, ICoreConstants.ECLIPSE_EXPORT_EXTERNAL_ANNOTATIONS, ICoreConstants.ECLIPSE_GENERIC_CAPABILITY, ICoreConstants.ECLIPSE_GENERIC_REQUIRED, ICoreConstants.ECLIPSE_LAZYSTART,
 			ICoreConstants.PLATFORM_FILTER, ICoreConstants.ECLIPSE_REGISTER_BUDDY, ICoreConstants.ECLIPSE_SOURCE_REFERENCES, Constants.EXPORT_PACKAGE, ICoreConstants.EXPORT_SERVICE, Constants.FRAGMENT_HOST, Constants.IMPORT_PACKAGE, ICoreConstants.IMPORT_SERVICE, Constants.PROVIDE_CAPABILITY, Constants.REQUIRE_BUNDLE, Constants.REQUIRE_CAPABILITY};
 
 
@@ -229,6 +229,10 @@
 			return handleBuddyPolicyCompletion(value.substring(ICoreConstants.ECLIPSE_BUDDY_POLICY.length() + 1), offset);
 		if (value.regionMatches(true, 0, ICoreConstants.ECLIPSE_BUNDLE_SHAPE, 0, Math.min(length, ICoreConstants.ECLIPSE_BUNDLE_SHAPE.length())))
 			return handleEclipseBundleShape(value.substring(ICoreConstants.ECLIPSE_BUNDLE_SHAPE.length() + 1), offset);
+		if (value.regionMatches(true, 0, ICoreConstants.ECLIPSE_EXPORT_EXTERNAL_ANNOTATIONS, 0,
+				Math.min(length, ICoreConstants.ECLIPSE_EXPORT_EXTERNAL_ANNOTATIONS.length())))
+			return handleTrueFalseValue(
+					value.substring(ICoreConstants.ECLIPSE_EXPORT_EXTERNAL_ANNOTATIONS.length() + 1), offset);
 		return new ICompletionProposal[0];
 	}