org.codehaus.mojo.nbm.NetBeansManifestUpdateMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.codehaus.mojo.nbm.NetBeansManifestUpdateMojo.java

Source

/* ==========================================================================
 * Copyright 2003-2007 Mevenide Team
 *
 * Licensed 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.
 * =========================================================================
 */
package org.codehaus.mojo.nbm;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.net.URL;
import java.text.BreakIterator;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Pattern;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactCollector;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.codehaus.mojo.nbm.model.Dependency;
import org.codehaus.mojo.nbm.model.NetBeansModule;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.dependency.analyzer.DefaultClassAnalyzer;
import org.apache.maven.shared.dependency.analyzer.asm.ASMDependencyAnalyzer;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.maven.shared.dependency.graph.DependencyNode;
import org.apache.tools.ant.taskdefs.Manifest;
import org.apache.tools.ant.taskdefs.ManifestException;
import org.codehaus.mojo.nbm.utils.ExamineManifest;
import org.codehaus.plexus.util.IOUtil;

/**
 * Goal for generating NetBeans module system specific manifest entries, part of <code>nbm</code> lifecycle/packaging.
 *
 * In order to have the generated manifest picked up by the maven-jar-plugin,
 * one shall add the following configuration snippet to maven-jar-plugin.
 * 
 * <pre>
 *  {@code
   <plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-jar-plugin</artifactId>
   <version>3.0.2</version>
   <configuration>
       <archive>
           <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
       </archive>
   </configuration>
   </plugin>
 * }
 * </pre>
 *
 * @author <a href="mailto:mkleint@codehaus.org">Milos Kleint</a>
 */
@Mojo(name = "manifest", defaultPhase = LifecyclePhase.PROCESS_CLASSES, requiresProject = true, threadSafe = true, requiresDependencyResolution = ResolutionScope.RUNTIME)
public class NetBeansManifestUpdateMojo extends AbstractNbmMojo {

    /**
     * NetBeans module assembly build directory.
     * directory where the the NetBeans jar and nbm file get constructed.
     */
    @Parameter(defaultValue = "${project.build.directory}/nbm", property = "maven.nbm.buildDir")
    protected File nbmBuildDir;

    /**
     * a NetBeans module descriptor containing dependency information and more
     * @deprecated all content from the module descriptor can be defined as plugin configuration now, will be removed in 4.0 entirely
     */
    @Parameter(defaultValue = "${basedir}/src/main/nbm/module.xml")
    protected File descriptor;

    /**
     * maven project
     */
    @Parameter(required = true, readonly = true, property = "project")
    private MavenProject project;

    /**
     * The location of JavaHelp sources for the project. The documentation
     * itself is expected to be in the directory structure based on codenamebase of the module.
     * eg. if your codenamebase is "org.netbeans.modules.apisupport", then the actual docs
     * files shall go to ${basedir}/src/main/javahelp/org/netbeans/modules/apisupport/docs.
     * Obsolete as of NetBeans 7.0 with &#64;HelpSetRegistration.
     * @since 2.7
     */
    @Parameter(defaultValue = "${basedir}/src/main/javahelp")
    protected File nbmJavahelpSource;

    /**
     * Path to manifest file that will be used as base and enhanced with generated content. Any entry specified in the original file
     * will not be overwritten
     * @since 3.0
     */
    @Parameter(required = true, defaultValue = "${basedir}/src/main/nbm/manifest.mf")
    private File sourceManifestFile;

    /**
     * Path to the generated MANIFEST file to use. It will be used by jar:jar plugin.
     * @since 3.0
     */
    @Parameter(required = true, readonly = true, defaultValue = "${project.build.outputDirectory}/META-INF/MANIFEST.MF")
    private File targetManifestFile;

