org.jahia.utils.maven.plugin.osgi.BuildFrameworkPackageListMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.jahia.utils.maven.plugin.osgi.BuildFrameworkPackageListMojo.java

Source

/**
 * ==========================================================================================
 * =                   JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION                       =
 * ==========================================================================================
 *
 *                                 http://www.jahia.com
 *
 *     Copyright (C) 2002-2018 Jahia Solutions Group SA. All rights reserved.
 *
 *     THIS FILE IS AVAILABLE UNDER TWO DIFFERENT LICENSES:
 *     1/GPL OR 2/JSEL
 *
 *     1/ GPL
 *     ==================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE GPL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *     2/ JSEL - Commercial and Supported Versions of the program
 *     ===================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE JSEL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     Alternatively, commercial and supported versions of the program - also known as
 *     Enterprise Distributions - must be used in accordance with the terms and conditions
 *     contained in a separate written agreement between you and Jahia Solutions Group SA.
 *
 *     If you are unsure which license is appropriate for your use,
 *     please contact the sales department at sales@jahia.com.
 */
package org.jahia.utils.maven.plugin.osgi;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.codehaus.plexus.util.DirectoryScanner;
import org.eclipse.osgi.util.ManifestElement;
import org.jahia.utils.maven.plugin.AetherAwareMojo;
import org.jahia.utils.maven.plugin.SLF4JLoggerToMojoLogBridge;
import org.jahia.utils.osgi.ManifestValueClause;
import org.jahia.utils.osgi.ManifestValueParser;
import org.jahia.utils.osgi.PropertyFileUtils;
import org.osgi.framework.BundleException;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This maven goal will build the list of system packages that is exposed by the OSGi framework by default.
 * In order to this it can use as input:
 * - An existing property file that already includes a package list in OSGi format.
 * - A previously generated MANIFEST.MF by the Maven Bundle Plugin
 * - The contents of WEB-INF/classes
 * - The contents of WEB-INF/lib
 * - Dependencies of the project marked with "provided" scope.
 *
 * @goal build-framework-package-list
 * @requiresDependencyResolution test
 */
public class BuildFrameworkPackageListMojo extends AetherAwareMojo {

    public static final String VERSION_NUMBER_PATTERN_STRING = "([\\d\\.]*\\d)(.*)";
    //private static final Pattern VERSION_NUMBER_PATTERN = Pattern.compile(VERSION_NUMBER_PATTERN_STRING);

    /**
     * Clean up version parameters. Other builders use more fuzzy definitions of
     * the version syntax. This method cleans up such a version to match an OSGi
     * version.
     *
     * @param VERSION_STRING
     * @return
     */
    static final Pattern FUZZY_VERSION = Pattern.compile("(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?",
            Pattern.DOTALL);

    /**
     * @parameter default-value="${project.build.directory}/classes/META-INF/MANIFEST.MF"
     */
    protected File inputManifestFile;

    /**
     * @parameter default-value="${project.build.directory}/${project.build.finalName}/WEB-INF/lib"
     */
    protected List<String> jarDirectories;

    /**
     * @parameter default-value="${project.basedir}/src/main/webapp/WEB-INF/etc/config/felix-framework.properties"
     */
    protected File propertiesInputFile;

    /**
     * @parameter default-value="${project.build.directory}/generated-resources/felix-framework.properties"
     */
    protected File propertiesOutputFile;

    /**
     * @parameter
     */
    protected List<String> manualPackageList;

    /**
     * @parameter default-value="true"
     */
    protected boolean scanDependencies = true;

    /**
     * @parameter default-value="false"
     */
    protected boolean exportEachPackageOnce = false;

    /**
     * Because of bug http://jira.codehaus.org/browse/MNG-5440 we cannot use a default-value otherwise the values from
     * the POM will not be used, so we use a constant (below) and initialization code to set it if the project has
     * not set it.
     * @parameter
     */
    protected List<String> artifactExcludes;

    /**
     * Because of bug http://jira.codehaus.org/browse/MNG-5440 we set the default value at runtime using the following
     * constant
     */
    private static final String ARTIFACT_EXCLUDE_DEFAULT_VALUE = "org.jahia.modules:*,org.jahia.templates:*,org.jahia.test:*,*.jahia.modules";

