You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2020/04/02 13:53:52 UTC

[sling-slingfeature-maven-plugin] branch master updated: SLING-9324 : Improve api jar generation

This is an automated email from the ASF dual-hosted git repository.

cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-slingfeature-maven-plugin.git


The following commit(s) were added to refs/heads/master by this push:
     new c30072f  SLING-9324 : Improve api jar generation
c30072f is described below

commit c30072f224f5f9b4a28b0a6c45ac8421988eea97
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Thu Apr 2 15:53:14 2020 +0200

    SLING-9324 : Improve api jar generation
---
 pom.xml                                            |   1 -
 .../sling/feature/maven/mojos/ApisJarContext.java  | 155 ++++-
 .../sling/feature/maven/mojos/ApisJarMojo.java     | 647 +++++++++------------
 3 files changed, 438 insertions(+), 365 deletions(-)

diff --git a/pom.xml b/pom.xml
index cee5351..9e0f187 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,7 +43,6 @@
         <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-slingfeature-maven-plugin.git</connection>
         <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-slingfeature-maven-plugin.git</developerConnection>
         <url>https://gitbox.apache.org/repos/asf?p=sling-slingfeature-maven-plugin.git</url>
-      <tag>slingfeature-maven-plugin-1.1.20</tag>
   </scm>
 
     <build>
diff --git a/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarContext.java b/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarContext.java
index 9dc3a9c..271a441 100644
--- a/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarContext.java
+++ b/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarContext.java
@@ -16,27 +16,170 @@
  */
 package org.apache.sling.feature.maven.mojos;
 
+import java.io.File;
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
+import org.apache.felix.utils.manifest.Clause;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.extension.apiregions.api.ApiRegions;
+
 class ApisJarContext {
 
-    private Set<String> javadocClasspath = new HashSet<>();
-    private Set<String> packagesWithoutJavaClasses = new HashSet<>();
-    
+    public static final class ArtifactInfo {
+
+        private ArtifactId id;
+
+        private File binDirectory;
+
+        private File sourceDirectory;
+
+        private Clause[] exportedPackageClauses;
+
+        private Set<String> usedExportedPackages;
+
+        private final  Set<File> includedResources = new HashSet<>();
+
+        public ArtifactInfo(final ArtifactId id) {
+            this.id = id;
+        }
+
+        public ArtifactId getId() {
+            return this.id;
+        }
+
+        public File getBinDirectory() {
+            return binDirectory;
+        }
+
+        public void setBinDirectory(File binDirectory) {
+            this.binDirectory = binDirectory;
+        }
+
+        public File getSourceDirectory() {
+            return sourceDirectory;
+        }
+
+        public void setSourceDirectory(File sourceDirectory) {
+            this.sourceDirectory = sourceDirectory;
+        }
+
+        public Clause[] getExportedPackageClauses() {
+            return exportedPackageClauses;
+        }
+
+        public void setExportedPackageClauses(final Clause[] exportedPackageClauses) {
+            this.exportedPackageClauses = exportedPackageClauses;
+        }
+
+        public Set<String> getUsedExportedPackages() {
+            return usedExportedPackages;
+        }
+
+        public void setUsedExportedPackages(Set<String> usedExportedPackages) {
+            this.usedExportedPackages = usedExportedPackages;
+        }
+
+        public String[] getUsedExportedPackageIncludes() {
+            final Set<String> includes = new HashSet<>();
+            for(final String pck : usedExportedPackages) {
+                includes.add(pck.replace('.', '/').concat("/*"));
+            }
+            return includes.toArray(new String[includes.size()]);
+        }
+
+        public Set<File> getIncludedResources() {
+            return includedResources;
+        }
+    }
+
+    private final Set<String> javadocClasspath = new HashSet<>();
+    private final Set<String> packagesWithoutJavaClasses = new HashSet<>();
+
+    private final File deflatedBinDir;
+
+    private final File deflatedSourcesDir;
+
+    private final File checkedOutSourcesDir;
+
+    private File javadocDir;
+
+    private final List<ArtifactInfo> infos = new ArrayList<>();
+
+    private final ArtifactId featureId;
+
+    private final ApiRegions apiRegions;
+
+    public ApisJarContext(final File mainDir, final ArtifactId featureId, final ApiRegions regions) {
+        this.featureId = featureId;
+
+        // deflated and source dirs can be shared
+        this.deflatedBinDir = newDir(mainDir, "deflated-bin");
+        this.deflatedSourcesDir = newDir(mainDir, "deflated-sources");
+        this.checkedOutSourcesDir = newDir(mainDir, "checkouts");
+        this.apiRegions = regions;
+    }
+
+    public ArtifactId getFeatureId() {
+        return featureId;
+    }
+
+    public ApiRegions getApiRegions() {
+        return this.apiRegions;
+    }
+
+    public File getDeflatedBinDir() {
+        return deflatedBinDir;
+    }
+
+    public File getDeflatedSourcesDir() {
+        return deflatedSourcesDir;
+    }
+
+    public File getCheckedOutSourcesDir() {
+        return checkedOutSourcesDir;
+    }
+
     public boolean addJavadocClasspath(String classpathItem) {
         return javadocClasspath.add(classpathItem);
     }
-    
+
     public boolean addPackageWithoutJavaClasses(String packageName) {
         return packagesWithoutJavaClasses.add(packageName);
     }
-    
+
     public Set<String> getJavadocClasspath() {
         return javadocClasspath;
     }
-    
+
     public Set<String> getPackagesWithoutJavaClasses() {
         return packagesWithoutJavaClasses;
     }
+
+    private static File newDir(File parent, String child) {
+        File dir = new File(parent, child);
+        dir.mkdirs();
+        return dir;
+    }
+
+    public ArtifactInfo addArtifactInfo(final ArtifactId id) {
+        final ArtifactInfo info = new ArtifactInfo(id);
+        this.infos.add(info);
+
+        return info;
+    }
+
+    public List<ArtifactInfo> getArtifactInfos() {
+        return this.infos;
+    }
+
+    public File getJavadocDir() {
+        return javadocDir;
+    }
+
+    public void setJavadocDir(File javadocDir) {
+        this.javadocDir = javadocDir;
+    }
 }
diff --git a/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarMojo.java b/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarMojo.java
index c96ff54..c5823f6 100644
--- a/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarMojo.java
+++ b/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarMojo.java
@@ -17,7 +17,6 @@
 package org.apache.sling.feature.maven.mojos;
 
 import java.io.File;
-import java.io.FileFilter;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.net.URL;
@@ -25,13 +24,11 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -39,10 +36,8 @@ import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 import java.util.StringTokenizer;
-import java.util.function.BiPredicate;
 import java.util.function.Predicate;
 import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
 import java.util.jar.JarInputStream;
 import java.util.jar.Manifest;
 import java.util.regex.Pattern;
@@ -96,11 +91,12 @@ import org.apache.sling.feature.extension.apiregions.api.ApiExport;
 import org.apache.sling.feature.extension.apiregions.api.ApiRegion;
 import org.apache.sling.feature.extension.apiregions.api.ApiRegions;
 import org.apache.sling.feature.io.IOUtils;