    /**
     * Verify the runtime NetBeans module dependencies and Class-Path items
     * generated from Maven dependencies. The check is done by matching classes used
     * in current project. Allowed values for the parameter are <code>fail</code>, <code>warn</code> and <code>skip</code>.
     * The default is <code>fail</code> in which case the validation failure results in a failed build,
     * in the vast majority of cases the module would fail at runtime anyway.
     *
     * @since 3.0
     */
    @Parameter(property = "maven.nbm.verify", defaultValue = "fail")
    private String verifyRuntime;

    private static final String FAIL = "fail";
    private static final String WARN = "warn";
    private static final String SKIP = "skip";

    /**
     * A list of module's public packages. If not defined, no packages are exported as public.
     * Allowed values are single package names
     * or package names ending with .* which represent the package and all subpackages.
     * <p/>
     * Eg. "org.kleint.milos.api" designates just the one package, while "org.kleint.milos.spi.*"
     * denotes the spi package an all it's subpackages.
     * @since 3.0
     */
    @Parameter
    private List<String> publicPackages;

    /**
     * When encountering an OSGi bundle among dependencies, the plugin will generate a direct dependency
     * on the bundle and will not include the bundle's jar into the nbm. Will only work with NetBeans 6.9+ runtime.
     * Therefore it is off by default.
     * WARNING: Additionally existing applications/modules need to check modules wrapping
     * external libraries for library jars that are also OSGi bundles. Such modules will no longer include the OSGi bundles
     * as part of the module but will include a modular dependency on the bundle. Modules depending on these old wrappers
     * shall depend directly on the bundle, eventually rendering the old library wrapper module obsolete.
     *
     * @since 3.2
     */
    @Parameter(defaultValue = "false")
    private boolean useOSGiDependencies;

    /**
     * codename base of the module, uniquely identifying the module within the NetBeans runtime. usually the package name equivalent.
     * Can include the major release version.
     * See <a href="http://bits.netbeans.org/dev/javadoc/org-openide-modules/org/openide/modules/doc-files/api.html#how-manifest"> NetBeans Module system docs</a>
     * @since 3.8
     */
    @Parameter(defaultValue = "${project.groupId}.${project.artifactId}")
    private String codeNameBase;

    /**
     * List of explicit module dependency declarations overriding the default specification dependency. Useful when depending on a range of major versions,
     * depending on implementation version etc.
     * <p>The format is:
     * <pre>
     * &lt;dependency&gt;
     *    &lt;id&gt;groupId:artifactId&lt;/id&gt;
     *    &lt;type&gt;spec|impl|loose&lt;/type&gt;
     *    &lt;explicitValue&gt;the entire dependency token&lt;/explicitValue&gt;
     * &lt;/dependency&gt;
     * </pre>
     * </p>
     * <p>
     * where <code>id</code> is composed of grouId and artifactId of a dependency defined in effective pom, separated by double colon. This is mandatory.</p>
     * <p>
     * Then there are 2 exclusively optional fields <code>type</code> and <code>explicitValue</code>, if both are defined <code>explicitValue</code> gets applied.
     * </p>
     * <p><code>type</code> values: <code>spec</code> means specification dependency.That's the default. 
     * <code>impl</code> means implementation dependency, only the exact version match will satisfy the constraint. 
     * <code>loose</code> means loose dependency, no requirement on version, the module just has to be present. Not very common option.
     * 
     * @since 3.8
     */
    @Parameter
    private Dependency[] moduleDependencies;

    /**
         * Deployment type of the module, allowed values are <code>normal</code>,<code>eager</code>,<code>autoload</code>,
         * <code>disabled</code>.
         * <p>
         * <code>autoload</code> - Such a module is
         * automatically enabled when some other module requires it and
         * automatically disabled otherwise.</p>
         *                     <p><code>eager</code> - This module type gets
         * automatically enabled when all it's dependencies are
         * satisfied. Disabled otherwise.</p>
         *                     <p><code>normal</code> - This is the default
         * value. This kind of module is enabled/disabled manually by
         * the user. It installs enabled.</p>
         *                     <p><code>disabled</code> - This kind of module is enabled/disabled manually by
         * the user. It installs disabled. Since 3.11</p>
         *
         * For details, see <a href="http://bits.netbeans.org/dev/javadoc/org-openide-modules/org/openide/modules/doc-files/api.html#enablement">Netbeans Module system docs</a>
         * 
         * Since 3.14, for autoload and eager modules, we automatically set AutoUpdate-Show-In-Client manifest entry to false, if not defined already otherwise in the manifest.
         * See issue <a href="http://jira.codehaus.org/browse/MNBMODULE-194">MNBMODULE-194</a>
         * 
         * @since 3.8 (3.14 in manifest goal)
         */
    @Parameter(defaultValue = "normal")
    protected String moduleType;