    /**
     * Because of bug http://jira.codehaus.org/browse/MNG-5440 we cannot use a default-value otherwise the values from
     * the POM will not be used, so we use a constant (below) and initialization code to set it if the project has
     * not set it.
     * @parameter
     */
    protected List<String> packageExcludes;

    /**
     * Because of bug http://jira.codehaus.org/browse/MNG-5440 we set the default value at runtime using the following
     * constant. Note that if any of these packages are exported by default by Felix already, they won't be excluded since
     * we only deal with "extra" packages, not the "base" exported packages.
     */
    private static final String PACKAGE_EXCLUDE_DEFAULT_VALUE = "org.jahia.taglibs*,org.apache.taglibs.standard*,javax.servlet.jsp*,org.codehaus.groovy.ast*,javax.el*,de.odysseus.el*";

    /**
     * @parameter default-value="true"
     */
    protected boolean outputPackagesWithNoVersions = true;

    /**
     * @parameter default-value="org.osgi.framework.system.packages.extra"
     */
    protected String propertyFilePropertyName = "org.osgi.framework.system.packages.extra";

    /**
     * @parameter default-value="org.osgi.framework.system.packages"
     */
    protected String propertyFileSystemPackagesPropertyName = "org.osgi.framework.system.packages";

    /**
     * @parameter default-value="org.osgi.framework.bootdelegation"
     */
    protected String propertyFileBootDelegationPropertyName = "org.osgi.framework.bootdelegation";

    private List<Pattern> artifactExclusionPatterns = new ArrayList<Pattern>();
    private List<Pattern> packageExclusionPatterns = new ArrayList<Pattern>();

    private class VersionLocation {
        private String location;
        private String version;
        private String specificationVersion;
        private long counter = 0;

        public VersionLocation(String location, String version, String specificationVersion) {
            this.location = location;
            this.version = version;
            this.specificationVersion = specificationVersion;
        }

        @SuppressWarnings("unused")
        public String getLocation() {
            return location;
        }

        public String getVersion() {
            return version;
        }

        public String getSpecificationVersion() {
            return specificationVersion;
        }

        public void incrementCounter() {
            counter++;
        }

        public long getCounter() {
            return counter;
        }
    }

