Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian W. Damus2016-04-02 08:46:08 -0400
committerChristian W. Damus2016-04-02 16:46:42 -0400
commit4dc7c0eece9695c48ca2a4997657e9a265fcb63e (patch)
treea879c8f5048922207fd40b86dc096832539edb8c
parent91bb8fc92e41b1d98316f2a2cf1ae7d5daeafe99 (diff)
downloadorg.eclipse.papyrus-4dc7c0eece9695c48ca2a4997657e9a265fcb63e.tar.gz
org.eclipse.papyrus-4dc7c0eece9695c48ca2a4997657e9a265fcb63e.tar.xz
org.eclipse.papyrus-4dc7c0eece9695c48ca2a4997657e9a265fcb63e.zip
Bug 440910: [API] API Evolution Report generator in the buildbugs/440910-api-tools
https://bugs.eclipse.org/bugs/show_bug.cgi?id=440910 Add an APIReports pseudo-test-suite class that, when activated by the required system properties, generates a report of the API changes since the specified baseline release. Enable generation of the API delta report in the build. Change-Id: Ica2d8633c1a68d1f23612f778965769585affbd3
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/.classpath6
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/.gitignore1
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/META-INF/MANIFEST.MF6
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/build.properties3
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/pom.xml68
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/api.css30
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/addition.gifbin0 -> 316 bytes
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/annotation.gifbin0 -> 347 bytes
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/change.gifbin0 -> 312 bytes
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/class.gifbin0 -> 585 bytes
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/empty.gifbin0 -> 49 bytes
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/enum.gifbin0 -> 360 bytes
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/interface.gifbin0 -> 573 bytes
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/minus.gifbin0 -> 135 bytes
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/plugin.gifbin0 -> 327 bytes
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/plus.gifbin0 -> 143 bytes
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/removal.gifbin0 -> 350 bytes
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/report.gifbin0 -> 610 bytes
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/APIReports.java95
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/Activator.java22
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/BundlesTests.java3
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/API2HTML.java634
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/APIReportGenerator.java392
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/ReportFixture.java120
-rw-r--r--tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/excludes.txt51
-rw-r--r--tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/src/org/eclipse/papyrus/junit/utils/PrintingProgressMonitor.java137
26 files changed, 1552 insertions, 16 deletions
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/.classpath b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/.classpath
index eca7bdba8f0..7c3a2a77507 100644
--- a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/.classpath
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/.classpath
@@ -1,7 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
- <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins">
+ <accessrules>
+ <accessrule kind="accessible" pattern="org/eclipse/pde/api/tools/internal/provisional/**"/>
+ </accessrules>
+ </classpathentry>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/.gitignore b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/.gitignore
new file mode 100644
index 00000000000..b7c02c4cfac
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/.gitignore
@@ -0,0 +1 @@
+/apireports/
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/META-INF/MANIFEST.MF b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/META-INF/MANIFEST.MF
index be953fe545f..a32432a4115 100644
--- a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/META-INF/MANIFEST.MF
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/META-INF/MANIFEST.MF
@@ -8,7 +8,11 @@ Require-Bundle: org.eclipse.ui;bundle-version="[3.107.0,4.0.0)",
org.eclipse.update.configurator;bundle-version="[3.3.0,4.0.0)",
org.junit;bundle-version="[4.12.0,5.0.0)",
org.eclipse.papyrus.junit.framework;bundle-version="[1.2.0,2.0.0)";visibility:=reexport,
- org.eclipse.papyrus.junit.utils;bundle-version="[2.0.0,3.0.0)"
+ org.eclipse.papyrus.junit.utils;bundle-version="[2.0.0,3.0.0)",
+ org.eclipse.core.resources;bundle-version="[3.11.0,4.0.0)",
+ org.eclipse.pde.api.tools;bundle-version="[1.0.1,2.0.0)",
+ org.eclipse.jdt.core;bundle-version="[3.11.0,4.0.0)",
+ org.eclipse.ui.ide;bundle-version="[3.11.0,4.0.0)"
Export-Package: org.eclipse.papyrus.bundles.tests
Bundle-Vendor: %Bundle-Vendor
Bundle-ActivationPolicy: lazy
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/build.properties b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/build.properties
index dfc79e0933b..62808b06f2b 100644
--- a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/build.properties
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/build.properties
@@ -4,5 +4,6 @@ bin.includes = META-INF/,\
.,\
OSGI-INF/,\
about.html,\
- build.properties
+ build.properties,\
+ resources/
src.includes = about.html
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/pom.xml b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/pom.xml
index c9f2df2b099..a841518e6ec 100644
--- a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/pom.xml
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/pom.xml
@@ -11,5 +11,71 @@
<groupId>org.eclipse.papyrus</groupId>
<artifactId>org.eclipse.papyrus.bundles.tests</artifactId>
<version>1.2.0-SNAPSHOT</version>
- <packaging>eclipse-plugin</packaging>
+ <packaging>eclipse-test-plugin</packaging>
+
+ <build>
+ <plugins>
+ <plugin>
+ <!-- By default, don't do tests -->
+ <groupId>org.eclipse.tycho</groupId>
+ <artifactId>tycho-surefire-plugin</artifactId>
+ <version>${tycho-version}</version>
+ <configuration>
+ <skipTests>true</skipTests>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <!-- Profile for API Evolution Report generation. -->
+ <profile>
+ <id>api-report-gen</id>
+ <activation>
+ <property>
+ <name>apireport.baseline</name>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.eclipse.tycho</groupId>
+ <artifactId>tycho-surefire-plugin</artifactId>
+ <version>${tycho-version}</version>
+ <configuration>
+ <failIfNoTests>false</failIfNoTests>
+ <useUIHarness>false</useUIHarness>
+ <testFailureIgnore>true</testFailureIgnore>
+ <argLine>-Dapireport.baseline=${apireport.baseline} -Dapireport.outputdir=${apireport.outputdir} -Xms512m -Xmx2048m</argLine>
+ <appArgLine>-testConfig=CI_TESTS_CONFIG</appArgLine>
+ <product>org.eclipse.sdk.ide</product>
+ <skipTests>false</skipTests>
+ <testClass>org.eclipse.papyrus.bundles.tests.APIReports</testClass>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.eclipse.tycho</groupId>
+ <artifactId>target-platform-configuration</artifactId>
+ <version>${tycho-version}</version>
+ <configuration>
+ <dependency-resolution>
+ <extraRequirements>
+ <requirement>
+ <type>p2-installable-unit</type>
+ <id>org.eclipse.sdk.feature.group</id>
+ <versionRange>0.0.0</versionRange>
+ </requirement>
+ <requirement>
+ <type>eclipse-feature</type>
+ <id>org.eclipse.papyrus.sdk.feature</id>
+ <versionRange>0.0.0</versionRange>
+ </requirement>
+ </extraRequirements>
+ </dependency-resolution>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
</project>
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/api.css b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/api.css
new file mode 100644
index 00000000000..92b5fc19e59
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/api.css
@@ -0,0 +1,30 @@
+body, h1, h2, h3, h4, h5, h6, p, table, td, caption, th, ul, ol, dl, li, dd, dt {
+ font-family: Arial, Helvetica, sans-serif;
+ color: #000000;
+}
+
+a:link {
+ text-decoration: none;
+ border: none;
+ color: #000000;
+}
+
+a:visited {
+ color: #000000;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+.category {
+ font-size: 16px;
+ font-weight: bold;
+ margin-top: 20px;
+ margin-bottom: 10px;
+}
+
+.component, .type, .change {
+ margin-top: 2px;
+ margin-bottom: 2px;
+}
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/addition.gif b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/addition.gif
new file mode 100644
index 00000000000..5d9e47ce206
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/addition.gif
Binary files differ
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/annotation.gif b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/annotation.gif
new file mode 100644
index 00000000000..4006917b5a2
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/annotation.gif
Binary files differ
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/change.gif b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/change.gif
new file mode 100644
index 00000000000..553311ff9fe
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/change.gif
Binary files differ
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/class.gif b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/class.gif
new file mode 100644
index 00000000000..9430b0fcee4
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/class.gif
Binary files differ
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/empty.gif b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/empty.gif
new file mode 100644
index 00000000000..571f9583ac6
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/empty.gif
Binary files differ
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/enum.gif b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/enum.gif
new file mode 100644
index 00000000000..ae416a5a394
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/enum.gif
Binary files differ
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/interface.gif b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/interface.gif
new file mode 100644
index 00000000000..3096f8a14e3
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/interface.gif
Binary files differ
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/minus.gif b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/minus.gif
new file mode 100644
index 00000000000..f7ac8ede9ed
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/minus.gif
Binary files differ
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/plugin.gif b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/plugin.gif
new file mode 100644
index 00000000000..ce7243c0164
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/plugin.gif
Binary files differ
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/plus.gif b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/plus.gif
new file mode 100644
index 00000000000..36790eac46d
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/plus.gif
Binary files differ
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/removal.gif b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/removal.gif
new file mode 100644
index 00000000000..afb251a83e9
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/removal.gif
Binary files differ
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/report.gif b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/report.gif
new file mode 100644
index 00000000000..911e362e73a
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/resources/apireports/html/images/report.gif
Binary files differ
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/APIReports.java b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/APIReports.java
new file mode 100644
index 00000000000..74b1a85d223
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/APIReports.java
@@ -0,0 +1,95 @@
+/*****************************************************************************
+ * Copyright (c) 2016 Christian W. Damus 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.bundles.tests;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assume.assumeThat;
+
+import java.io.File;
+import java.util.Calendar;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.papyrus.bundles.tests.apireport.API2HTML;
+import org.eclipse.papyrus.bundles.tests.apireport.APIReportGenerator;
+import org.eclipse.papyrus.bundles.tests.apireport.ReportFixture;
+import org.eclipse.papyrus.junit.framework.classification.tests.AbstractPapyrusTest;
+import org.eclipse.papyrus.junit.utils.PrintingProgressMonitor;
+import org.junit.Test;
+
+/**
+ * Pseudo-test cases that generate API reports.
+ *
+ * @since 1.2
+ */
+public class APIReports extends AbstractPapyrusTest {
+
+ /**
+ * System property name for the location of the API Baseline for calculation of
+ * the API delta report. The value must be an absolute path in the local filesystem.
+ * If this property is not specified, the {@link #apiDeltaReport()} pseudo-test is skipped.
+ */
+ public static final String APIREPORT_BASELINE_PROPERTY = "apireport.baseline"; //$NON-NLS-1$
+
+ /**
+ * System property name for the location of the API delta report to generate.
+ * The value must be an absolute path in the local filesystem. If this property
+ * is not specified, the report will be generated in the current working directory.
+ */
+ public static final String APIREPORT_OUTPUT_DIR_PROPERTY = "apireport.outputdir"; //$NON-NLS-1$
+
+ /**
+ * System property name for a boolean indicating whether to log verbose progress
+ * of the API report generation to stdout.
+ */
+ public static final String APIREPORT_VERBOSE = "apireport.verbose"; //$NON-NLS-1$
+
+ /**
+ * Constructor.
+ *
+ */
+ public APIReports() {
+ super();
+ }
+
+ /**
+ * Run the API change analysis report, if the baseline is provided by the
+ * {@linkplain #APIREPORT_BASELINE_PROPERTY system property}.
+ */
+ @Test
+ public void apiDeltaReport() throws Exception {
+ String baseline = System.getProperty(APIREPORT_BASELINE_PROPERTY, "");
+ assumeThat("No API baseline specified via -D" + APIREPORT_BASELINE_PROPERTY, baseline, not(""));
+
+ File baselineLocation = new File(baseline);
+ IPath outputDir = new Path(System.getProperty(APIREPORT_OUTPUT_DIR_PROPERTY, System.getProperty("user.dir")));
+
+ // Generate the report XML
+ ReportFixture fixture = new ReportFixture(outputDir);
+
+ PrintingProgressMonitor progress = new PrintingProgressMonitor();
+ if (!Boolean.getBoolean(APIREPORT_VERBOSE)) {
+ progress = progress.filter("^\\s+add");
+ }
+ new APIReportGenerator(baselineLocation, fixture.getXMLReportFile()).generate(progress);
+
+ // And the HTML from that
+ Calendar today = Calendar.getInstance();
+ String qualifier = String.format("v%04d%02d%02d-%02d%02d%02d",
+ today.get(Calendar.YEAR), today.get(Calendar.MONTH) + 1, today.get(Calendar.DATE),
+ today.get(Calendar.HOUR_OF_DAY), today.get(Calendar.MINUTE), today.get(Calendar.SECOND));
+ new API2HTML(fixture.getXMLReportFile(), qualifier).generate(fixture.getHTMLReportFile());
+ }
+
+}
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/Activator.java b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/Activator.java
index ca011465622..ab3930bc6ec 100644
--- a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/Activator.java
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/Activator.java
@@ -1,7 +1,9 @@
package org.eclipse.papyrus.bundles.tests;
+import org.eclipse.pde.core.target.ITargetPlatformService;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
/**
* The activator class controls the plug-in life cycle
@@ -14,30 +16,27 @@ public class Activator extends AbstractUIPlugin {
// The shared instance
private static Activator plugin;
+ private ITargetPlatformService tpService;
+
/**
* The constructor
*/
public Activator() {
+ super();
}
- /*
- * (non-Javadoc)
- *
- * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
- */
@Override
public void start(BundleContext context) throws Exception {
super.start(context);
plugin = this;
+
+ ServiceReference<? extends ITargetPlatformService> ref = context.getServiceReference(ITargetPlatformService.class);
+ tpService = (ref == null) ? null : context.getService(ref);
}
- /*
- * (non-Javadoc)
- *
- * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
- */
@Override
public void stop(BundleContext context) throws Exception {
+ tpService = null;
plugin = null;
super.stop(context);
}
@@ -51,4 +50,7 @@ public class Activator extends AbstractUIPlugin {
return plugin;
}
+ public ITargetPlatformService getTargetPlatformService() {
+ return tpService;
+ }
}
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/BundlesTests.java b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/BundlesTests.java
index ca6eb65e089..83eda7ca9bd 100644
--- a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/BundlesTests.java
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/BundlesTests.java
@@ -9,7 +9,7 @@
* Contributors:
* Vincent Lorenzo (CEA LIST) Vincent.Lorenzo@cea.fr - Initial API and implementation
* Christian W. Damus - Skip the feature-version test when running in development mode
- * Christian W. Damus - bugs 433206, 485220
+ * Christian W. Damus - bugs 433206, 485220, 440910
*
*****************************************************************************/
package org.eclipse.papyrus.bundles.tests;
@@ -438,5 +438,4 @@ public class BundlesTests extends AbstractPapyrusTest {
// Do not fail on warnings
// Assert.assertTrue(nbWarning + "warning!" + warningMessage, nbWarning == 0);//$NON-NLS-1$
}
-
}
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/API2HTML.java b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/API2HTML.java
new file mode 100644
index 00000000000..840a4b7aa82
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/API2HTML.java
@@ -0,0 +1,634 @@
+/*
+ * Copyright (c) 2012, 2015 Eike Stepper (Berlin, Germany) 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:
+ * Eike Stepper - initial API and implementation
+ * Christian W. Damus - adapt for Papyrus bundle tests (bug 440910)
+ */
+package org.eclipse.papyrus.bundles.tests.apireport;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.jar.Manifest;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jdt.core.Flags;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.search.IJavaSearchConstants;
+import org.eclipse.jdt.core.search.IJavaSearchScope;
+import org.eclipse.jdt.core.search.SearchEngine;
+import org.eclipse.jdt.core.search.SearchPattern;
+import org.eclipse.jdt.core.search.TypeNameRequestor;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * A SAX XML handler that transforms the API report XML created by the
+ * {@link APIReportGenerator} to an easy-to-read collapsible tree
+ * presentation in HTML.
+ */
+public class API2HTML extends DefaultHandler {
+ private static final String ANNOTATION = "annotation";
+
+ private static final String ENUM = "enum";
+
+ private static final String INTERFACE = "interface";
+
+ private static final String CLASS = "class";
+
+ private static final String PLUS = "plus.gif";
+
+ private static final String MINUS = "minus.gif";
+
+ private static final Pattern VERSION_CHANGED = Pattern.compile(
+ "The ([^ ]+) version has been changed for the api component ([^ ]+) \\(from version ([^ ]+) to ([^ ]+)\\)");
+
+ private int lastNodeID;
+
+ private Category breaking = new Category("Breaking API Changes");
+
+ private Category compatible = new Category("Compatible API Changes");
+
+ private Category reexports = new Category("Re-Exported API Changes");
+
+ private String buildQualifier;
+
+ public API2HTML(File inputXML, String buildQualifier) throws Exception {
+ this.buildQualifier = buildQualifier;
+
+ try (InputStream in = new FileInputStream(inputXML)) {
+ SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+ parser.parse(in, this);
+ }
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+ if ("delta".equalsIgnoreCase(qName)) {
+ try {
+ String componentVersion = null;
+ String componentChange = null;
+
+ String componentID = attributes.getValue("componentId");
+ String typeName = attributes.getValue("type_name");
+ String elementType = attributes.getValue("element_type");
+ String kind = attributes.getValue("kind");
+ String message = attributes.getValue("message");
+
+ if (componentID == null || componentID.length() == 0) {
+ if (message.startsWith("The API component ")) {
+ componentID = message.substring("The API component ".length());
+ componentID = componentID.substring(0, componentID.indexOf(' '));
+
+ if (message.endsWith("added")) {
+ componentChange = "The plugin has been added";
+ componentVersion = readComponentVersion(componentID);
+ } else if (message.endsWith("removed")) {
+ componentChange = "The plugin has been removed";
+ } else {
+ System.out.println("No componentID: " + message);
+ return;
+ }
+ }
+ }
+
+ if (componentChange == null && (typeName == null || typeName.length() == 0)) {
+ Matcher matcher = VERSION_CHANGED.matcher(message);
+ if (matcher.matches()) {
+ componentChange = "The " + matcher.group(1) + " version has been changed from " + matcher.group(3) + " to "
+ + matcher.group(4);
+ }
+ }
+
+ int pos = componentID.indexOf('(');
+ if (pos != -1) {
+ componentVersion = componentID.substring(pos + 1, componentID.length() - 1);
+ componentID = componentID.substring(0, pos);
+ }
+
+ message = remove(message, typeName + ".");
+ message = remove(message, " in an interface that is tagged with '@noimplement'");
+ message = remove(message, " for interface " + typeName);
+ message = remove(message, " for class " + typeName);
+ if (!message.contains("modifier has been")) {
+ message = remove(message, " to " + typeName);
+ }
+
+ if (message != null && message.startsWith("The deprecation modifiers has")) {
+ message = "The deprecation modifier has" + message.substring("The deprecation modifiers has".length());
+ }
+
+ Category category;
+ if (message.startsWith("The re-exported type")) {
+ componentChange = message;
+ category = reexports;
+ } else {
+ category = "true".equals(attributes.getValue("compatible")) ? compatible : breaking;
+ }
+
+ Map<String, Component> components = category.getComponents();
+
+ Component component = components.get(componentID);
+ if (component == null) {
+ component = new Component(componentID);
+ components.put(componentID, component);
+ }
+
+ if (componentVersion != null) {
+ component.setComponentVersion(componentVersion);
+ }
+
+ if (componentChange != null) {
+ component.getChanges().add(new Change(componentChange, kind));
+ } else {
+ if (typeName == null || typeName.length() == 0) {
+ System.out.println("No typeName: " + message);
+ return;
+ }
+
+ Type type = component.getTypes().get(typeName);
+ if (type == null) {
+ type = new Type(component, typeName);
+ component.getTypes().put(typeName, type);
+ }
+
+ type.setElementType(elementType);
+ type.getChanges().add(new Change(message, kind));
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ private String readComponentVersion(String componentID) throws Exception {
+ String result = null;
+
+ IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(componentID);
+ if ((project != null) && project.isAccessible()) {
+ IFolder metaInf = project.getFolder("META-INF");
+ if ((metaInf != null) && metaInf.isAccessible()) {
+ IFile manifestFile = metaInf.getFile("MANIFEST.MF");
+ if ((manifestFile != null) && manifestFile.isAccessible()) {
+ try (InputStream in = manifestFile.getContents()) {
+ Manifest manifest = new Manifest(in);
+ java.util.jar.Attributes attributes = manifest.getMainAttributes();
+ result = attributes.getValue("Bundle-Version");
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public void generate(File htmlFile) throws Exception {
+ PrintStream out = new PrintStream(htmlFile);
+
+ try {
+ out.println("<!DOCTYPE HTML>");
+ out.println("<html>");
+ out.println("<head>");
+ out.println("<title>API Evolution Report for Papyrus " + buildQualifier + "</title>");
+ out.println("<link rel=stylesheet type='text/css' href='api.css'>");
+ out.println("<base href='images/'>");
+ out.println("<script type='text/javascript'>");
+ out.println(" function toggle(id)");
+ out.println(" {");
+ out.println(" e = document.getElementById(id);");
+ out.println(" e.style.display = (e.style.display == '' ? 'none' : '');");
+ out.println(" img = document.getElementById('img_' + id);");
+ out.println(" img.src = (e.style.display == 'none' ? '" + PLUS + "' : '" + MINUS + "');");
+ out.println(" }");
+ out.println("</script>");
+ out.println("</head>");
+ out.println("<body>");
+ out.println("<h1>API Evolution Report for Papyrus " + buildQualifier + "</h1>");
+
+ breaking.generate(out, "");
+ out.println("<p/>");
+ compatible.generate(out, "");
+ out.println("<p/>");
+ reexports.generate(out, "");
+
+ out.println("</body>");
+ out.println("</html>");
+ } finally {
+ out.close();
+ }
+ }
+
+ private List<String> sortedKeys(Map<String, ?> map) {
+ List<String> list = new ArrayList<String>(map.keySet());
+ Collections.sort(list);
+ return list;
+ }
+
+ private String remove(String string, String remove) {
+ if (string != null) {
+ int pos = string.indexOf(remove);
+ if (pos != -1) {
+ string = string.substring(0, pos) + string.substring(pos + remove.length());
+ }
+ }
+
+ return string;
+ }
+
+ public static void main(String[] args) throws Exception {
+ new API2HTML(new File(args[0]), args[2]).generate(new File(args[1]));
+ }
+
+ /**
+ * @author Eike Stepper
+ */
+ public static final class Version implements Comparable<Version> {
+ private static final String SEPARATOR = ".";
+
+ private int major = 0;
+
+ private int minor = 0;
+
+ private int micro = 0;
+
+ public Version(String version) {
+ StringTokenizer st = new StringTokenizer(version, SEPARATOR, true);
+ major = Integer.parseInt(st.nextToken());
+
+ if (st.hasMoreTokens()) {
+ st.nextToken();
+ minor = Integer.parseInt(st.nextToken());
+
+ if (st.hasMoreTokens()) {
+ st.nextToken();
+ micro = Integer.parseInt(st.nextToken());
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return major + SEPARATOR + minor + SEPARATOR + micro;
+ }
+
+ @Override
+ public int compareTo(Version o) {
+ if (o == this) {
+ return 0;
+ }
+
+ int result = major - o.major;
+ if (result != 0) {
+ return result;
+ }
+
+ result = minor - o.minor;
+ if (result != 0) {
+ return result;
+ }
+
+ result = micro - o.micro;
+ if (result != 0) {
+ return result;
+ }
+
+ return 0;
+ }
+ }
+
+ /**
+ * @author Eike Stepper
+ */
+ protected abstract class AbstractNode {
+ private final String text;
+
+ public AbstractNode(String text) {
+ this.text = text;
+ }
+
+ public String getText() {
+ return text.replaceAll("<", "&lt;").replaceAll("\"", "&quot;");
+ }
+
+ public String getIcon() {
+ return "";
+ }
+
+ public void generate(PrintStream out, String indent) throws Exception {
+ String href = getHref();
+ out.print(indent + getIcon() + " " + (href != null ? "<a href='" + href + "' target='_blank'>" : "") + getText()
+ + (href != null ? "</a>" : ""));
+ }
+
+ protected String getHref() throws Exception {
+ return null;
+ }
+ }
+
+ /**
+ * @author Eike Stepper
+ */
+ protected abstract class AbstractTreeNode extends AbstractNode {
+ private int id;
+
+ public AbstractTreeNode(String text) {
+ super(text);
+ id = ++lastNodeID;
+ }
+
+ @Override
+ public void generate(PrintStream out, String indent) throws Exception {
+ out.print(
+ indent + "<div class='" + getClass().getSimpleName().toLowerCase() + "'><a href=\"javascript:toggle('node"
+ + id + "')\"><img src='" + (isCollapsed() ? PLUS : MINUS) + "' id='img_node" + id + "'></a>");
+ super.generate(out, "");
+ out.println("</div>");
+
+ out.println(indent + "<div id=\"node" + id + "\" style='" + (isCollapsed() ? "display:none; " : "")
+ + "margin-left:20px;'>");
+
+ generateChildren(out, indent + " ");
+
+ out.println(indent + "</div>");
+ }
+
+ protected abstract void generateChildren(PrintStream out, String indent) throws Exception;
+
+ protected boolean isCollapsed() {
+ return true;
+ }
+ }
+
+ /**
+ * @author Eike Stepper
+ */
+ private final class Category extends AbstractTreeNode {
+ private final Map<String, Component> components = new HashMap<String, Component>();
+
+ public Category(String text) {
+ super(text);
+ }
+
+ public Map<String, Component> getComponents() {
+ return components;
+ }
+
+ @Override
+ protected void generateChildren(PrintStream out, String indent) throws Exception {
+ if (components.isEmpty()) {
+ out.println(indent + "<em>There are no " + getText().toLowerCase() + ".</em>");
+ } else {
+ for (String key : sortedKeys(components)) {
+ Component component = components.get(key);
+ component.generate(out, indent);
+ }
+ }
+ }
+
+ @Override
+ protected boolean isCollapsed() {
+ return false;
+ }
+ }
+
+ /**
+ * @author Eike Stepper
+ */
+ private final class Component extends AbstractTreeNode {
+ private final List<Change> changes = new ArrayList<Change>();
+
+ private final Map<String, Type> types = new HashMap<String, Type>();
+
+ private Version componentVersion;
+
+ public Component(String componentID) {
+ super(componentID);
+ }
+
+ public String getComponentID() {
+ return super.getText();
+ }
+
+ public void setComponentVersion(String componentVersion) {
+ Version version = new Version(componentVersion);
+ if (this.componentVersion == null || this.componentVersion.compareTo(version) < 0) {
+ this.componentVersion = version;
+ }
+ }
+
+ @Override
+ public String getText() {
+ String componentID = getComponentID();
+ if (componentVersion != null) {
+ componentID += "&nbsp;" + componentVersion;
+ }
+
+ return componentID;
+ }
+
+ @Override
+ public String getIcon() {
+ return "<img src='plugin.gif'>";
+ }
+
+ public List<Change> getChanges() {
+ return changes;
+ }
+
+ public Map<String, Type> getTypes() {
+ return types;
+ }
+
+ @Override
+ protected void generateChildren(PrintStream out, String indent) throws Exception {
+ for (Change change : changes) {
+ change.generate(out, indent);
+ }
+
+ for (String key : sortedKeys(types)) {
+ Type type = types.get(key);
+ type.generate(out, indent);
+ }
+ }
+
+ @Override
+ protected String getHref() throws Exception {
+ return null;
+ }
+ }
+
+ /**
+ * @author Eike Stepper
+ */
+ private final class Type extends AbstractTreeNode {
+ private final List<Change> changes = new ArrayList<Change>();
+
+ @SuppressWarnings("unused")
+ private final Component component;
+
+ private String elementType;
+
+ public Type(Component component, String text) {
+ super(text);
+ this.component = component;
+ }
+
+ public String getTypeName() {
+ return super.getText();
+ }
+
+ @Override
+ public String getText() {
+ String typeName = getTypeName();
+ return typeName.replace('$', '.');
+ }
+
+ @Override
+ public String getIcon() {
+ try {
+ return "<img src='" + getElementType() + ".gif'>";
+ } catch (Exception ex) {
+ return super.getIcon();
+ }
+ }
+
+ public List<Change> getChanges() {
+ return changes;
+ }
+
+ public void setElementType(String elementType) {
+ if ("CLASS_ELEMENT_TYPE".equals(elementType)) {
+ this.elementType = CLASS;
+ } else if ("INTERFACE_ELEMENT_TYPE".equals(elementType)) {
+ this.elementType = INTERFACE;
+ } else if ("ENUM_ELEMENT_TYPE".equals(elementType)) {
+ this.elementType = ENUM;
+ } else if ("ANNOTATION_ELEMENT_TYPE".equals(elementType)) {
+ this.elementType = ANNOTATION;
+ }
+ }
+
+ public String getElementType() throws Exception {
+ if (elementType == null) {
+ String typeName = getTypeName();
+ elementType = determineElementType(typeName);
+ }
+
+ return elementType;
+ }
+
+ @Override
+ protected void generateChildren(PrintStream out, String indent) throws Exception {
+ for (Change change : changes) {
+ change.generate(out, indent);
+ }
+ }
+
+ @Override
+ protected String getHref() throws Exception {
+ return null;
+ }
+
+ private String determineElementType(String typeName) throws MalformedURLException {
+ final String[] result = { null };
+
+ int lastDot = typeName.lastIndexOf('.');
+ String packageName = typeName.substring(0, lastDot);
+ typeName = typeName.substring(lastDot + 1).replace('$', '.');
+
+ // This only finds top-level and nested type, not local or anonymous types.
+ // But that's okay, because those are usually classes, anyways (anonymous
+ // types always are)
+ TypeNameRequestor requestor = new TypeNameRequestor() {
+ @Override
+ public void acceptType(int modifiers, char[] packageName, char[] simpleTypeName, char[][] enclosingTypeNames, String path) {
+ // Only process the first match
+ if (result[0] == null) {
+ if (Flags.isAnnotation(modifiers)) {
+ result[0] = ANNOTATION;
+ } else if (Flags.isInterface(modifiers)) {
+ result[0] = INTERFACE;
+ } else if (Flags.isEnum(modifiers)) {
+ result[0] = ENUM;
+ } else {
+ result[0] = CLASS;
+ }
+ }
+ }
+ };
+
+ IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
+ SearchEngine engine = new SearchEngine();
+
+ try {
+ engine.searchAllTypeNames(packageName.toCharArray(), SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE,
+ typeName.toCharArray(), SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE,
+ IJavaSearchConstants.TYPE, scope,
+ requestor,
+ IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, new NullProgressMonitor());
+ } catch (JavaModelException e) {
+ // No matter. We'll just assume it's a class
+ result[0] = CLASS;
+ }
+
+ return (result[0] == null) ? CLASS : result[0];
+ }
+ }
+
+ /**
+ * @author Eike Stepper
+ */
+ private final class Change extends AbstractNode {
+ private final String kind;
+
+ public Change(String text, String kind) {
+ super(text);
+ if ("REMOVED".equals(kind)) {
+ this.kind = "removal";
+ } else if ("ADDED".equals(kind)) {
+ this.kind = "addition";
+ } else {
+ this.kind = "change";
+ }
+ }
+
+ @Override
+ public String getIcon() {
+ try {
+ return "<img src='" + kind + ".gif'>";
+ } catch (Exception ex) {
+ return super.getIcon();
+ }
+ }
+
+ @Override
+ public void generate(PrintStream out, String indent) throws Exception {
+ out.print(indent + "<img src='empty.gif'>");
+ super.generate(out, "");
+ out.println("<br>");
+ }
+ }
+}
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/APIReportGenerator.java b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/APIReportGenerator.java
new file mode 100644
index 00000000000..6af26edb8c8
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/APIReportGenerator.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (c) 2012, 2015 Eike Stepper (Berlin, Germany) 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:
+ * Eike Stepper - initial API and implementation
+ * Christian W. Damus - adapt for Papyrus bundle tests (bug 440910)
+ */
+package org.eclipse.papyrus.bundles.tests.apireport;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.papyrus.bundles.tests.Activator;
+import org.eclipse.papyrus.bundles.tests.BundleTestsUtils;
+import org.eclipse.pde.api.tools.internal.comparator.DeltaXmlVisitor;
+import org.eclipse.pde.api.tools.internal.model.ApiModelFactory;
+import org.eclipse.pde.api.tools.internal.model.BundleComponent;
+import org.eclipse.pde.api.tools.internal.provisional.VisibilityModifiers;
+import org.eclipse.pde.api.tools.internal.provisional.comparator.ApiComparator;
+import org.eclipse.pde.api.tools.internal.provisional.comparator.ApiScope;
+import org.eclipse.pde.api.tools.internal.provisional.comparator.IDelta;
+import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline;
+import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
+import org.eclipse.pde.api.tools.internal.provisional.model.IApiScope;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * <p>
+ * A generator of API delta reports: given an API baseline (a set of plug-ins
+ * from the "previous" or "latest" stable release of Papyrus), computes the
+ * changes in the current installed/workspace versions of the same bundles.
+ * These changes are of three kinds:
+ * </p>
+ * <ul>
+ * <li>incompatible/breaking changes in public APIs</li>
+ * <li>compatible changes in public APIs</li>
+ * <li>changes in APIs re-exported by the plug-ins included in the report</li>
+ * </ul>
+ * <p>
+ * The plug-ins in the scope of the report are all plug-ins that don't match any
+ * of the exclusion filters in the <tt>excludes.txt</tt> file in this package.
+ * The format of the file is the same as the exclusion/inclusion filters used
+ * by the PDE API Tools Ant tasks.
+ * </p>
+ */
+public class APIReportGenerator {
+ // A decreasing sequence of bundle IDs that won't clash with those allocated by API Tools
+ private static AtomicInteger nextDevWorkspaceBundleID = new AtomicInteger(Integer.MAX_VALUE);
+
+ /**
+ * Match a bundle location that is a <tt>file:</tt> URI optionally preceded
+ * by <tt>reference:</tt>, <tt>initial@reference:</tt>, or other. for the purpose
+ * of extracting the file URI.
+ */
+ private final Pattern bundleLocation = Pattern.compile("([^:]+:)?file:(.*)");
+
+ private final File baselineLocation;
+ private final File apiXML;
+
+ /**
+ * Initializes me.
+ *
+ * @param baselineLocation
+ * a directory in the local filesystem containing the plug-ins
+ * that comprise the baseline of API comparison. This should usually be either a
+ * self-contained (non-bundle-pooled) Eclipse installation or a leaf-level (single release)
+ * p2 repository. In any case, the report generator searches within this location for a
+ * <tt>plugins/</tt> directory and scans that for JAR and directory bundles. This must be
+ * an absolute path
+ * @param apiXML
+ * the XML file (absolute path) to generate
+ */
+ public APIReportGenerator(File baselineLocation, File apiXML) {
+ super();
+
+ this.baselineLocation = baselineLocation;
+ this.apiXML = apiXML;
+ }
+
+ /**
+ * Generates the XML API delta report file.
+ */
+ public IStatus generate(IProgressMonitor monitor) throws CoreException {
+ Pattern[] exclusionPatterns = loadExclusions("excludes.txt"); //$NON-NLS-1$
+
+ SubMonitor progress = SubMonitor.convert(monitor, 100);
+
+ try {
+ try {
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(apiXML))) {
+ progress.subTask("Discovering API baseline...");
+ IApiBaseline baseline = getBaseline(exclusionPatterns, progress.newChild(25));
+ if (baseline == null) {
+ return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "No API baseline configured");
+ }
+ checkCancellation(progress);
+
+ progress.subTask("Discovering current API...");
+ IApiScope scope = getAPIToCompare(exclusionPatterns, progress.newChild(25));
+ checkCancellation(progress);
+
+ progress.subTask("Computing deltas...");
+ IDelta delta = ApiComparator.compare(scope, baseline, VisibilityModifiers.API, false, true, progress.newChild(25));
+ if (delta != null) {
+ checkCancellation(progress);
+
+ DeltaXmlVisitor visitor = new DeltaXmlVisitor();
+ delta.accept(visitor);
+
+ writer.write(visitor.getXML());
+ writer.flush();
+
+ progress.done();
+ }
+ } catch (IOException e) {
+ Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "I/O problem in API analysis", e));
+ } catch (CoreException e) {
+ Activator.getDefault().getLog().log(e.getStatus());
+ }
+
+ progress.worked(25);
+ return Status.OK_STATUS;
+ } catch (OperationCanceledException e) {
+ // ignore
+ }
+ } finally {
+ monitor.done();
+ }
+
+ return Status.CANCEL_STATUS;
+ }
+
+ IApiBaseline getBaseline(Pattern[] exclusionPatterns, IProgressMonitor monitor) throws IOException, CoreException {
+ IApiBaseline result = ApiModelFactory.newApiBaseline("Configured Baseline");
+ List<IApiComponent> components = new ArrayList<>();
+
+ Files.walkFileTree(Paths.get(baselineLocation.toURI()), new SimpleFileVisitor<Path>() {
+ private boolean inPlugins;
+
+ @Override
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+ FileVisitResult result = FileVisitResult.CONTINUE;
+
+ // Don't delve into directory bundles
+ if (inPlugins) {
+ result = FileVisitResult.SKIP_SUBTREE;
+ }
+ // Don't clear this flag on directories nested within 'plugins'
+ else if ("plugins".equals(dir.getFileName().toString())) {
+ inPlugins = true;
+ }
+
+ return result;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+ FileVisitResult result = FileVisitResult.CONTINUE;
+
+ if ("plugins".equals(dir.getFileName().toString())) {
+ inPlugins = false;
+ result = FileVisitResult.TERMINATE;
+ }
+
+ return result;
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ if (inPlugins) {
+ try {
+ IApiComponent component = ApiModelFactory.newApiComponent(result, file.toString());
+ if ((component != null) && !isExcluded(exclusionPatterns, component.getSymbolicName())) {
+ components.add(component);
+ monitor.subTask(" added component " + component.getSymbolicName());
+ }
+ } catch (CoreException e) {
+ // It's fine, it's not a bundle (maybe it's a pack200 archive)
+ }
+ }
+
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
+ System.out.printf("Error visiting %s: %s%n", file, exc.getMessage());
+ return FileVisitResult.CONTINUE;
+ }
+ });
+
+ result.addApiComponents(components.toArray(new IApiComponent[components.size()]));
+
+ return result;
+ }
+
+ private static boolean isExcluded(Pattern[] patterns, String name) {
+ for (Pattern pattern : patterns) {
+ Matcher matcher = pattern.matcher(name);
+ if (matcher.matches()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ApiScope getAPIToCompare(Pattern[] exclusionPatterns, IProgressMonitor monitor) throws CoreException {
+ ApiScope result = new ApiScope();
+
+ IApiBaseline currentBaseline = ApiModelFactory.newApiBaseline("Test Baseline");
+ if (currentBaseline != null) {
+ for (Bundle next : BundleTestsUtils.getPapyrusBundles()) {
+ if (((next.getState() & (Bundle.INSTALLED | Bundle.STARTING | Bundle.ACTIVE)) != 0)
+ && !isExcluded(exclusionPatterns, next.getSymbolicName())) {
+
+ try {
+ String installLocation = getInstallLocation(next);
+ if (installLocation != null) {
+ IApiComponent component = null;
+ Path installPath = Paths.get(installLocation);
+ Path dotClasspath = installPath.resolve(".classpath");
+ if (Files.isDirectory(installPath) && Files.exists(dotClasspath)) {
+ // It's a project in the development workspace (we are a run-time instance)
+ component = createDevWorkspaceComponent(currentBaseline, installPath, dotClasspath);
+ } else {
+ // Standard approach for JAR bundles and expanded installed bundles
+ component = ApiModelFactory.newApiComponent(currentBaseline, installLocation);
+ }
+
+ if (component != null) {
+ result.addElement(component);
+ monitor.subTask(" added component " + component.getSymbolicName());
+ }
+ }
+ } catch (CoreException e) {
+ // Hmm, shouldn't happen for a successfully installed bundle
+ Activator.getDefault().getLog().log(e.getStatus());
+ }
+ }
+ }
+
+ List<IApiComponent> allComponents = Stream.of(result.getApiElements())
+ .filter(IApiComponent.class::isInstance)
+ .map(IApiComponent.class::cast)
+ .collect(Collectors.toList());
+ currentBaseline.addApiComponents(allComponents.toArray(new IApiComponent[allComponents.size()]));
+ }
+
+ return result;
+ }
+
+ /**
+ * Obtains the location in the local filesystem where the specified {@code bundle} is installed.
+ *
+ * @param bundle
+ * an installed bundle
+ * @return its location in the local filesystem, or {@code null} if it could not be determined
+ */
+ String getInstallLocation(Bundle bundle) {
+ String result = null;
+
+ Matcher m = bundleLocation.matcher(bundle.getLocation());
+ if (m.matches()) {
+ // Don't try to create a URI-based file using the file: URI because
+ // in some installations, it will actually be a relative URI, which
+ // the File(URI) constructor would reject
+ result = new File(m.group(2)).getAbsolutePath();
+ }
+
+ return result;
+ }
+
+ protected IApiComponent createDevWorkspaceComponent(IApiBaseline parent, Path installLocation, Path dotClasspath) throws CoreException {
+ BundleComponent result = new BundleComponent(parent, installLocation.toString(), nextDevWorkspaceBundleID()) {
+ @Override
+ protected String[] getClasspathEntries(Map<String, String> manifest) throws BundleException {
+ List<String> classpathEntries = parseClasspath(dotClasspath);
+ return classpathEntries.toArray(new String[classpathEntries.size()]);
+ }
+ };
+
+ return result;
+ }
+
+ private static int nextDevWorkspaceBundleID() {
+ return nextDevWorkspaceBundleID.getAndDecrement();
+ }
+
+ protected List<String> parseClasspath(Path dotClasspath) throws BundleException {
+ List<String> result = new ArrayList<>();
+
+ try {
+ SAXParserFactory.newInstance().newSAXParser().parse(dotClasspath.toFile(), new DefaultHandler() {
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+ if ("classpathentry".equals(qName)) {
+ String kind = attributes.getValue("kind");
+ if (kind != null) {
+ switch (kind) {
+ case "lib":
+ case "output":
+ result.add(attributes.getValue("path"));
+ break;
+ }
+ }
+ }
+ }
+ });
+ } catch (SAXException | IOException | ParserConfigurationException e) {
+ throw new BundleException("Failed to parse bundle classpath", e);
+ }
+
+ return result;
+ }
+
+ private static void checkCancellation(IProgressMonitor monitor) throws OperationCanceledException {
+ if (monitor == null) {
+ return;
+ }
+
+ if (monitor.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+ }
+
+ private static Pattern[] loadExclusions(String resourceName) {
+ List<Pattern> result;
+
+ URL url = APIReportGenerator.class.getResource(resourceName);
+
+ try (BufferedReader input = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
+ result = input.lines()
+ .map(String::trim)
+ .filter(((Predicate<String>) String::isEmpty).negate())
+ .filter(line -> !line.startsWith("#")) //$NON-NLS-1$
+ .map(pattern -> {
+ if (pattern.startsWith("R:")) { //$NON-NLS-1$
+ pattern = pattern.substring("R:".length()); //$NON-NLS-1$
+ } else {
+ pattern = Pattern.quote(pattern);
+ }
+ return Pattern.compile(pattern);
+ })
+ .collect(Collectors.toList());
+ } catch (IOException e) {
+ // Fine, no exclusions, then
+ result = Collections.emptyList();
+ }
+
+ return result.toArray(new Pattern[result.size()]);
+ }
+}
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/ReportFixture.java b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/ReportFixture.java
new file mode 100644
index 00000000000..20bdb251b2c
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/ReportFixture.java
@@ -0,0 +1,120 @@
+/*****************************************************************************
+ * Copyright (c) 2015 Christian W. Damus 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.bundles.tests.apireport;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.Queue;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.papyrus.bundles.tests.Activator;
+
+/**
+ * Encapsulation of the report resources in the workspace metadata area.
+ */
+public class ReportFixture {
+ private static final IPath XML_REPORTS = new Path("apireports/xml"); //$NON-NLS-1$
+ private static final IPath HTML_REPORTS = new Path("apireports/html"); //$NON-NLS-1$
+
+ private static final IPath XML_REPORT_FILE = XML_REPORTS.append("api.xml"); //$NON-NLS-1$
+ private static final IPath HTML_REPORT_FILE = HTML_REPORTS.append("api.html"); //$NON-NLS-1$
+
+ private final File xmlReportFile;
+ private final File htmlReportFile;
+
+ /**
+ * Initializes the XML and HTML outputs of the API report. For example, certain
+ * stylesheets and images are emitted if necessary for the HTML report.
+ *
+ * @param baseOutputDir
+ * the base directory in which to generate the resulting reports
+ *
+ * @throws IOException
+ * on any problem in initializing the contents of the output directory
+ */
+ public ReportFixture(IPath baseOutputDir) throws IOException {
+ super();
+
+ xmlReportFile = baseOutputDir.append(XML_REPORT_FILE).toFile();
+ htmlReportFile = baseOutputDir.append(HTML_REPORT_FILE).toFile();
+
+ ensureContents(xmlReportFile.getParentFile(), XML_REPORTS);
+ ensureContents(htmlReportFile.getParentFile(), HTML_REPORTS);
+ }
+
+ public File getXMLReportFile() {
+ return xmlReportFile;
+ }
+
+ public File getHTMLReportFile() {
+ return htmlReportFile;
+ }
+
+ private void ensureContents(File directory, IPath resourcePath) throws IOException {
+ if (!directory.exists()) {
+ directory.mkdirs();
+ }
+
+ IPath base = new Path(directory.getAbsolutePath());
+
+ // Initial queue of resources to fetch
+ Queue<String> queue = new LinkedList<>();
+ enqueueResources(new Path("resources").append(resourcePath).toString(), queue); //$NON-NLS-1$
+
+ for (String next = queue.poll(); next != null; next = queue.poll()) {
+ // Enqueue further resources
+ enqueueResources(next, queue);
+
+ IPath path = new Path(next);
+ if (!path.hasTrailingSeparator()) {
+ // It's a file to be copied
+ URL url = Activator.getDefault().getBundle().getEntry(next);
+ if (url != null) {
+ // Strip the "resources" segment also (the +1)
+ copyResource(url, base.append(path.removeFirstSegments(resourcePath.segmentCount() + 1)));
+ }
+ }
+ }
+ }
+
+ private void enqueueResources(String basePath, Queue<? super String> queue) {
+ Enumeration<String> entries = Activator.getDefault().getBundle().getEntryPaths(basePath);
+ if (entries != null) {
+ while (entries.hasMoreElements()) {
+ queue.add(entries.nextElement());
+ }
+ }
+ }
+
+ private void copyResource(URL source, IPath destination) throws IOException {
+ File localFile = destination.toFile();
+
+ if (!localFile.exists()) {
+ if (!localFile.getParentFile().exists()) {
+ localFile.getParentFile().mkdirs();
+ }
+
+ try (InputStream input = source.openStream()) {
+ Files.copy(input, Paths.get(localFile.getAbsolutePath()));
+ }
+ }
+ }
+}
diff --git a/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/excludes.txt b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/excludes.txt
new file mode 100644
index 00000000000..b8d0d0e16f4
--- /dev/null
+++ b/tests/junit/plugins/developer/org.eclipse.papyrus.bundles.tests/src/org/eclipse/papyrus/bundles/tests/apireport/excludes.txt
@@ -0,0 +1,51 @@
+#
+# Copyright (c) 2015 Christian W. Damus 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:
+# Christian W. Damus - Initial API and implementation
+#
+
+#
+# Manifest of bundles and bundle-name patterns that are excluded
+# from API Analysis.
+#
+# Patterns are Java-style regular expressions prefixed by "R:".
+# (This is the file format used by PDE API Tools, so this file
+# could be re-used if necessary in an Ant script).
+#
+
+# Don't anaylyze non-Papyrus bundles
+R:^(?!org\.eclipse\.papyrus\.).*
+
+# Don't analyze test bundles
+R:.*\.tests?\b
+R:.*\.junit?\b
+
+# Don't analyze developer bundles
+org.eclipse.papyrus.dev.feature
+org.eclipse.papyrus.codegen
+org.eclipse.papyrus.def
+org.eclipse.papyrus.dev.assistants.codegen
+org.eclipse.papyrus.dev.java.utils
+org.eclipse.papyrus.dev.project.management
+org.eclipse.papyrus.dev.tests.framework
+org.eclipse.papyrus.dev.tests.framework.ui
+org.eclipse.papyrus.developer.profile
+org.eclipse.papyrus.domaincodegen.ui
+org.eclipse.papyrus.domaincontextcodegen
+org.eclipse.papyrus.domaincontextcodegen.edit
+org.eclipse.papyrus.domaincontextcodegen.editor
+org.eclipse.papyrus.elementtypesconfigurations.developer
+org.eclipse.papyrus.gmf.editpartview
+org.eclipse.papyrus.gmf.editpoliciesstates
+org.eclipse.papyrus.gmf.figureview
+org.eclipse.papyrus.gmfgenextension
+org.eclipse.papyrus.infra.emf.commandstack
+org.eclipse.papyrus.mwe2.utils
+org.eclipse.papyrus.releng.tools
+org.eclipse.papyrus.uml.developer.mde
diff --git a/tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/src/org/eclipse/papyrus/junit/utils/PrintingProgressMonitor.java b/tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/src/org/eclipse/papyrus/junit/utils/PrintingProgressMonitor.java
new file mode 100644
index 00000000000..e2ca54127c3
--- /dev/null
+++ b/tests/junit/plugins/junit/org.eclipse.papyrus.junit.utils/src/org/eclipse/papyrus/junit/utils/PrintingProgressMonitor.java
@@ -0,0 +1,137 @@
+/*****************************************************************************
+ * Copyright (c) 2015 Christian W. Damus 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:
+ * Christian W. Damus - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.junit.utils;
+
+import java.io.PrintStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.ProgressMonitorWrapper;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+/**
+ * A progress monitor that prints progress to standard output or some other
+ * {@link PrintStream}, optionally wrapping some other monitor.
+ */
+public class PrintingProgressMonitor extends ProgressMonitorWrapper {
+ private final PrintStream printTo;
+
+ private boolean first;
+
+ private Predicate<String> filter = Predicates.alwaysTrue();
+
+ /**
+ * Initializes me to print to standard output.
+ */
+ public PrintingProgressMonitor() {
+ this(System.out, new NullProgressMonitor());
+ }
+
+ /**
+ * Initializes me to print to some stream.
+ */
+ public PrintingProgressMonitor(PrintStream printTo) {
+ this(printTo, new NullProgressMonitor());
+ }
+
+ /**
+ * Initializes me to print to some stream and wrap another {@code monitor).
+ */
+ public PrintingProgressMonitor(PrintStream printTo, IProgressMonitor monitor) {
+ super(monitor);
+
+ this.printTo = printTo;
+ }
+
+ /**
+ * Adds a filter regular expression that matches task messages to exclude from
+ * the output (to promote quieter progress when appropriate).
+ *
+ * @param pattern
+ * a regular expression pattern for task messages to suppress
+ *
+ * @return myself, for the convenience of call chaining
+ */
+ public PrintingProgressMonitor filter(String pattern) {
+ Pattern regex = Pattern.compile(pattern);
+ final Matcher m = regex.matcher(""); //$NON-NLS-1$
+
+ Predicate<String> filter = new Predicate<String>() {
+ @Override
+ public boolean apply(String input) {
+ m.reset(input);
+ return !m.find();
+ }
+ };
+
+ this.filter = Predicates.and(filter, this.filter);
+
+ return this;
+ }
+
+ private void echo(boolean dashN, String text) {
+ echo(true, false, text);
+ }
+
+ private void echo(boolean initialNewline, boolean terminalNewline, String text) {
+ if (filter.apply(text)) {
+ if (first) {
+ first = false;
+ } else if (initialNewline) {
+ printTo.println();
+ }
+
+ printTo.print(text);
+
+ if (terminalNewline) {
+ printTo.println();
+ }
+ }
+ }
+
+ @Override
+ public void beginTask(String name, int totalWork) {
+ echo(true, name);
+ super.beginTask(name, totalWork);
+ }
+
+ @Override
+ public void setTaskName(String name) {
+ echo(true, name);
+ super.setTaskName(name);
+ }
+
+ @Override
+ public void subTask(String name) {
+ echo(true, name);
+ super.subTask(name);
+ }
+
+ @Override
+ public void worked(int work) {
+ echo(false, false, ".");
+ super.worked(work);
+ }
+
+ @Override
+ public void done() {
+ echo(false, true, " Done.");
+ super.done();
+ }
+
+}

Back to the top