    // <editor-fold defaultstate="collapsed" desc="Component parameters">

    /**
     * The artifact repository to use.
        
     */
    @Parameter(required = true, readonly = true, defaultValue = "${localRepository}")
    private ArtifactRepository localRepository;

    /**
     * The artifact factory to use.
     */
    @Component
    private ArtifactFactory artifactFactory;

    /**
     * The artifact metadata source to use.
     */
    @Component
    private ArtifactMetadataSource artifactMetadataSource;

    /**
     * The artifact collector to use.
     */
    @Component
    private ArtifactCollector artifactCollector;

    /**
     * The dependency tree builder to use.
     */
    @Component(hint = "default")
    private DependencyGraphBuilder dependencyGraphBuilder;

    // end of component params custom code folding
    // </editor-fold> 

    /**
     * execute plugin
     * @throws MojoExecutionException if an unexpected problem occurs
     * @throws MojoFailureException if an expected problem occurs
     */
    public void execute() throws MojoExecutionException, MojoFailureException

    {
        //need to do this to chekc for javahelp on CP.
        super.registerNbmAntTasks();
        NetBeansModule module;
        if (descriptor != null && descriptor.exists()) {
            module = readModuleDescriptor(descriptor);
            getLog().warn("descriptor parameter is deprecated, use equivalent mojo parameters instead.");
        } else {
            module = createDefaultDescriptor(project, false);
        }

        String mtype = moduleType;
        //same moduleType related code in CreateNetBeansFileStructure.java
        if ("normal".equals(mtype) && module.getModuleType() != null) {
            mtype = module.getModuleType();
            getLog().warn("moduleType in module descriptor is deprecated, use the plugin's parameter moduleType");
        }
        if (!"normal".equals(mtype) && !"autoload".equals(mtype) && !"eager".equals(mtype)
                && !"disabled".equals(mtype)) {
            getLog().error("Only 'normal,autoload,eager,disabled' are allowed values in the moduleType parameter");
        }
        boolean autoload = "autoload".equals(mtype);
        boolean eager = "eager".equals(mtype);

        String moduleName = codeNameBase;
        if (module.getCodeNameBase() != null) {
            moduleName = module.getCodeNameBase();
            getLog().warn(
                    "codeNameBase in module descriptor is deprecated, use the plugin's parameter codeNameBase");
        }
        moduleName = moduleName.replaceAll("-", ".");
        //<!-- if a NetBeans specific manifest is defined, examine this one, otherwise the already included one.
        // ignoring the case when some of the NetBeans attributes are already defined in the jar and more is included.
        File specialManifest = sourceManifestFile;
        File nbmManifest = (module.getManifest() != null ? new File(project.getBasedir(), module.getManifest())
                : null);
        if (nbmManifest != null && nbmManifest.exists()) {
            //deprecated, but if actually defined, will use it.
            specialManifest = nbmManifest;
        }
        ExamineManifest examinator = new ExamineManifest(getLog());
        if (specialManifest != null && specialManifest.exists()) {
            examinator.setManifestFile(specialManifest);
            examinator.checkFile();
        } else {
            //            examinator.setJarFile( jarFile );
        }

        getLog().info("NBM Plugin generates manifest");

        Manifest manifest = null;
        if (specialManifest != null && specialManifest.exists()) {
            Reader reader = null;
            try {
                reader = new InputStreamReader(new FileInputStream(specialManifest));
                manifest = new Manifest(reader);
            } catch (IOException exc) {
                manifest = new Manifest();
                getLog().warn("Error reading manifest at " + specialManifest, exc);
            } catch (ManifestException ex) {
                getLog().warn("Error reading manifest at " + specialManifest, ex);
                manifest = new Manifest();
            } finally {
                IOUtil.close(reader);
            }
        } else {
            manifest = new Manifest();
        }
        Date date = new Date();
        String specVersion = AdaptNbVersion.adaptVersion(project.getVersion(), AdaptNbVersion.TYPE_SPECIFICATION,
                date);
        String implVersion = AdaptNbVersion.adaptVersion(project.getVersion(), AdaptNbVersion.TYPE_IMPLEMENTATION,
                date);
        Manifest.Section mainSection = manifest.getMainSection();
        conditionallyAddAttribute(mainSection, "OpenIDE-Module-Specification-Version", specVersion);
        conditionallyAddAttribute(mainSection, "OpenIDE-Module-Implementation-Version", implVersion);
        if (autoload || eager) { //MNBMODULE-194
            conditionallyAddAttribute(mainSection, "AutoUpdate-Show-In-Client", "false");
        }
        final String timestamp = createTimestamp(date);
        conditionallyAddAttribute(mainSection, "OpenIDE-Module-Build-Version", timestamp);
        String projectCNB = conditionallyAddAttribute(mainSection, "OpenIDE-Module", moduleName);
        String packagesValue;
        if (publicPackages != null && publicPackages.size() > 0) {
            StringBuilder sb = new StringBuilder();
            for (String pub : publicPackages) {
                if (pub == null) { //#MNBMODULE-237
                    continue;
                }
                if (pub.endsWith(".**")) {
                    // well, just sort of wrong value but accept
                    sb.append(pub);
                } else if (pub.endsWith(".*")) {
                    //multipackage value
                    sb.append(pub).append("*");
                } else {
                    sb.append(pub).append(".*");
                }
                sb.append(", ");
            }
            if (sb.length() > 1) { //if only item is null, we have empty builder
                sb.setLength(sb.length() - 2); //cut the last 2 ", " characters
                packagesValue = sb.toString();
            } else {
                // no packages available
                packagesValue = "-";
            }
        } else {
            // no packages available
            packagesValue = "-";
        }
        conditionallyAddAttribute(mainSection, "OpenIDE-Module-Public-Packages", packagesValue);

        //See http://www.netbeans.org/download/dev/javadoc/org-openide-modules/apichanges.html#split-of-openide-jar
        conditionallyAddAttribute(mainSection, "OpenIDE-Module-Requires", "org.openide.modules.ModuleFormat1");
        //        conditionallyAddAttribute(mainSection, "OpenIDE-Module-IDE-Dependencies", "IDE/1 > 3.40");
        // localization items
        if (!examinator.isLocalized()) {
            conditionallyAddAttribute(mainSection, "OpenIDE-Module-Display-Category", project.getGroupId());
            conditionallyAddAttribute(mainSection, "OpenIDE-Module-Name", project.getName());
            conditionallyAddAttribute(mainSection, "OpenIDE-Module-Short-Description",
                    shorten(project.getDescription()));
            conditionallyAddAttribute(mainSection, "OpenIDE-Module-Long-Description", project.getDescription());
        }
        getLog().debug("module =" + module);

        DependencyNode treeroot = createDependencyTree(project, dependencyGraphBuilder, "compile");
        Map<Artifact, ExamineManifest> examinerCache = new HashMap<Artifact, ExamineManifest>();
        @SuppressWarnings("unchecked")
        List<Artifact> libArtifacts = getLibraryArtifacts(treeroot, module, project.getRuntimeArtifacts(),
                examinerCache, getLog(), useOSGiDependencies);
        List<ModuleWrapper> moduleArtifacts = getModuleDependencyArtifacts(treeroot, module, moduleDependencies,
                project, examinerCache, libArtifacts, getLog(), useOSGiDependencies);
        StringBuilder classPath = new StringBuilder();
        StringBuilder mavenClassPath = new StringBuilder();
        String dependencies = "";
        String depSeparator = " ";

        for (Artifact a : libArtifacts) {
            if (classPath.length() > 0) {
                classPath.append(' ');
            }
            classPath.append(artifactToClassPathEntry(a, codeNameBase));
            if (mavenClassPath.length() > 0) {
                mavenClassPath.append(' ');
            }
            mavenClassPath.append(a.getGroupId()).append(':').append(a.getArtifactId()).append(':')
                    .append(a.getBaseVersion());
            if (a.getClassifier() != null) {
                mavenClassPath.append(":").append(a.getClassifier());
            }
        }

        for (ModuleWrapper wr : moduleArtifacts) {
            if (wr.transitive) {
                continue;
            }
            Dependency dep = wr.dependency;
            Artifact artifact = wr.artifact;
            ExamineManifest depExaminator = examinerCache.get(artifact);
            String type = dep.getType();
            String depToken = dep.getExplicitValue();
            if (depToken == null) {
                if ("loose".equals(type)) {
                    depToken = depExaminator.getModuleWithRelease();
                } else if ("spec".equals(type)) {
                    depToken = depExaminator.getModuleWithRelease() + " > "
                            + (depExaminator.isNetBeansModule() ? depExaminator.getSpecVersion()
                                    : AdaptNbVersion.adaptVersion(depExaminator.getSpecVersion(),
                                            AdaptNbVersion.TYPE_SPECIFICATION, date));
                } else if ("impl".equals(type)) {
                    depToken = depExaminator.getModuleWithRelease() + " = "
                            + (depExaminator.isNetBeansModule() ? depExaminator.getImplVersion()
                                    : AdaptNbVersion.adaptVersion(depExaminator.getImplVersion(),
                                            AdaptNbVersion.TYPE_IMPLEMENTATION, date));
                } else {
                    throw new MojoExecutionException("Wrong type of NetBeans dependency: " + type
                            + " Allowed values are: loose, spec, impl.");
                }
            }
            if (depToken == null) {
                //TODO report
                getLog().error("Cannot properly resolve the NetBeans dependency for " + dep.getId());
            } else {
                dependencies = dependencies + depSeparator + depToken;
                depSeparator = ", ";
            }
        }
        if (!verifyRuntime.equalsIgnoreCase(SKIP)) {
            try {
                checkModuleClassPath(treeroot, libArtifacts, examinerCache, moduleArtifacts, projectCNB);
            } catch (IOException ex) {
                throw new MojoExecutionException("Error while checking runtime dependencies", ex);
            }
        }

        if (nbmJavahelpSource.exists()) {
            String moduleJarName = stripVersionFromCodebaseName(moduleName).replace(".", "-");
            classPath.append(" docs/").append(moduleJarName).append(".jar");
        }

        if (classPath.length() > 0) {
            conditionallyAddAttribute(mainSection, "X-Class-Path", classPath.toString().trim());
        }
        if (mavenClassPath.length() > 0) {
            conditionallyAddAttribute(mainSection, "Maven-Class-Path", mavenClassPath.toString());
        }
        if (dependencies.length() > 0) {
            conditionallyAddAttribute(mainSection, "OpenIDE-Module-Module-Dependencies", dependencies);
        }
        //            if ( librList.size() > 0 )
        //            {
        //                String list = "";
        //                for ( int i = 0; i < librList.size(); i++ )
        //                {
        //                    list = list + " " + librList.get( i );
        //                }
        //                getLog().warn(
        //                        "Some libraries could not be found in the dependency chain: " + list );
        //            }
        PrintWriter writer = null;
        try {
            if (!targetManifestFile.exists()) {
                targetManifestFile.getParentFile().mkdirs();
                targetManifestFile.createNewFile();
            }
            writer = new PrintWriter(targetManifestFile, "UTF-8"); //TODO really UTF-8??
            manifest.write(writer);
        } catch (IOException ex) {
            throw new MojoExecutionException(ex.getMessage(), ex);
        } finally {
            IOUtil.close(writer);
        }
    }