    public void setPackageExcludes(List<String> packageExcludes) {
        this.packageExcludes = packageExcludes;
    }

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {

        buildExclusionPatterns();

        buildPackageExcludes();

        Map<String, Map<String, Map<String, VersionLocation>>> packageVersionCounts = new TreeMap<String, Map<String, Map<String, VersionLocation>>>();
        Map<String, Set<String>> packageVersions = new TreeMap<String, Set<String>>();
        String generatedPackageList = null;

        try {
            scanExistingExports(packageVersionCounts);

            if (project != null) {

                if (scanDependencies) {
                    scanDependencies(packageVersionCounts);
                }

                // now let's scan the current project. Assuming it's been built already.

                scanClassesBuildDirectory(packageVersionCounts);

                scanJarDirectories(packageVersionCounts);

            }

            scanExistingManifest(packageVersionCounts);

            excludeSystemPackages(packageVersionCounts);
            resolveSplitPackages(packageVersionCounts, packageVersions);

            if (propertiesOutputFile != null && !propertiesOutputFile.exists()) {
                propertiesOutputFile.getParentFile().mkdirs();
                propertiesOutputFile.createNewFile();
            }

            List<String> packageList = new ArrayList<String>();

            StringBuilder generatedPackageBuffer = new StringBuilder();
            for (Map.Entry<String, Set<String>> packageVersion : packageVersions.entrySet()) {
                if (packageVersion.getValue() != null) {
                    // @todo we should perform parent lookup here and re-use version if activated.
                    boolean allVersionsAreNull = true;
                    for (String versionString : packageVersion.getValue()) {
                        if (versionString != null) {
                            allVersionsAreNull = false;
                            StringBuilder packageExport = new StringBuilder();
                            packageExport.append(packageVersion.getKey());
                            versionString = cleanupVersion(versionString);
                            packageExport.append(";version=\"");
                            packageExport.append(versionString);
                            packageExport.append("\"");
                            if (!isPackageExcluded(packageExport.toString())
                                    && !isPackageExcluded(packageVersion.getKey())) {
                                packageExport.append(",");
                                if (packageList.contains(packageExport.toString())) {
                                    getLog().warn("Package export " + packageExport.toString()
                                            + " already present in list, will not add again!");
                                } else {
                                    packageList.add(packageExport.toString());
                                }
                                generatedPackageBuffer.append(packageExport);
                            } else {
                                getLog().info("Package " + packageExport.toString()
                                        + " matched exclusion list, will not be included !");
                            }
                        }
                    }
                    if (allVersionsAreNull && outputPackagesWithNoVersions) {
                        StringBuilder packageExport = new StringBuilder();
                        packageExport.append(packageVersion.getKey());
                        if (!isPackageExcluded(packageExport.toString())) {
                            packageExport.append(",");
                            if (packageList.contains(packageExport.toString())) {
                                getLog().warn("Package export " + packageExport.toString()
                                        + " already present in list, will not add again!");
                            } else {
                                getLog().info("Adding package " + packageExport.toString() + " with no version");
                                packageList.add(packageExport.toString());
                            }
                            generatedPackageBuffer.append(packageExport);
                        } else {
                            getLog().info("Package " + packageExport.toString()
                                    + " matched exclusion list, will not be included !");
                        }
                    }
                }
            }
            if (manualPackageList != null) {
                for (String manualPackage : manualPackageList) {
                    if (!packageList.contains(manualPackage + ",") && !isPackageExcluded(manualPackage)) {
                        /*
                        if (manualPackage.contains("=")) {
                        manualPackage = manualPackage.replaceAll("=", "\\=");
                        }
                        */
                        packageList.add(manualPackage + ",");
                        generatedPackageBuffer.append(manualPackage);
                        generatedPackageBuffer.append(",");
                    } else if (isPackageExcluded(manualPackage)) {
                        getLog().info(
                                "Package " + manualPackage + " matched exclusion list, will not be included !");
                    }
                }
            }
            generatedPackageList = generatedPackageBuffer.toString();
            generatedPackageList = generatedPackageList.substring(0, generatedPackageList.length() - 1); // remove the last comma
            String lastPackage = packageList.remove(packageList.size() - 1);
            packageList.add(lastPackage.substring(0, lastPackage.length() - 1)); // remove the last comma
            getLog().info("Found " + packageVersions.size() + " packages in dependencies.");
            // getLog().debug("org.osgi.framework.system.packages.extra="+ generatedPackageList);
            if (generatedPackageList != null && project != null) {
                project.getProperties().put("jahiaGeneratedFrameworkPackageList", generatedPackageList);
            }
            PropertyFileUtils.updatePropertyFile(propertiesInputFile, propertiesOutputFile,
                    propertyFilePropertyName, packageList.toArray(new String[packageList.size()]),
                    new SLF4JLoggerToMojoLogBridge(getLog()));
        } catch (FileNotFoundException e) {
            e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
        } catch (IOException e) {
            e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
        } catch (Exception e) {
            e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
        }
    }

    private void buildExclusionPatterns() {
        if (artifactExcludes == null) {
            // We put the default value here because of bug http://jira.codehaus.org/browse/MNG-5440
            String[] artifactExcludesArray = ARTIFACT_EXCLUDE_DEFAULT_VALUE.split(",");
            artifactExcludes = new ArrayList<String>(Arrays.asList(artifactExcludesArray));
        }
        for (String artifactExclude : artifactExcludes) {
            int colonPos = artifactExclude.indexOf(":");
            String groupPattern = ".*";
            String artifactPattern = null;
            if (colonPos > -1) {
                groupPattern = artifactExclude.substring(0, colonPos);
                artifactPattern = artifactExclude.substring(colonPos + 1);
            } else {
                artifactPattern = artifactExclude;
            }
            groupPattern = groupPattern.replaceAll("\\.", "\\\\.");
            groupPattern = groupPattern.replaceAll("\\*", ".*");
            artifactPattern = artifactPattern.replaceAll("\\.", "\\\\.");
            artifactPattern = artifactPattern.replaceAll("\\*", ".*");
            artifactExclusionPatterns.add(Pattern.compile(groupPattern + ":" + artifactPattern));
        }
    }

