summaryrefslogtreecommitdiffstats
path: root/bundle-plugin
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /bundle-plugin
Publish
Diffstat (limited to 'bundle-plugin')
-rw-r--r--bundle-plugin/.gitignore2
-rw-r--r--bundle-plugin/OWNERS1
-rw-r--r--bundle-plugin/pom.xml144
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateSourcesMojo.java164
-rw-r--r--bundle-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml18
-rw-r--r--bundle-plugin/src/main/resources/META-INF/maven/.gitignore0
-rw-r--r--bundle-plugin/src/main/resources/META-INF/plexus/components.xml50
-rw-r--r--bundle-plugin/src/main/resources/build.properties1
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/AnalyzeBundle.scala72
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/TransformExportPackages.scala54
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Analyze.scala28
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.scala102
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.scala88
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.scala68
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnnotationVisitorTrait.scala39
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AttributeVisitorTrait.scala15
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.scala10
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.scala24
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/PackageTally.scala46
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Packages.scala27
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/SubVisitorTrait.scala19
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/package.scala24
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/Artifacts.scala30
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/AssembleContainerPluginMojo.scala113
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateBundleClassPathMappingsMojo.scala96
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.scala283
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackageParser.scala89
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackages.scala27
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ImportPackages.scala51
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Extractors.scala17
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Files.scala23
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/IO.scala46
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Iteration.scala14
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/JarFiles.scala24
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Maps.scala19
-rw-r--r--bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Strings.scala14
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Base.java27
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/CatchException.java15
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassAnnotation.java10
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassReference.java11
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassWithMethod.java9
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Derived.java9
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Dummy.java7
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/DummyAnnotation.java9
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Fields.java17
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Interface1.java17
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Interface2.java9
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/MethodAnnotation.java11
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/MethodInvocation.java12
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Methods.java32
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/invalid/package-info.java6
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/package-info.java6
-rw-r--r--bundle-plugin/src/test/java/com/yahoo/container/plugin/mojo/GenerateSourcesMojoTest.java25
-rw-r--r--bundle-plugin/src/test/resources/class/Utf8.classbin0 -> 1120 bytes
-rw-r--r--bundle-plugin/src/test/resources/jar/errorExport.jarbin0 -> 497 bytes
-rw-r--r--bundle-plugin/src/test/resources/jar/notAOsgiBundle.jarbin0 -> 462 bytes
-rw-r--r--bundle-plugin/src/test/resources/jar/simple1.jarbin0 -> 490 bytes
-rw-r--r--bundle-plugin/src/test/resources/manifest/simple.MF11
-rw-r--r--bundle-plugin/src/test/scala/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.scala39
-rw-r--r--bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassTest.scala115
-rw-r--r--bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodBodyTest.scala50
-rw-r--r--bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/TestUtilities.scala18
-rw-r--r--bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ExportPackageParserTest.scala63
-rw-r--r--bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ImportPackageTest.scala95
-rw-r--r--bundle-plugin/src/test/scala/com/yahoo/container/plugin/util/IOTest.scala44
65 files changed, 2509 insertions, 0 deletions
diff --git a/bundle-plugin/.gitignore b/bundle-plugin/.gitignore
new file mode 100644
index 00000000000..12251442258
--- /dev/null
+++ b/bundle-plugin/.gitignore
@@ -0,0 +1,2 @@
+/target
+/pom.xml.build
diff --git a/bundle-plugin/OWNERS b/bundle-plugin/OWNERS
new file mode 100644
index 00000000000..3b2ba1ede81
--- /dev/null
+++ b/bundle-plugin/OWNERS
@@ -0,0 +1 @@
+gjoranv
diff --git a/bundle-plugin/pom.xml b/bundle-plugin/pom.xml
new file mode 100644
index 00000000000..a57d1a41bf8
--- /dev/null
+++ b/bundle-plugin/pom.xml
@@ -0,0 +1,144 @@
+<?xml version="1.0"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>parent</artifactId>
+ <version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
+ </parent>
+ <artifactId>bundle-plugin</artifactId>
+ <version>6-SNAPSHOT</version>
+ <packaging>maven-plugin</packaging>
+ <name>${project.artifactId}</name>
+ <description>Maven Plugin for creating OSGi bundles for the JDisc Container.</description>
+ <prerequisites>
+ <maven>2.2.1</maven>
+ </prerequisites>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-plugin-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-model</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-artifact</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.plugin-tools</groupId>
+ <artifactId>maven-plugin-annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-project</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.scala-lang</groupId>
+ <artifactId>scala-compiler</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.scala-lang</groupId>
+ <artifactId>scala-library</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.scala-lang.modules</groupId>
+ <artifactId>scala-parser-combinators_${scala.major-version}</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.scalatest</groupId>
+ <artifactId>scalatest_${scala.major-version}</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.twdata.maven</groupId>
+ <artifactId>mojo-executor</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>annotations</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>scalalib</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+ <build>
+ <finalName>${project.artifactId}</finalName>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>org.scala-tools</groupId>
+ <artifactId>maven-scala-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>add-source</goal>
+ <goal>compile</goal>
+ <goal>testCompile</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <args>
+ <arg>-unchecked</arg>
+ <arg>-deprecation</arg>
+ <arg>-feature</arg>
+ </args>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-plugin-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <compilerArgs>
+ <arg>-Xlint:rawtypes</arg>
+ <arg>-Xlint:unchecked</arg>
+ <arg>-Xlint:deprecation</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <forkMode>once</forkMode>
+ <systemPropertyVariables>
+ <expectedDefaultConfigGenVersion>${project.version}</expectedDefaultConfigGenVersion>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateSourcesMojo.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateSourcesMojo.java
new file mode 100644
index 00000000000..cb2e54024b4
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateSourcesMojo.java
@@ -0,0 +1,164 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.mojo;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.BuildPluginManager;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.component.annotations.Requirement;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.regex.Pattern;
+
+import static org.twdata.maven.mojoexecutor.MojoExecutor.*;
+
+/**
+ * Calls the generate-sources phase in the container lifecycle defined in lifecycle.xml.
+ *
+ * @author tonytv
+ */
+@Mojo(name = "generateSources", requiresDependencyResolution = ResolutionScope.COMPILE)
+public class GenerateSourcesMojo extends AbstractMojo {
+
+ @Parameter(defaultValue = "${project}")
+ protected org.apache.maven.project.MavenProject project;
+
+ @Parameter(defaultValue = "${session}", readonly = true, required = true)
+ protected MavenSession session;
+
+ @Component
+ @Requirement
+ private BuildPluginManager pluginManager;
+
+ @Parameter
+ protected String configGenVersion;
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ String configGenVersion = getConfigGenVersion();
+ getLog().debug("configGenVersion = " + configGenVersion);
+
+ executeMojo(
+ plugin(
+ groupId("com.yahoo.vespa"),
+ artifactId("config-class-plugin"),
+ version(releaseVersion(configGenVersion))),
+ goal("config-gen"),
+ configuration(
+ element(name("defFilesDirectories"), "src/main/resources/configdefinitions")),
+ createExecutionEnvironment());
+ //Compile source roots added in container-lifecycle is not currently
+ //propagated automatically to this project.
+ project.addCompileSourceRoot(project.getBuild().getDirectory() + "/generated-sources/vespa-configgen-plugin");
+ }
+
+ private ExecutionEnvironment createExecutionEnvironment() throws MojoExecutionException {
+ return executionEnvironment(
+ project,
+ session,
+ pluginManager);
+ }
+
+ private String getConfigGenVersion() throws MojoExecutionException {
+ if (configGenVersion != null && !configGenVersion.isEmpty()) {
+ return configGenVersion;
+ }
+ Dependency containerDev = getVespaDependency("container-dev");
+ if (containerDev != null)
+ return containerDev.getVersion();
+
+ Dependency prelude = getVespaDependency("prelude");
+ if (prelude != null)
+ return prelude.getVersion();
+
+ Dependency docproc = getVespaDependency("docproc");
+ if (docproc != null)
+ return docproc.getVersion();
+
+ MavenProject parent = getVespaParent();
+ if (parent != null)
+ return parent.getVersion();
+
+ String defaultConfigGenVersion = loadDefaultConfigGenVersion();
+ getLog().warn(
+ String.format("Did not find container-dev, guessing that version '%s' of config_gen should be used.",
+ defaultConfigGenVersion));
+
+ return defaultConfigGenVersion;
+ }
+
+ static String loadDefaultConfigGenVersion() throws MojoExecutionException {
+ Properties props = new Properties();
+ try {
+ props.load(GenerateSourcesMojo.class.getResourceAsStream("/build.properties"));
+ } catch (IOException e) {
+ throw new MojoExecutionException("Failed to resolve version of com.yahoo.vespa:config-class-plugin.",
+ new FileNotFoundException("/build.properties"));
+ }
+ return props.getProperty("projectVersion");
+ }
+
+ private MavenProject getVespaParent() {
+ MavenProject parent = project.getParent();
+ if (parent != null &&
+ "com.yahoo.vespa".equals(parent.getGroupId()) &&
+ "parent".equals(parent.getArtifactId())) {
+
+ return parent;
+ }
+
+ return null;
+ }
+
+ private Dependency getVespaDependency(String artifactId) {
+ for (Object element : project.getDependencies()) {
+ Dependency dependency = (Dependency) element;
+
+ if ("com.yahoo.vespa".equals(dependency.getGroupId()) &&
+ artifactId.equals(dependency.getArtifactId())) {
+ return dependency;
+ }
+ }
+
+ return null;
+ }
+
+ static String releaseVersion(String mavenVersion) {
+ if (mavenVersion.endsWith("-SNAPSHOT")) {
+ return mavenVersion;
+ } else {
+ String[] parts = mavenVersion.split(Pattern.quote("."));
+ if (parts.length <= 3) {
+ return mavenVersion;
+ } else {
+ return stringJoin(Arrays.asList(parts).subList(0, 3), ".");
+ }
+ }
+ }
+
+ static String stringJoin(Collection<String> elements, String sep) {
+ StringBuilder builder = new StringBuilder();
+ Iterator<String> i = elements.iterator();
+
+ if (i.hasNext())
+ builder.append(i.next());
+
+ while(i.hasNext()) {
+ builder.append(sep).append(i.next());
+ }
+
+ return builder.toString();
+ }
+}
diff --git a/bundle-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml b/bundle-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml
new file mode 100644
index 00000000000..a2665c223d1
--- /dev/null
+++ b/bundle-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml
@@ -0,0 +1,18 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<lifecycleMappingMetadata>
+ <pluginExecutions>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <goals>
+ <goal>generateSources</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <execute>
+ <runOnIncremental>false</runOnIncremental>
+ <runOnConfiguration>true</runOnConfiguration>
+ </execute>
+ </action>
+ </pluginExecution>
+ </pluginExecutions>
+</lifecycleMappingMetadata>
diff --git a/bundle-plugin/src/main/resources/META-INF/maven/.gitignore b/bundle-plugin/src/main/resources/META-INF/maven/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/bundle-plugin/src/main/resources/META-INF/maven/.gitignore
diff --git a/bundle-plugin/src/main/resources/META-INF/plexus/components.xml b/bundle-plugin/src/main/resources/META-INF/plexus/components.xml
new file mode 100644
index 00000000000..126c9435ffa
--- /dev/null
+++ b/bundle-plugin/src/main/resources/META-INF/plexus/components.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+
+<component-set>
+ <components>
+ <component>
+ <role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
+ <role-hint>container-plugin</role-hint>
+ <implementation>
+ org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping
+ </implementation>
+ <configuration>
+
+ <phases>
+ <process-resources>org.apache.maven.plugins:maven-resources-plugin:resources</process-resources>
+ <generate-sources>com.yahoo.vespa:bundle-plugin:generateSources</generate-sources>
+ <compile>org.apache.maven.plugins:maven-compiler-plugin:compile</compile>
+ <process-test-resources>
+ org.apache.maven.plugins:maven-resources-plugin:testResources,
+ com.yahoo.vespa:bundle-plugin:generate-bundle-classpath-mappings
+ </process-test-resources>
+ <test-compile>org.apache.maven.plugins:maven-compiler-plugin:testCompile</test-compile>
+ <test>org.apache.maven.plugins:maven-surefire-plugin:test</test>
+ <package>
+ com.yahoo.vespa:bundle-plugin:generate-osgi-manifest,
+ com.yahoo.vespa:bundle-plugin:assemble-container-plugin
+ </package>
+ <install>org.apache.maven.plugins:maven-install-plugin:install</install>
+ <deploy>org.apache.maven.plugins:maven-deploy-plugin:deploy</deploy>
+ </phases>
+
+ </configuration>
+ </component>
+
+ <component>
+ <role>org.apache.maven.artifact.handler.ArtifactHandler</role>
+ <role-hint>container-plugin</role-hint>
+ <implementation>
+ org.apache.maven.artifact.handler.DefaultArtifactHandler
+ </implementation>
+ <configuration>
+ <type>container-plugin</type>
+ <extension>jar</extension>
+ <language>java</language>
+ <addedToClasspath>true</addedToClasspath>
+ </configuration>
+ </component>
+
+ </components>
+</component-set>
diff --git a/bundle-plugin/src/main/resources/build.properties b/bundle-plugin/src/main/resources/build.properties
new file mode 100644
index 00000000000..adcd64886bd
--- /dev/null
+++ b/bundle-plugin/src/main/resources/build.properties
@@ -0,0 +1 @@
+projectVersion=${project.version}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/AnalyzeBundle.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/AnalyzeBundle.scala
new file mode 100644
index 00000000000..b9137a3c6bc
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/AnalyzeBundle.scala
@@ -0,0 +1,72 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.bundle
+
+import java.util.jar.{Manifest => JarManifest}
+import java.io.File
+import com.yahoo.container.plugin.osgi.{ExportPackages, ExportPackageParser}
+import ExportPackages.Export
+import collection.immutable.LinearSeq
+import com.yahoo.container.plugin.util.JarFiles
+
+
+/**
+ * @author tonytv
+ */
+object AnalyzeBundle {
+ case class PublicPackages(exports : List[Export], globals : List[String])
+
+ def publicPackagesAggregated(jarFiles : Iterable[File]) = aggregate(jarFiles map {publicPackages(_)})
+
+ def aggregate(publicPackagesList : Iterable[PublicPackages]) =
+ (PublicPackages(List(), List()) /: publicPackagesList) { (a,b) =>
+ PublicPackages(a.exports ++ b.exports, a.globals ++ b.globals)
+ }
+
+ def publicPackages(jarFile: File): PublicPackages = {
+ try {
+
+ (for {
+ manifest <- JarFiles.getManifest(jarFile)
+ if isOsgiManifest(manifest)
+ } yield PublicPackages(parseExports(manifest), parseGlobals(manifest))).
+ getOrElse(PublicPackages(List(), List()))
+
+ } catch {
+ case e : Exception => throw new RuntimeException("Invalid manifest in bundle '%s'".format(jarFile.getPath), e)
+ }
+ }
+
+ def bundleSymbolicName(jarFile: File): Option[String] = {
+ JarFiles.getManifest(jarFile).flatMap(getBundleSymbolicName)
+ }
+
+ private def parseExportsFromAttribute(manifest : JarManifest, attributeName : String) = {
+ (for (export <- getMainAttributeValue(manifest, attributeName)) yield
+ ExportPackageParser.parseAll(export) match {
+ case noSuccess: ExportPackageParser.NoSuccess => throw new RuntimeException(
+ "Failed parsing %s: %s".format(attributeName, noSuccess))
+ case success => success.get
+ }).
+ getOrElse(List())
+ }
+
+ private def parseExports = parseExportsFromAttribute(_ : JarManifest, "Export-Package")
+
+ private def parseGlobals(manifest : JarManifest) = {
+ //TODO: Use separate parser for global packages.
+ val globals = parseExportsFromAttribute(manifest, "Global-Package")
+
+ if (globals map {_.parameters} exists {!_.isEmpty}) {
+ throw new RuntimeException("Parameters not valid for Global-Package.")
+ }
+
+ globals flatMap {_.packageNames}
+ }
+
+ private def getMainAttributeValue(manifest: JarManifest, name: String): Option[String] =
+ Option(manifest.getMainAttributes.getValue(name))
+
+ private def isOsgiManifest = getBundleSymbolicName(_: JarManifest).isDefined
+
+ private def getBundleSymbolicName = getMainAttributeValue(_: JarManifest, "Bundle-SymbolicName")
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/TransformExportPackages.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/TransformExportPackages.scala
new file mode 100644
index 00000000000..baaef756911
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/bundle/TransformExportPackages.scala
@@ -0,0 +1,54 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.bundle
+
+import java.io.File
+import com.yahoo.container.plugin.osgi.ExportPackages.{Export, Parameter}
+import com.yahoo.container.plugin.osgi.ExportPackages.Export
+
+/**
+ * @author tonytv
+ */
+object TransformExportPackages extends App {
+ def replaceVersions(exports: List[Export], newVersion: String): List[Export] = {
+ mapParameters(exports) { parameters =>
+ parameters map replaceVersion(newVersion)
+ }
+ }
+
+ def removeUses(exports: List[Export]): List[Export] = {
+ mapParameters(exports) { parameters =>
+ parameters filter {_.name != "uses"}
+ }
+ }
+
+ def mapParameters(exports: List[Export])(f: List[Parameter] => List[Parameter]): List[Export] = {
+ exports map { case Export(packageNames: List[String], parameters: List[Parameter]) =>
+ Export(packageNames, f(parameters))
+ }
+ }
+
+ private def replaceVersion(newVersion: String)(parameter: Parameter) = {
+ parameter match {
+ case Parameter("version", _) => Parameter("version", newVersion)
+ case other => other
+ }
+ }
+
+ def toExportPackageProperty(exports: List[Export]): String = {
+ val exportPackages =
+ exports map { case Export(packageNames: List[String], parameters: List[Parameter]) =>
+ val parameterString = nameEqualsValue(parameters)
+ (packageNames ++ parameterString) mkString ";"
+ }
+
+ exportPackages mkString ","
+ }
+
+ private def nameEqualsValue(parameters: List[Parameter]) = {
+ parameters map { case Parameter(name, value) =>
+ s"$name=${quote(value)}"
+ }
+ }
+
+ def quote(s: String) = '"' + s + '"'
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Analyze.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Analyze.scala
new file mode 100644
index 00000000000..ed15c1bcc74
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Analyze.scala
@@ -0,0 +1,28 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis
+
+import org.objectweb.asm._
+import java.io.{InputStream, File}
+import com.yahoo.container.plugin.util.IO.withFileInputStream
+
+/**
+ * Main entry point for class analysis
+ * @author tonytv
+ */
+object Analyze {
+ def analyzeClass(classFile : File) : ClassFileMetaData = {
+ try {
+ withFileInputStream(classFile) { fileInputStream =>
+ analyzeClass(fileInputStream)
+ }
+ } catch {
+ case e : RuntimeException => throw new RuntimeException("An error occurred when analyzing " + classFile.getPath, e)
+ }
+ }
+
+ def analyzeClass(inputStream : InputStream) : ClassFileMetaData = {
+ val visitor = new AnalyzeClassVisitor()
+ new ClassReader(inputStream).accept(visitor, ClassReader.SKIP_DEBUG)
+ visitor.result
+ }
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.scala
new file mode 100644
index 00000000000..b57a07d5c30
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.scala
@@ -0,0 +1,102 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis
+
+import org.objectweb.asm._
+import com.yahoo.osgi.annotation.{ExportPackage, Version}
+import collection.mutable
+
+/**
+ * Picks up classes used in class files.
+ * @author tonytv
+ */
+private class AnalyzeClassVisitor extends ClassVisitor(Opcodes.ASM5) with AnnotationVisitorTrait with AttributeVisitorTrait {
+ private var name : String = null
+ protected val imports : ImportsSet = mutable.Set()
+ protected var exportPackageAnnotation: Option[ExportPackageAnnotation] = None
+
+
+ override def visitAttribute(attribute: Attribute): Unit = super.visitAttribute(attribute)
+
+ override def visitMethod(access: Int, name: String, desc: String, signature: String,
+ exceptions: Array[String]): MethodVisitor = {
+
+ imports ++= (Type.getReturnType(desc) +: Type.getArgumentTypes(desc)).flatMap(getClassName)
+
+ imports ++= Option(exceptions) getOrElse(Array()) flatMap internalNameToClassName
+
+ AnalyzeSignatureVisitor.analyzeMethod(signature, this)
+ new AnalyzeMethodVisitor(this)
+ }
+
+ override def visitField(access: Int, name: String, desc: String, signature: String, value: AnyRef): FieldVisitor = {
+ imports ++= getClassName(Type.getType(desc)).toList
+
+ AnalyzeSignatureVisitor.analyzeField(signature, this)
+ new FieldVisitor(Opcodes.ASM5) with SubVisitorTrait with AttributeVisitorTrait with AnnotationVisitorTrait {
+ val analyzeClassVisitor = AnalyzeClassVisitor.this
+
+ override def visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor = super.visitAnnotation(desc, visible)
+ override def visitAttribute(attribute: Attribute): Unit = super.visitAttribute(attribute)
+ override def visitEnd(): Unit = super.visitEnd()
+ }
+ }
+
+ override def visit(version: Int, access: Int, name: String, signature: String, superName: String, interfaces: Array[String]) {
+ this.name = internalNameToClassName(name).get
+
+ imports ++= (superName +: interfaces) flatMap internalNameToClassName
+ AnalyzeSignatureVisitor.analyzeClass(signature, this)
+ }
+
+ override def visitInnerClass(name: String, outerName: String, innerName: String, access: Int) {}
+ override def visitOuterClass(owner: String, name: String, desc: String) {}
+ override def visitSource(source: String, debug: String) {}
+ override def visitEnd() {}
+
+ def addImports(imports: TraversableOnce[String]) {
+ this.imports ++= imports
+ }
+
+ override def visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor = {
+ if (Type.getType(desc).getClassName == classOf[ExportPackage].getName) {
+ visitExportPackage()
+ } else {
+ super.visitAnnotation(desc, visible)
+ }
+ }
+
+ def visitExportPackage(): AnnotationVisitor = {
+ def defaultVersionValue[T](name: String) = classOf[Version].getMethod(name).getDefaultValue().asInstanceOf[T]
+
+ new AnnotationVisitor(Opcodes.ASM5) {
+ var major: Int = defaultVersionValue("major")
+ var minor: Int = defaultVersionValue("minor")
+ var micro: Int = defaultVersionValue("micro")
+ var qualifier: String = defaultVersionValue("qualifier")
+
+ override def visit(name: String, value: AnyRef) {
+ def valueAsInt = value.asInstanceOf[Int]
+
+ name match {
+ case "major" => major = valueAsInt
+ case "minor" => minor = valueAsInt
+ case "micro" => micro = valueAsInt
+ case "qualifier" => qualifier = value.asInstanceOf[String]
+ }
+ }
+
+ override def visitEnd() {
+ exportPackageAnnotation = Some(ExportPackageAnnotation(major, minor, micro, qualifier))
+ }
+
+ override def visitEnum(name: String, desc: String, value: String) {}
+ override def visitArray(name: String): AnnotationVisitor = this
+ override def visitAnnotation(name: String, desc: String): AnnotationVisitor = this
+ }
+ }
+
+ def result = {
+ assert(!imports.contains("int"))
+ new ClassFileMetaData(name, imports.toSet, exportPackageAnnotation)
+ }
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.scala
new file mode 100644
index 00000000000..5d65b3972c0
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.scala
@@ -0,0 +1,88 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis
+
+import org.objectweb.asm._
+
+/**
+ * Picks up classes used in method bodies.
+ * @author tonytv
+ */
+private class AnalyzeMethodVisitor(val analyzeClassVisitor : AnalyzeClassVisitor)
+ extends MethodVisitor(Opcodes.ASM5) with AnnotationVisitorTrait with AttributeVisitorTrait with SubVisitorTrait {
+
+
+ override def visitParameterAnnotation(parameter: Int, desc: String, visible: Boolean): AnnotationVisitor = super.visitParameterAnnotation(parameter, desc, visible)
+ override def visitAnnotationDefault(): AnnotationVisitor = super.visitAnnotationDefault()
+ override def visitAttribute(attribute: Attribute): Unit = super.visitAttribute(attribute)
+ override def visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor = super.visitAnnotation(desc, visible)
+ override def visitEnd(): Unit = super.visitEnd()
+
+ override def visitMultiANewArrayInsn(desc: String, dims: Int) {
+ imports ++= getClassName(Type.getType(desc)).toList
+ }
+
+
+ override def visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
+ imports ++= internalNameToClassName(owner)
+ imports ++= Type.getArgumentTypes(desc).flatMap(getClassName)
+ imports ++= getClassName(Type.getReturnType(desc))
+ }
+
+ override def visitFieldInsn(opcode: Int, owner: String, name: String, desc: String) {
+ imports ++= internalNameToClassName(owner) ++ getClassName(Type.getType(desc)).toList
+
+ }
+
+ override def visitTypeInsn(opcode: Int, `type` : String) {
+ imports ++= internalNameToClassName(`type`)
+ }
+
+ override def visitTryCatchBlock(start: Label, end: Label, handler: Label, `type` : String) {
+ if (`type` != null) //null means finally block
+ imports ++= internalNameToClassName(`type`)
+ }
+
+ override def visitLocalVariable(name: String, desc: String, signature: String, start: Label, end: Label, index: Int) {
+ imports += Type.getType(desc).getClassName
+ }
+
+ override def visitLdcInsn(constant: AnyRef) {
+ constant match {
+ case typeConstant: Type => imports ++= getClassName(typeConstant)
+ case _ =>
+ }
+ }
+
+ override def visitInvokeDynamicInsn(name: String, desc: String, bootstrapMethod: Handle, bootstrapMethodArgs: AnyRef*) {
+ bootstrapMethodArgs.foreach {
+ case typeConstant: Type =>
+ imports ++= getClassName(typeConstant)
+ case handle: Handle =>
+ imports ++= internalNameToClassName(handle.getOwner)
+ imports ++= Type.getArgumentTypes(desc).flatMap(getClassName)
+ case _ : Number =>
+ case _ : String =>
+ case other => throw new AssertionError(s"Unexpected type ${other.getClass} with value '$other'")
+ }
+ }
+
+ override def visitMaxs(maxStack: Int, maxLocals: Int) {}
+ override def visitLineNumber(line: Int, start: Label) {}
+ //only for debugging
+ override def visitLookupSwitchInsn(dflt: Label, keys: Array[Int], labels: Array[Label]) {}
+
+
+ override def visitTableSwitchInsn(min: Int, max: Int, dflt: Label, labels: Label*): Unit = super.visitTableSwitchInsn(min, max, dflt, labels: _*)
+ override def visitIincInsn(`var` : Int, increment: Int) {}
+ override def visitLabel(label: Label) {}
+ override def visitJumpInsn(opcode: Int, label: Label) {}
+ override def visitVarInsn(opcode: Int, `var` : Int) {}
+ override def visitIntInsn(opcode: Int, operand: Int) {}
+ override def visitInsn(opcode: Int) {}
+ override def visitFrame(`type` : Int, nLocal: Int, local: Array[AnyRef], nStack: Int, stack: Array[AnyRef]) {}
+ override def visitCode() {}
+}
+
+
+
+
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.scala
new file mode 100644
index 00000000000..693e0e17482
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.scala
@@ -0,0 +1,68 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis
+
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.signature.{SignatureReader, SignatureVisitor}
+
+
+/**
+ * @author tonytv
+ */
+
+private class AnalyzeSignatureVisitor(val analyzeClassVisitor: AnalyzeClassVisitor)
+ extends SignatureVisitor(Opcodes.ASM5)
+ with SubVisitorTrait {
+
+
+ override def visitEnd(): Unit = super.visitEnd()
+
+ override def visitClassType(className: String) {
+ imports ++= internalNameToClassName(className)
+ }
+
+ override def visitFormalTypeParameter(name: String) {}
+
+ override def visitClassBound() = this
+
+ override def visitInterfaceBound() = this
+
+ override def visitSuperclass() = this
+
+ override def visitInterface() = this
+
+ override def visitParameterType() = this
+
+ override def visitReturnType() = this
+
+ override def visitExceptionType() = this
+
+ override def visitBaseType(descriptor: Char) {}
+
+ override def visitTypeVariable(name: String) {}
+
+ override def visitArrayType() = this
+
+ override def visitInnerClassType(name: String) {}
+
+ override def visitTypeArgument() {}
+
+ override def visitTypeArgument(wildcard: Char) = this
+}
+
+
+object AnalyzeSignatureVisitor {
+ def analyzeClass(signature: String, analyzeClassVisitor: AnalyzeClassVisitor) {
+ if (signature != null) {
+ new SignatureReader(signature).accept(new AnalyzeSignatureVisitor(analyzeClassVisitor))
+ }
+ }
+
+ def analyzeMethod(signature: String, analyzeClassVisitor: AnalyzeClassVisitor) {
+ analyzeClass(signature, analyzeClassVisitor)
+ }
+
+ def analyzeField(signature: String, analyzeClassVisitor: AnalyzeClassVisitor) {
+ if (signature != null)
+ new SignatureReader(signature).acceptType(new AnalyzeSignatureVisitor(analyzeClassVisitor))
+ }
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnnotationVisitorTrait.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnnotationVisitorTrait.scala
new file mode 100644
index 00000000000..8beb47c765f
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AnnotationVisitorTrait.scala
@@ -0,0 +1,39 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis
+
+import org.objectweb.asm.{Opcodes, AnnotationVisitor, Type}
+
+/**
+ * Picks up classes used in annotations.
+ * @author tonytv
+ */
+private trait AnnotationVisitorTrait {
+ protected val imports: ImportsSet
+
+ def visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor = {
+ imports ++= getClassName(Type.getType(desc)).toList
+
+ visitAnnotationDefault()
+ }
+
+ def visitAnnotationDefault(): AnnotationVisitor =
+ new AnnotationVisitor(Opcodes.ASM5) {
+ override def visit(name: String, value: AnyRef) {}
+
+ override def visitEnum(name: String, desc: String, value: String) {
+ imports ++= getClassName(Type.getType(desc)).toList
+ }
+
+ override def visitArray(name: String): AnnotationVisitor = this
+
+ override def visitAnnotation(name: String, desc: String): AnnotationVisitor = {
+ imports ++= getClassName(Type.getType(desc)).toList
+ this
+ }
+
+ override def visitEnd() {}
+ }
+
+ def visitParameterAnnotation(parameter: Int, desc: String, visible: Boolean): AnnotationVisitor =
+ visitAnnotation(desc, visible)
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AttributeVisitorTrait.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AttributeVisitorTrait.scala
new file mode 100644
index 00000000000..0603cebf5af
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/AttributeVisitorTrait.scala
@@ -0,0 +1,15 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis
+
+import org.objectweb.asm.{Type, Attribute}
+
+/**
+ * @author tonytv
+ */
+private trait AttributeVisitorTrait {
+ protected val imports: ImportsSet
+
+ def visitAttribute(attribute: Attribute) {
+ imports ++= getClassName(Type.getObjectType(attribute.`type`)).toList
+ }
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.scala
new file mode 100644
index 00000000000..00ed9efb360
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.scala
@@ -0,0 +1,10 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis
+
+/**
+ * The result of analyzing a .class file.
+ * @author tonytv
+ */
+sealed case class ClassFileMetaData(name:String,
+ referencedClasses : Set[String],
+ exportPackage : Option[ExportPackageAnnotation])
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.scala
new file mode 100644
index 00000000000..00265b80761
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.scala
@@ -0,0 +1,24 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis
+
+import com.yahoo.container.plugin.util.Strings
+
+/**
+ * @author tonytv
+ */
+case class ExportPackageAnnotation(major: Int, minor: Int, micro: Int, qualifier: String) {
+ requireNonNegative(major, "major")
+ requireNonNegative(minor, "minor")
+ requireNonNegative(micro, "micro")
+ require(qualifier.matches("""(\p{Alpha}|\p{Digit}|_|-)*"""),
+ exportPackageError("qualifier must follow the format (alpha|digit|'_'|'-')* but was '%s'.".format(qualifier)))
+
+
+ private def requireNonNegative(i: Int, fieldName: String) {
+ require(i >= 0, exportPackageError("%s must be non-negative but was %d.".format(fieldName, i)))
+ }
+
+ private def exportPackageError(s: String) = "ExportPackage anntotation: " + s
+
+ def osgiVersion : String = (List(major, minor, micro) ++ Strings.noneIfEmpty(qualifier)).mkString(".")
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/PackageTally.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/PackageTally.scala
new file mode 100644
index 00000000000..ecbf9448dfd
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/PackageTally.scala
@@ -0,0 +1,46 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis
+
+import com.yahoo.container.plugin.util.Maps
+
+/**
+ *
+ * @author tonytv
+ */
+final class PackageTally (private val definedPackagesMap : Map[String, Option[ExportPackageAnnotation]],
+ referencedPackagesUnfiltered : Set[String]) {
+
+ val referencedPackages = referencedPackagesUnfiltered diff definedPackages
+
+ def definedPackages = definedPackagesMap.keySet
+
+ def exportedPackages = definedPackagesMap collect { case (name, Some(export)) => (name, export) }
+
+ /**
+ * Represents the classes for two package tallies that are deployed as a single unit.
+ *
+ * ExportPackageAnnotations from this has precedence over the other.
+ */
+ def combine(other: PackageTally): PackageTally = {
+ new PackageTally(
+ Maps.combine(definedPackagesMap, other.definedPackagesMap)(_ orElse _),
+ referencedPackages ++ other.referencedPackages)
+ }
+}
+
+
+object PackageTally {
+ def fromAnalyzedClassFiles(analyzedClassFiles : Seq[ClassFileMetaData]) : PackageTally = {
+ combine(
+ for (metaData <- analyzedClassFiles)
+ yield {
+ new PackageTally(
+ Map(Packages.packageName(metaData.name) -> metaData.exportPackage),
+ metaData.referencedClasses.map(Packages.packageName))
+ })
+ }
+
+ def combine(packageTallies : Iterable[PackageTally]) : PackageTally = (empty /: packageTallies)(_.combine(_))
+
+ val empty : PackageTally = new PackageTally(Map(), Set())
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Packages.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Packages.scala
new file mode 100644
index 00000000000..2900d3f1551
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/Packages.scala
@@ -0,0 +1,27 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis
+
+/**
+ * Utility methods related to packages.
+ * @author tonytv
+ */
+object Packages {
+ case class PackageMetaData(definedPackages: Set[String], referencedExternalPackages: Set[String])
+
+ def packageName(fullClassName: String) = {
+ def nullIfNotFound(index : Int) = if (index == -1) 0 else index
+
+ fullClassName.substring(0, nullIfNotFound(fullClassName.lastIndexOf(".")))
+ }
+
+
+
+ def analyzePackages(allClasses: Seq[ClassFileMetaData]): PackageMetaData = {
+ val (definedPackages, referencedClasses) =
+ (for (classMetaData <- allClasses)
+ yield (packageName(classMetaData.name), classMetaData.referencedClasses.map(packageName))).
+ unzip
+
+ PackageMetaData(definedPackages.toSet, referencedClasses.flatten.toSet diff definedPackages.toSet)
+ }
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/SubVisitorTrait.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/SubVisitorTrait.scala
new file mode 100644
index 00000000000..d4e7f4fb028
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/SubVisitorTrait.scala
@@ -0,0 +1,19 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis
+
+import collection.mutable
+
+/**
+ * A visitor that's run for sub construct of a class
+ * and forwards all its imports the the owning ClassVisitor at the end.
+ * @author tonytv
+ */
+private trait SubVisitorTrait {
+ val analyzeClassVisitor : AnalyzeClassVisitor
+
+ val imports : ImportsSet = mutable.Set()
+
+ def visitEnd() {
+ analyzeClassVisitor.addImports(imports)
+ }
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/package.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/package.scala
new file mode 100644
index 00000000000..a94bc7710d2
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/classanalysis/package.scala
@@ -0,0 +1,24 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin
+
+import org.objectweb.asm.Type
+import collection.mutable
+
+package object classanalysis {
+ type ImportsSet = mutable.Set[String]
+
+ def internalNameToClassName(internalClassName: String) : Option[String] = {
+ getClassName(Type.getObjectType(internalClassName))
+ }
+
+ def getClassName(aType: Type): Option[String] = {
+ import Type._
+
+ aType.getSort match {
+ case ARRAY => getClassName(aType.getElementType)
+ case OBJECT => Some(aType.getClassName)
+ case _ => None
+ }
+ }
+}
+
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/Artifacts.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/Artifacts.scala
new file mode 100644
index 00000000000..3b2e52be8c4
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/Artifacts.scala
@@ -0,0 +1,30 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.mojo
+
+
+import scala.collection.JavaConversions._
+import org.apache.maven.project.MavenProject
+import org.apache.maven.artifact.Artifact
+
+
+/**
+ * @author tonytv
+ */
+object Artifacts {
+ def getArtifacts(project : MavenProject) = {
+ type artifactSet = java.util.Set[Artifact]
+ val artifacts = project.getArtifacts.asInstanceOf[artifactSet].groupBy(_.getScope)
+
+ def isTypeJar(artifact : Artifact) = artifact.getType == "jar"
+ def getByScope(scope: String) =
+ artifacts.getOrElse(scope, Iterable.empty).partition(isTypeJar)
+
+
+ val (jarArtifactsToInclude, nonJarArtifactsToInclude) = getByScope(Artifact.SCOPE_COMPILE)
+ val (jarArtifactsProvided, nonJarArtifactsProvided) = getByScope(Artifact.SCOPE_PROVIDED)
+
+ (jarArtifactsToInclude, jarArtifactsProvided, nonJarArtifactsToInclude ++ nonJarArtifactsProvided)
+ }
+
+ def getArtifactsToInclude(project: MavenProject) = getArtifacts(project)._1
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/AssembleContainerPluginMojo.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/AssembleContainerPluginMojo.scala
new file mode 100644
index 00000000000..50379cee858
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/AssembleContainerPluginMojo.scala
@@ -0,0 +1,113 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.mojo
+
+import java.io.File
+import java.nio.channels.Channels
+import java.util.jar.JarFile
+import java.util.zip.ZipEntry
+
+import com.yahoo.container.plugin.util.{Files, JarFiles}
+import org.apache.maven.archiver.{MavenArchiveConfiguration, MavenArchiver}
+import org.apache.maven.plugin.AbstractMojo
+import org.apache.maven.plugins.annotations.{Component, Mojo, Parameter, ResolutionScope}
+import org.apache.maven.project.MavenProject
+import org.codehaus.plexus.archiver.Archiver
+import org.codehaus.plexus.archiver.jar.JarArchiver
+
+import scala.collection.convert.wrapAsScala._
+
+/**
+ * @author tonytv
+ */
+@Mojo(name = "assemble-container-plugin", requiresDependencyResolution = ResolutionScope.COMPILE)
+class AssembleContainerPluginMojo extends AbstractMojo {
+ object withDependencies
+ object withoutDependencies
+
+ @Parameter(defaultValue = "${project}")
+ var project: MavenProject = null
+
+ @Component(role = classOf[Archiver], hint = "jar")
+ var jarArchiver: JarArchiver = null
+
+ @Parameter
+ var archiveConfiguration: MavenArchiveConfiguration = new MavenArchiveConfiguration
+
+ @Parameter(alias = "UseCommonAssemblyIds", defaultValue = "false")
+ var useCommonAssemblyIds: Boolean = false
+
+
+ def execute() {
+ val jarSuffixes =
+ if (useCommonAssemblyIds) Map(withoutDependencies -> ".jar", withDependencies -> "-jar-with-dependencies.jar")
+ else Map(withoutDependencies -> "-without-dependencies.jar", withDependencies -> "-deploy.jar")
+
+ val jarFiles = jarSuffixes mapValues jarFileInBuildDirectory(build.getFinalName)
+
+ //force recreating the archive
+ archiveConfiguration.setForced(true)
+ archiveConfiguration.setManifestFile(new File(new File(project.getBuild.getOutputDirectory), JarFile.MANIFEST_NAME))
+
+ addClassesDirectory()
+ createArchive(jarFiles(withoutDependencies))
+ project.getArtifact.setFile(jarFiles(withoutDependencies))
+
+ addDependencies()
+ createArchive(jarFiles(withDependencies))
+ }
+
+ private def jarFileInBuildDirectory(name: String)(jarSuffix: String) = {
+ new File(build.getDirectory, name + jarSuffix)
+ }
+
+ private def addClassesDirectory() {
+ val classesDirectory = new File(build.getOutputDirectory)
+ if (classesDirectory.isDirectory) {
+ jarArchiver.addDirectory(classesDirectory)
+ }
+ }
+
+ private def createArchive(jarFile: File) {
+ val mavenArchiver = new MavenArchiver
+ mavenArchiver.setArchiver(jarArchiver)
+ mavenArchiver.setOutputFile(jarFile)
+ mavenArchiver.createArchive(project, archiveConfiguration)
+ }
+
+ private def addDependencies() {
+ Artifacts.getArtifactsToInclude(project).foreach { artifact =>
+ if (artifact.getType == "jar") {
+ jarArchiver.addFile(artifact.getFile, "dependencies/" + artifact.getFile.getName)
+ copyConfigDefinitions(artifact.getFile)
+ }
+ else
+ getLog.warn("Unkown artifact type " + artifact.getType)
+ }
+ }
+
+ private def copyConfigDefinitions(file: File) {
+ JarFiles.withJarFile(file) { jarFile =>
+ for {
+ entry <- jarFile.entries()
+ name = entry.getName
+ if name.startsWith("configdefinitions/") && name.endsWith(".def")
+
+ } copyConfigDefinition(jarFile, entry)
+ }
+ }
+
+ private def copyConfigDefinition(jarFile: JarFile, entry: ZipEntry) {
+ JarFiles.withInputStream(jarFile, entry) { input =>
+ val defPath = entry.getName.replace("/", File.separator)
+ val destinationFile = new File(project.getBuild.getOutputDirectory, defPath)
+ destinationFile.getParentFile.mkdirs()
+
+ Files.withFileOutputStream(destinationFile) { output =>
+ output.getChannel.transferFrom(Channels.newChannel(input), 0, Long.MaxValue)
+ }
+ jarArchiver.addFile(destinationFile, entry.getName)
+ }
+ }
+
+ private def build = project.getBuild
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateBundleClassPathMappingsMojo.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateBundleClassPathMappingsMojo.scala
new file mode 100644
index 00000000000..0700a2cf3b2
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateBundleClassPathMappingsMojo.scala
@@ -0,0 +1,96 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.mojo
+
+import java.io.File
+import java.nio.file.Paths
+
+import com.google.common.base.Preconditions
+import com.yahoo.container.plugin.bundle.AnalyzeBundle
+import com.yahoo.vespa.scalalib.osgi.maven.ProjectBundleClassPaths
+import ProjectBundleClassPaths.BundleClasspathMapping
+import com.yahoo.vespa.scalalib.osgi.maven.ProjectBundleClassPaths
+import org.apache.maven.artifact.Artifact
+import org.apache.maven.plugin.AbstractMojo
+import org.apache.maven.plugins.annotations.{ResolutionScope, Mojo, Parameter}
+import org.apache.maven.project.MavenProject
+
+
+
+/**
+ * Generates mapping from Bundle-SymbolicName to classpath elements, e.g
+ * myBundle -> List(.m2/repository/com/mylib/Mylib.jar, myBundleProject/target/classes)
+ * The mapping in stored in a json file.
+ * @author tonytv
+ */
+@Mojo(name = "generate-bundle-classpath-mappings", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
+class GenerateBundleClassPathMappingsMojo extends AbstractMojo {
+ @Parameter(defaultValue = "${project}")
+ private var project: MavenProject = null
+
+ //TODO: Combine with com.yahoo.container.plugin.mojo.GenerateOsgiManifestMojo.bundleSymbolicName
+ @Parameter(alias = "Bundle-SymbolicName", defaultValue = "${project.artifactId}")
+ private var bundleSymbolicName: String = null
+
+
+ /* Sample output -- target/test-classes/bundle-plugin.bundle-classpath-mappings.json
+ {
+ "mainBundle": {
+ "bundleSymbolicName": "bundle-plugin-test",
+ "classPathElements": [
+ "/Users/tonyv/Repos/vespa/bundle-plugin-test/target/classes",
+ "/Users/tonyv/.m2/repository/com/yahoo/vespa/jrt/6-SNAPSHOT/jrt-6-SNAPSHOT.jar",
+ "/Users/tonyv/.m2/repository/com/yahoo/vespa/annotations/6-SNAPSHOT/annotations-6-SNAPSHOT.jar"
+ ]
+ },
+ "providedDependencies": [
+ {
+ "bundleSymbolicName": "jrt",
+ "classPathElements": [
+ "/Users/tonyv/.m2/repository/com/yahoo/vespa/jrt/6-SNAPSHOT/jrt-6-SNAPSHOT.jar"
+ ]
+ }
+ ]
+ }
+ */
+ override def execute(): Unit = {
+ Preconditions.checkNotNull(bundleSymbolicName)
+
+ val (embeddedArtifacts, providedJarArtifacts, _) = asLists(Artifacts.getArtifacts(project))
+
+ val embeddedArtifactsFiles = embeddedArtifacts.map(_.getFile)
+
+ val classPathElements = (outputDirectory +: embeddedArtifactsFiles).map(_.getAbsolutePath)
+
+ val classPathMappings = ProjectBundleClassPaths(
+ mainBundle = BundleClasspathMapping(bundleSymbolicName, classPathElements),
+ providedDependencies = providedJarArtifacts flatMap createDependencyClasspathMapping
+ )
+
+ ProjectBundleClassPaths.save(
+ testOutputPath.resolve(ProjectBundleClassPaths.classPathMappingsFileName),
+ classPathMappings)
+ }
+
+ private def outputDirectory = new File(project.getBuild.getOutputDirectory)
+ private def testOutputPath = Paths.get(project.getBuild.getTestOutputDirectory)
+
+ /* TODO:
+ * 1) add the dependencies of the artifact in the future(i.e. dependencies of dependencies)
+ * or
+ * 2) obtain bundles with embedded dependencies from the maven repository,
+ * and support loading classes from the nested jar files in those bundles.
+ */
+ def createDependencyClasspathMapping(artifact: Artifact): Option[BundleClasspathMapping] = {
+ for (bundleSymbolicName <- bundleSymbolicNameForArtifact(artifact))
+ yield BundleClasspathMapping(bundleSymbolicName, classPathElements = List(artifact.getFile.getAbsolutePath))
+ }
+
+ def bundleSymbolicNameForArtifact(artifact: Artifact): Option[String] = {
+ if (artifact.getFile.getName.endsWith(".jar")) AnalyzeBundle.bundleSymbolicName(artifact.getFile)
+ else Some(artifact.getArtifactId) //Not the best heuristic. The other alternatives are parsing the pom file or
+ //storing information in target/classes when building the provided bundles.
+ }
+
+ def asLists[A](tuple: (Iterable[A], Iterable[A], Iterable[A])) =
+ (tuple._1.toList, tuple._2.toList, tuple._3.toList)
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.scala
new file mode 100644
index 00000000000..5e1ea589f36
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/mojo/GenerateOsgiManifestMojo.scala
@@ -0,0 +1,283 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.mojo
+
+import java.io.File
+import java.util.jar.{Attributes, JarEntry, JarFile}
+import java.util.regex.Pattern
+
+import com.yahoo.container.plugin.bundle.AnalyzeBundle
+import com.yahoo.container.plugin.classanalysis.{Analyze, ExportPackageAnnotation, PackageTally}
+import com.yahoo.container.plugin.mojo.GenerateOsgiManifestMojo._
+import com.yahoo.container.plugin.osgi.ExportPackages
+import com.yahoo.container.plugin.osgi.ExportPackages.Export
+import com.yahoo.container.plugin.osgi.ImportPackages.Import
+import com.yahoo.container.plugin.osgi.{ExportPackageParser, ExportPackages, ImportPackages}
+import com.yahoo.container.plugin.util.Files.allDescendantFiles
+import com.yahoo.container.plugin.util.IO.withFileOutputStream
+import com.yahoo.container.plugin.util.Iteration.toStream
+import com.yahoo.container.plugin.util.JarFiles.{withInputStream, withJarFile}
+import com.yahoo.container.plugin.util.Strings
+import org.apache.maven.artifact.Artifact
+import org.apache.maven.plugin.{AbstractMojo, MojoExecutionException, MojoFailureException}
+import org.apache.maven.plugins.annotations.{Mojo, Parameter, ResolutionScope}
+import org.apache.maven.project.MavenProject
+
+import scala.collection.immutable.Map
+
+
+/**
+ * @author tonytv
+ */
+@Mojo(name = "generate-osgi-manifest", requiresDependencyResolution = ResolutionScope.TEST)
+class GenerateOsgiManifestMojo extends AbstractMojo {
+
+ @Parameter(defaultValue = "${project}")
+ var project: MavenProject = null
+
+ @Parameter
+ var discApplicationClass: String = null
+
+ @Parameter
+ var discPreInstallBundle: String = null
+
+ @Parameter(alias = "Bundle-Version", defaultValue = "${project.version}")
+ var bundleVersion: String = null
+
+ @Parameter(alias = "Bundle-SymbolicName", defaultValue = "${project.artifactId}")
+ var bundleSymbolicName: String = null
+
+ @Parameter(alias = "Bundle-Activator")
+ var bundleActivator: String = null
+
+ @Parameter(alias = "X-JDisc-Privileged-Activator")
+ var jdiscPrivilegedActivator: String = null
+
+ @Parameter(alias = "X-Config-Models")
+ var configModels: String = null
+
+ @Parameter(alias = "Import-Package")
+ var importPackage: String = null
+
+ @Parameter(alias = "WebInfUrl")
+ var webInfUrl: String = null
+
+ @Parameter(alias = "Main-Class")
+ var mainClass: String = null
+
+ @Parameter(alias = "X-Jersey-Binding")
+ var jerseyBinding: String = null
+
+ case class PackageInfo(name : String, exportAnnotation : Option[ExportPackageAnnotation])
+
+ def execute() {
+ try {
+ val (jarArtifactsToInclude, jarArtifactsProvided, nonJarArtifacts) = Artifacts.getArtifacts(project)
+ warnOnUnsupportedArtifacts(nonJarArtifacts)
+
+ val publicPackagesFromProvidedJars = AnalyzeBundle.publicPackagesAggregated(jarArtifactsProvided.map(_.getFile))
+ val includedJarPackageTally = definedPackages(jarArtifactsToInclude)
+
+ val projectPackageTally = analyzeProjectClasses()
+
+ val pluginPackageTally = projectPackageTally.combine(includedJarPackageTally)
+
+ warnIfPackagesDefinedOverlapsGlobalPackages(projectPackageTally.definedPackages ++ includedJarPackageTally.definedPackages,
+ publicPackagesFromProvidedJars.globals)
+
+ if (getLog.isDebugEnabled) {
+ getLog.debug("Referenced packages = " + pluginPackageTally.referencedPackages)
+ getLog.debug("Defined packages = " + pluginPackageTally.definedPackages)
+ getLog.debug("Exported packages of dependencies = " +
+ publicPackagesFromProvidedJars.exports.map(e => (e.packageNames, e.version getOrElse "")).toSet )
+ }
+
+ val calculatedImports = ImportPackages.calculateImports(
+ pluginPackageTally.referencedPackages,
+ pluginPackageTally.definedPackages,
+ ExportPackages.exportsByPackageName(publicPackagesFromProvidedJars.exports))
+
+ val manualImports = emptyToNone(importPackage) map getManualImports getOrElse Map()
+
+ createManifestFile(new File(project.getBuild.getOutputDirectory),
+ manifestContent(
+ project,
+ jarArtifactsToInclude,
+ manualImports,
+ (calculatedImports -- manualImports.keys).values.toSet,
+ pluginPackageTally))
+
+ } catch {
+ case e: MojoFailureException => throw e
+ case e: MojoExecutionException => throw e
+ case e: Exception => throw new MojoExecutionException("Failed generating osgi manifest.", e)
+ }
+ }
+
+ //TODO: Tell which dependency overlaps
+ private def warnIfPackagesDefinedOverlapsGlobalPackages(internalPackages: Set[String], globalPackages: List[String]) {
+ val overlap = internalPackages intersect globalPackages.toSet
+ if (overlap.nonEmpty)
+ throw new MojoExecutionException(
+ "The following packages are both global and included in the bundle:\n%s".format(overlap map (" " + _) mkString ("\n")))
+ }
+
+
+ def osgiExportPackages(exportedPackages: Map[String, ExportPackageAnnotation]): Iterable[String] = {
+ for ((name, annotation) <- exportedPackages)
+ yield name + ";version=" + annotation.osgiVersion
+ }
+
+ def trimWhitespace(lines: Option[String]): String = {
+ lines.getOrElse("").split(",").map(_.trim).mkString(",")
+ }
+
+ def manifestContent(project: MavenProject, jarArtifactsToInclude: Traversable[Artifact],
+ manualImports: Map[String, Option[String]], imports : Set[Import],
+ pluginPackageTally : PackageTally) = {
+ Map[String, String](
+ "Created-By" -> "vespa container maven plugin",
+ "Bundle-ManifestVersion" -> "2",
+ "Bundle-Name" -> project.getName,
+ "Bundle-SymbolicName" -> bundleSymbolicName,
+ "Bundle-Version" -> asBundleVersion(bundleVersion),
+ "Bundle-Vendor" -> "Yahoo!",
+ "Bundle-ClassPath" -> bundleClassPath(jarArtifactsToInclude),
+ "Bundle-Activator" -> bundleActivator,
+ "X-JDisc-Privileged-Activator" -> jdiscPrivilegedActivator,
+ "Main-Class" -> mainClass,
+ "X-JDisc-Application" -> discApplicationClass,
+ "X-JDisc-Preinstall-Bundle" -> trimWhitespace(Option(discPreInstallBundle)),
+ "X-Config-Models" -> configModels,
+ "X-Jersey-Binding" -> jerseyBinding,
+ "WebInfUrl" -> webInfUrl,
+ "Import-Package" -> ((manualImports map asOsgiImport) ++ (imports map {_.asOsgiImport})).toList.sorted.mkString(","),
+ "Export-Package" -> osgiExportPackages(pluginPackageTally.exportedPackages).toList.sorted.mkString(","))
+ .filterNot { case (key, value) => value == null || value.isEmpty }
+
+ }
+
+ def asOsgiImport(importSpec: (String, Option[String])) = importSpec match {
+ case (packageName, Some(version)) => packageName + ";version=" + quote(version)
+ case (packageName, None) => packageName
+ }
+
+ def quote(s: String) = '"' + s + '"'
+
+ def createManifestFile(outputDirectory: File, manifestContent: Map[String, String]) {
+ val manifest = toManifest(manifestContent)
+
+ withFileOutputStream(new File(outputDirectory, JarFile.MANIFEST_NAME)) {
+ outputStream =>
+ manifest.write(outputStream)
+ }
+ }
+
+ def toManifest(manifestContent: Map[String, String]) = {
+ val manifest = new java.util.jar.Manifest
+ val mainAttributes = manifest.getMainAttributes
+
+ mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0")
+ for ((key, value) <- manifestContent)
+ mainAttributes.putValue(key, value)
+
+ manifest
+ }
+
+ private def bundleClassPath(artifactsToInclude : Traversable[Artifact]) =
+ ("." +: artifactsToInclude.map(dependencyPath).toList).mkString(",")
+
+ private def dependencyPath(artifact : Artifact) =
+ "dependencies/" + artifact.getFile.getName
+
+ private def asBundleVersion(projectVersion: String) = {
+ require(projectVersion != null, "Missing project version.")
+
+ val parts = projectVersion.split(Pattern.quote("-"), 2)
+ val numericPart = parts.head.split('.').map(Strings.emptyStringTo("0")).padTo(3, "0").toList
+
+ val majorMinorMicro = numericPart take 3
+ majorMinorMicro.mkString(".")
+ }
+
+ private def warnOnUnsupportedArtifacts(nonJarArtifacts: Traversable[Artifact]) {
+ val unsupportedArtifacts = nonJarArtifacts.toSet.filter(_.getType != "pom")
+
+ for (artifact <- unsupportedArtifacts) {
+ getLog.warn(s"Unsupported artifact '${artifact.getId}': Type '${artifact.getType}' is not supported. Please file a feature request.")
+ }
+ }
+
+ private def analyzeProjectClasses() : PackageTally = {
+ val outputDirectory = new File(project.getBuild.getOutputDirectory)
+
+ val analyzedClasses = allDescendantFiles(outputDirectory).filter(_.getName.endsWith(".class")).
+ map(Analyze.analyzeClass)
+
+ PackageTally.fromAnalyzedClassFiles(analyzedClasses)
+ }
+
+ def definedPackages(jarArtifacts: Iterable[Artifact]) : PackageTally = {
+ PackageTally.combine(
+ for (jarArtifact <- jarArtifacts) yield {
+ withJarFile(jarArtifact.getFile) { jarFile =>
+ definedPackages(jarFile)
+ }
+ })
+ }
+
+ def definedPackages(jarFile: JarFile) = {
+ val analyzedClasses =
+ for {
+ entry <- toStream(jarFile.entries())
+ if !entry.isDirectory
+ if entry.getName.endsWith(".class")
+ metaData = analyzeClass(jarFile, entry)
+ } yield metaData
+
+ PackageTally.fromAnalyzedClassFiles(analyzedClasses)
+ }
+
+ def analyzeClass(jarFile : JarFile, entry : JarEntry) = {
+ try {
+ withInputStream(jarFile, entry)(Analyze.analyzeClass)
+ } catch {
+ case e : Exception =>
+ throw new MojoExecutionException(
+ "While analyzing the class '%s' in jar file '%s'".format(entry.getName, jarFile.getName),
+ e)
+ }
+ }
+}
+
+object GenerateOsgiManifestMojo {
+ def getManualImports(importPackage: String): Map[String, Option[String]] = {
+ try {
+ (for {
+ importDirective <- parseImportPackages(importPackage)
+ packageName <- importDirective.packageNames
+ } yield packageName -> getVersionThrowOthers(importDirective.parameters)).
+ toMap
+
+ } catch {
+ case e: Exception => throw new RuntimeException("Error in Import-Package:" + importPackage, e)
+ }
+ }
+
+ def getVersionThrowOthers(parameters: List[ExportPackages.Parameter]): Option[String] = {
+ parameters match {
+ case List() => None
+ case List(ExportPackages.Parameter("version", v)) => Some(v)
+ case default => throw new RuntimeException("A single, optional version parameter expected, but got " + default)
+ }
+ }
+
+ def parseImportPackages(importPackages: String): List[Export] = {
+ ExportPackageParser.parseAll(importPackages) match {
+ case ExportPackageParser.NoSuccess(msg, _) => throw new RuntimeException(msg)
+ case ExportPackageParser.Success(packages, _) => packages
+ }
+ }
+
+ def emptyToNone(str: String) =
+ Option(str) map {_.trim} filterNot {_.isEmpty}
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackageParser.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackageParser.scala
new file mode 100644
index 00000000000..6c84a39b975
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackageParser.scala
@@ -0,0 +1,89 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.osgi
+
+import scala.util.parsing.combinator.JavaTokenParsers
+import ExportPackages.{Parameter, Export}
+import com.yahoo.container.plugin.util.Extractors.ListOf
+import scala.util.parsing.input.CharSequenceReader
+import scala.annotation.tailrec
+
+/**
+ * @author tonytv
+ */
+object ExportPackageParser extends JavaTokenParsers {
+ val ListOfParameter = new ListOf(classOf[Parameter])
+
+
+ def exportPackage = rep1sep(export, ",")
+
+ //TODO: remove when fix is in current scala library
+ //Fix for https://github.com/scala/scala-parser-combinators/pull/4
+ def stringLiteral_fixed: Parser[String] = ("\""+"""([^"\p{Cntrl}\\]|\\[\\'"bfnrt]|\\u[a-fA-F0-9]{4})*+"""+"\"").r
+
+ @SuppressWarnings(Array("unchecked"))
+ def export : Parser[Export] = packageName ~ opt(";" ~> (parameters | export)) ^^ {
+ case (packageName : String) ~ optional => {
+ optional match {
+ case None => Export(List(packageName.asInstanceOf[String]), List())
+ case Some(e: Export) => e.copy(packageNames = packageName +: e.packageNames)
+ case Some(ListOfParameter(parameters)) => Export(List(packageName), parameters)
+ }
+ }
+ }
+
+ def parameters = rep1sep(parameter, ";")
+
+ def parameter = (directive | attribute) ^^ {
+ case k ~ v => Parameter(k.toString, v.toString)
+ }
+
+ def directive = (extended_ <~ ":=") ~ argument
+ def attribute = (extended_ <~ "=") ~ argument
+
+ def packageName = rep1sep(ident_, ".") ^^ {
+ x => x.mkString(".")
+ }
+
+ def extended = rep1("""\p{Alnum}""".r | "_" | "-" | ".") ^^ {
+ _.mkString
+ }
+
+ def argument = (extended_ | stringLiteral_ | failure("argument expected")) ^^ {
+ val quote = '"'.toString
+ _.toString.stripPrefix(quote).stripSuffix(quote)
+ }
+
+ def parseAll(in: CharSequence): ParseResult[List[Export]] = {
+ try {
+ parseAll(exportPackage, in)
+ } catch {
+ case e: StackOverflowError =>
+ throw new RuntimeException("Failed parsing Export-Package: '''\n" + in + "\n'''", e)
+ }
+ }
+
+ //*** For debugging StackOverflow error **/
+ def ident_ = printStackOverflow(ident)("ident")
+ def stringLiteral_ = printStackOverflow(stringLiteral_fixed)("stringLiteral_fixed")
+ def extended_ = printStackOverflow(extended)("extended")
+
+ def printStackOverflow[T](p: => Parser[T])(name: String): Parser[T] = Parser{ in =>
+ try {
+ p(in)
+ } catch {
+ case e: StackOverflowError =>
+ val input = in match {
+ case reader: CharSequenceReader => readerToString(reader)
+ case other => other.toString
+ }
+ println(s"***StackOverflow for $name with input '''$input'''")
+ throw e
+ }
+ }
+
+ @tailrec
+ def readerToString(reader: CharSequenceReader, current: String = ""): String = {
+ if (reader.atEnd) current
+ else readerToString(reader.rest, current + reader.first)
+ }
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackages.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackages.scala
new file mode 100644
index 00000000000..d0e63cdc212
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ExportPackages.scala
@@ -0,0 +1,27 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.osgi
+
+/**
+ * @author tonytv
+ */
+object ExportPackages {
+
+ case class Export(packageNames: List[String], parameters: List[Parameter]) {
+ def version: Option[String] = {
+ (for (
+ param <- parameters if param.name == "version"
+ ) yield param.value).
+ headOption
+ }
+ }
+
+ case class Parameter(name: String, value: String)
+
+ def exportsByPackageName(exports: Seq[Export]): Map[String, Export] = {
+ (for {
+ export <- exports.reverse //ensure that earlier exports of a package overrides later exports.
+ packageName <- export.packageNames
+ } yield packageName -> export).
+ toMap
+ }
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ImportPackages.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ImportPackages.scala
new file mode 100644
index 00000000000..651f389640a
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/osgi/ImportPackages.scala
@@ -0,0 +1,51 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.osgi
+
+import ExportPackages.Export
+import util.control.Exception
+
+/**
+ * @author tonytv
+ */
+object ImportPackages {
+ case class Import(packageName : String, version : Option[String]) {
+ val majorMinorMicroVersion = Exception.handling(classOf[NumberFormatException]).
+ by( e => throw new IllegalArgumentException(
+ "Invalid version number '%s' for package '%s'.".format(version.get, packageName), e)) {
+
+ version map { _.split('.') take 3 map {_.toInt} }
+ }
+
+ def majorVersion = majorMinorMicroVersion map { _.head }
+
+ // TODO: Detecting guava packages should be based on Bundle-SymbolicName, not package name.
+ def importVersionRange = {
+ def upperLimit =
+ if (isGuavaPackage) InfiniteVersion // guava increases major version for each release
+ else majorVersion.get + 1
+
+ version map (v => "[%s,%s)".format(majorMinorMicroVersion.get.mkString("."), upperLimit))
+ }
+
+ def isGuavaPackage = packageName.equals(GuavaBasePackage) || packageName.startsWith(GuavaBasePackage + ".")
+
+ def asOsgiImport = packageName + (importVersionRange map {";version=\"" + _ + '"'} getOrElse(""))
+ }
+
+
+ val GuavaBasePackage = "com.google.common"
+ val InfiniteVersion = 99999
+
+ def calculateImports(referencedPackages : Set[String],
+ implementedPackages : Set[String],
+ exportedPackages : Map[String, Export]) : Map[String, Import] = {
+ (for {
+ undefinedPackage <- referencedPackages diff implementedPackages
+ export <- exportedPackages.get(undefinedPackage)
+ } yield undefinedPackage -> Import(undefinedPackage, version(export)))(
+ collection.breakOut)
+ }
+
+ def version(export: Export): Option[String] =
+ export.parameters.find(_.name == "version").map(_.value)
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Extractors.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Extractors.scala
new file mode 100644
index 00000000000..9d51d7c6d6d
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Extractors.scala
@@ -0,0 +1,17 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.util
+
+/**
+* @author tonytv
+*/
+object Extractors {
+ class ListOf[C](val c : Class[C]) {
+ def unapply[X](xs : X) : Option[List[C]] = {
+ xs match {
+ case x :: xr if c.isInstance(x) => unapply(xr) map ( c.cast(x) :: _)
+ case Nil => Some(Nil)
+ case _ => None
+ }
+ }
+ }
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Files.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Files.scala
new file mode 100644
index 00000000000..b84bd253867
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Files.scala
@@ -0,0 +1,23 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.util
+
+import java.io.{FileOutputStream, File}
+import com.yahoo.container.plugin.util.IO._
+
+/**
+ * @author tonytv
+ */
+object Files {
+ def allDescendantFiles(file: File): Stream[File] = {
+ if (file.isFile)
+ Stream(file)
+ else if (file.isDirectory)
+ file.listFiles().toStream.map(allDescendantFiles).flatten
+ else
+ Stream.empty
+ }
+
+ def withFileOutputStream[T](file: File)(f: FileOutputStream => T): T = {
+ using(new FileOutputStream(file), readOnly = false)(f)
+ }
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/IO.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/IO.scala
new file mode 100644
index 00000000000..e33a9575d55
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/IO.scala
@@ -0,0 +1,46 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.util
+
+import java.io.{Closeable, FileOutputStream, OutputStream, FileInputStream, File}
+import util.control.Exception
+import scala.Either
+
+/** Utility methods relating to IO
+ * @author tonytv
+ */
+object IO {
+ def withFileInputStream[T](file : File)(f : FileInputStream => T) = {
+ using(new FileInputStream(file), readOnly = true)(f)
+ }
+
+ /**
+ * Creates a new file and all it's parent directories,
+ * and provides a file output stream to the file.
+ *
+ * Exceptions from closing have priority over exceptions from f.
+ */
+ def withFileOutputStream[T](file: File)(f: OutputStream => T) {
+ makeDirectoriesRecursive(file.getParentFile)
+ using(new FileOutputStream(file), readOnly = false )(f)
+ }
+
+ def makeDirectoriesRecursive(file: File) {
+ if (!file.mkdirs() && !file.isDirectory) {
+ throw new RuntimeException("Could not create directory " + file.getPath)
+ }
+ }
+
+ def using[RESOURCE <: Closeable, T](resource : RESOURCE, readOnly : Boolean)(f : RESOURCE => T) : T = {
+ def catchPromiscuously = Exception.catchingPromiscuously(classOf[Throwable])
+
+ val resultOrException = catchPromiscuously either f(resource)
+ val closeException = Exception.allCatch either resource.close()
+
+ prioritizeFirstException(
+ resultOrException,
+ if (readOnly) Right(()) else closeException) fold (throw _, identity)
+ }
+
+ private def prioritizeFirstException[T](first: Either[Throwable, T], second: Either[Throwable, Unit]) =
+ first fold ( Left(_), value => second fold ( Left(_), _ => Right(value) ) )
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Iteration.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Iteration.scala
new file mode 100644
index 00000000000..ec16ce712e6
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Iteration.scala
@@ -0,0 +1,14 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.util
+
+ /**
+ * @author tonytv
+ */
+object Iteration {
+ def toStream[T](enumeration: java.util.Enumeration[T]): Stream[T] = {
+ if (enumeration.hasMoreElements)
+ Stream.cons(enumeration.nextElement(), toStream(enumeration))
+ else
+ Stream.Empty
+ }
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/JarFiles.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/JarFiles.scala
new file mode 100644
index 00000000000..86e46295448
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/JarFiles.scala
@@ -0,0 +1,24 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.util
+
+import java.util.jar.JarFile
+import java.util.zip.{ZipFile, ZipEntry}
+import IO.using
+import java.io.{Closeable, InputStream, File}
+
+/**
+ * @author tonytv
+ */
+object JarFiles {
+ def withJarFile[T](file : File)(f : JarFile => T ) : T =
+ using(new JarFile(file) with Closeable, readOnly = true)(f)
+
+ def withInputStream[T](zipFile: ZipFile, zipEntry: ZipEntry)(f: InputStream => T): T =
+ using(zipFile.getInputStream(zipEntry), readOnly = true)(f)
+
+ def getManifest(jarFile : File) : Option[java.util.jar.Manifest] = {
+ withJarFile(jarFile) { jar =>
+ Option(jar.getManifest)
+ }
+ }
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Maps.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Maps.scala
new file mode 100644
index 00000000000..7f12c5ba95d
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Maps.scala
@@ -0,0 +1,19 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.util
+
+import collection.mutable.MultiMap
+
+/**
+ * @author tonytv
+ */
+object Maps {
+ def combine[K, V](map1 : Map[K, V], map2 : Map[K, V])(f : (V, V) => V) : Map[K, V] = {
+ def logicError : V = throw new RuntimeException("Logic error.")
+ def combineValues(key : K) = key -> f(map1.getOrElse(key, logicError), map2.getOrElse(key, logicError))
+
+ val keysInBoth = map1.keySet intersect map2.keySet
+ def notInBoth = !keysInBoth.contains(_ : K)
+
+ map1.filterKeys(notInBoth) ++ map2.filterKeys(notInBoth) ++ keysInBoth.map(combineValues)
+ }
+}
diff --git a/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Strings.scala b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Strings.scala
new file mode 100644
index 00000000000..a0c3c1d001f
--- /dev/null
+++ b/bundle-plugin/src/main/scala/com/yahoo/container/plugin/util/Strings.scala
@@ -0,0 +1,14 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.util
+
+ /**
+ * @author tonytv
+ */
+object Strings {
+ def emptyStringTo(replacement: String)(s: String) = {
+ if (s.isEmpty) replacement
+ else s
+ }
+
+ def noneIfEmpty(s: String) = Option(s).filterNot(_.isEmpty)
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Base.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Base.java
new file mode 100644
index 00000000000..e3e33d394ab
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Base.java
@@ -0,0 +1,27 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis.sampleclasses;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.Kernel;
+
+/**
+ * Input for class analysis tests.
+ * @author tonytv
+ */
+public class Base implements Interface1, Interface2 {
+ @Override
+ public Image methodSignatureTest(Kernel kernel, BufferedImage image) {
+ return null;
+ }
+
+ @Override
+ public int ignoreBasicTypes(float f) {
+ return 0;
+ }
+
+ @Override
+ public int[] ignoreArrayOfBasicTypes(int[][] l) {
+ return new int[0];
+ }
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/CatchException.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/CatchException.java
new file mode 100644
index 00000000000..86bbb06c265
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/CatchException.java
@@ -0,0 +1,15 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis.sampleclasses;
+
+import javax.security.auth.login.LoginException;
+
+/**
+ * @author tonytv
+ */
+public class CatchException {
+ void ignored() throws Exception{
+ try {
+ throw new Exception("dummy");
+ } catch(LoginException classToDetect) {}
+ }
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassAnnotation.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassAnnotation.java
new file mode 100644
index 00000000000..53fee52faac
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassAnnotation.java
@@ -0,0 +1,10 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis.sampleclasses;
+
+/**
+ * Input for class analysis tests.*
+ * @author tonytv
+ */
+@DummyAnnotation
+public class ClassAnnotation {
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassReference.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassReference.java
new file mode 100644
index 00000000000..d75317aa0fc
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassReference.java
@@ -0,0 +1,11 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis.sampleclasses;
+
+/**
+ * @author tonytv
+ */
+public class ClassReference {
+ void classReference() {
+ Class<?> clazz = Interface1.class;
+ }
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassWithMethod.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassWithMethod.java
new file mode 100644
index 00000000000..213f809cc31
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/ClassWithMethod.java
@@ -0,0 +1,9 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis.sampleclasses;
+
+/**
+ * @author tonytv
+ */
+public class ClassWithMethod {
+ public static void test() {}
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Derived.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Derived.java
new file mode 100644
index 00000000000..c5a3ffc8481
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Derived.java
@@ -0,0 +1,9 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis.sampleclasses;
+
+/**
+ * Input for class analysis tests.
+ * @author tonytv
+ */
+public class Derived extends Base {
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Dummy.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Dummy.java
new file mode 100644
index 00000000000..7d193dcdb40
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Dummy.java
@@ -0,0 +1,7 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis.sampleclasses;
+
+/**
+ * @author tonytv
+ */
+public class Dummy {}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/DummyAnnotation.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/DummyAnnotation.java
new file mode 100644
index 00000000000..155171d00dd
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/DummyAnnotation.java
@@ -0,0 +1,9 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis.sampleclasses;
+
+/**
+ * Input for class analysis tests.
+ * @author tonytv
+ */
+public @interface DummyAnnotation {
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Fields.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Fields.java
new file mode 100644
index 00000000000..60eea01fb40
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Fields.java
@@ -0,0 +1,17 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis.sampleclasses;
+
+import java.util.List;
+
+/**
+ * Input for class analysis tests.
+ * @author tonytv
+ */
+public class Fields {
+ @DummyAnnotation
+ public String field1;
+ public static List<Object> field2;
+
+ public int field3;
+ public int[] field4;
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Interface1.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Interface1.java
new file mode 100644
index 00000000000..71395c224fb
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Interface1.java
@@ -0,0 +1,17 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis.sampleclasses;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.ImagingOpException;
+import java.awt.image.Kernel;
+
+/**
+ * Input for class analysis tests.
+ * @author tonytv
+ */
+public interface Interface1 extends Interface2 {
+ Image methodSignatureTest(Kernel kernel, BufferedImage image);
+ int ignoreBasicTypes(float f) throws ImagingOpException;
+ int[] ignoreArrayOfBasicTypes(int[][] l);
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Interface2.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Interface2.java
new file mode 100644
index 00000000000..2591f3299c9
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Interface2.java
@@ -0,0 +1,9 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis.sampleclasses;
+
+/**
+ * Input for class analysis tests.
+ * @author tonytv
+ */
+public interface Interface2 {
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/MethodAnnotation.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/MethodAnnotation.java
new file mode 100644
index 00000000000..3c0ce1e92dd
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/MethodAnnotation.java
@@ -0,0 +1,11 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis.sampleclasses;
+
+/**
+ * Input for class analysis tests.
+ * @author tonytv
+ */
+public interface MethodAnnotation {
+ @DummyAnnotation
+ void dummy();
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/MethodInvocation.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/MethodInvocation.java
new file mode 100644
index 00000000000..af2933dc673
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/MethodInvocation.java
@@ -0,0 +1,12 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis.sampleclasses;
+
+/**
+ * @author tonytv
+ */
+public class MethodInvocation {
+ void invokeMethod() {
+ Interface1 interface1 = null;
+ Object o = interface1.methodSignatureTest(null, null);
+ }
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Methods.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Methods.java
new file mode 100644
index 00000000000..37518093e36
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/Methods.java
@@ -0,0 +1,32 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis.sampleclasses;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Input for class analysis tests.
+ * @author tonytv
+ */
+public class Methods {
+ public void method1() {
+ Base b = new Base();
+ System.out.println(Fields.field2.size());
+
+ Object o = new Interface1[1][2][3];
+ int[][][] arr = new int[1][2][3];
+ int[] arr2 = new int[1];
+
+ System.out.println(new int[1].length + "--" + arr2[0]);
+
+ int[][] exerciseTypeInsn = new int[1][];
+
+ Runnable methodHandle = ClassWithMethod::test;
+ }
+
+ public static void method2() {
+ Derived d = new Derived();
+ }
+
+ public void methodTakingGenericArgument(Map<String, List<Dummy>> map) {}
+}
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/invalid/package-info.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/invalid/package-info.java
new file mode 100644
index 00000000000..500e955168e
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/invalid/package-info.java
@@ -0,0 +1,6 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage(version = @Version(qualifier = "EXAMPLE INVALID QUALIFIER"))
+package com.yahoo.container.plugin.classanalysis.sampleclasses.invalid;
+
+import com.yahoo.osgi.annotation.ExportPackage;
+import com.yahoo.osgi.annotation.Version;
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/package-info.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/package-info.java
new file mode 100644
index 00000000000..c17e0e2b158
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/classanalysis/sampleclasses/package-info.java
@@ -0,0 +1,6 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage(version = @Version(major = 3, minor = 1, micro = 4, qualifier = "TEST_QUALIFIER-2"))
+package com.yahoo.container.plugin.classanalysis.sampleclasses;
+
+import com.yahoo.osgi.annotation.ExportPackage;
+import com.yahoo.osgi.annotation.Version;
diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/mojo/GenerateSourcesMojoTest.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/mojo/GenerateSourcesMojoTest.java
new file mode 100644
index 00000000000..119100d9a5e
--- /dev/null
+++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/mojo/GenerateSourcesMojoTest.java
@@ -0,0 +1,25 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.mojo;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeNotNull;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class GenerateSourcesMojoTest {
+
+ @Test
+ public void requireThatDefaultConfigGenVersionIsLoadedFromBuildProperties() throws MojoExecutionException {
+ String expected = System.getProperty("expectedDefaultConfigGenVersion");
+ System.out.println("expectedDefaultConfigGenVersion = " + expected);
+ assumeNotNull(expected);
+
+ String actual = GenerateSourcesMojo.loadDefaultConfigGenVersion();
+ System.out.println("defaultConfigGenVersion = " + actual);
+ assertEquals(expected, actual);
+ }
+}
diff --git a/bundle-plugin/src/test/resources/class/Utf8.class b/bundle-plugin/src/test/resources/class/Utf8.class
new file mode 100644
index 00000000000..0650a4eb061
--- /dev/null
+++ b/bundle-plugin/src/test/resources/class/Utf8.class
Binary files differ
diff --git a/bundle-plugin/src/test/resources/jar/errorExport.jar b/bundle-plugin/src/test/resources/jar/errorExport.jar
new file mode 100644
index 00000000000..6b9eeb94244
--- /dev/null
+++ b/bundle-plugin/src/test/resources/jar/errorExport.jar
Binary files differ
diff --git a/bundle-plugin/src/test/resources/jar/notAOsgiBundle.jar b/bundle-plugin/src/test/resources/jar/notAOsgiBundle.jar
new file mode 100644
index 00000000000..922baa37591
--- /dev/null
+++ b/bundle-plugin/src/test/resources/jar/notAOsgiBundle.jar
Binary files differ
diff --git a/bundle-plugin/src/test/resources/jar/simple1.jar b/bundle-plugin/src/test/resources/jar/simple1.jar
new file mode 100644
index 00000000000..6a9a5fbcbe7
--- /dev/null
+++ b/bundle-plugin/src/test/resources/jar/simple1.jar
Binary files differ
diff --git a/bundle-plugin/src/test/resources/manifest/simple.MF b/bundle-plugin/src/test/resources/manifest/simple.MF
new file mode 100644
index 00000000000..74c0c1fc19a
--- /dev/null
+++ b/bundle-plugin/src/test/resources/manifest/simple.MF
@@ -0,0 +1,11 @@
+Manifest-Version: 1.0
+Archiver-Version: Plexus Archiver
+Created-By: Apache Maven
+Built-By: root
+Build-Jdk: 1.6.0_17
+Bundle-ManifestVersion: 2
+Bundle-Name: com.yahoo.sample.bundle.name
+Bundle-SymbolicName: com.yahoo.sample.symboli.name
+Bundle-Version: 1
+Bundle-Vendor: Yahoo!
+Export-Package: com.yahoo.sample.exported.package;version="1.2.3.sample"
diff --git a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.scala b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.scala
new file mode 100644
index 00000000000..7b2a840bc44
--- /dev/null
+++ b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/bundle/AnalyzeBundleTest.scala
@@ -0,0 +1,39 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.bundle
+
+import org.scalatest.junit.{JUnitSuite, ShouldMatchersForJUnit}
+import org.junit.Test
+import com.yahoo.container.plugin.bundle.AnalyzeBundle.PublicPackages
+import com.yahoo.container.plugin.osgi.ExportPackages
+import java.io.File
+
+/**
+ * @author tonytv
+ */
+class AnalyzeBundleTest extends JUnitSuite with ShouldMatchersForJUnit {
+ val jarDir = new File("src/test/resources/jar")
+
+ val PublicPackages(exports, globals) = AnalyzeBundle.publicPackagesAggregated(
+ List("notAOsgiBundle.jar", "simple1.jar").map(jarFile))
+
+ val exportsByPackageName = ExportPackages.exportsByPackageName(exports)
+
+ def jarFile(name : String) : File = new File(jarDir, name)
+
+ @Test
+ def require_that_non_osgi_bundles_are_ignored() {
+ exportsByPackageName should not contain key("com.yahoo.sample.exported.package.ignored")
+ }
+
+ @Test
+ def require_that_exports_are_retrieved_from_manifest_in_jars() {
+ exportsByPackageName.keySet should be (Set("com.yahoo.sample.exported.package"))
+ }
+
+ @Test
+ def require_that_invalid_exports_throws_exception() {
+ val exception = intercept[Exception](AnalyzeBundle.publicPackages(jarFile("errorExport.jar")))
+ exception.getMessage should startWith regex ("Invalid manifest in bundle '.*errorExport.jar'")
+ exception.getCause.getMessage should startWith ("Failed parsing Export-Package")
+ }
+}
diff --git a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassTest.scala b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassTest.scala
new file mode 100644
index 00000000000..3a8e67272a9
--- /dev/null
+++ b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeClassTest.scala
@@ -0,0 +1,115 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis
+
+import org.junit.Test
+import org.scalatest.junit.{ShouldMatchersForJUnit, JUnitSuite}
+import java.awt.Image
+import java.awt.image.{ImagingOpException, Kernel}
+import sampleclasses._
+import TestUtilities._
+import com.yahoo.osgi.annotation.{ExportPackage, Version}
+import javax.security.auth.login.LoginException
+
+/**
+ * Tests that analysis of class files works.
+ * @author tonytv
+ */
+class AnalyzeClassTest extends JUnitSuite with ShouldMatchersForJUnit {
+ @Test def require_that_full_class_name_is_returned() {
+ analyzeClass[Base].name should be(name[Base])
+ }
+
+ @Test def require_that_base_class_is_included() {
+ analyzeClass[Derived].referencedClasses should contain (name[Base])
+ }
+
+ @Test def require_that_implemented_interfaces_are_included() {
+ analyzeClass[Base].referencedClasses should (contain (name[Interface1]) and contain (name[Interface2]))
+ }
+
+ @Test def require_that_interface_can_be_analyzed() {
+ val classMetaData = analyzeClass[Interface1]
+
+ classMetaData.name should be(name[Interface1])
+ classMetaData.referencedClasses should contain(name[Interface2])
+ }
+
+ @Test def require_that_return_type_is_included() {
+ analyzeClass[Interface1].referencedClasses should contain (name[Image])
+ }
+
+ @Test def require_that_parameters_are_included() {
+ analyzeClass[Interface1].referencedClasses should contain (name[Kernel])
+ }
+
+ @Test def require_that_exceptions_are_included() {
+ analyzeClass[Interface1].referencedClasses should contain (name[ImagingOpException])
+ }
+
+ @Test def require_that_basic_types_ignored() {
+ analyzeClass[Interface1].referencedClasses should not (contain ("int") or contain ("float"))
+ }
+
+ @Test def require_that_arrays_of_basic_types_ignored() {
+ analyzeClass[Interface1].referencedClasses should not (contain ("int[]") or contain ("int[][]"))
+ }
+
+ @Test def require_that_instance_field_types_are_included() {
+ analyzeClass[Fields].referencedClasses should contain (name[String])
+ }
+
+ @Test def require_that_static_field_types_are_included() {
+ analyzeClass[Fields].referencedClasses should contain (name[java.util.List[_]])
+ }
+
+ @Test def require_that_field_annotation_is_included() {
+ analyzeClass[Fields].referencedClasses should contain (name[DummyAnnotation])
+ }
+
+ @Test def require_that_class_annotation_is_included() {
+ analyzeClass[ClassAnnotation].referencedClasses should contain(name[DummyAnnotation])
+ }
+
+ @Test def require_that_method_annotation_is_included() {
+ analyzeClass[MethodAnnotation].referencedClasses should contain(name[DummyAnnotation])
+ }
+
+ @Test def require_that_export_package_annotations_are_ignored() {
+ Analyze.analyzeClass(classFile("com.yahoo.container.plugin.classanalysis.sampleclasses.package-info")).
+ referencedClasses should not (contain (name[ExportPackage]) or contain (name[Version]))
+ }
+
+ @Test def require_that_export_annotations_are_processed() {
+ Analyze.analyzeClass(classFile("com.yahoo.container.plugin.classanalysis.sampleclasses.package-info")).
+ exportPackage should be (Some(ExportPackageAnnotation(3, 1, 4, "TEST_QUALIFIER-2")))
+ }
+
+ @Test def require_that_export_annotations_are_validated() {
+ val exception = intercept[RuntimeException] {
+ Analyze.analyzeClass(classFile("com.yahoo.container.plugin.classanalysis.sampleclasses.invalid.package-info"))
+ }
+
+ exception.getMessage should include ("invalid/package-info")
+ exception.getCause.getMessage should include ("qualifier must follow the format")
+ exception.getCause.getMessage should include ("'EXAMPLE INVALID QUALIFIER'")
+ }
+
+ @Test def require_that_catch_clauses_are_included() {
+ Analyze.analyzeClass(classFile("com.yahoo.container.plugin.classanalysis.sampleclasses.CatchException")).
+ referencedClasses should contain(name[LoginException])
+ }
+
+ @Test def require_that_class_references_are_included() {
+ Analyze.analyzeClass(classFile("com.yahoo.container.plugin.classanalysis.sampleclasses.ClassReference")).
+ referencedClasses should contain(name[Interface1])
+ }
+
+ @Test def require_that_return_type_of_method_invocations_are_included() {
+ analyzeClass[MethodInvocation].referencedClasses should contain(name[Image])
+ }
+
+ @Test def require_that_attributes_are_included() {
+ //Uses com/coremedia/iso/Utf8.class from com.googlecode.mp4parser:isoparser:1.0-RC-1
+ Analyze.analyzeClass(classFile("class/Utf8")).referencedClasses should contain("org.aspectj.weaver.MethodDeclarationLineNumber")
+ }
+}
diff --git a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodBodyTest.scala b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodBodyTest.scala
new file mode 100644
index 00000000000..24fa26b97e1
--- /dev/null
+++ b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/AnalyzeMethodBodyTest.scala
@@ -0,0 +1,50 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis
+
+import org.scalatest.junit.{JUnitSuite, ShouldMatchersForJUnit}
+import sampleclasses._
+import TestUtilities._
+import org.junit.Test
+import java.io.PrintStream
+
+/**
+ * Tests that classes used in method bodies are included in the imports list.
+ * @author tonytv
+ */
+class AnalyzeMethodBodyTest extends JUnitSuite with ShouldMatchersForJUnit {
+ @Test def require_that_class_of_locals_are_included() {
+ analyzeClass[Methods].referencedClasses should contain(name[Base])
+ }
+
+ @Test def require_that_class_of_locals_in_static_method_are_included() {
+ analyzeClass[Methods].referencedClasses should contain(name[Derived])
+ }
+
+ @Test def require_that_field_references_are_included() {
+ analyzeClass[Methods].referencedClasses should (contain (name[java.util.List[_]]) and contain (name[Fields]))
+ }
+
+ @Test def require_that_class_owning_field_is_included() {
+ analyzeClass[Methods].referencedClasses should contain (name[System])
+ }
+
+ @Test def require_that_class_containing_method_is_included() {
+ analyzeClass[Methods].referencedClasses should contain (name[PrintStream])
+ }
+
+ @Test def require_that_element_of_new_multidimensional_array_is_included() {
+ analyzeClass[Methods].referencedClasses should contain (name[Interface1])
+ }
+
+ @Test def require_that_basic_arrays_are_not_included() {
+ analyzeClass[Methods].referencedClasses should not (contain("int[]"))
+ }
+
+ @Test def require_that_container_generic_parameters_are_included() {
+ analyzeClass[Methods].referencedClasses should contain(name[Dummy])
+ }
+
+ @Test def require_that_class_owning_method_handler_is_included() {
+ analyzeClass[Methods].referencedClasses should contain(name[ClassWithMethod])
+ }
+}
diff --git a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/TestUtilities.scala b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/TestUtilities.scala
new file mode 100644
index 00000000000..8c3d9bc5d5c
--- /dev/null
+++ b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/classanalysis/TestUtilities.scala
@@ -0,0 +1,18 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis
+
+import scala.reflect.ClassTag
+import java.io.File
+
+/**
+ * @author tonytv
+ */
+object TestUtilities {
+ def analyzeClass[T](implicit m: ClassTag[T]) =
+ Analyze.analyzeClass(classFile(name(m)))
+
+ def classFile(className : String) =
+ new File("target/test-classes/" + className.replace('.', '/') + ".class")
+
+ def name[T](implicit m: ClassTag[T]) = m.runtimeClass.getName
+}
diff --git a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ExportPackageParserTest.scala b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ExportPackageParserTest.scala
new file mode 100644
index 00000000000..1a4767206a1
--- /dev/null
+++ b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ExportPackageParserTest.scala
@@ -0,0 +1,63 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.osgi
+
+import org.scalatest.junit.{JUnitSuite, ShouldMatchersForJUnit}
+import org.junit.Test
+
+import ExportPackages.{Parameter, Export}
+
+/**
+ * @author tonytv
+ */
+class ExportPackageParserTest extends JUnitSuite with ShouldMatchersForJUnit {
+ val versionParameter = Parameter("version", "1.2.3.sample")
+
+ @Test
+ def require_that_package_is_parsed_correctly() {
+ ExportPackageParser.parseAll("sample.exported.package").get should
+ be (List(Export(List("sample.exported.package"), List())))
+ }
+
+ @Test
+ def require_that_version_is_parsed_correctly() {
+ ExportPackageParser.parseAll("com.yahoo.sample.exported.package;version=\"1.2.3.sample\"").get should
+ be (List(Export(List("com.yahoo.sample.exported.package"), List(versionParameter))))
+ }
+
+ @Test
+ def require_that_multiple_packages_with_same_parameters_is_parsed_correctly() {
+ ExportPackageParser.parseAll("exported.package1;exported.package2;version=\"1.2.3.sample\"").get should
+ be (List(Export(List("exported.package1", "exported.package2"), List(versionParameter))))
+ }
+
+ @Test
+ def require_that_multiple_parameters_for_a_package_is_parsed_correctly() {
+ ExportPackageParser.parseAll("exported.package;version=\"1.2.3.sample\";param2=true").get.head.parameters should
+ be (List(versionParameter, Parameter("param2", "true")))
+ }
+
+ @Test
+ def require_that_multiple_exports_are_parsed_correctly() {
+ ExportPackageParser.parseAll("exported.package1,exported.package2").get should
+ be(List(
+ Export(List("exported.package1"), List()),
+ Export(List("exported.package2"), List())))
+
+ ExportPackageParser.parseAll("exported.package1;version=\"1.2.3.sample\",exported.package2").get should
+ be (List(
+ Export(List("exported.package1"), List(versionParameter)),
+ Export(List("exported.package2"), List())))
+
+ ExportPackageParser.parseAll("exported.package1,exported.package2;version=\"1.2.3.sample\"").get should
+ be(List(
+ Export(List("exported.package1"), List()),
+ Export(List("exported.package2"), List(versionParameter))))
+ }
+
+ @Test
+ def require_that_long_string_literals_do_not_cause_stack_overflow_error() {
+ //From jersey-server-1.13.jar
+ val export = """com.sun.jersey.server.impl.wadl;uses:="com.sun.jersey.api.model,com.sun.research.ws.wadl,com.sun.jersey.api.wadl.config,com.sun.jersey.server.wadl,com.sun.jersey.api.core,javax.xml.bind,javax.ws.rs.core,com.sun.jersey.server.impl.uri,com.sun.jersey.core.spi.factory,com.sun.jersey.server.impl.model.method,com.sun.jersey.api.uri,com.sun.jersey.core.header,com.sun.jersey.spi.dispatch,javax.ws.rs,com.sun.jersey.spi.resource";version="1.13.0",com.sun.jersey.server.impl.model.parameter.multivalued;uses:="com.sun.jersey.spi,javax.ws.rs.core,com.sun.jersey.api.container,com.sun.jersey.impl,javax.xml.parsers,org.xml.sax,javax.xml.transform,javax.xml.bind.annotation,javax.xml.transform.sax,com.sun.jersey.spi.inject,javax.xml.bind,javax.ws.rs.ext,com.sun.jersey.api.model,com.sun.jersey.core.reflection,javax.ws.rs,com.sun.jersey.core.spi.component,com.sun.jersey.core.header";version="1.13.0",com.sun.jersey.server.impl.model.parameter;uses:="com.sun.jersey.api.model,com.sun.jersey.core.spi.component,com.sun.jersey.server.impl.model.parameter.multivalued,com.sun.jersey.spi.inject,com.sun.jersey.api,com.sun.jersey.api.core,com.sun.jersey.server.impl.inject,javax.ws.rs.core,javax.ws.rs,com.sun.jersey.core.header,com.sun.jersey.api.representation";version="1.13.0",com.sun.jersey.server.impl.application;uses:="com.sun.jersey.core.spi.component,com.sun.jersey.api.core,com.sun.jersey.spi,com.sun.jersey.spi.inject,javax.ws.rs.core,com.sun.jersey.api.container,javax.ws.rs.ext,com.sun.jersey.spi.container,com.sun.jersey.core.reflection,com.sun.jersey.api.model,com.sun.jersey.impl,com.sun.jersey.spi.dispatch,com.sun.jersey.server.impl.model,com.sun.jersey.server.impl.wadl,com.sun.jersey.server.impl.uri,com.sun.jersey.core.spi.factory,com.sun.jersey.api.uri,com.sun.jersey.server.impl.uri.rules,com.sun.jersey.spi.uri.rules,com.sun.jersey.server.spi.component,com.sun.jersey.core.util,com.sun.jersey.core.header,com.sun.jersey.core.spi.component.ioc,javax.ws.rs,com.sun.jersey.server.impl,com.sun.jersey.server.wadl,com.sun.jersey.server.impl.inject,com.sun.jersey.server.impl.component,com.sun.jersey.spi.monitoring,com.sun.jersey.server.impl.monitoring,com.sun.jersey.api.container.filter,com.sun.jersey.server.impl.model.parameter.multivalued,com.sun.jersey.server.impl.model.parameter,com.sun.jersey.server.impl.template,com.sun.jersey.spi.template,com.sun.jersey.server.impl.resource,com.sun.jersey.server.impl.modelapi.annotation,com.sun.jersey.server.impl.container.filter,com.sun.jersey.server.impl.modelapi.validation,com.sun.jersey.api,com.sun.jersey.spi.service";version="1.13.0",com.sun.jersey.server.impl.component;uses:="com.sun.jersey.api.model,com.sun.jersey.core.spi.component,com.sun.jersey.server.spi.component,com.sun.jersey.api.core,com.sun.jersey.core.spi.component.ioc,com.sun.jersey.server.impl.inject,com.sun.jersey.server.impl.resource,com.sun.jersey.api.container,com.sun.jersey.core.reflection,com.sun.jersey.spi.inject";version="1.13.0",com.sun.jersey.server.impl.provider;uses:="com.sun.jersey.core.spi.factory,com.sun.jersey.api.container,com.sun.jersey.api.core,javax.ws.rs.core";version="1.13.0",com.sun.jersey.server.impl.template;uses:="com.sun.jersey.core.spi.component,com.sun.jersey.api.view,com.sun.jersey.spi.template,javax.ws.rs.core,com.sun.jersey.core.header,com.sun.jersey.server.impl.model.method,com.sun.jersey.spi.dispatch,com.sun.jersey.api.uri,javax.ws.rs,com.sun.jersey.spi.inject,javax.ws.rs.ext,com.sun.jersey.server.impl.uri.rules,com.sun.jersey.server.probes,com.sun.jersey.core.reflection,com.sun.jersey.spi.uri.rules,com.sun.jersey.spi.container,com.sun.jersey.api.core";version="1.13.0",com.sun.jersey.server.osgi;uses:="com.sun.jersey.server.impl.provider,org.osgi.framework,javax.ws.rs.ext";version="1.13.0",com.sun.jersey.server.wadl.generators.resourcedoc.model;uses:="javax.xml.bind.annotation,javax.xml.namespace";version="1.13.0",com.sun.jersey.server.impl.resource;uses:="com.sun.jersey.api.model,com.sun.jersey.core.spi.component,com.sun.jersey.server.spi.component,com.sun.jersey.api.core,com.sun.jersey.api.container,javax.ws.rs,com.sun.jersey.server.impl.inject,com.sun.jersey.core.spi.component.ioc,javax.ws.rs.core";version="1.13.0",com.sun.jersey.server.impl.monitoring;uses:="com.sun.jersey.spi.monitoring,com.sun.jersey.spi.service,com.sun.jersey.api.model,com.sun.jersey.spi.container,javax.ws.rs.ext,com.sun.jersey.core.spi.component";version="1.13.0",com.sun.jersey.server.impl.modelapi.annotation;uses:="com.sun.jersey.api.model,javax.ws.rs.core,javax.ws.rs,com.sun.jersey.core.reflection,com.sun.jersey.core.header,com.sun.jersey.impl";version="1.13.0",com.sun.jersey.server.impl.container;uses:="com.sun.jersey.server.impl.application,com.sun.jersey.spi.container,com.sun.jersey.api.container";version="1.13.0",com.sun.jersey.server.wadl;uses:="javax.ws.rs.core,com.sun.research.ws.wadl,javax.xml.namespace,com.sun.jersey.api.model,javax.xml.bind,javax.ws.rs,com.sun.jersey.server.wadl.generators,com.sun.jersey.server.impl.modelapi.annotation,com.sun.jersey.server.impl";version="1.13.0",com.sun.jersey.server.impl.model.method.dispatch;uses:="com.sun.jersey.api.model,com.sun.jersey.api.core,com.sun.jersey.spi.container,com.sun.jersey.server.impl.inject,com.sun.jersey.api,javax.ws.rs.core,com.sun.jersey.core.spi.factory,com.sun.jersey.spi.inject,com.sun.jersey.spi.dispatch,com.sun.jersey.core.spi.component,javax.ws.rs,com.sun.jersey.server.impl.model.parameter.multivalued,com.sun.jersey.api.representation,com.sun.jersey.api.container";version="1.13.0",com.sun.jersey.server.impl;uses:="javax.naming,com.sun.jersey.api.core,com.sun.jersey.core.header,javax.ws.rs.core,com.sun.jersey.server.impl.model,com.sun.jersey.spi.container";version="1.13.0",com.sun.jersey.server.wadl.generators.resourcedoc;uses:="com.sun.jersey.api.model,com.sun.jersey.server.wadl.generators.resourcedoc.model,com.sun.jersey.server.wadl.generators.resourcedoc.xhtml,com.sun.research.ws.wadl,javax.xml.namespace,com.sun.jersey.server.wadl,javax.xml.bind,javax.ws.rs.core";version="1.13.0",com.sun.jersey.server.impl.container.httpserver;uses:="com.sun.net.httpserver,com.sun.jersey.spi.container,javax.ws.rs.core,com.sun.jersey.core.header,com.sun.jersey.api.container,com.sun.jersey.core.util,com.sun.jersey.api.core";version="1.13.0",com.sun.jersey.server.impl.container.filter;uses:="com.sun.jersey.api.model,com.sun.jersey.spi.container,com.sun.jersey.core.spi.component,com.sun.jersey.api.core,javax.ws.rs,com.sun.jersey.server.impl.uri,javax.ws.rs.core";version="1.13.0",com.sun.jersey.server.wadl.generators.resourcedoc.xhtml;uses:="javax.xml.bind,javax.xml.namespace,javax.xml.bind.annotation";version="1.13.0",com.sun.jersey.server.impl.uri.rules;uses:="com.sun.jersey.spi.uri.rules,com.sun.jersey.api.uri,com.sun.jersey.api.core,com.sun.jersey.server.impl.model.method,com.sun.jersey.spi.dispatch,com.sun.jersey.core.header,javax.ws.rs.core,com.sun.jersey.api.model,com.sun.jersey.server.probes,com.sun.jersey.core.reflection,com.sun.jersey.server.impl.template,com.sun.jersey.spi.monitoring,com.sun.jersey.api,com.sun.jersey.spi.container,com.sun.jersey.server.impl.uri,javax.ws.rs,com.sun.jersey.api.container,com.sun.jersey.server.impl.inject,com.sun.jersey.spi.inject,com.sun.jersey.server.impl.uri.rules.automata";version="1.13.0",com.sun.jersey.server.spi.component;uses:="com.sun.jersey.spi.inject,com.sun.jersey.api.model,com.sun.jersey.core.spi.component,com.sun.jersey.api.core,com.sun.jersey.server.impl.inject,com.sun.jersey.api.container,com.sun.jersey.core.spi.component.ioc";version="1.13.0",com.sun.jersey.server.probes;version="1.13.0",com.sun.jersey.server.wadl.generators;uses:="com.sun.research.ws.wadl,javax.xml.bind.annotation,com.sun.jersey.api.model,com.sun.jersey.server.wadl,javax.xml.bind,javax.ws.rs.core,com.sun.jersey.api,javax.xml.namespace,javax.xml.transform,javax.xml.transform.stream";version="1.13.0",com.sun.jersey.server.impl.modelapi.validation;uses:="com.sun.jersey.api.model,javax.ws.rs,com.sun.jersey.impl,com.sun.jersey.api.core,com.sun.jersey.core.reflection,javax.ws.rs.core";version="1.13.0",com.sun.jersey.server.impl.model.method;uses:="com.sun.jersey.api.container,com.sun.jersey.spi.dispatch,com.sun.jersey.api.uri,com.sun.jersey.api.model,com.sun.jersey.server.impl.container.filter,com.sun.jersey.impl,com.sun.jersey.spi.container,com.sun.jersey.spi.inject,com.sun.jersey.api.core,javax.ws.rs.core,com.sun.jersey.core.header";version="1.13.0",com.sun.jersey.server.impl.model;uses:="javax.ws.rs,com.sun.jersey.impl,com.sun.jersey.api.container,com.sun.jersey.core.header,com.sun.jersey.core.header.reader,com.sun.jersey.api.core,javax.ws.rs.core,com.sun.jersey.server.impl.model.method,com.sun.jersey.server.impl.container.filter,com.sun.jersey.api.model,com.sun.jersey.server.impl.wadl,com.sun.jersey.spi.monitoring,com.sun.jersey.server.impl.uri,com.sun.jersey.spi.container,com.sun.jersey.server.impl.inject,com.sun.jersey.spi.inject,com.sun.jersey.api.uri,com.sun.jersey.core.spi.component,com.sun.jersey.server.impl.uri.rules,com.sun.jersey.server.impl.template,com.sun.jersey.api.view,com.sun.jersey.spi.uri.rules";version="1.13.0",com.sun.jersey.server.impl.uri.rules.automata;uses:="com.sun.jersey.server.impl.uri,com.sun.jersey.spi.uri.rules,com.sun.jersey.server.impl.uri.rules,com.sun.jersey.api.uri";version="1.13.0",com.sun.jersey.server.impl.uri;uses:="com.sun.jersey.api.uri,javax.ws.rs.core";version="1.13.0",com.sun.jersey.server.impl.inject;uses:="com.sun.jersey.api.core,com.sun.jersey.spi.inject,javax.ws.rs,com.sun.jersey.api.container,com.sun.jersey.api.model,com.sun.jersey.core.spi.component,com.sun.jersey.core.spi.factory";version="1.13.0",com.sun.jersey.spi.scanning;uses:="org.objectweb.asm,com.sun.jersey.core.reflection,com.sun.jersey.core.spi.scanning,javax.ws.rs,javax.ws.rs.ext";version="1.13.0",com.sun.jersey.spi.resource;uses:="com.sun.jersey.server.impl.resource,com.sun.jersey.server.spi.component";version="1.13.0",com.sun.jersey.spi.template;uses:="com.sun.jersey.api.view,javax.ws.rs.core,com.sun.jersey.api.container";version="1.13.0",com.sun.jersey.spi.dispatch;uses:="com.sun.jersey.api.core";version="1.13.0",com.sun.jersey.spi.uri.rules;uses:="com.sun.jersey.api.core,com.sun.jersey.api.model,com.sun.jersey.spi.container,com.sun.jersey.api.uri";version="1.13.0",com.sun.jersey.spi.container;uses:="javax.ws.rs,com.sun.jersey.api.representation,com.sun.jersey.core.header,com.sun.jersey.spi,javax.ws.rs.core,com.sun.jersey.api.container,com.sun.jersey.api.core,com.sun.jersey.core.util,com.sun.jersey.core.header.reader,com.sun.jersey.server.impl,com.sun.jersey.core.reflection,javax.ws.rs.ext,com.sun.jersey.server.impl.model,com.sun.jersey.api,com.sun.jersey.api.uri,com.sun.jersey.core.spi.factory,com.sun.jersey.spi.monitoring,com.sun.jersey.api.model,com.sun.jersey.core.spi.component,com.sun.jersey.server.impl.application,com.sun.jersey.impl,com.sun.jersey.spi.inject,com.sun.jersey.spi.dispatch,com.sun.jersey.server.impl.inject,com.sun.jersey.core.spi.component.ioc,com.sun.jersey.spi.service";version="1.13.0",com.sun.jersey.spi.monitoring;uses:="com.sun.jersey.api.model,com.sun.jersey.spi.container,javax.ws.rs.ext";version="1.13.0",com.sun.jersey.api;uses:="javax.ws.rs,javax.ws.rs.core,com.sun.jersey.core.header,com.sun.jersey.core.spi.factory";version="1.13.0",com.sun.jersey.api.core;uses:="javax.ws.rs.core,com.sun.jersey.core.spi.scanning,com.sun.jersey.api.model,com.sun.jersey.api.uri,javax.ws.rs,com.sun.jersey.core.header,com.sun.jersey.api.representation,com.sun.jersey.core.util,javax.ws.rs.ext,com.sun.jersey.api.container,com.sun.jersey.spi.scanning,com.sun.jersey.spi.container,com.sun.jersey.server.impl.application";version="1.13.0",com.sun.jersey.api.wadl.config;uses:="com.sun.jersey.server.wadl,com.sun.jersey.api.core,com.sun.jersey.core.reflection,com.sun.jersey.server.wadl.generators";version="1.13.0",com.sun.jersey.api.model;uses:="javax.ws.rs.core,com.sun.jersey.spi.container";version="1.13.0",com.sun.jersey.api.view;version="1.13.0",com.sun.jersey.api.container.filter;uses:="javax.ws.rs,com.sun.jersey.spi.container,javax.ws.rs.core,com.sun.jersey.api.container,com.sun.jersey.api.core,com.sun.jersey.core.util,com.sun.jersey.core.header,com.sun.jersey.api.representation,com.sun.jersey.api.model,javax.annotation.security";version="1.13.0",com.sun.jersey.api.container;uses:="com.sun.jersey.api.core,com.sun.jersey.spi.container,com.sun.jersey.spi.service,com.sun.jersey.core.spi.component.ioc";version="1.13.0",com.sun.jersey.api.container.httpserver;uses:="com.sun.net.httpserver,com.sun.jersey.api.core,com.sun.jersey.api.container,com.sun.jersey.core.spi.component.ioc";version="1.13.0",com.sun.research.ws.wadl;uses:="javax.xml.bind.annotation,javax.xml.bind.annotation.adapters,javax.xml.namespace";version="1.13.0"""
+ ExportPackageParser.parseAll(export) //throws StackOverflow exception on scala library 2.10.2
+ }
+}
diff --git a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ImportPackageTest.scala b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ImportPackageTest.scala
new file mode 100644
index 00000000000..c3e3dd235e3
--- /dev/null
+++ b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/osgi/ImportPackageTest.scala
@@ -0,0 +1,95 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.osgi
+
+import org.scalatest.junit.{JUnitSuite, ShouldMatchersForJUnit}
+import org.junit.Test
+
+import ImportPackages.Import
+import ExportPackages.{Parameter, Export}
+
+/**
+ * @author tonytv
+ */
+class ImportPackageTest extends JUnitSuite with ShouldMatchersForJUnit {
+ val referencedPackages = Set("com.yahoo.exported")
+ val exports = exportByPackageName(Export(List("com.yahoo.exported"), List()))
+ val exportsWithVersion = exportByPackageName(exports.head._2.copy(parameters = List(Parameter("version", "1.3"))))
+
+ def exportByPackageName(export : Export) = ExportPackages.exportsByPackageName(List(export))
+ @Test
+ def require_that_non_implemented_import_with_matching_export_is_included() {
+ val imports = calculateImports(referencedPackages, implementedPackages = Set(), exportedPackages = exports)
+ imports should be (Set(Import("com.yahoo.exported", None)))
+ }
+
+
+ @Test
+ def require_that_non_implemented_import_without_matching_export_is_excluded() {
+ val imports = calculateImports(referencedPackages, implementedPackages = Set(), exportedPackages = Map())
+ imports should be (Set())
+ }
+
+ @Test
+ def require_that_implemented_import_with_matching_export_is_excluded() {
+ val imports = calculateImports(
+ referencedPackages,
+ implementedPackages = referencedPackages,
+ exportedPackages = exports)
+
+ imports should be (Set())
+ }
+
+ @Test
+ def require_that_version_is_included() {
+ val imports = calculateImports(referencedPackages, implementedPackages = Set(), exportedPackages = exportsWithVersion)
+
+ imports should be (Set(Import("com.yahoo.exported", Some("1.3"))))
+ }
+
+ @Test
+ def require_that_all_versions_up_to_the_next_major_version_is_in_range() {
+ Import("foo", Some("1.2")).importVersionRange should be (Some("[1.2,2)"))
+ }
+
+ // TODO: Detecting guava packages should be based on bundle-symbolicName, not package name.
+ @Test
+ def require_that_for_guava_all_future_major_versions_are_in_range() {
+ val rangeWithInfiniteUpperLimit = Some("[18.1," + ImportPackages.InfiniteVersion + ")")
+ Import("com.google.common", Some("18.1")).importVersionRange should be (rangeWithInfiniteUpperLimit)
+ Import("com.google.common.foo", Some("18.1")).importVersionRange should be (rangeWithInfiniteUpperLimit)
+
+ Import("com.google.commonality", Some("18.1")).importVersionRange should be (Some("[18.1,19)"))
+ }
+
+ @Test
+ def require_that_none_version_gives_non_version_range() {
+ Import("foo", None).importVersionRange should be (None)
+ }
+
+ @Test
+ def require_that_exception_is_thrown_when_major_component_is_non_numeric() {
+ intercept[IllegalArgumentException](Import("foo", Some("1notValid.2")))
+ }
+
+ @Test
+ def require_that_osgi_import_supports_missing_version() {
+ Import("com.yahoo.exported", None).asOsgiImport should be ("com.yahoo.exported")
+ }
+
+ @Test
+ def require_that_osgi_import_version_range_includes_all_versions_from_the_current_up_to_the_next_major_version() {
+ Import("com.yahoo.exported", Some("1.2")).asOsgiImport should be ("com.yahoo.exported;version=\"[1.2,2)\"")
+ }
+
+ @Test
+ def require_that_osgi_import_version_range_ignores_qualifier() {
+ Import("com.yahoo.exported", Some("1.2.3.qualifier")).asOsgiImport should be ("com.yahoo.exported;version=\"[1.2.3,2)\"")
+ }
+
+
+ def calculateImports(referencedPackages : Set[String],
+ implementedPackages : Set[String],
+ exportedPackages : Map[String, Export]) : Set[Import] = {
+ ImportPackages.calculateImports(referencedPackages, implementedPackages, exportedPackages).values.toSet
+ }
+}
diff --git a/bundle-plugin/src/test/scala/com/yahoo/container/plugin/util/IOTest.scala b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/util/IOTest.scala
new file mode 100644
index 00000000000..16f4f5237f1
--- /dev/null
+++ b/bundle-plugin/src/test/scala/com/yahoo/container/plugin/util/IOTest.scala
@@ -0,0 +1,44 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.util
+
+import org.scalatest.junit.{JUnitSuite, ShouldMatchersForJUnit}
+import org.junit.Test
+
+import IO.using
+import java.io.Closeable
+
+/**
+ * @author tonytv
+ */
+class IOTest extends JUnitSuite with ShouldMatchersForJUnit {
+ class ClosingException extends RuntimeException
+ class FunctionException extends RuntimeException
+
+ object throwWhenClosingResource extends Closeable {
+ def close() {
+ throw new ClosingException();
+ }
+ }
+
+ def throwFunction(r : throwWhenClosingResource.type) = throw new FunctionException
+ def nonThrowingFunction(r : throwWhenClosingResource.type) = 42
+
+ @Test
+ def require_that_function_exception_is_prioritized_over_closing_exception() {
+ intercept[FunctionException]{
+ using(throwWhenClosingResource, readOnly = false)(throwFunction)
+ }
+ }
+
+ @Test
+ def require_that_closing_exception_is_ignored_when_read_only() {
+ using(throwWhenClosingResource, readOnly = true)(nonThrowingFunction) should be (nonThrowingFunction(null))
+ }
+
+ @Test
+ def require_that_closing_exception_is_rethrown_when_not_read_only() {
+ intercept[ClosingException] {
+ using(throwWhenClosingResource, readOnly = false)(nonThrowingFunction)
+ }
+ }
+}