    //MNBMODULE-137
    static String artifactToClassPathEntry(Artifact a, String codenamebase) {
        return "ext/" + codenamebase + "/" + a.getGroupId().replace('.', '-') + "/" + a.getArtifactId()
                + (a.getClassifier() != null ? "-" + a.getClassifier() : "") + "."
                + a.getArtifactHandler().getExtension();
    }

    /**
     * Create a timestamp for <code>OpenIDE-Module-Build-Version</code> manifest
     * entry.
     *
     * It's created from the current time and formatted using a UTC timezone
     * explicitly which makes created timestamp timezone-independent.
     *
     * @return timestamp represented as <code>201012292045</code>
     */
    private static String createTimestamp(Date date) {
        final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmm");
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
        final String timestamp = dateFormat.format(date);
        return timestamp;
    }

    static String stripVersionFromCodebaseName(String cnb) {
        // it can happen the moduleName is in format org.milos/1
        String base = cnb;
        int index = base.indexOf('/');
        if (index > -1) {
            base = base.substring(0, index).trim();
        }
        return base;
    }

    String conditionallyAddAttribute(Manifest.Section section, String key, String value) {
        Manifest.Attribute attr = section.getAttribute(key);
        if (attr == null) {
            attr = new Manifest.Attribute();
            attr.setName(key);
            attr.setValue(value != null ? value.replaceAll("\\s+", " ").trim() : "<undefined>");
            try {
                section.addConfiguredAttribute(attr);
            } catch (ManifestException ex) {
                getLog().error("Cannot update manifest (key=" + key + ")");
                ex.printStackTrace();
            }
        }
        return attr.getValue();
    }