    private void buildPackageExcludes() {
        if (packageExcludes == null) {
            // We put the default value here because of bug http://jira.codehaus.org/browse/MNG-5440
            String[] packageExcludesArray = PACKAGE_EXCLUDE_DEFAULT_VALUE.split(",");
            packageExcludes = new ArrayList<String>(Arrays.asList(packageExcludesArray));
        }
        for (String packageExclude : packageExcludes) {
            String packageExcludePattern = packageExclude;
            packageExcludePattern = packageExcludePattern.replaceAll("\\.", "\\\\.");
            packageExcludePattern = packageExcludePattern.replaceAll("\\*", ".*");
            packageExclusionPatterns.add(Pattern.compile(packageExcludePattern));
        }
    }

    private boolean isPackageExcluded(String packageExport) {
        for (Pattern packageExclusionPattern : packageExclusionPatterns) {
            Matcher packageExclusionMatcher = packageExclusionPattern.matcher(packageExport);
            if (packageExclusionMatcher.matches()) {
                return true;
            }
        }
        return false;
    }

    private void scanExistingExports(Map<String, Map<String, Map<String, VersionLocation>>> packageVersionCounts) {
        if (!propertiesInputFile.exists()) {
            return;
        }
        FileInputStream fileInputStream = null;
        try {
            Properties properties = new Properties();
            fileInputStream = new FileInputStream(propertiesInputFile);
            properties.load(fileInputStream);
            String exportPropertyValue = (String) properties.get(propertyFilePropertyName);
            if (exportPropertyValue == null) {
                return;
            }
            getLog().info("Processing existing property " + propertyFilePropertyName + " from file "
                    + propertiesInputFile + "...");
            ManifestElement[] manifestElements = ManifestElement.parseHeader("Export-Package", exportPropertyValue);
            for (ManifestElement manifestElement : manifestElements) {
                String[] packageNames = manifestElement.getValueComponents();
                String version = manifestElement.getAttribute("version");
                for (String packageName : packageNames) {
                    updateVersionLocationCounts(packageVersionCounts, propertiesInputFile.toString(), version, null,
                            packageName);
                }
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
        } catch (IOException e) {
            e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
        } catch (Exception e) {
            e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
        } finally {
            IOUtils.closeQuietly(fileInputStream);
        }
    }

    private void scanExistingManifest(Map<String, Map<String, Map<String, VersionLocation>>> packageVersionCounts)
            throws IOException, Exception {
        FileInputStream in = null;
        try {
            if (inputManifestFile.exists()) {
                in = new FileInputStream(inputManifestFile);
                Manifest mf = new Manifest(in);
                String exportPackageStr = mf.getMainAttributes().getValue("Export-Package");
                String bundleVersion = mf.getMainAttributes().getValue("Bundle-Version");
                ManifestElement[] manifestElements = ManifestElement.parseHeader("Export-Package",
                        exportPackageStr);
                if (manifestElements != null) {
                    for (ManifestElement manifestElement : manifestElements) {
                        String[] packageNames = manifestElement.getValueComponents();
                        String version = manifestElement.getAttribute("version");
                        if (version != null) {
                            for (String packageName : packageNames) {
                                if (version.equals(bundleVersion)) {
                                    if (packageName.startsWith("org.jahia")) {
                                        updateVersionLocationCounts(packageVersionCounts,
                                                inputManifestFile.toString(), version, bundleVersion, packageName);
                                    } else {
                                        updateVersionLocationCounts(packageVersionCounts,
                                                inputManifestFile.toString(), null, bundleVersion, packageName);
                                    }
                                } else {
                                    updateVersionLocationCounts(packageVersionCounts, inputManifestFile.toString(),
                                            version, bundleVersion, packageName);
                                }
                            }
                        } else {
                            for (String packageName : packageNames) {
                                updateVersionLocationCounts(packageVersionCounts, inputManifestFile.toString(),
                                        null, bundleVersion, packageName);
                            }
                        }
                    }
                    getLog().info("Found " + manifestElements.length + " package exports.");
                }
            }
        } finally {
            IOUtils.closeQuietly(in);
        }
    }

    private void resolveSplitPackages(Map<String, Map<String, Map<String, VersionLocation>>> packageVersionCounts,
            Map<String, Set<String>> packageVersions) {
        for (Map.Entry<String, Map<String, Map<String, VersionLocation>>> resolvedPackageVersion : packageVersionCounts
                .entrySet()) {
            boolean allVersionsEqual = true;
            Set<String> previousVersions = null;
            for (Map.Entry<String, Map<String, VersionLocation>> versionLocationEntry : resolvedPackageVersion
                    .getValue().entrySet()) {
                if (previousVersions != null
                        && !previousVersions.equals(versionLocationEntry.getValue().keySet())) {
                    allVersionsEqual = false;
                    break;
                }
                previousVersions = versionLocationEntry.getValue().keySet();
            }
            if (resolvedPackageVersion.getValue().size() > 1 && !allVersionsEqual) {
                getLog().warn("Split-package with different versions detected for package "
                        + resolvedPackageVersion.getKey() + ":");
            }
            Set<String> versions = new HashSet<String>();
            for (Map.Entry<String, Map<String, VersionLocation>> versionLocationEntry : resolvedPackageVersion
                    .getValue().entrySet()) {
                if (resolvedPackageVersion.getValue().size() > 1 && !allVersionsEqual) {
                    for (Map.Entry<String, VersionLocation> versionLocationsEntry : versionLocationEntry.getValue()
                            .entrySet()) {
                        getLog().warn("  - " + versionLocationEntry.getKey() + " v"
                                + versionLocationsEntry.getValue().getVersion() + " count="
                                + versionLocationsEntry.getValue().getCounter() + " Specification-Version="
                                + versionLocationsEntry.getValue().getSpecificationVersion());
                    }
                }
                if (versionLocationEntry.getValue() == null) {
                    continue;
                }
                for (String version : versionLocationEntry.getValue().keySet()) {
                    if (!versions.contains(version)) {
                        versions.add(version);
                    }
                }
            }
            packageVersions.put(resolvedPackageVersion.getKey(), versions);
        }
    }

    private void scanClassesBuildDirectory(
            Map<String, Map<String, Map<String, VersionLocation>>> packageVersionCounts) throws IOException {
        File outputDirectoryFile = new File(project.getBuild().getOutputDirectory());
        getLog().info("Scanning project build directory " + outputDirectoryFile.getCanonicalPath());
        DirectoryScanner ds = new DirectoryScanner();
        String[] excludes = { "META-INF/**", "OSGI-INF/**", "OSGI-OPT/**", "WEB-INF/**" };
        ds.setExcludes(excludes);
        ds.setBasedir(outputDirectoryFile);
        ds.setCaseSensitive(true);
        ds.scan();
        String[] includedFiles = ds.getIncludedFiles();
        for (String includedFile : includedFiles) {
            // getLog().debug("Processing file " + includedFile + "...");
            String entryPackage = "";
            int lastSlash = includedFile.lastIndexOf("/");
            if (lastSlash > -1) {
                entryPackage = includedFile.substring(0, lastSlash);
                entryPackage = entryPackage.replaceAll("/", ".");
                if (StringUtils.isNotEmpty(entryPackage) && !entryPackage.startsWith("META-INF")
                        && !entryPackage.startsWith("OSGI-INF") && !entryPackage.startsWith("OSGI-OPT")
                        && !entryPackage.startsWith("WEB-INF") && !entryPackage.startsWith("org.osgi")) {
                    updateVersionLocationCounts(packageVersionCounts, project.getBuild().getFinalName(),
                            project.getVersion(), null, entryPackage);
                }
            }
        }
    }

    private void scanDependencies(Map<String, Map<String, Map<String, VersionLocation>>> packageVersionCounts)
            throws IOException {
        getLog().info("Scanning project dependencies...");
        for (Artifact artifact : project.getArtifacts()) {
            String exclusionMatched = null;
            for (Pattern exclusionPattern : artifactExclusionPatterns) {
                Matcher exclusionMatcher = exclusionPattern
                        .matcher(artifact.getGroupId() + ":" + artifact.getArtifactId());
                if (exclusionMatcher.matches()) {
                    exclusionMatched = artifact.getGroupId() + ":" + artifact.getArtifactId();
                    break;
                }
            }
            if (exclusionMatched != null) {
                getLog().info("Matched exclusion " + exclusionMatched + ", ignoring artifact.");
                continue;
            }

            if (artifact.getScope().contains(Artifact.SCOPE_PROVIDED)
                    || artifact.getScope().contains(Artifact.SCOPE_COMPILE)
                    || artifact.getScope().contains(Artifact.SCOPE_RUNTIME)) {
                if (!artifact.getType().equals("jar")) {
                    getLog().warn("Ignoring artifact " + artifact.getFile() + " since it is of type "
                            + artifact.getType());
                    continue;
                }
                getLog().debug("Scanning dependency " + artifact.getFile());
                scanJar(packageVersionCounts, artifact.getFile(), artifact.getBaseVersion());
            }
        }
    }

    private void scanJar(Map<String, Map<String, Map<String, VersionLocation>>> packageVersionCounts, File jarFile,
            String defaultVersion) throws IOException {
        JarInputStream jarInputStream = new JarInputStream(new FileInputStream(jarFile));
        Manifest jarManifest = jarInputStream.getManifest();
        // Map<String, String> manifestVersions = new HashMap<String,String>();
        String specificationVersion = null;
        if (jarManifest == null) {
            getLog().warn("No MANIFEST.MF file found for dependency " + jarFile);
        } else {
            if (jarManifest.getMainAttributes() == null) {
                getLog().warn("No main attributes found in MANIFEST.MF file found for dependency " + jarFile);
            } else {
                specificationVersion = jarManifest.getMainAttributes().getValue("Specification-Version");
                if (defaultVersion == null) {
                    if (jarManifest.getMainAttributes().getValue("Bundle-Version") != null) {
                    } else if (specificationVersion != null) {
                        defaultVersion = specificationVersion;
                    } else {
                        defaultVersion = jarManifest.getMainAttributes().getValue("Implementation-Version");
                    }
                }
                String exportPackageHeaderValue = jarManifest.getMainAttributes().getValue("Export-Package");
                if (exportPackageHeaderValue != null) {
                    ManifestElement[] manifestElements = new ManifestElement[0];
                    try {
                        manifestElements = ManifestElement.parseHeader("Export-Package", exportPackageHeaderValue);
                    } catch (BundleException e) {
                        getLog().warn("Error while parsing Export-Package header value for jar " + jarFile, e);
                    }
                    for (ManifestElement manifestElement : manifestElements) {
                        String[] packageNames = manifestElement.getValueComponents();
                        String version = manifestElement.getAttribute("version");
                        for (String packageName : packageNames) {
                            updateVersionLocationCounts(packageVersionCounts, jarFile.getCanonicalPath(), version,
                                    version, packageName);
                        }
                    }
                }
                for (Map.Entry<String, Attributes> manifestEntries : jarManifest.getEntries().entrySet()) {
                    String packageName = manifestEntries.getKey().replaceAll("/", ".");
                    if (packageName.endsWith(".class")) {
                        continue;
                    }
                    if (packageName.endsWith(".")) {
                        packageName = packageName.substring(0, packageName.length() - 1);
                    }
                    if (packageName.endsWith(".*")) {
                        packageName = packageName.substring(0, packageName.length() - 1);
                    }
                    int lastDotPos = packageName.lastIndexOf(".");
                    String lastPackage = packageName;
                    if (lastDotPos > -1) {
                        lastPackage = packageName.substring(lastDotPos + 1);
                    }
                    if (lastPackage.length() > 0 && Character.isUpperCase(lastPackage.charAt(0))) {
                        // ignore non package version
                        continue;
                    }
                    if (StringUtils.isEmpty(packageName) || packageName.startsWith("META-INF")
                            || packageName.startsWith("OSGI-INF") || packageName.startsWith("OSGI-OPT")
                            || packageName.startsWith("WEB-INF") || packageName.startsWith("org.osgi")) {
                        // ignore private package names
                        continue;
                    }
                    String packageVersion = null;
                    if (manifestEntries.getValue().getValue("Specification-Version") != null) {
                        packageVersion = manifestEntries.getValue().getValue("Specification-Version");
                    } else {
                        packageVersion = manifestEntries.getValue().getValue("Implementation-Version");
                    }
                    if (packageVersion != null) {
                        getLog().info("Found package version in " + jarFile.getName() + " MANIFEST : " + packageName
                                + " v" + packageVersion);
                        updateVersionLocationCounts(packageVersionCounts, jarFile.getCanonicalPath(),
                                packageVersion, specificationVersion, packageName);
                        // manifestVersions.put(packageName, packageVersion);
                    }
                }
            }
        }
        JarEntry jarEntry = null;
        // getLog().debug("Processing file " + artifact.getFile() + "...");
        while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
            if (!jarEntry.isDirectory()) {
                String entryName = jarEntry.getName();
                String entryPackage = "";
                int lastSlash = entryName.lastIndexOf("/");
                if (lastSlash > -1) {
                    entryPackage = entryName.substring(0, lastSlash);
                    entryPackage = entryPackage.replaceAll("/", ".");
                    if (StringUtils.isNotEmpty(entryPackage) && !entryPackage.startsWith("META-INF")
                            && !entryPackage.startsWith("OSGI-INF") && !entryPackage.startsWith("OSGI-OPT")
                            && !entryPackage.startsWith("WEB-INF") && !entryPackage.startsWith("org.osgi")) {
                        updateVersionLocationCounts(packageVersionCounts, jarFile.getCanonicalPath(),
                                defaultVersion, specificationVersion, entryPackage);
                    }
                }
            }
        }
        jarInputStream.close();
    }