-import org.apache.sling.feature.maven.ProjectHelper;
+import org.apache.sling.feature.maven.mojos.ApisJarContext.ArtifactInfo;
 import org.codehaus.plexus.archiver.UnArchiver;
 import org.codehaus.plexus.archiver.jar.JarArchiver;
 import org.codehaus.plexus.archiver.manager.ArchiverManager;
 import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
+import org.codehaus.plexus.archiver.util.DefaultFileSet;
 import org.codehaus.plexus.components.io.fileselectors.FileSelector;
 import org.codehaus.plexus.components.io.fileselectors.IncludeExcludeFileSelector;
 import org.osgi.framework.Constants;
@@ -113,7 +109,7 @@ import org.osgi.framework.Constants;
     requiresDependencyResolution = ResolutionScope.TEST,
     threadSafe = true
 )
-public class ApisJarMojo extends AbstractIncludingFeatureMojo implements ArtifactFilter {
+public class ApisJarMojo extends AbstractIncludingFeatureMojo {
 
     /** Alternative ID to a source artifact. */
     private static final String SCM_ID = "source-ids";
@@ -122,6 +118,8 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
 
     private static final String SCM_LOCATION = "scm-location";
 
+    private static final String SCM_ENCODING = "scm-encoding";
+
     private static final String APIS = "apis";
 
     private static final String SOURCES = "sources";
@@ -132,30 +130,22 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
 
     private static final String JAVA_EXTENSION = ".java";
 
-    private static final String CND_EXTENSION = ".cnd";
-
-    private static final String NON_ASCII_PATTERN = "[^\\p{ASCII}]";
-
-    private static final String SPACE = " ";
-
-    private static final String PROPERTY_FILTER = ApisJarMojo.class.getName() + ".filter";
-
     private static final String PROPERTY_CLAUSE = ApisJarMojo.class.getName() + ".clause";
 
     private static final String PROPERTY_BUNDLE = ApisJarMojo.class.getName() + ".bundle";
 
-    private static final BiPredicate<Path, BasicFileAttributes> IS_JAVA_FILE = (p,a) -> p.toFile().isFile() && p.toFile().getName().endsWith(JAVA_EXTENSION);
-
     private static final Predicate<Path> IS_JAVA_CLASS_FILE = (p) -> p.toFile().isFile() && p.toFile().getName().endsWith(".class");
 
     /**
      * Select the features for api generation.
+     * Separate api jars will be generated for each feature.
      */
     @Parameter
     private FeatureSelectionConfig selection;
 
     /**
-     * Patterns identifying which resources to include from bundles
+     * Patterns identifying which resources to include from bundles.
+     * This can be used to include files like license or notices files.
      */
     @Parameter
     private String[] includeResources;
@@ -211,7 +201,7 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
     private List<File> apiJavadocResources;
 
     /**
-     * If enabled, the created api jars will be atttached to the project
+     * If enabled, the created api jars will be attached to the project
      */
     @Parameter(defaultValue = "true")
     private boolean attachApiJars;
@@ -266,6 +256,9 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
 
     private final Pattern pomPropertiesPattern = Pattern.compile("META-INF/maven/[^/]+/[^/]+/pom.properties");
 
+    /** Artifact Provider. */
+    private final ArtifactProvider artifactProvider = new BaseArtifactProvider();
+
     @Override
     public void execute() throws MojoExecutionException, MojoFailureException {
         checkPreconditions();
@@ -279,9 +272,8 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
         } else {
             getLog().debug("Starting APIs JARs creation...");
 
-            final ArtifactProvider artifactProvider = this.createArtifactProvider();
             for (final Feature feature : features) {
-                onFeature(artifactProvider, feature);
+                onFeature(feature);
             }
         }
     }
@@ -305,28 +297,13 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
      * @param classifier The classifier
      * @return The mapped classifier or the original classifier
      */
-    private String mapApClassifier(final String classifier) {
+    private String mapApiClassifier(final String classifier) {
         if (this.apiClassifierMappings != null && this.apiClassifierMappings.containsKey(classifier)) {
             return this.apiClassifierMappings.get(classifier);
         }
         return classifier;
     }
 
-    private ArtifactProvider createArtifactProvider() {
-        return new ArtifactProvider() {
-
-            @Override
-            public URL provide(final ArtifactId id) {
-                try {
-                    return ProjectHelper.getOrResolveArtifact(project, mavenSession, artifactHandlerManager, artifactResolver, id).getFile().toURI().toURL();
-                } catch (final Exception e) {
-                    getLog().debug("Unable to provide artifact " + id.toMvnId() + " : " + e.getMessage(), e);
-                    return null;
-                }
-            }
-        };
-    }
-
     /**
      * Check if the region is included
      *
@@ -396,13 +373,6 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
                     }
                 }
 
-                // prepare filter
-                for (final ApiRegion r : regions.listRegions()) {
-                    for (final ApiExport e : r.listExports()) {
-                        e.getProperties().put(PROPERTY_FILTER, packageToScannerFiler(e.getName(), true));
-                    }
-                }
-
                 if (regions.isEmpty()) {
                     getLog().info("Feature file " + feature.getId().toMvnId()
                             + " has no included api regions, no API JAR will be created");
@@ -431,35 +401,24 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
     /**
      * Create api jars for a feature
      */
-    private void onFeature(final ArtifactProvider artifactProvider, final Feature feature)
+    private void onFeature(final Feature feature)
             throws MojoExecutionException {
         getLog().info(MessageUtils.buffer().a("Creating API JARs for Feature ").strong(feature.getId().toMvnId())
                 .a(" ...").toString());
 
         final ApiRegions regions = getApiRegions(feature);
         if (regions == null) {
-            // wrongly configured api regions - skip execution
+            // wrongly configured api regions - skip execution, info is logged already so we can just return
             return;
         }
 
-        if (!mainOutputDir.exists()) {
-            mainOutputDir.mkdirs();
-        }
-
-        // deflated and source dirs can be shared
-        final File deflatedBinDir = newDir(mainOutputDir, "deflated-bin");
-        final File deflatedSourcesDir = newDir(mainOutputDir, "deflated-sources");
-        final File checkedOutSourcesDir = newDir(mainOutputDir, "checkouts");
-
         // create an output directory per feature
         final File featureDir = new File(mainOutputDir, feature.getId().getArtifactId());
+        final ApisJarContext ctx = new ApisJarContext(this.mainOutputDir, feature.getId(), regions);
 
-        final ApisJarContext ctx = new ApisJarContext();
-
-        // for each bundle included in the feature file:
-        for (Artifact artifact : feature.getBundles()) {
-            onArtifact(artifactProvider, artifact, null, regions, ctx, deflatedBinDir,
-                    deflatedSourcesDir, checkedOutSourcesDir);
+        // for each bundle included in the feature file and record directories
+        for (final Artifact artifact : feature.getBundles()) {
+            onArtifact(ctx, artifact);
         }
 
         ctx.getPackagesWithoutJavaClasses().forEach( p -> getLog().info("Exported package " + p + " does not contain any java classes"));
@@ -469,35 +428,27 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
             final File regionDir = new File(featureDir, apiRegion.getName());
 
             if (generateApiJar) {
-                final File apisDir = new File(regionDir, APIS);
-                final List<String> nodeTypes = recollect(featureDir, deflatedBinDir, apiRegion, apisDir);
-                final File apiJar = createArchive(feature.getId(), apisDir, apiRegion, APIS, nodeTypes,
-                        this.apiResources);
+                final List<String> nodeTypes = collectNodeTypes(ctx.getArtifactInfos().stream().map(info -> info.getBinDirectory()).collect(Collectors.toList()));
+                final File apiJar = createArchive(ctx, apiRegion, APIS, nodeTypes, this.apiResources);
                 report(apiJar, APIS, apiRegion, "class", ctx);
             }
 
-            final File sourcesDir = new File(regionDir, SOURCES);
-            // we need sources for both source and javadoc jar
-            if (generateSourceJar || generateJavadocJar) {
-                recollect(featureDir, deflatedSourcesDir, apiRegion, sourcesDir);
-            }
-
             if (generateSourceJar) {
-                final File sourceJar = createArchive(feature.getId(), sourcesDir, apiRegion, SOURCES, null,
+                final File sourceJar = createArchive(ctx, apiRegion, SOURCES, null,
                         this.apiSourceResources);
                 report(sourceJar, SOURCES, apiRegion, "java", ctx);
             }
 
             if (generateJavadocJar) {
-                final List<String> subpackageDirectories = calculateSubpackageDirectories(sourcesDir);
+                final Set<String> subpackageDirectories = calculateSubpackageDirectories(ctx.getArtifactInfos().stream().map(info -> info.getSourceDirectory()).filter(dir -> dir != null).collect(Collectors.toList()));
                 if (!subpackageDirectories.isEmpty()) {
                     final File javadocsDir = new File(regionDir, JAVADOC);
-                    generateJavadoc(sourcesDir, javadocsDir, ctx, subpackageDirectories);
-                    final File javadocJar = createArchive(feature.getId(), javadocsDir, apiRegion, JAVADOC, null,
-                            this.apiJavadocResources);
+                    generateJavadoc(ctx, javadocsDir, subpackageDirectories);
+                    ctx.setJavadocDir(javadocsDir);
+                    final File javadocJar = createArchive(ctx, apiRegion, JAVADOC, null, this.apiJavadocResources);
                     report(javadocJar, JAVADOC, apiRegion, "html", ctx);
                 } else {
-                    getLog().warn("Javadoc JAR will NOT be generated - sources directory " + sourcesDir
+                    getLog().warn("Javadoc JAR will NOT be generated - sources directory " + ctx.getDeflatedSourcesDir()
                             + " was empty or contained no Java files!");
                 }
             }
@@ -549,10 +500,10 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
     }
 
     private Manifest getManifest(final ArtifactId artifactId, final File bundleFile) throws MojoExecutionException {
-        try (JarFile bundle = new JarFile(bundleFile)) {
+        try (JarInputStream jis = new JarInputStream(new FileInputStream(bundleFile))) {
             getLog().debug("Reading Manifest headers from bundle " + bundleFile);
 
-            final Manifest manifest = bundle.getManifest();
+            final Manifest manifest = jis.getManifest();
 
             if (manifest == null) {
                 throw new MojoExecutionException("Artifact + " + artifactId.toMvnId() + " does not  have a manifest.");
@@ -564,70 +515,94 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
         }
     }
 
-    private void onArtifact(final ArtifactProvider artifactProvider, Artifact artifact, Manifest wrappingBundleManifest,
-            ApiRegions apiRegions, ApisJarContext ctx, File deflatedBinDir, File deflatedSourcesDir,
-            File checkedOutSourcesDir) throws MojoExecutionException {
-        final ArtifactId artifactId = artifact.getId();
-        final File bundleFile = getArtifactFile(artifactProvider, artifactId);
+    private void onArtifact(final ApisJarContext ctx, final Artifact artifact)
+    throws MojoExecutionException {
+        final File bundleFile = getArtifactFile(artifactProvider, artifact.getId());
 
-        final Manifest manifest;
-        if (wrappingBundleManifest == null) {
-            manifest = getManifest(artifactId, bundleFile);
-        } else {
-            manifest = wrappingBundleManifest;
-        }
+        final Manifest manifest = getManifest(artifact.getId(), bundleFile);
 
         // check if the bundle is exporting packages?
-        final Clause[] exportedPackages = this.getExportedPackages(manifest);
-        if (exportedPackages.length > 0) {
+        final Clause[] exportedPackageClauses = this.getExportedPackages(manifest);
+        if (exportedPackageClauses.length > 0) {
 
             // calculate the exported versioned packages in the manifest file for each
             // region
             // and calculate the exported versioned packages in the manifest file for each
             // region
-            if (computeExports(apiRegions, exportedPackages, artifactId)) {
-                // get includes for deflating
-                final String[] exportPackagesIncludes = computeExportPackageIncludes(exportedPackages);
-
-                // deflate all bundles first, in order to copy APIs and resources later,
-                // depending to the region
-                final String[] exportedPackagesAndWrappedBundles = Stream
-                        .concat(Stream.concat(Stream.of(exportPackagesIncludes), Stream.of("**/*.jar")),
-                                Stream.of(includeResources))
-                        .toArray(String[]::new);
-                final File deflatedBundleDirectory = deflate(deflatedBinDir, bundleFile,
-                        exportedPackagesAndWrappedBundles);
-                // renaming potential name-collapsing resources
-                renameResources(deflatedBundleDirectory, artifactId);
-
-                // download sources
-                downloadSources(artifactProvider, artifact, deflatedSourcesDir, checkedOutSourcesDir,
-                        exportPackagesIncludes);
+            final Set<String> usedExportedPackages = computeUsedExportPackages(ctx.getApiRegions(), exportedPackageClauses, artifact.getId());
 
-                try {
-                    findExportsWithoutJavaClasses(deflatedBundleDirectory, exportedPackages, ctx);
-                } catch (IOException e) {
-                    throw new MojoExecutionException("Failed finding exported packages without Java classes", e);
-                }
+            if ( !usedExportedPackages.isEmpty()) {
+                final ArtifactInfo info = ctx.addArtifactInfo(artifact.getId());
+                info.setExportedPackageClauses(exportedPackageClauses);
+                info.setUsedExportedPackages(usedExportedPackages);
+
+                processBinary(ctx, info, bundleFile, artifact);
 
                 // check if the bundle wraps other bundles
-                if (wrappingBundleManifest == null) { // wrappers of wrappers do not exist
-                    computeWrappedBundles(manifest, deflatedBundleDirectory, apiRegions, ctx,
-                            deflatedBinDir, deflatedSourcesDir, checkedOutSourcesDir, artifactProvider);
+                computeWrappedBundles(ctx, info, manifest);
+
+                findExportsWithoutJavaClasses(info.getBinDirectory(), usedExportedPackages, ctx);
+
+                if ( generateSourceJar || generateJavadocJar && info.getSourceDirectory() != null ) {
+                    final String encoding = artifact.getMetadata().getOrDefault(SCM_ENCODING, "UTF-8");
+                    if ( !"UTF-8".equals(encoding)) {
+                        this.cleanupSources(info.getSourceDirectory(), encoding);
+                    }
                 }
             }
 
-            buildJavadocClasspath(artifactId).forEach( ctx::addJavadocClasspath );
+            if ( generateJavadocJar ) {
+                buildJavadocClasspath(artifact.getId()).forEach( ctx::addJavadocClasspath );
+            }
+        }
+    }
+
+    private void processBinary(final ApisJarContext ctx,
+            final ArtifactInfo info,
+            final File binFile,
+            final Artifact binArtifact)
+    throws MojoExecutionException {
+        // deflate all bundles first, in order to copy APIs and resources later,
+        // depending to the region
+        final String[] exportedPackagesAndWrappedBundles = Stream
+                .concat(Stream.concat(Stream.of(info.getUsedExportedPackageIncludes()),
+                        Stream.of("**/*.jar")),
+                        Stream.of(includeResources))
+                .toArray(String[]::new);
+        final File destDirectory = new File(ctx.getDeflatedBinDir(), info.getId().toMvnName());
+        info.setBinDirectory(destDirectory);
+
+        deflate(destDirectory, binFile, exportedPackagesAndWrappedBundles);
+
+        // renaming potential name-collapsing resources
+        renameResources(info, binArtifact.getId());
+
+        // download sources
+        if ( generateSourceJar || generateJavadocJar ) {
+            downloadSources(ctx, info, binArtifact);
+        }
+
+    }
+
+    private void cleanupSources(final File dir, final String readEncoding) throws MojoExecutionException {
+        for(final File child : dir.listFiles()) {
+            if ( child.isDirectory() ) {
+                cleanupSources(child, readEncoding);
+            } else if ( child.getName().endsWith(JAVA_EXTENSION)) {
+                try {
+                    final String javaSource = FileUtils.fileRead(child, readEncoding);
+                    FileUtils.fileWrite(child, StandardCharsets.UTF_8.name(), javaSource);
+                } catch ( final IOException ioe) {
+                    throw new MojoExecutionException("Unable to clean up java source " + child, ioe);
+                }
+            }
         }
     }
 
-    private void computeWrappedBundles(Manifest manifest,
-                                       File deflatedBundleDirectory,
-            ApiRegions apiRegions,
-                                       ApisJarContext ctx,
-                                       File deflatedBinDir,
-                                       File deflatedSourcesDir,
-            File checkedOutSourcesDir, final ArtifactProvider artifactProvider) throws MojoExecutionException {
+    private void computeWrappedBundles(final ApisJarContext ctx,
+            final ArtifactInfo info,
+            final Manifest manifest)
+    throws MojoExecutionException {
 
         final String bundleClassPath = manifest.getMainAttributes().getValue(Constants.BUNDLE_CLASSPATH);
         if (bundleClassPath == null || bundleClassPath.isEmpty()) {
@@ -641,20 +616,21 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
                 continue;
             }
 
-            final File wrappedJar = new File(deflatedBundleDirectory, jarName);
+            final File wrappedJar = new File(info.getBinDirectory(), jarName);
             getLog().debug("Processing wrapped bundle " + wrappedJar);
 
             final Properties properties = new Properties();
 
-            try (JarFile jarFile = new JarFile(wrappedJar)) {
-                Enumeration<JarEntry> jarEntries = jarFile.entries();
-                while (jarEntries.hasMoreElements()) {
-                    JarEntry jarEntry = jarEntries.nextElement();
+            try (final JarInputStream jis = new JarInputStream(new FileInputStream(wrappedJar))) {
+                JarEntry jarEntry = null;
+                while ( (jarEntry = jis.getNextJarEntry()) != null ) {
                     if (!jarEntry.isDirectory()
                             && pomPropertiesPattern.matcher(jarEntry.getName()).matches()) {
                         getLog().debug("Loading Maven GAV from " + wrappedJar + '!' + jarEntry.getName());
-                        properties.load(jarFile.getInputStream(jarEntry));
+                        properties.load(jis);
+                        break;
                     }
+                    jis.closeEntry();
                 }
             } catch (IOException e) {
                 throw new MojoExecutionException("An error occurred while processing wrapped bundle " + wrappedJar, e);
@@ -675,8 +651,9 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
 
                 Artifact syntheticArtifact = new Artifact(
                         new ArtifactId(groupId, artifactId, version, classifier, null));
-                onArtifact(artifactProvider, syntheticArtifact, manifest, apiRegions, ctx, deflatedBinDir,
-                        deflatedSourcesDir, checkedOutSourcesDir);
+                final File bundleFile = getArtifactFile(artifactProvider, syntheticArtifact.getId());
+
+                processBinary(ctx, info, bundleFile, syntheticArtifact);
             }
         }
     }
@@ -690,14 +667,12 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
      * @param deflatedBundleDirectory
      * @param exportedPackages
      * @param ctx
-     * @throws IOException
      */
-    private void findExportsWithoutJavaClasses(File deflatedBundleDirectory, Clause[] exportedPackages, ApisJarContext ctx) throws IOException {
+    private void findExportsWithoutJavaClasses(File deflatedBundleDirectory, Set<String> exportedPackages, ApisJarContext ctx) {
 
         Path root = deflatedBundleDirectory.toPath();
 
-        Arrays.stream(exportedPackages)
-            .map( Clause::getName )
+        exportedPackages.stream()
             .map( pkg -> pkg.split("\\.") )
             .map( p -> Paths.get(root.toString(), p) )
             .filter ( p -> p.toFile().exists()) // don't look for packages picked up from wrapped bundles
@@ -766,7 +741,15 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
                                             .setForceUpdate(false)
                                             .setResolveRoot(true)
                                             .setResolveTransitively(true)
-                                            .setCollectionFilter(this);
+                                            .setCollectionFilter(new ArtifactFilter() {
+                                                    // artifact filter
+                                                    @Override
+                                                    public boolean include(org.apache.maven.artifact.Artifact artifact) {
+                                                        if (org.apache.maven.artifact.Artifact.SCOPE_TEST.equals(artifact.getScope())) {
+                                                            return false;
+                                                        }
+                                                        return true;
+                                                    }});
 
         ArtifactResolutionResult result = repositorySystem.resolve(request);
 
@@ -832,8 +815,7 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
         return sourceFile;
     }
 
-    private File deflate(File deflatedDir, File artifact, String...includes) throws MojoExecutionException {
-        File destDirectory = new File(deflatedDir, artifact.getName());
+    private void deflate(File destDirectory, File artifact, String...includes) throws MojoExecutionException {
         if (destDirectory.exists()) {
             getLog().debug("Bundle " + artifact.getName() + " already deflated");
         } else {
@@ -849,72 +831,79 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
                 selector.setIncludes(includes);
                 selector.setExcludes(new String[] { "OSGI-OPT/**" });
                 unArchiver.setFileSelectors(new FileSelector[] { selector });
+                unArchiver.setOverwrite(false);
                 unArchiver.extract();
             } catch (NoSuchArchiverException e) {
                 throw new MojoExecutionException(
                         "An error occurred while deflating file " + artifact + " to directory " + destDirectory, e);
             }
 
-            getLog().debug("Bundle " + artifact + " successfully deflated");
+            getLog().debug("Artifact " + artifact + " successfully deflated");
         }
-        return destDirectory;
     }
 
-    private void renameResources(File destDirectory, ArtifactId artifactId) throws MojoExecutionException {
+    private void renameResources(final ArtifactInfo info, final ArtifactId artifactId) throws MojoExecutionException {
         if (includeResources == null || includeResources.length == 0) {
-            getLog().debug("No configured resources to rename in " + destDirectory);
+            getLog().debug("No configured resources to rename in " + info.getBinDirectory());
         }
 
-        getLog().debug("Renaming " + Arrays.toString(includeResources) + " files in " + destDirectory + "...");
+        getLog().debug("Renaming " + Arrays.toString(includeResources) + " files in " + info.getBinDirectory() + "...");
 
         DirectoryScanner directoryScanner = new DirectoryScanner();
-        directoryScanner.setBasedir(destDirectory);
+        directoryScanner.setBasedir(info.getBinDirectory());
         directoryScanner.setIncludes(includeResources);
         directoryScanner.scan();
 
         if (directoryScanner.getIncludedFiles().length == 0) {
-            getLog().debug("No " + Arrays.toString(includeResources) + " resources in " + destDirectory + " to be renamed renamed.");
+            getLog().debug("No " + Arrays.toString(includeResources) + " resources in " + info.getBinDirectory() + " to be renamed found.");
             return;
         }
 
-        for (String resourceName : directoryScanner.getIncludedFiles()) {
-            final File resource = new File(destDirectory, resourceName);
-            final String prefix = artifactId.getGroupId().concat("-").concat(artifactId.getArtifactId().concat("-"));
+        for (final String resourceName : directoryScanner.getIncludedFiles()) {
+            final File resource = new File(info.getBinDirectory(), resourceName);
+            if ( !info.getIncludedResources().contains(resource) ) {
+                final String prefix = artifactId.toMvnName().concat("-");
 
-            if (resource.getName().startsWith(prefix)) {
-                getLog().debug("No need to rename " + resource);
-            } else {
-                File renamed = new File(resource.getParentFile(), prefix.concat(resource.getName()));
+                if (resource.getName().startsWith(prefix)) {
+                    getLog().debug("No need to rename " + resource);
+                    info.getIncludedResources().add(resource);
+                } else {
+                    File renamed = new File(resource.getParentFile(), prefix.concat(resource.getName()));
 
-                getLog().debug("Renaming resource " + resource + " to " + renamed + "...");
+                    getLog().debug("Renaming resource " + resource + " to " + renamed + "...");
 
-                if (resource.renameTo(renamed)) {
-                    getLog().debug("Resource renamed to " + renamed);
-                } else {
-                    throw new MojoExecutionException("Impossible to rename resource " + resource + " to " + renamed
-                            + ", please check the current user has enough rights on the File System");
+                    if (resource.renameTo(renamed)) {
+                        getLog().debug("Resource renamed to " + renamed);
+                        info.getIncludedResources().add(renamed);
+                    } else {
+                        throw new MojoExecutionException("Impossible to rename resource " + resource + " to " + renamed
+                                + ", please check the current user has enough rights on the File System");
+                    }
                 }
             }
         }
 
-        getLog().debug(Arrays.toString(includeResources) + " resources in " + destDirectory + " successfully renamed");
+        getLog().debug(Arrays.toString(includeResources) + " resources in " + info.getBinDirectory() + " successfully renamed");
     }
 
-    private boolean downloadSourceAndDeflate(final ArtifactProvider artifactProvider,
-            final ArtifactId sourcesArtifactId, final File deflatedSourcesDir, final String[] exportPackageIncludes,
-            final ArtifactId artifactId, final boolean allowFallback) throws MojoExecutionException {
+    private boolean downloadSourceAndDeflate(final ApisJarContext ctx,
+            final ArtifactInfo info,
+            final ArtifactId sourcesArtifactId,
+            final boolean allowFallback) throws MojoExecutionException {
         boolean failed = false;
         try {
             final URL url = retrieve(artifactProvider, sourcesArtifactId);
             if (url != null) {
                 File sourcesBundle = IOUtils.getFileFromURL(url, true, null);
-                deflate(deflatedSourcesDir, sourcesBundle, exportPackageIncludes);
+                final File sourceDirectory = new File(ctx.getDeflatedSourcesDir(), info.getId().toMvnName());
+                info.setSourceDirectory(sourceDirectory);
+                deflate(sourceDirectory, sourcesBundle, info.getUsedExportedPackageIncludes());
             } else {
                 if (!allowFallback) {
-                    throw new MojoExecutionException("Unable to download sources for " + artifactId.toMvnId()
+                    throw new MojoExecutionException("Unable to download sources for " + info.getId().toMvnId()
                             + " due to missing artifact " + sourcesArtifactId.toMvnId());
                 }
-                getLog().warn("Unable to download sources for " + artifactId.toMvnId() + " due to missing artifact "
+                getLog().warn("Unable to download sources for " + info.getId().toMvnId() + " due to missing artifact "
                         + sourcesArtifactId.toMvnId() + ", trying source checkout next...");
                 failed = true;
             }
@@ -922,25 +911,25 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
             throw mee;
         } catch (final Throwable t) {
             if (!allowFallback) {
-                throw new MojoExecutionException("Unable to download sources for " + artifactId.toMvnId()
+                throw new MojoExecutionException("Unable to download sources for " + info.getId().toMvnId()
                         + " due to missing artifact " + sourcesArtifactId.toMvnId());
             }
-            getLog().warn("Unable to download sources for " + artifactId.toMvnId() + " from "
+            getLog().warn("Unable to download sources for " + info.getId().toMvnId() + " from "
                     + sourcesArtifactId.toMvnId() + " due to " + t.getMessage() + ", trying source checkout next...");
             failed = true;
         }
         return failed;
     }
 
-    private void downloadSources(final ArtifactProvider artifactProvider, Artifact artifact, File deflatedSourcesDir,
-            File checkedOutSourcesDir, String[] exportPackageIncludes) throws MojoExecutionException {
+    private void downloadSources(final ApisJarContext ctx, final ArtifactInfo info, final Artifact artifact) throws MojoExecutionException {
         ArtifactId artifactId = artifact.getId();
         getLog().debug("Downloading sources for " + artifactId.toMvnId() + "...");
 
         String scmId = artifact.getMetadata().get(SCM_ID);
         String scmLocation = artifact.getMetadata().get(SCM_LOCATION);
-        if ( scmId != null && scmLocation != null)
+        if ( scmId != null && scmLocation != null) {
             throw new MojoExecutionException("Both " + SCM_ID + " and " + SCM_LOCATION + " are defined for " + artifactId);
+        }
 
         boolean fallbackToScmCheckout = false;
 
@@ -948,35 +937,30 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
             final String value = scmId;
             for (final String id : value.split(",")) {
                 final ArtifactId sourcesArtifactId = ArtifactId.parse(id);
-                downloadSourceAndDeflate(artifactProvider, sourcesArtifactId, deflatedSourcesDir, exportPackageIncludes,
-                        artifactId, false);
+                downloadSourceAndDeflate(ctx, info, sourcesArtifactId, false);
             }
         } else if ( scmLocation != null ) {
-            checkoutSourcesFromSCM(artifactProvider, artifact, deflatedSourcesDir, checkedOutSourcesDir,
-                    exportPackageIncludes, artifactId);
+            checkoutSourcesFromSCM(ctx, info, artifact);
         } else {
-            final ArtifactId sourcesArtifactId = newArtifacId(artifactId,
-                                                    "sources",
-                                                    "jar");
-            fallbackToScmCheckout = downloadSourceAndDeflate(artifactProvider, sourcesArtifactId, deflatedSourcesDir,
-                    exportPackageIncludes, artifactId, true);
+            final ArtifactId sourcesArtifactId = artifactId.changeClassifier("sources").changeType("jar");
+            fallbackToScmCheckout = downloadSourceAndDeflate(ctx, info, sourcesArtifactId, true);
         }
 
         if ( fallbackToScmCheckout ) {
-            checkoutSourcesFromSCM(artifactProvider, artifact, deflatedSourcesDir, checkedOutSourcesDir,
-                    exportPackageIncludes, artifactId);
+            checkoutSourcesFromSCM(ctx, info, artifact);
         }
     }
 
-    private void checkoutSourcesFromSCM(final ArtifactProvider artifactProvider, Artifact artifact,
-            File deflatedSourcesDir, File checkedOutSourcesDir, String[] exportPackageIncludes, ArtifactId artifactId)
-            throws MojoExecutionException {
+    private void checkoutSourcesFromSCM(final ApisJarContext ctx,
+            final ArtifactInfo info,
+            final Artifact sourceArtifact)
+    throws MojoExecutionException {
         // fallback to Artifacts SCM metadata first
-        String connection = artifact.getMetadata().get(SCM_LOCATION);
-        String tag = artifact.getMetadata().get(SCM_TAG);
+        String connection = sourceArtifact.getMetadata().get(SCM_LOCATION);
+        String tag = sourceArtifact.getMetadata().get(SCM_TAG);
 
         // Artifacts SCM metadata may not available or are an override, let's fallback to the POM
-        ArtifactId pomArtifactId = newArtifacId(artifactId, null, "pom");
+        final ArtifactId pomArtifactId = sourceArtifact.getId().changeClassifier(null).changeType("pom");
         getLog().debug("Falling back to SCM checkout, retrieving POM " + pomArtifactId.toMvnId() + "...");
         // POM file must exist, let the plugin fail otherwise
         final URL pomURL = retrieve(artifactProvider, pomArtifactId);
@@ -985,8 +969,7 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
         }
 
         File pomFile = null;
-        try
-        {
+        try {
             pomFile = IOUtils.getFileFromURL(pomURL, true, null);
         } catch (IOException e) {
             throw new MojoExecutionException(e.getMessage());
@@ -997,15 +980,15 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
         Model pomModel = modelBuilder.buildRawModel(pomFile, ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL, false).get();
         getLog().debug("POM model " + pomArtifactId.toMvnId() + " successfully read, processing the SCM...");
 
-        Scm scm = pomModel.getScm();
+        final Scm scm = pomModel.getScm();
         if (scm != null) {
             connection = setIfNull(connection, scm.getConnection());
             tag = setIfNull(tag, scm.getTag());
         }
 
         if (connection == null) {
-            getLog().warn("Ignoring sources for artifact " + artifactId.toMvnId() + " : SCM not defined in "
-                    + artifactId.toMvnId()
+            getLog().warn("Ignoring sources for artifact " + sourceArtifact.getId().toMvnId() + " : SCM not defined in "
+                    + sourceArtifact.getId().toMvnId()
                           + " bundle neither in "
                     + pomArtifactId.toMvnId() + " POM file.");
             return;
@@ -1019,7 +1002,7 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
                 scmVersion = new ScmTag(tag);
             }
 
-            File basedir = new File(checkedOutSourcesDir, artifactId.getArtifactId());
+            File basedir = new File(ctx.getCheckedOutSourcesDir(), sourceArtifact.getId().toMvnName());
             if (basedir.exists()) {
                 getLog().debug("Source checkout directory " + basedir + " already exists");
             } else {
@@ -1036,11 +1019,11 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
                     }
                 } catch (ScmException se) {
                     throw new MojoExecutionException("An error occurred while checking sources from " + repository
-                            + " for artifact " + artifactId + " model", se);
+                            + " for artifact " + sourceArtifact.getId().toMvnId() + " model", se);
                 }
 
                 if (!result.isSuccess()) {
-                    getLog().warn("Ignoring sources for artifact " + artifactId.toMvnId()
+                    getLog().warn("Ignoring sources for artifact " + sourceArtifact.getId().toMvnId()
                             + " : An error occurred while checking out sources from " + connection + ": "
                             + result.getProviderMessage());
                     return;
@@ -1057,7 +1040,7 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
                 pomModel = modelBuilder.buildRawModel(pomFile, ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL, false)
                         .get();
 
-                if (artifactId.getArtifactId().equals(pomModel.getArtifactId())) {
+                if (sourceArtifact.getId().getArtifactId().equals(pomModel.getArtifactId())) {
                     basedir = pomFile.getParentFile();
                     break;
                 }
@@ -1070,31 +1053,31 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
 
                 // there could be just resources artifacts
                 if (!javaSources.exists()) {
-                    getLog().warn("Ignoring sources for artifact " + artifactId.toMvnId() + " : SCM checkout for "
-                            + artifactId.toMvnId()
+                    getLog().warn("Ignoring sources for artifact " + sourceArtifact.getId().toMvnId() + " : SCM checkout for "
+                            + sourceArtifact.getId().toMvnId()
                             + " does not contain any source.");
                     return;
                 }
             }
 
-            File destDirectory = new File(deflatedSourcesDir, artifactId.toMvnId());
-            if (!destDirectory.exists()) {
-                destDirectory.mkdir();
-                DirectoryScanner directoryScanner = new DirectoryScanner();
-                directoryScanner.setBasedir(javaSources);
-                directoryScanner.setIncludes(exportPackageIncludes);
-                directoryScanner.scan();
-
-                for (String file : directoryScanner.getIncludedFiles()) {
-                    File source = new File(javaSources, file);
-                    File destination = new File(destDirectory, file);
-                    destination.getParentFile().mkdirs();
-                    try {
-                        FileUtils.copyFile(source, destination);
-                    } catch (IOException e) {
-                        throw new MojoExecutionException(
+            final File sourceDirectory = new File(ctx.getDeflatedSourcesDir(), info.getId().toMvnName());
+            info.setSourceDirectory(sourceDirectory);
+            sourceDirectory.mkdir();
+
+            final DirectoryScanner directoryScanner = new DirectoryScanner();
+            directoryScanner.setBasedir(javaSources);
+            directoryScanner.setIncludes(info.getUsedExportedPackageIncludes());
+            directoryScanner.scan();
+
+            for (String file : directoryScanner.getIncludedFiles()) {
+                final File source = new File(javaSources, file);
+                final File destination = new File(sourceDirectory, file);
+                destination.getParentFile().mkdirs();
+                try {
+                    FileUtils.copyFile(source, destination);
+                } catch (IOException e) {
+                    throw new MojoExecutionException(
                                 "An error occurred while copying sources from " + source + " to " + destination, e);
-                    }
                 }
             }
 
@@ -1102,9 +1085,9 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
             throw new MojoExecutionException("An error occurred while reading SCM from "
                                              + connection
                                              + " connection for bundle "
-                                             + artifactId, se);
+                                             + sourceArtifact.getId(), se);
         } catch (NoSuchScmProviderException nsspe) {
-            getLog().warn("Ignoring sources for artifact " + artifactId.toMvnId()
+            getLog().warn("Ignoring sources for artifact " + sourceArtifact.getId().toMvnId()
                     + " : bundle points to an SCM connection "
                            + connection
                            + " which does not specify a valid or supported SCM provider", nsspe);
@@ -1118,26 +1101,16 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
         return exportPackages;
     }
 
-    private String[] computeExportPackageIncludes(final Clause[] exportedPackages) throws MojoExecutionException {
-        final Set<String> exports = new HashSet<>();
-
-        for (Clause exportedPackage : exportedPackages) {
-            final String api = exportedPackage.getName();
-            exports.add(packageToScannerFiler(api, false));
-        }
-
-        return exports.toArray(new String[exports.size()]);
-    }
-
     /**
      * Compute exports based on api regions
      *
-     * @return {@code true} if any region exports a package from this set
+     * @return List of packages exported by this bundle and used in the region
      */
-    private boolean computeExports(final ApiRegions apiRegions, final Clause[] exportedPackages,
+    private Set<String> computeUsedExportPackages(final ApiRegions apiRegions,
+            final Clause[] exportedPackages,
             final ArtifactId bundle)
             throws MojoExecutionException {
-        boolean hasExport = false;
+        final Set<String> result = new HashSet<>();
 
         // filter for each region
         for (final Clause exportedPackage : exportedPackages) {
@@ -1155,25 +1128,12 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
                         exp.getProperties().put(PROPERTY_CLAUSE, exportedPackage.toString());
                         exp.getProperties().put(PROPERTY_BUNDLE, bundle.toMvnId());
                     }
-                    hasExport = true;
+                    result.add(exportedPackage.getName());
                 }
             }
         }
 
-        return hasExport;
-    }
-
-    private String[] getApiFilters(final ApiRegion region) {
-        final List<String> filters = new ArrayList<>();
-        for (final ApiExport exp : region.listExports()) {
-            final String f = exp.getProperties().get(PROPERTY_FILTER);
-            if (f != null) {
-                for (final String v : f.split(",")) {
-                    filters.add(v);
-                }
-            }
-        }
-        return filters.toArray(new String[filters.size()]);
+        return result;
     }
 
     private String getApiExportClause(final ApiRegion region) {
@@ -1193,67 +1153,61 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
         return sb.toString();
     }
 
-    private List<String> recollect(File featureDir, File deflatedDir, ApiRegion apiRegion, File destination) throws MojoExecutionException {
+    private List<String> collectNodeTypes(final List<File> binDirs) {
         final List<String> nodeTypes = new LinkedList<>();
 
-        destination.mkdirs();
+        for(final File binDir : binDirs) {
+            final DirectoryScanner directoryScanner = new DirectoryScanner();
+            directoryScanner.setBasedir(binDir);
+            directoryScanner.setIncludes("**/*.cnd");
+            directoryScanner.scan();
 
-        DirectoryScanner directoryScanner = new DirectoryScanner();
-        directoryScanner.setBasedir(deflatedDir);
+            for (String includedFile : directoryScanner.getIncludedFiles()) {
+                String fileName = includedFile.substring(includedFile.indexOf(File.separator) + 1);
+
+                nodeTypes.add(fileName);
+            }
 
-        // for each region, include both APIs and resources
-        String[] includes;
-        if (APIS.equals(destination.getName())) {
-            includes = concatenate(getApiFilters(apiRegion), includeResources);
-        } else {
-            includes = getApiFilters(apiRegion);
         }
-        directoryScanner.setIncludes(includes);
-        directoryScanner.scan();
 
-        for (String includedFile : directoryScanner.getIncludedFiles()) {
-            String fileName = includedFile.substring(includedFile.indexOf(File.separator) + 1);
+        return nodeTypes;
+    }
 
-            File target = new File(destination, fileName);
-            target.getParentFile().mkdirs();
+    private File createArchive(final ApisJarContext ctx,
+            final ApiRegion apiRegion,
+            final String classifier,
+            final List<String> nodeTypes,
+            final List<File> resources) throws MojoExecutionException {
+        final JarArchiver jarArchiver = new JarArchiver();
 
-            try {
-                File source = new File(deflatedDir, includedFile);
+        if ( APIS.equals(classifier) || SOURCES.equals(classifier) ) {
+            // api or source
+            for(final ArtifactInfo includeEntry : ctx.getArtifactInfos()) {
+                final File dir = APIS.equals(classifier) ? includeEntry.getBinDirectory() : includeEntry.getSourceDirectory();
 
-                // this to prevent 'unmappable character for encoding UTF8' error
-                if (includedFile.endsWith(JAVA_EXTENSION)) {
-                    String javaSource = FileUtils.fileRead(source, StandardCharsets.UTF_8.name())
-                                                 .replaceAll(NON_ASCII_PATTERN, SPACE);
-                    FileUtils.fileWrite(target, StandardCharsets.UTF_8.name(), javaSource);
-                } else {
-                    if (includedFile.endsWith(CND_EXTENSION)) {
-                        nodeTypes.add(fileName);
-                    }
+                getLog().debug("Adding directory " + dir);
+                final DefaultFileSet fileSet = new DefaultFileSet(dir);
 
-                    FileUtils.copyFile(source, target);
-                }
-            } catch (IOException e) {
-                throw new MojoExecutionException("An error occurred while copying file "
-                                                 + includedFile
-                                                 + " to "
-                                                 + destination, e);
+                fileSet.setIncludes(includeEntry.getUsedExportedPackageIncludes());
+                jarArchiver.addFileSet(fileSet);
             }
+        } else {
+            // javadoc
+            final DefaultFileSet fileSet = new DefaultFileSet(ctx.getJavadocDir());
+            jarArchiver.addFileSet(fileSet);
         }
 
-        return nodeTypes;
-    }
-
-    private File createArchive(ArtifactId featureId, File collectedDir, ApiRegion apiRegion, String classifier,
-            List<String> nodeTypes, List<File> resources) throws MojoExecutionException {
-        DirectoryScanner directoryScanner = new DirectoryScanner();
-        directoryScanner.setBasedir(collectedDir);
-        directoryScanner.setIncludes("**/*.*");
-        directoryScanner.scan();
-
-        JarArchiver jarArchiver = new JarArchiver();
-        for (String includedFile : directoryScanner.getIncludedFiles()) {
-            jarArchiver.addFile(new File(collectedDir, includedFile), includedFile);
+        // add included resources
+        for(final ArtifactInfo includeEntry : ctx.getArtifactInfos()) {
+            final int prefixLength = includeEntry.getBinDirectory().getAbsolutePath().length() + 1;
+            for(final File resource : includeEntry.getIncludedResources()) {
+                final String name = resource.getAbsolutePath().substring(prefixLength);
+                jarArchiver.addFile(resource, name);
+                getLog().debug("Adding resource " + name);
+            }
         }
+
+        // add additional resources
         if (resources != null) {
             for (final File rsrc : resources) {
                 getLog().debug("Adding resource " + rsrc);
@@ -1271,39 +1225,41 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
                 }
             }
         }
-        StringBuilder classifierBuilder = new StringBuilder();
-        if (featureId.getClassifier() != null) {
-            classifierBuilder.append(mapApClassifier(featureId.getClassifier()))
+
+        // build classifier
+        final StringBuilder classifierBuilder = new StringBuilder();
+        if (ctx.getFeatureId().getClassifier() != null) {
+            classifierBuilder.append(mapApiClassifier(ctx.getFeatureId().getClassifier()))
                              .append('-');
         }
-        String finalClassifier = classifierBuilder.append(mapApiRegionName(apiRegion.getName()))
+        final String finalClassifier = classifierBuilder.append(mapApiRegionName(apiRegion.getName()))
                                                   .append('-')
                                                   .append(classifier)
                                                   .toString();
 
-        String bundleName = String.format("%s-%s", project.getArtifactId(), finalClassifier);
-        String symbolicName = bundleName.replace('-', '.');
+        final String artifactName = String.format("%s-%s", project.getArtifactId(), finalClassifier);
 
         MavenArchiveConfiguration archiveConfiguration = new MavenArchiveConfiguration();
         archiveConfiguration.setAddMavenDescriptor(false);
         if (APIS.equals(classifier)) {
             // APIs need OSGi Manifest entry
+            String symbolicName = artifactName.replace('-', '.');
             archiveConfiguration.addManifestEntry("Export-Package", getApiExportClause(apiRegion));
             archiveConfiguration.addManifestEntry("Bundle-Description", project.getDescription());
-            archiveConfiguration.addManifestEntry("Bundle-Version", featureId.getOSGiVersion().toString());
+            archiveConfiguration.addManifestEntry("Bundle-Version", ctx.getFeatureId().getOSGiVersion().toString());
             archiveConfiguration.addManifestEntry("Bundle-ManifestVersion", "2");
             archiveConfiguration.addManifestEntry("Bundle-SymbolicName", symbolicName);
-            archiveConfiguration.addManifestEntry("Bundle-Name", bundleName);
+            archiveConfiguration.addManifestEntry("Bundle-Name", artifactName);
 
             if (nodeTypes != null && !nodeTypes.isEmpty()) {
                 archiveConfiguration.addManifestEntry("Sling-Nodetypes", StringUtils.join(nodeTypes.iterator(), ","));
             }
+            if (project.getOrganization() != null) {
+                archiveConfiguration.addManifestEntry("Bundle-Vendor", project.getOrganization().getName());
+            }
         }
-        if (project.getOrganization() != null) {
-            archiveConfiguration.addManifestEntry("Bundle-Vendor", project.getOrganization().getName());
-        }
-        archiveConfiguration.addManifestEntry("Specification-Version", featureId.getVersion());
-        archiveConfiguration.addManifestEntry("Implementation-Title", bundleName);
+        archiveConfiguration.addManifestEntry("Specification-Version", ctx.getFeatureId().getVersion());
+        archiveConfiguration.addManifestEntry("Implementation-Title", artifactName);
 
         String targetName = String.format("%s-%s-%s.jar", project.getArtifactId(), project.getVersion(), finalClassifier);
         File target = new File(mainOutputDir, targetName);
@@ -1325,16 +1281,22 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
         return target;
     }
 
-    private void generateJavadoc(File sourcesDir, File javadocDir, ApisJarContext ctx, List<String> subpackageDirectories)
+    private void generateJavadoc(final ApisJarContext ctx, final File javadocDir, final Set<String> subpackageDirectories)
             throws MojoExecutionException {
         javadocDir.mkdirs();
 
         JavadocExecutor javadocExecutor = new JavadocExecutor(javadocDir.getParentFile())
                                           .addArgument("-public")
+                                          .addArgument("-encoding", false)
+                                          .addArgument("UTF-8")
+                                          .addArgument("-charset", false)
+                                          .addArgument("UTF-8")
+                                          .addArgument("-docencoding", false)
+                                          .addArgument("UTF-8")
                                           .addArgument("-d", false)
                                           .addArgument(javadocDir.getAbsolutePath())
                                           .addArgument("-sourcepath", false)
-                                          .addArgument(sourcesDir.getAbsolutePath());
+                                          .addArgument(String.join(":", ctx.getArtifactInfos().stream().map( info -> info.getSourceDirectory()).filter(dir -> dir != null).map(dir -> dir.getAbsolutePath()).collect(Collectors.toList())));
 
         if (isNotEmpty(javadocSourceLevel)) {
             javadocExecutor.addArgument("-source", false)
@@ -1392,48 +1354,27 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
      * @param sourcesDir the directory with source files
      * @return a list of subpackages, potentially empty, never <code>null</code>
      */
-    private List<String> calculateSubpackageDirectories(File sourcesDir) {
-        // make sure to only include files that have at least one java source file
-        return Arrays.stream(sourcesDir.listFiles( new FileFilter() {
-            @Override
-            public boolean accept(File pathname) {
-                try {
-                    try ( Stream<Path> javaFiles = Files.find(pathname.toPath(), Integer.MAX_VALUE, IS_JAVA_FILE)) {
-                        return javaFiles.findFirst().isPresent();
-                    }
-                } catch (IOException e) {
-                    getLog().warn("Failed scanning " + pathname + " for Java files", e);
-                    return false;
-                }
-            }
-        })).map( File::getName ).collect(Collectors.toList());
-    }
-
-    private static ArtifactId newArtifacId(ArtifactId original, String classifier, String type) {
-        return new ArtifactId(original.getGroupId(),
-                              original.getArtifactId(),
-                              original.getVersion(),
-                              classifier,
-                              type);
-    }
-
+    private Set<String> calculateSubpackageDirectories(List<File> sourcesDirs) {
+        final Set<String> result = new HashSet<>();
+        for(final File sourcesDir : sourcesDirs) {
+            final int prefixLength = sourcesDir.getAbsolutePath().length() + 1;
 
+            // make sure to only include directories that have at least one java source file
+            collectSubpackageDirectories(prefixLength, sourcesDir, result);
+        }
 
-    private static String packageToScannerFiler(String api, boolean strict) {
-        return (strict ? "*": "**") + '/' + api.replace('.', '/') + "/*";
-    }
-
-    private static String[] concatenate(String[] a, String[] b) {
-        String[] result = new String[a.length + b.length];
-        System.arraycopy(a, 0, result, 0, a.length);
-        System.arraycopy(b, 0, result, a.length, b.length);
         return result;
     }
 
-    private static File newDir(File parent, String child) {
-        File dir = new File(parent, child);
-        dir.mkdirs();
-        return dir;
+    private void collectSubpackageDirectories(final int prefixLength, final File dir, final Set<String> result) {
+        for(final File child : dir.listFiles()) {
+            if ( child.isDirectory() ) {
+                collectSubpackageDirectories(prefixLength, child, result);
+            } else if ( child.getName().endsWith(JAVA_EXTENSION) ) {
+                final String javaPackage = dir.getAbsolutePath().substring(prefixLength).replace(File.separatorChar, '.');
+                result.add(javaPackage);
+            }
+        }
     }
 
     private static <T> T setIfNull(T what, T with) {
@@ -1447,16 +1388,6 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo implements Artifac
         return s != null && !s.isEmpty();
     }
 
-    // artifact filter
-
-    @Override
-    public boolean include(org.apache.maven.artifact.Artifact artifact) {
-        if (org.apache.maven.artifact.Artifact.SCOPE_TEST.equals(artifact.getScope())) {
-            return false;
-        }
-        return true;
-    }
-
     private List<String> getPackages(final File file, final String extension) throws MojoExecutionException {
         final String postfix = ".".concat(extension);
         final Set<String> packages = new HashSet<>();