    /**
     * Pick out the first sentence of a paragraph.
     * @param paragraph some text (may be null)
     * @return the first sentence (may be null)
     */
    static String shorten(String paragraph) {
        if (paragraph == null || paragraph.length() == 0) {
            return null;
        }
        BreakIterator breaker = BreakIterator.getSentenceInstance();
        breaker.setText(paragraph);
        return paragraph.substring(0, breaker.following(0)).trim();
    }

    //----------------------------------------------------------------------------------
    // classpat checking related.
    //----------------------------------------------------------------------------------
    private void checkModuleClassPath(DependencyNode treeroot, List<Artifact> libArtifacts,
            Map<Artifact, ExamineManifest> examinerCache, List<ModuleWrapper> moduleArtifacts,
            String projectCodeNameBase) throws IOException, MojoExecutionException, MojoFailureException {
        Set<String> deps = buildProjectDependencyClasses(project, libArtifacts);
        deps.retainAll(allProjectClasses(project));

        Set<String> own = projectModuleOwnClasses(project, libArtifacts);
        deps.removeAll(own);
        CollectModuleLibrariesNodeVisitor visitor = new CollectModuleLibrariesNodeVisitor(
                project.getRuntimeArtifacts(), examinerCache, getLog(), treeroot, useOSGiDependencies);
        treeroot.accept(visitor);
        Map<String, List<Artifact>> modules = visitor.getDeclaredArtifacts();
        Map<Artifact, Set<String>> moduleAllClasses = new HashMap<Artifact, Set<String>>();

        for (ModuleWrapper wr : moduleArtifacts) {
            if (modules.containsKey(wr.artifact.getDependencyConflictId())) {
                ExamineManifest man = examinerCache.get(wr.artifact);
                List<Artifact> arts = modules.get(wr.artifact.getDependencyConflictId());
                Set<String>[] classes = visibleModuleClasses(arts, man, wr.dependency, projectCodeNameBase, false);
                deps.removeAll(classes[0]);
                moduleAllClasses.put(wr.artifact, classes[1]);
            }
        }

        //now we have the classes that are not in public packages of declared modules,
        //but are being used
        if (!deps.isEmpty()) {
            Map<String, List<Artifact>> transmodules = visitor.getTransitiveArtifacts();
            for (ModuleWrapper wr : moduleArtifacts) {
                if (transmodules.containsKey(wr.artifact.getDependencyConflictId())) {
                    ExamineManifest man = examinerCache.get(wr.artifact);
                    List<Artifact> arts = transmodules.get(wr.artifact.getDependencyConflictId());
                    Set<String>[] classes = visibleModuleClasses(arts, man, wr.dependency, projectCodeNameBase,
                            true);
                    classes[0].retainAll(deps);
                    if (classes[0].size() > 0) {
                        String module = wr.osgi ? "OSGi bundle" : "module";
                        getLog().error("Project uses classes from transitive " + module + " " + wr.artifact.getId()
                                + " which will not be accessible at runtime.");
                        getLog().info(
                                "    To fix the problem, add this module as direct dependency. For OSGi bundles that are supposed to be wrapped in NetBeans modules, use the useOSGiDependencies=false parameter");
                        deps.removeAll(classes[0]);
                    }
                    classes[1].retainAll(deps);
                    if (classes[1].size() > 0) {
                        getLog().info("Private classes referenced in transitive module: "
                                + Arrays.toString(classes[1].toArray()));
                        getLog().error("Project depends on packages not accessible at runtime in transitive module "
                                + wr.artifact.getId() + " which will not be accessible at runtime.");
                        deps.removeAll(classes[1]);
                    }
                }
            }
            for (Map.Entry<Artifact, Set<String>> e : moduleAllClasses.entrySet()) {
                List<String> strs = new ArrayList<String>(deps);
                if (deps.removeAll(e.getValue())) {
                    strs.retainAll(e.getValue());
                    getLog().info("Private classes referenced in module: " + Arrays.toString(strs.toArray()));
                    getLog().error("Project depends on packages not accessible at runtime in module "
                            + e.getKey().getId());
                }
            }
            if (verifyRuntime.equalsIgnoreCase(FAIL)) {
                if (!deps.isEmpty()) {
                    throw new MojoFailureException(
                            "Uncategorized problems with NetBeans dependency verification (maybe MNBMODULE-102 or wrong maven dependency metadata). Supposedly external classes are used in the project's binaries but the classes are not found on classpath. Class usages: "
                                    + deps);
                } else {
                    throw new MojoFailureException(
                            "See above for failures in runtime NetBeans dependencies verification.");
                }
            }
        }
    }