    private void excludePackages(Map<String, Map<String, Map<String, VersionLocation>>> packageVersionCounts,
            String propertyFileExclusionPropertyName) throws IOException, MojoExecutionException {
        if (!propertiesInputFile.exists()) {
            return;
        }
        FileInputStream fileInputStream = null;
        try {
            Properties properties = new Properties();
            fileInputStream = new FileInputStream(propertiesInputFile);
            properties.load(fileInputStream);
            String packageExclusionList = (String) properties.get(propertyFileExclusionPropertyName);
            if (packageExclusionList == null) {
                return;
            }
            ManifestValueParser manifestValueParser = new ManifestValueParser(propertyFileExclusionPropertyName,
                    packageExclusionList, true);
            List<ManifestValueClause> exclusionValueClauses = manifestValueParser.getManifestValueClauses();
            List<String> excludedPatterns = new ArrayList<String>();
            List<String> excludedPackages = new ArrayList<String>();
            for (ManifestValueClause exclusionValueClause : exclusionValueClauses) {
                for (String exclusionPath : exclusionValueClause.getPaths()) {
                    if (exclusionPath.endsWith(".*")) {
                        excludedPatterns.add(exclusionPath.substring(0, exclusionPath.length() - 2));
                    } else {
                        excludedPackages.add(exclusionPath);
                    }
                }
            }
            for (String pack : packageVersionCounts.keySet()) {
                for (String pattern : excludedPatterns) {
                    if (pack.startsWith(pattern)) {
                        excludedPackages.add(pack);
                    }
                }
            }
            packageVersionCounts.keySet().removeAll(excludedPackages);
        } finally {
            IOUtils.closeQuietly(fileInputStream);
        }
    }

