Bug 551417 - An application to generate API metadata without Ant
Change-Id: I0c5d404bb7839a3d1da2a66bd4464c1b299eecea
Signed-off-by: Mickael Istria <mistria@redhat.com>
diff --git a/apitools/org.eclipse.pde.api.tools/plugin.xml b/apitools/org.eclipse.pde.api.tools/plugin.xml
index 09a10bf..ce7f170 100644
--- a/apitools/org.eclipse.pde.api.tools/plugin.xml
+++ b/apitools/org.eclipse.pde.api.tools/plugin.xml
@@ -316,4 +316,16 @@
</run>
</application>
</extension>
+ <extension
+ id="apiGeneration"
+ point="org.eclipse.core.runtime.applications">
+ <application
+ cardinality="singleton-global"
+ thread="any"
+ visible="true">
+ <run
+ class="org.eclipse.pde.api.tools.internal.ApiFileGenerationApplication">
+ </run>
+ </application>
+ </extension>
</plugin>
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/APIFileGenerator.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/APIFileGenerator.java
new file mode 100644
index 0000000..d395261
--- /dev/null
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/APIFileGenerator.java
@@ -0,0 +1,491 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Red Hat Inc. and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * - Mickael Istria (Red Hat Inc.)
+ *******************************************************************************/
+package org.eclipse.pde.api.tools.internal;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import javax.xml.parsers.FactoryConfigurationError;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.osgi.util.ManifestElement;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.pde.api.tools.internal.model.ArchiveApiTypeContainer;
+import org.eclipse.pde.api.tools.internal.model.CompositeApiTypeContainer;
+import org.eclipse.pde.api.tools.internal.model.DirectoryApiTypeContainer;
+import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
+import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeContainer;
+import org.eclipse.pde.api.tools.internal.provisional.scanner.TagScanner;
+import org.eclipse.pde.api.tools.internal.util.Util;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+public class APIFileGenerator {
+
+ static class APIToolsNatureDefaultHandler extends DefaultHandler {
+ private static final String NATURE_ELEMENT_NAME = "nature"; //$NON-NLS-1$
+ boolean isAPIToolsNature = false;
+ boolean insideNature = false;
+ StringBuilder buffer;
+
+ @Override
+ public void error(SAXParseException e) throws SAXException {
+ e.printStackTrace();
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
+ if (this.isAPIToolsNature) {
+ return;
+ }
+ this.insideNature = NATURE_ELEMENT_NAME.equals(name);
+ if (this.insideNature) {
+ this.buffer = new StringBuilder();
+ }
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ if (this.insideNature) {
+ this.buffer.append(ch, start, length);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String name) throws SAXException {
+ if (this.insideNature) {
+ // check the contents of the characters
+ String natureName = String.valueOf(this.buffer).trim();
+ this.isAPIToolsNature = ApiPlugin.NATURE_ID.equals(natureName);
+ }
+ this.insideNature = false;
+ }
+
+ public boolean isAPIToolsNature() {
+ return this.isAPIToolsNature;
+ }
+ }
+
+ public boolean debug;
+ public String projectName;
+ public String projectLocation;
+ public String targetFolder;
+ public String binaryLocations;
+ public Set<String> apiPackages = new HashSet<>(0);
+ public String manifests;
+ public String sourceLocations;
+ public boolean allowNonApiProject = false;
+ public String encoding;
+
+ private static boolean isZipJarFile(String fileName) {
+ String normalizedFileName = fileName.toLowerCase();
+ return normalizedFileName.endsWith(".zip") //$NON-NLS-1$
+ || normalizedFileName.endsWith(".jar"); //$NON-NLS-1$
+ }
+
+ public void generateAPIFile() {
+ if (this.binaryLocations == null || this.projectName == null || this.projectLocation == null || this.targetFolder == null) {
+ StringWriter out = new StringWriter();
+ PrintWriter writer = new PrintWriter(out);
+ writer.println(NLS.bind(CoreMessages.api_generation_printArguments,
+ new String[] {
+ this.projectName, this.projectLocation,
+ this.binaryLocations, this.targetFolder }));
+ writer.flush();
+ writer.close();
+ throw new IllegalArgumentException(String.valueOf(out.getBuffer()));
+ }
+ if (this.debug) {
+ System.out.println("Project name : " + this.projectName); //$NON-NLS-1$
+ System.out.println("Encoding: " + this.encoding); //$NON-NLS-1$
+ System.out.println("Project location : " + this.projectLocation); //$NON-NLS-1$
+ System.out.println("Binary locations : " + this.binaryLocations); //$NON-NLS-1$
+ System.out.println("Target folder : " + this.targetFolder); //$NON-NLS-1$
+ if (this.manifests != null) {
+ System.out.println("Extra manifest entries : " + this.manifests); //$NON-NLS-1$
+ }
+ if (this.sourceLocations != null) {
+ System.out.println("Extra source locations entries : " + this.sourceLocations); //$NON-NLS-1$
+ }
+ }
+ // collect all compilation units
+ File root = new File(this.projectLocation);
+ if (!root.exists() || !root.isDirectory()) {
+ if (this.debug) {
+ System.err.println("Must be a directory : " + this.projectLocation); //$NON-NLS-1$
+ }
+ throw new IllegalArgumentException(
+ NLS.bind(CoreMessages.api_generation_projectLocationNotADirectory, this.projectLocation));
+ }
+ // check if the project contains the API tools nature
+ File dotProjectFile = new File(root, ".project"); //$NON-NLS-1$
+
+ if (!this.allowNonApiProject && !isAPIToolsNature(dotProjectFile)) {
+ System.err.println("The project does not have an API Tools nature so a api_description file will not be generated"); //$NON-NLS-1$
+ return;
+ }
+ // check if the .api_description file exists
+ File targetProjectFolder = new File(this.targetFolder);
+ if (!targetProjectFolder.exists()) {
+ targetProjectFolder.mkdirs();
+ } else if (!targetProjectFolder.isDirectory()) {
+ if (this.debug) {
+ System.err.println("Must be a directory : " + this.targetFolder); //$NON-NLS-1$
+ }
+ throw new IllegalArgumentException(
+ NLS.bind(CoreMessages.api_generation_targetFolderNotADirectory, this.targetFolder));
+ }
+ File apiDescriptionFile = new File(targetProjectFolder, IApiCoreConstants.API_DESCRIPTION_XML_NAME);
+ if (apiDescriptionFile.exists()) {
+ // get rid of the existing one
+ // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=414053
+ if (this.debug) {
+ System.out.println("Existing api description file deleted"); //$NON-NLS-1$
+ }
+ apiDescriptionFile.delete();
+ }
+ File[] allFiles = null;
+ Map<String, String> manifestMap = null;
+ IApiTypeContainer classFileContainer = null;
+ if (!this.projectLocation.endsWith(Util.ORG_ECLIPSE_SWT)) {
+ // create the directory class file container used to resolve
+ // signatures during tag scanning
+ String[] allBinaryLocations = this.binaryLocations.split(File.pathSeparator);
+ List<IApiTypeContainer> allContainers = new ArrayList<>();
+ IApiTypeContainer container = null;
+ for (String allBinaryLocation : allBinaryLocations) {
+ container = getContainer(allBinaryLocation);
+ if (container == null) {
+ throw new IllegalArgumentException(
+ NLS.bind(CoreMessages.api_generation_invalidBinaryLocation, allBinaryLocation));
+ }
+ allContainers.add(container);
+ }
+ classFileContainer = new CompositeApiTypeContainer(null, allContainers);
+ File manifestFile = null;
+ File manifestDir = new File(root, "META-INF"); //$NON-NLS-1$
+ if (manifestDir.exists() && manifestDir.isDirectory()) {
+ manifestFile = new File(manifestDir, "MANIFEST.MF"); //$NON-NLS-1$
+ }
+ if (manifestFile != null && manifestFile.exists()) {
+ try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(manifestFile));) {
+ manifestMap = ManifestElement.parseBundleManifest(inputStream, null);
+ this.apiPackages = collectApiPackageNames(manifestMap);
+ } catch (IOException | BundleException e) {
+ ApiPlugin.log(e);
+ }
+ }
+ if (this.manifests != null) {
+ String[] allManifestFiles = this.manifests.split(File.pathSeparator);
+ for (String allManifestFile : allManifestFiles) {
+ File currentManifest = new File(allManifestFile);
+ Set<String> currentApiPackages = null;
+ if (currentManifest.exists()) {
+ BufferedInputStream inputStream = null;
+ ZipFile zipFile = null;
+ try {
+ if (isZipJarFile(currentManifest.getName())) {
+ zipFile = new ZipFile(currentManifest);
+ final ZipEntry entry = zipFile.getEntry("META-INF/MANIFEST.MF"); //$NON-NLS-1$
+ if (entry != null) {
+ inputStream = new BufferedInputStream(zipFile.getInputStream(entry));
+ }
+ } else {
+ inputStream = new BufferedInputStream(new FileInputStream(currentManifest));
+ }
+ if (inputStream != null) {
+ manifestMap = ManifestElement.parseBundleManifest(inputStream, null);
+ currentApiPackages = collectApiPackageNames(manifestMap);
+ }
+ } catch (IOException | BundleException e) {
+ ApiPlugin.log(e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ if (zipFile != null) {
+ try {
+ zipFile.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ }
+ if (currentApiPackages != null) {
+ if (this.apiPackages == null) {
+ this.apiPackages = currentApiPackages;
+ } else {
+ this.apiPackages.addAll(currentApiPackages);
+ }
+ }
+ }
+ }
+ FileFilter fileFilter = path -> (path.isFile() && Util.isJavaFileName(path.getName()) && isApi(path.getParent())) || path.isDirectory();
+ allFiles = Util.getAllFiles(root, fileFilter);
+ if (this.sourceLocations != null) {
+ String[] allSourceLocations = this.sourceLocations.split(File.pathSeparator);
+ for (String currentSourceLocation : allSourceLocations) {
+ File[] allFiles2 = Util.getAllFiles(new File(currentSourceLocation), fileFilter);
+ if (allFiles2 != null) {
+ if (allFiles == null) {
+ allFiles = allFiles2;
+ } else {
+ int length = allFiles.length;
+ int length2 = allFiles2.length;
+ System.arraycopy(allFiles, 0, (allFiles = new File[length + length2]), 0, length);
+ System.arraycopy(allFiles2, 0, allFiles, length, length2);
+ }
+ }
+ }
+ }
+ }
+ ApiDescription apiDescription = new ApiDescription(this.projectName);
+ TagScanner tagScanner = TagScanner.newScanner();
+ if (allFiles != null && allFiles.length != 0) {
+ Map<String, String> options = JavaCore.getOptions();
+ options.put(JavaCore.COMPILER_COMPLIANCE, resolveCompliance(manifestMap));
+ CompilationUnit unit = null;
+ for (int i = 0, max = allFiles.length; i < max; i++) {
+ unit = new CompilationUnit(allFiles[i].getAbsolutePath(), this.encoding);
+ if (this.debug) {
+ System.out.println("Unit name[" + i + "] : " + unit.getName()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ try {
+ tagScanner.scan(unit, apiDescription, classFileContainer, options, null);
+ } catch (CoreException e) {
+ ApiPlugin.log(e);
+ } finally {
+ try {
+ if (classFileContainer != null) {
+ classFileContainer.close();
+ }
+ } catch (CoreException e) {
+ // ignore
+ }
+ }
+ }
+ }
+ try {
+ ApiDescriptionXmlCreator xmlVisitor = new ApiDescriptionXmlCreator(this.projectName, this.projectName);
+ apiDescription.accept(xmlVisitor, null);
+ String xml = xmlVisitor.getXML();
+ Util.saveFile(apiDescriptionFile, xml);
+ } catch (CoreException | IOException e) {
+ ApiPlugin.log(e);
+ }
+ }
+
+ /**
+ * Returns if the given path ends with one of the collected API path names
+ *
+ * @param path
+ * @return true if the given path name ends with one of the collected API
+ * package names
+ */
+ boolean isApi(String path) {
+ for (String pkg : apiPackages) {
+ if (path.endsWith(pkg.replace('.', File.separatorChar))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Collects the names of the packages that are API for the bundle the API
+ * description is being created for
+ *
+ * @param manifestmap
+ * @return the names of the packages that are API for the bundle the API
+ * description is being created for
+ * @throws BundleException if parsing the manifest map to get API package
+ * names fail for some reason
+ */
+ private Set<String> collectApiPackageNames(Map<String, String> manifestmap) throws BundleException {
+ HashSet<String> set = new HashSet<>();
+ ManifestElement[] packages = ManifestElement.parseHeader(Constants.EXPORT_PACKAGE, manifestmap.get(Constants.EXPORT_PACKAGE));
+ if (packages != null) {
+ for (int i = 0; i < packages.length; i++) {
+ ManifestElement packageName = packages[i];
+ Enumeration<String> directiveKeys = packageName.getDirectiveKeys();
+ if (directiveKeys == null) {
+ set.add(packageName.getValue());
+ } else {
+ boolean include = true;
+ loop: for (; directiveKeys.hasMoreElements();) {
+ Object directive = directiveKeys.nextElement();
+ if ("x-internal".equals(directive)) { //$NON-NLS-1$
+ String value = packageName.getDirective((String) directive);
+ if (Boolean.parseBoolean(value)) {
+ include = false;
+ break loop;
+ }
+ }
+ if ("x-friends".equals(directive)) { //$NON-NLS-1$
+ include = false;
+ break loop;
+ }
+ }
+ if (include) {
+ set.add(packageName.getValue());
+ }
+ }
+ }
+ }
+ return set;
+ }
+
+ private IApiTypeContainer getContainer(String location) {
+ File f = new File(location);
+ if (!f.exists()) {
+ return null;
+ }
+ if (isZipJarFile(location)) {
+ return new ArchiveApiTypeContainer(null, location);
+ } else {
+ return new DirectoryApiTypeContainer(null, location);
+ }
+ }
+
+ /**
+ * Resolves the compiler compliance based on the BREE entry in the
+ * MANIFEST.MF file
+ *
+ * @param manifestmap
+ * @return The derived {@link JavaCore#COMPILER_COMPLIANCE} from the BREE in
+ * the manifest map, or {@link JavaCore#VERSION_1_3} if there is no
+ * BREE entry in the map or if the BREE entry does not directly map
+ * to one of {"1.3", "1.4", "1.5", "1.6", "1.7","1.8"}.
+ */
+ private String resolveCompliance(Map<String, String> manifestmap) {
+ if (manifestmap != null) {
+ String eename = manifestmap.get(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT);
+ if (eename != null) {
+ if ("J2SE-1.4".equals(eename)) { //$NON-NLS-1$
+ return JavaCore.VERSION_1_4;
+ }
+ if ("J2SE-1.5".equals(eename)) { //$NON-NLS-1$
+ return JavaCore.VERSION_1_5;
+ }
+ if ("JavaSE-1.6".equals(eename)) { //$NON-NLS-1$
+ return JavaCore.VERSION_1_6;
+ }
+ if ("JavaSE-1.7".equals(eename)) { //$NON-NLS-1$
+ return JavaCore.VERSION_1_7;
+ }
+ if ("JavaSE-1.8".equals(eename)) { //$NON-NLS-1$
+ return JavaCore.VERSION_1_8;
+ }
+ }
+ }
+ return JavaCore.VERSION_1_3;
+ }
+
+ /**
+ * Resolves if the '.project' file belongs to an API enabled project or not
+ *
+ * @param dotProjectFile
+ * @return true if the '.project' file is for an API enabled project, false
+ * otherwise
+ */
+ private boolean isAPIToolsNature(File dotProjectFile) {
+ if (!dotProjectFile.exists()) {
+ return false;
+ }
+ BufferedInputStream stream = null;
+ try {
+ stream = new BufferedInputStream(new FileInputStream(dotProjectFile));
+ String contents = new String(Util.getInputStreamAsCharArray(stream, -1, StandardCharsets.UTF_8));
+ return containsAPIToolsNature(contents);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if the given source contains an source extension point.
+ *
+ * @param pluginXMLContents the given file contents
+ * @return true if it contains a source extension point, false otherwise
+ */
+ private boolean containsAPIToolsNature(String pluginXMLContents) {
+ SAXParserFactory factory = null;
+ try {
+ factory = SAXParserFactory.newInstance();
+ } catch (FactoryConfigurationError e) {
+ return false;
+ }
+ SAXParser saxParser = null;
+ try {
+ saxParser = factory.newSAXParser();
+ } catch (ParserConfigurationException | SAXException e) {
+ // ignore
+ }
+
+ if (saxParser == null) {
+ return false;
+ }
+
+ // Parse
+ InputSource inputSource = new InputSource(new BufferedReader(new StringReader(pluginXMLContents)));
+ try {
+ APIToolsNatureDefaultHandler defaultHandler = new APIToolsNatureDefaultHandler();
+ saxParser.parse(inputSource, defaultHandler);
+ return defaultHandler.isAPIToolsNature();
+ } catch (SAXException | IOException e) {
+ // ignore
+ }
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiFileGenerationApplication.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiFileGenerationApplication.java
new file mode 100644
index 0000000..d11276b
--- /dev/null
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiFileGenerationApplication.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Red Hat Inc. and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * - Mickael Istria (Red Hat Inc.)
+ *******************************************************************************/
+package org.eclipse.pde.api.tools.internal;
+
+import org.eclipse.equinox.app.IApplication;
+import org.eclipse.equinox.app.IApplicationContext;
+import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
+
+public class ApiFileGenerationApplication implements IApplication {
+
+ @Override
+ public Object start(IApplicationContext context) throws Exception {
+ APIFileGenerator generator = new APIFileGenerator();
+ String[] args = (String[]) context.getArguments().get(IApplicationContext.APPLICATION_ARGS);
+ generator.projectName = find("projectName", args); //$NON-NLS-1$
+ generator.projectLocation = find("project", args); //$NON-NLS-1$
+ generator.binaryLocations = find("binary", args); //$NON-NLS-1$
+ generator.targetFolder = find("target", args); //$NON-NLS-1$
+ try {
+ generator.generateAPIFile();
+ return 0;
+ } catch (Exception ex) {
+ ApiPlugin.log(ex);
+ return 1;
+ }
+ }
+
+ private String find(String argName, String[] args) {
+ if (argName == null || argName.isEmpty()) {
+ return null;
+ }
+ String token = argName;
+ if (!argName.startsWith("-")) { //$NON-NLS-1$
+ token = "-" + argName; //$NON-NLS-1$
+ }
+ int tokenIndex = -1;
+ for (int i = 0; i < args.length && tokenIndex == -1; i++) {
+ if (token.equals(args[i])) {
+ tokenIndex = i + 1;
+ }
+ }
+ if (tokenIndex >= 0 && tokenIndex < args.length) {
+ return args[tokenIndex];
+ }
+ return null;
+ }
+
+ @Override
+ public void stop() {
+ // Nothing to do
+ }
+
+}
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/CoreMessages.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/CoreMessages.java
index 3614f62..829592b 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/CoreMessages.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/CoreMessages.java
@@ -43,6 +43,11 @@
public static String JavadocTagManager_method_no_overried;
public static String JavadocTagManager_method_no_reference;
public static String ProjectComponent_could_not_locate_model;
+ public static String api_generation_printArguments;
+ public static String api_generation_projectLocationNotADirectory;
+ public static String api_generation_targetFolderNotADirectory;
+ public static String api_generation_invalidBinaryLocation;
+
static {
// initialize resource bundle
NLS.initializeMessages(BUNDLE_NAME, CoreMessages.class);
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/coremessages.properties b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/coremessages.properties
index 3a0bae3..f0a992e 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/coremessages.properties
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/coremessages.properties
@@ -35,3 +35,11 @@
JavadocTagManager_method_no_overried=This method is not intended to be re-implemented or extended by clients.
JavadocTagManager_method_no_reference=This method is not intended to be referenced by clients.
ProjectComponent_could_not_locate_model=Could not locate the plug-in model base for project: {0}
+api_generation_printArguments=Missing arguments:\n\
+project name : {0}\n\
+project location : {1}\n\
+binary locations: {2}\n\
+target folder: {3}
+api_generation_projectLocationNotADirectory=The project argument {0} must be a directory
+api_generation_targetFolderNotADirectory=The target argument {0} must be a directory
+api_generation_invalidBinaryLocation=The location {0} specified in the binary argument does not exist
diff --git a/apitools/org.eclipse.pde.api.tools/src_ant/org/eclipse/pde/api/tools/internal/tasks/ApiFileGenerationTask.java b/apitools/org.eclipse.pde.api.tools/src_ant/org/eclipse/pde/api/tools/internal/tasks/ApiFileGenerationTask.java
index b68bc6e..2650106 100644
--- a/apitools/org.eclipse.pde.api.tools/src_ant/org/eclipse/pde/api/tools/internal/tasks/ApiFileGenerationTask.java
+++ b/apitools/org.eclipse.pde.api.tools/src_ant/org/eclipse/pde/api/tools/internal/tasks/ApiFileGenerationTask.java
@@ -13,128 +13,27 @@
*******************************************************************************/
package org.eclipse.pde.api.tools.internal.tasks;
-import java.io.BufferedInputStream;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-import javax.xml.parsers.FactoryConfigurationError;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.jdt.core.JavaCore;
-import org.eclipse.osgi.util.ManifestElement;
-import org.eclipse.osgi.util.NLS;
-import org.eclipse.pde.api.tools.internal.ApiDescription;
-import org.eclipse.pde.api.tools.internal.ApiDescriptionXmlCreator;
-import org.eclipse.pde.api.tools.internal.CompilationUnit;
-import org.eclipse.pde.api.tools.internal.IApiCoreConstants;
-import org.eclipse.pde.api.tools.internal.model.ArchiveApiTypeContainer;
-import org.eclipse.pde.api.tools.internal.model.CompositeApiTypeContainer;
-import org.eclipse.pde.api.tools.internal.model.DirectoryApiTypeContainer;
-import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
-import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeContainer;
-import org.eclipse.pde.api.tools.internal.provisional.scanner.TagScanner;
-import org.eclipse.pde.api.tools.internal.util.Util;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.Constants;
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-import org.xml.sax.helpers.DefaultHandler;
+import org.eclipse.pde.api.tools.internal.APIFileGenerator;
/**
* Ant task to generate the .api_description file during the Eclipse build.
*/
-public class ApiFileGenerationTask extends Task {
+public class ApiFileGenerationTask extends Task/* APIFileGenerator */ {
- static class APIToolsNatureDefaultHandler extends DefaultHandler {
- private static final String NATURE_ELEMENT_NAME = "nature"; //$NON-NLS-1$
- boolean isAPIToolsNature = false;
- boolean insideNature = false;
- StringBuilder buffer;
+ private APIFileGenerator apiFileGenerator;
- @Override
- public void error(SAXParseException e) throws SAXException {
- e.printStackTrace();
- }
-
- @Override
- public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
- if (this.isAPIToolsNature) {
- return;
- }
- this.insideNature = NATURE_ELEMENT_NAME.equals(name);
- if (this.insideNature) {
- this.buffer = new StringBuilder();
- }
- }
-
- @Override
- public void characters(char[] ch, int start, int length) throws SAXException {
- if (this.insideNature) {
- this.buffer.append(ch, start, length);
- }
- }
-
- @Override
- public void endElement(String uri, String localName, String name) throws SAXException {
- if (this.insideNature) {
- // check the contents of the characters
- String natureName = String.valueOf(this.buffer).trim();
- this.isAPIToolsNature = ApiPlugin.NATURE_ID.equals(natureName);
- }
- this.insideNature = false;
- }
-
- public boolean isAPIToolsNature() {
- return this.isAPIToolsNature;
- }
+ public ApiFileGenerationTask() {
+ this.apiFileGenerator = new APIFileGenerator();
}
- boolean debug;
-
- String projectName;
- String projectLocation;
- String targetFolder;
- String binaryLocations;
- String manifests;
- String sourceLocations;
- boolean allowNonApiProject = false;
- /**
- * The encoding to read the source files with
- *
- * @since 1.0.600
- */
- String encoding;
- Set<String> apiPackages = new HashSet<>(0);
-
/**
* Set the project name.
*
* @param projectName the given project name
*/
public void setProjectName(String projectName) {
- this.projectName = projectName;
+ apiFileGenerator.projectName = projectName;
}
/**
@@ -150,7 +49,7 @@
* @param projectLocation the given project location
*/
public void setProject(String projectLocation) {
- this.projectLocation = projectLocation;
+ apiFileGenerator.projectLocation = projectLocation;
}
/**
@@ -165,7 +64,7 @@
* @param targetLocation the given target location
*/
public void setTarget(String targetLocation) {
- this.targetFolder = targetLocation;
+ apiFileGenerator.targetFolder = targetLocation;
}
/**
@@ -182,7 +81,7 @@
* @param binaryLocations the given binary locations
*/
public void setBinary(String binaryLocations) {
- this.binaryLocations = binaryLocations;
+ apiFileGenerator.binaryLocations = binaryLocations;
}
/**
@@ -199,7 +98,7 @@
* @param allow
*/
public void setAllowNonApiProject(String allow) {
- this.allowNonApiProject = Boolean.parseBoolean(allow);
+ apiFileGenerator.allowNonApiProject = Boolean.parseBoolean(allow);
}
/**
@@ -209,7 +108,7 @@
* @since 1.0.600
*/
public void setEncoding(String encoding) {
- this.encoding = encoding;
+ apiFileGenerator.encoding = encoding;
}
/**
@@ -224,7 +123,7 @@
* @param debugValue the given debug value
*/
public void setDebug(String debugValue) {
- this.debug = Boolean.toString(true).equals(debugValue);
+ apiFileGenerator.debug = Boolean.toString(true).equals(debugValue);
}
/**
@@ -247,7 +146,7 @@
* @param manifests the given extra manifest files' locations
*/
public void setExtraManifests(String manifests) {
- this.manifests = manifests;
+ apiFileGenerator.manifests = manifests;
}
/**
@@ -263,381 +162,11 @@
* @param manifests the given extra source locations
*/
public void setExtraSourceLocations(String sourceLocations) {
- this.sourceLocations = sourceLocations;
+ apiFileGenerator.sourceLocations = sourceLocations;
}
- /**
- * Execute the ant task
- */
@Override
- public void execute() throws BuildException {
- if (this.binaryLocations == null || this.projectName == null || this.projectLocation == null || this.targetFolder == null) {
- StringWriter out = new StringWriter();
- PrintWriter writer = new PrintWriter(out);
- writer.println(NLS.bind(Messages.api_generation_printArguments, new String[] {
- this.projectName, this.projectLocation,
- this.binaryLocations, this.targetFolder }));
- writer.flush();
- writer.close();
- throw new BuildException(String.valueOf(out.getBuffer()));
- }
- if (this.debug) {
- System.out.println("Project name : " + this.projectName); //$NON-NLS-1$
- System.out.println("Encoding: " + this.encoding); //$NON-NLS-1$
- System.out.println("Project location : " + this.projectLocation); //$NON-NLS-1$
- System.out.println("Binary locations : " + this.binaryLocations); //$NON-NLS-1$
- System.out.println("Target folder : " + this.targetFolder); //$NON-NLS-1$
- if (this.manifests != null) {
- System.out.println("Extra manifest entries : " + this.manifests); //$NON-NLS-1$
- }
- if (this.sourceLocations != null) {
- System.out.println("Extra source locations entries : " + this.sourceLocations); //$NON-NLS-1$
- }
- }
- // collect all compilation units
- File root = new File(this.projectLocation);
- if (!root.exists() || !root.isDirectory()) {
- if (this.debug) {
- System.err.println("Must be a directory : " + this.projectLocation); //$NON-NLS-1$
- }
- throw new BuildException(NLS.bind(Messages.api_generation_projectLocationNotADirectory, this.projectLocation));
- }
- // check if the project contains the API tools nature
- File dotProjectFile = new File(root, ".project"); //$NON-NLS-1$
-
- if (!this.allowNonApiProject && !isAPIToolsNature(dotProjectFile)) {
- System.err.println("The project does not have an API Tools nature so a api_description file will not be generated"); //$NON-NLS-1$
- return;
- }
- // check if the .api_description file exists
- File targetProjectFolder = new File(this.targetFolder);
- if (!targetProjectFolder.exists()) {
- targetProjectFolder.mkdirs();
- } else if (!targetProjectFolder.isDirectory()) {
- if (this.debug) {
- System.err.println("Must be a directory : " + this.targetFolder); //$NON-NLS-1$
- }
- throw new BuildException(NLS.bind(Messages.api_generation_targetFolderNotADirectory, this.targetFolder));
- }
- File apiDescriptionFile = new File(targetProjectFolder, IApiCoreConstants.API_DESCRIPTION_XML_NAME);
- if (apiDescriptionFile.exists()) {
- // get rid of the existing one
- // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=414053
- if (this.debug) {
- System.out.println("Existing api description file deleted"); //$NON-NLS-1$
- }
- apiDescriptionFile.delete();
- }
- File[] allFiles = null;
- Map<String, String> manifestMap = null;
- IApiTypeContainer classFileContainer = null;
- if (!this.projectLocation.endsWith(Util.ORG_ECLIPSE_SWT)) {
- // create the directory class file container used to resolve
- // signatures during tag scanning
- String[] allBinaryLocations = this.binaryLocations.split(File.pathSeparator);
- List<IApiTypeContainer> allContainers = new ArrayList<>();
- IApiTypeContainer container = null;
- for (String allBinaryLocation : allBinaryLocations) {
- container = getContainer(allBinaryLocation);
- if (container == null) {
- throw new BuildException(NLS.bind(Messages.api_generation_invalidBinaryLocation, allBinaryLocation));
- }
- allContainers.add(container);
- }
- classFileContainer = new CompositeApiTypeContainer(null, allContainers);
- File manifestFile = null;
- File manifestDir = new File(root, "META-INF"); //$NON-NLS-1$
- if (manifestDir.exists() && manifestDir.isDirectory()) {
- manifestFile = new File(manifestDir, "MANIFEST.MF"); //$NON-NLS-1$
- }
- if (manifestFile != null && manifestFile.exists()) {
- try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(manifestFile));) {
- manifestMap = ManifestElement.parseBundleManifest(inputStream, null);
- this.apiPackages = collectApiPackageNames(manifestMap);
- } catch (IOException | BundleException e) {
- ApiPlugin.log(e);
- }
- }
- if (this.manifests != null) {
- String[] allManifestFiles = this.manifests.split(File.pathSeparator);
- for (String allManifestFile : allManifestFiles) {
- File currentManifest = new File(allManifestFile);
- Set<String> currentApiPackages = null;
- if (currentManifest.exists()) {
- BufferedInputStream inputStream = null;
- ZipFile zipFile = null;
- try {
- if (isZipJarFile(currentManifest.getName())) {
- zipFile = new ZipFile(currentManifest);
- final ZipEntry entry = zipFile.getEntry("META-INF/MANIFEST.MF"); //$NON-NLS-1$
- if (entry != null) {
- inputStream = new BufferedInputStream(zipFile.getInputStream(entry));
- }
- } else {
- inputStream = new BufferedInputStream(new FileInputStream(currentManifest));
- }
- if (inputStream != null) {
- manifestMap = ManifestElement.parseBundleManifest(inputStream, null);
- currentApiPackages = collectApiPackageNames(manifestMap);
- }
- } catch (IOException | BundleException e) {
- ApiPlugin.log(e);
- } finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (IOException e) {
- // ignore
- }
- }
- if (zipFile != null) {
- try {
- zipFile.close();
- } catch (IOException e) {
- // ignore
- }
- }
- }
- }
- if (currentApiPackages != null) {
- if (this.apiPackages == null) {
- this.apiPackages = currentApiPackages;
- } else {
- this.apiPackages.addAll(currentApiPackages);
- }
- }
- }
- }
- FileFilter fileFilter = path -> (path.isFile() && Util.isJavaFileName(path.getName()) && isApi(path.getParent())) || path.isDirectory();
- allFiles = Util.getAllFiles(root, fileFilter);
- if (this.sourceLocations != null) {
- String[] allSourceLocations = this.sourceLocations.split(File.pathSeparator);
- for (String currentSourceLocation : allSourceLocations) {
- File[] allFiles2 = Util.getAllFiles(new File(currentSourceLocation), fileFilter);
- if (allFiles2 != null) {
- if (allFiles == null) {
- allFiles = allFiles2;
- } else {
- int length = allFiles.length;
- int length2 = allFiles2.length;
- System.arraycopy(allFiles, 0, (allFiles = new File[length + length2]), 0, length);
- System.arraycopy(allFiles2, 0, allFiles, length, length2);
- }
- }
- }
- }
- }
- ApiDescription apiDescription = new ApiDescription(this.projectName);
- TagScanner tagScanner = TagScanner.newScanner();
- if (allFiles != null && allFiles.length != 0) {
- Map<String, String> options = JavaCore.getOptions();
- options.put(JavaCore.COMPILER_COMPLIANCE, resolveCompliance(manifestMap));
- CompilationUnit unit = null;
- for (int i = 0, max = allFiles.length; i < max; i++) {
- unit = new CompilationUnit(allFiles[i].getAbsolutePath(), this.encoding);
- if (this.debug) {
- System.out.println("Unit name[" + i + "] : " + unit.getName()); //$NON-NLS-1$ //$NON-NLS-2$
- }
- try {
- tagScanner.scan(unit, apiDescription, classFileContainer, options, null);
- } catch (CoreException e) {
- ApiPlugin.log(e);
- } finally {
- try {
- if (classFileContainer != null) {
- classFileContainer.close();
- }
- } catch (CoreException e) {
- // ignore
- }
- }
- }
- }
- try {
- ApiDescriptionXmlCreator xmlVisitor = new ApiDescriptionXmlCreator(this.projectName, this.projectName);
- apiDescription.accept(xmlVisitor, null);
- String xml = xmlVisitor.getXML();
- Util.saveFile(apiDescriptionFile, xml);
- } catch (CoreException | IOException e) {
- ApiPlugin.log(e);
- }
- }
-
- /**
- * Returns if the given path ends with one of the collected API path names
- *
- * @param path
- * @return true if the given path name ends with one of the collected API
- * package names
- */
- boolean isApi(String path) {
- for (String pkg : apiPackages) {
- if (path.endsWith(pkg.replace('.', File.separatorChar))) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Collects the names of the packages that are API for the bundle the API
- * description is being created for
- *
- * @param manifestmap
- * @return the names of the packages that are API for the bundle the API
- * description is being created for
- * @throws BundleException if parsing the manifest map to get API package
- * names fail for some reason
- */
- private Set<String> collectApiPackageNames(Map<String, String> manifestmap) throws BundleException {
- HashSet<String> set = new HashSet<>();
- ManifestElement[] packages = ManifestElement.parseHeader(Constants.EXPORT_PACKAGE, manifestmap.get(Constants.EXPORT_PACKAGE));
- if (packages != null) {
- for (int i = 0; i < packages.length; i++) {
- ManifestElement packageName = packages[i];
- Enumeration<String> directiveKeys = packageName.getDirectiveKeys();
- if (directiveKeys == null) {
- set.add(packageName.getValue());
- } else {
- boolean include = true;
- loop: for (; directiveKeys.hasMoreElements();) {
- Object directive = directiveKeys.nextElement();
- if ("x-internal".equals(directive)) { //$NON-NLS-1$
- String value = packageName.getDirective((String) directive);
- if (Boolean.parseBoolean(value)) {
- include = false;
- break loop;
- }
- }
- if ("x-friends".equals(directive)) { //$NON-NLS-1$
- include = false;
- break loop;
- }
- }
- if (include) {
- set.add(packageName.getValue());
- }
- }
- }
- }
- return set;
- }
-
- private IApiTypeContainer getContainer(String location) {
- File f = new File(location);
- if (!f.exists()) {
- return null;
- }
- if (isZipJarFile(location)) {
- return new ArchiveApiTypeContainer(null, location);
- } else {
- return new DirectoryApiTypeContainer(null, location);
- }
- }
-
- /**
- * Resolves the compiler compliance based on the BREE entry in the
- * MANIFEST.MF file
- *
- * @param manifestmap
- * @return The derived {@link JavaCore#COMPILER_COMPLIANCE} from the BREE in
- * the manifest map, or {@link JavaCore#VERSION_1_3} if there is no
- * BREE entry in the map or if the BREE entry does not directly map
- * to one of {"1.3", "1.4", "1.5", "1.6", "1.7","1.8"}.
- */
- private String resolveCompliance(Map<String, String> manifestmap) {
- if (manifestmap != null) {
- String eename = manifestmap.get(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT);
- if (eename != null) {
- if ("J2SE-1.4".equals(eename)) { //$NON-NLS-1$
- return JavaCore.VERSION_1_4;
- }
- if ("J2SE-1.5".equals(eename)) { //$NON-NLS-1$
- return JavaCore.VERSION_1_5;
- }
- if ("JavaSE-1.6".equals(eename)) { //$NON-NLS-1$
- return JavaCore.VERSION_1_6;
- }
- if ("JavaSE-1.7".equals(eename)) { //$NON-NLS-1$
- return JavaCore.VERSION_1_7;
- }
- if ("JavaSE-1.8".equals(eename)) { //$NON-NLS-1$
- return JavaCore.VERSION_1_8;
- }
- }
- }
- return JavaCore.VERSION_1_3;
- }
-
- /**
- * Resolves if the '.project' file belongs to an API enabled project or not
- *
- * @param dotProjectFile
- * @return true if the '.project' file is for an API enabled project, false
- * otherwise
- */
- private boolean isAPIToolsNature(File dotProjectFile) {
- if (!dotProjectFile.exists()) {
- return false;
- }
- BufferedInputStream stream = null;
- try {
- stream = new BufferedInputStream(new FileInputStream(dotProjectFile));
- String contents = new String(Util.getInputStreamAsCharArray(stream, -1, StandardCharsets.UTF_8));
- return containsAPIToolsNature(contents);
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (stream != null) {
- try {
- stream.close();
- } catch (IOException e) {
- // ignore
- }
- }
- }
- return false;
- }
-
- private static boolean isZipJarFile(String fileName) {
- String normalizedFileName = fileName.toLowerCase();
- return normalizedFileName.endsWith(".zip") //$NON-NLS-1$
- || normalizedFileName.endsWith(".jar"); //$NON-NLS-1$
- }
-
- /**
- * Check if the given source contains an source extension point.
- *
- * @param pluginXMLContents the given file contents
- * @return true if it contains a source extension point, false otherwise
- */
- private boolean containsAPIToolsNature(String pluginXMLContents) {
- SAXParserFactory factory = null;
- try {
- factory = SAXParserFactory.newInstance();
- } catch (FactoryConfigurationError e) {
- return false;
- }
- SAXParser saxParser = null;
- try {
- saxParser = factory.newSAXParser();
- } catch (ParserConfigurationException | SAXException e) {
- // ignore
- }
-
- if (saxParser == null) {
- return false;
- }
-
- // Parse
- InputSource inputSource = new InputSource(new BufferedReader(new StringReader(pluginXMLContents)));
- try {
- APIToolsNatureDefaultHandler defaultHandler = new APIToolsNatureDefaultHandler();
- saxParser.parse(inputSource, defaultHandler);
- return defaultHandler.isAPIToolsNature();
- } catch (SAXException | IOException e) {
- // ignore
- }
- return false;
+ public void execute() {
+ apiFileGenerator.generateAPIFile();
}
}
diff --git a/apitools/org.eclipse.pde.api.tools/src_ant/org/eclipse/pde/api/tools/internal/tasks/Messages.java b/apitools/org.eclipse.pde.api.tools/src_ant/org/eclipse/pde/api/tools/internal/tasks/Messages.java
index 9c055d3..042bd34 100644
--- a/apitools/org.eclipse.pde.api.tools/src_ant/org/eclipse/pde/api/tools/internal/tasks/Messages.java
+++ b/apitools/org.eclipse.pde.api.tools/src_ant/org/eclipse/pde/api/tools/internal/tasks/Messages.java
@@ -86,11 +86,6 @@
public static String couldNotUntar;
public static String reportLocationHasToBeAFile;
- public static String api_generation_printArguments;
- public static String api_generation_projectLocationNotADirectory;
- public static String api_generation_targetFolderNotADirectory;
- public static String api_generation_invalidBinaryLocation;
-
public static String ApiMigrationTask_missing_scan_location;
public static String ApiMigrationTask_scan_location_not_dir;
public static String ApiMigrationTask_scan_location_not_exist;
diff --git a/apitools/org.eclipse.pde.api.tools/src_ant/org/eclipse/pde/api/tools/internal/tasks/messages.properties b/apitools/org.eclipse.pde.api.tools/src_ant/org/eclipse/pde/api/tools/internal/tasks/messages.properties
index 35ef22b..215640e 100644
--- a/apitools/org.eclipse.pde.api.tools/src_ant/org/eclipse/pde/api/tools/internal/tasks/messages.properties
+++ b/apitools/org.eclipse.pde.api.tools/src_ant/org/eclipse/pde/api/tools/internal/tasks/messages.properties
@@ -15,14 +15,6 @@
reference location : {0}\n\
current baseline location : {1}\n\
report location : {2}
-api_generation_printArguments=Missing arguments:\n\
-project name : {0}\n\
-project location : {1}\n\
-binary locations: {2}\n\
-target folder: {3}
-api_generation_projectLocationNotADirectory=The project argument {0} must be a directory
-api_generation_targetFolderNotADirectory=The target argument {0} must be a directory
-api_generation_invalidBinaryLocation=The location {0} specified in the binary argument does not exist
ApiMigrationTask_missing_scan_location=Missing Arguments: scanLocation: {0}
ApiMigrationTask_scan_location_not_dir=Invalid Arguments: scanLocation must be a directory: {0}
ApiMigrationTask_scan_location_not_exist=Invalid Arguments: scanLocation does not exist: {0}