    /**
     * The current projects's dependencies, includes classes used in teh module itself
     * and the classpath libraries as well.
     * @param project
     * @param libraries
     * @return
     * @throws java.io.IOException
     */
    private Set<String> buildProjectDependencyClasses(MavenProject project, List<Artifact> libraries)
            throws IOException {
        Set<String> dependencyClasses = new HashSet<String>();

        String outputDirectory = project.getBuild().getOutputDirectory();
        dependencyClasses.addAll(buildDependencyClasses(outputDirectory));

        for (Artifact lib : libraries) {
            dependencyClasses.addAll(buildDependencyClasses(lib.getFile().getAbsolutePath()));
        }
        return dependencyClasses;
    }

    @SuppressWarnings("unchecked")
    private Set<String> projectModuleOwnClasses(MavenProject project, List<Artifact> libraries) throws IOException {
        Set<String> projectClasses = new HashSet<String>();
        DefaultClassAnalyzer analyzer = new DefaultClassAnalyzer();

        String outputDirectory = project.getBuild().getOutputDirectory();
        URL fl = new File(outputDirectory).toURI().toURL();
        projectClasses.addAll(analyzer.analyze(fl));

        for (Artifact lib : libraries) {
            URL url = lib.getFile().toURI().toURL();
            projectClasses.addAll(analyzer.analyze(url));
        }

        return projectClasses;
    }