    private void excludeSystemPackages(Map<String, Map<String, Map<String, VersionLocation>>> packageVersionCounts)
            throws IOException, MojoExecutionException {
        excludePackages(packageVersionCounts, propertyFileSystemPackagesPropertyName);
    }

    private void updateVersionLocationCounts(
            Map<String, Map<String, Map<String, VersionLocation>>> packageVersionCounts, String originLocation,
            String newVersion, String specificationVersion, String packageName) throws IOException {
        // first check if we've already processed this package
        Map<String, Map<String, VersionLocation>> versionLocations = packageVersionCounts.get(packageName);
        if (versionLocations == null) {
            versionLocations = new HashMap<String, Map<String, VersionLocation>>();
        }

        Map<String, VersionLocation> existingVersionLocations = versionLocations.get(originLocation);
        if (existingVersionLocations != null && existingVersionLocations.containsKey(newVersion)) {
            VersionLocation existingVersionLocation = existingVersionLocations.get(newVersion);
            existingVersionLocation.incrementCounter();
            existingVersionLocations.put(newVersion, existingVersionLocation);
        } else {
            if (existingVersionLocations == null) {
                existingVersionLocations = new HashMap<String, VersionLocation>();
            }
            VersionLocation existingVersionLocation = new VersionLocation(originLocation,
                    cleanupVersion(newVersion), specificationVersion);
            existingVersionLocation.incrementCounter();
            existingVersionLocations.put(newVersion, existingVersionLocation);
        }
        versionLocations.put(originLocation, existingVersionLocations);

        packageVersionCounts.put(packageName, versionLocations);
    }

    private void scanJarDirectories(Map<String, Map<String, Map<String, VersionLocation>>> packageVersionCounts)
            throws IOException, MojoExecutionException {
        if (jarDirectories == null || jarDirectories.size() == 0) {
            return;
        }
        for (String jarDirectory : jarDirectories) {
            File jarDirectoryFile = new File(jarDirectory);
            if (!jarDirectoryFile.exists() || !jarDirectoryFile.isDirectory()) {
                getLog().warn("Ignoring invalid directory " + jarDirectory + ".");
                continue;
            }
            getLog().info("Scanning JARs in directory " + jarDirectory + "...");
            DirectoryScanner ds = new DirectoryScanner();
            String[] includes = { "*.jar" };
            ds.setIncludes(includes);
            ds.setBasedir(jarDirectory);
            ds.setCaseSensitive(true);
            ds.scan();
            String[] includedFiles = ds.getIncludedFiles();
            for (String includeFile : includedFiles) {
                String version = null;
                File includedFileFile = new File(jarDirectoryFile, includeFile);
                String artifactFileName = includedFileFile.getName();
                List<String> versions = getAetherHelper().getDependencyVersion(project, artifactFileName);

                if (versions.size() > 1) {
                    getLog().warn("multiple matching dependencies found for artifactId " + artifactFileName);
                } else if (versions.size() == 1) {
                    version = versions.iterator().next();
                } else {
                    getLog().warn("Couldn't find dependency for artifactId " + artifactFileName);
                    // @todo let's try to extract the version from the file name.
                }

                scanJar(packageVersionCounts, includedFileFile, version);
            }
        }
    }