    /**
     * complete list of classes on project runtime classpath (excluding
     * jdk bit)
     * @param project
     * @return
     * @throws java.io.IOException
     */
    @SuppressWarnings("unchecked")
    private Set<String> allProjectClasses(MavenProject project) throws IOException {
        Set<String> projectClasses = new HashSet<String>();
        DefaultClassAnalyzer analyzer = new DefaultClassAnalyzer();

        String outputDirectory = project.getBuild().getOutputDirectory();
        URL fl = new File(outputDirectory).toURI().toURL();
        projectClasses.addAll(analyzer.analyze(fl));

        List<Artifact> libs = project.getRuntimeArtifacts();

        for (Artifact lib : libs) {
            URL url = lib.getFile().toURI().toURL();
            projectClasses.addAll(analyzer.analyze(url));
        }

        return projectClasses;
    }

    private Set<String>[] visibleModuleClasses(List<Artifact> moduleLibraries, ExamineManifest manifest,
            Dependency dep, String projectCodeNameBase, boolean transitive)
            throws IOException, MojoFailureException {
        Set<String> moduleClasses = new HashSet<String>();
        Set<String> visibleModuleClasses = new HashSet<String>();
        DefaultClassAnalyzer analyzer = new DefaultClassAnalyzer();
        String type = dep.getType();
        if (dep.getExplicitValue() != null) {
            if (dep.getExplicitValue().contains("=")) {
                type = "impl";
            }
        }
        if (type == null || "loose".equals(type)) {
            type = "spec";
        }

        for (Artifact lib : moduleLibraries) {
            URL url = lib.getFile().toURI().toURL();
            moduleClasses.addAll(analyzer.analyze(url));
        }

        if ("spec".equals(type)) {
            String cnb = stripVersionFromCodebaseName(projectCodeNameBase);
            if (!transitive && manifest.hasFriendPackages() && !manifest.getFriends().contains(cnb)) {
                String message = "Module has friend dependency on " + manifest.getModule()
                        + " but is not listed as a friend.";
                if (verifyRuntime.equalsIgnoreCase(FAIL)) {
                    throw new MojoFailureException(message);
                } else {
                    getLog().warn(message);
                }
            }
            List<Pattern> compiled = createCompiledPatternList(manifest.getPackages());
            if (useOSGiDependencies && manifest.isOsgiBundle()) {
                // TODO how to extract the public packages in osgi bundles easily..
                compiled = Collections.singletonList(Pattern.compile("(.+)"));
            }
            for (String clazz : moduleClasses) {
                for (Pattern patt : compiled) {
                    if (patt.matcher(clazz).matches()) {
                        visibleModuleClasses.add(clazz);
                        break;
                    }
                }
            }

        } else if ("impl".equals(type)) {
            visibleModuleClasses.addAll(moduleClasses);
        } else {
            //HUH?
            throw new MojoFailureException("Wrong type of module dependency " + type);
        }

        return new Set[] { visibleModuleClasses, moduleClasses };
    }

    static List<Pattern> createCompiledPatternList(List<String> packages) {
        List<Pattern> toRet = new ArrayList<Pattern>();
        for (String token : packages) {
            if (token.endsWith(".**")) {
                String patt = "^" + Pattern.quote(token.substring(0, token.length() - 2)) + "(.+)";
                toRet.add(0, Pattern.compile(patt));
            } else {
                String patt = "^" + Pattern.quote(token.substring(0, token.length() - 1)) + "([^\\.]+)";
                toRet.add(Pattern.compile(patt));
            }
        }
        return toRet;
    }

    @SuppressWarnings("unchecked")
    private Set<String> buildDependencyClasses(String path) throws IOException {
        URL url = new File(path).toURI().toURL();
        ASMDependencyAnalyzer dependencyAnalyzer = new ASMDependencyAnalyzer();
        return dependencyAnalyzer.analyze(url);
    }
}