    // The following code was copied from the Maven Bundle Plugin code.
    /*
     * Licensed to the Apache Software Foundation (ASF) under one
     * or more contributor license agreements.  See the NOTICE file
     * distributed with this work for additional information
     * regarding copyright ownership.  The ASF licenses this file
     * to you under the Apache License, Version 2.0 (the
     * "License"); you may not use this file except in compliance
     * with the License.  You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing,
     * software distributed under the License is distributed on an
     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     * KIND, either express or implied.  See the License for the
     * specific language governing permissions and limitations
     * under the License.
     */

    static public String cleanupVersion(String version) {
        if (version != null) {
            StringBuffer result = new StringBuffer();
            Matcher m = FUZZY_VERSION.matcher(version);
            if (m.matches()) {
                String major = m.group(1);
                String minor = m.group(3);
                String micro = m.group(5);
                String qualifier = m.group(7);

                if (major != null) {
                    result.append(major);
                    if (minor != null) {
                        result.append(".");
                        result.append(minor);
                        if (micro != null) {
                            result.append(".");
                            result.append(micro);
                            if (qualifier != null) {
                                result.append(".");
                                cleanupModifier(result, qualifier);
                            }
                        } else if (qualifier != null) {
                            result.append(".0.");
                            cleanupModifier(result, qualifier);
                        } else {
                            result.append(".0");
                        }
                    } else if (qualifier != null) {
                        result.append(".0.0.");
                        cleanupModifier(result, qualifier);
                    } else {
                        result.append(".0.0");
                    }
                }
            } else {
                result.append("0.0.0.");
                cleanupModifier(result, version);
            }
            return result.toString();
        }

        return null;
    }

    static void cleanupModifier(StringBuffer result, String modifier) {
        for (int i = 0; i < modifier.length(); i++) {
            char c = modifier.charAt(i);
            if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '-')
                result.append(c);
            else
                result.append('_');
        }
    }

    // end of copied code.

}