org.codehaus.mojo.jsimport.AbstractImportMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.codehaus.mojo.jsimport.AbstractImportMojo.java

Source

package org.codehaus.mojo.jsimport;

/*
 * 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.    
 */

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.antlr.runtime.ANTLRFileStream;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.CommonToken;
import org.antlr.runtime.CommonTokenStream;
import org.apache.commons.io.FileUtils;
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.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.artifact.resolver.filter.TypeArtifactFilter;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.sonatype.plexus.build.incremental.BuildContext;

/**
 * Mojo for resolving dependencies either declared using an @import javadoc statement or by declaration of uninitialised
 * variables.
 */
public abstract class AbstractImportMojo extends AbstractMojo {
    /**
     * Small structure that permits Artifacts to exist as keys in a hash.
     */
    private class ArtifactId {
        private final String groupId;

        private final String artifactId;

        public ArtifactId(String groupId, String artifactId) {
            this.groupId = groupId;
            this.artifactId = artifactId;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (!(obj instanceof ArtifactId)) {
                return false;
            }
            ArtifactId other = (ArtifactId) obj;
            if (!getOuterType().equals(other.getOuterType())) {
                return false;
            }
            if (artifactId == null) {
                if (other.artifactId != null) {
                    return false;
                }
            } else if (!artifactId.equals(other.artifactId)) {
                return false;
            }
            if (groupId == null) {
                if (other.groupId != null) {
                    return false;
                }
            } else if (!groupId.equals(other.groupId)) {
                return false;
            }
            return true;
        }

        private AbstractImportMojo getOuterType() {
            return AbstractImportMojo.this;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + getOuterType().hashCode();
            result = prime * result + ((artifactId == null) ? 0 : artifactId.hashCode());
            result = prime * result + ((groupId == null) ? 0 : groupId.hashCode());
            return result;
        }

    };

    /**
     * The current project scope.
     */
    protected enum Scope {
        /** */
        COMPILE,
        /** */
        TEST
    }

    /**
     * The project.
     * 
     * @parameter default-value="${project}"
     * @required
     * @readonly
     */
    private MavenProject project;

    /**
     * The set of dependencies required by the project.
     * 
     * @parameter default-value="${project.dependencies}"
     * @required
     * @readonly
     */
    private List<Dependency> dependencies;

    /**
     * The local repository.
     * 
     * @parameter default-value="${localRepository}"
     * @required
     * @readonly
     */
    private ArtifactRepository localRepository;

    /**
     * The remote repositories.
     * 
     * @parameter default-value="${project.remoteArtifactRepositories}"
     * @required
     * @readonly
     */
    private List<?> remoteRepositories;

    /**
     * Files to include. Defaults to "**\/*.js".
     * 
     * @parameter
     */
    private List<String> includes;

    /**
     * Files to exclude. Nothing is excluded by default.
     * 
     * @parameter
     */
    private List<String> excludes;

    /**
     * true if the standard browser globals should be predefined. @see http://www.jslint.com/lint.html#browser TODO:
     * Provide the other JSLint "assume" options.
     * 
     * @parameter default-value="true"
     * @required
     */
    private boolean assumeABrowser;

    /**
     * The project's artifact factory.
     * 
     * @component
     */
    private ArtifactFactory artifactFactory;

    /**
     * The project's meta data source.
     * 
     * @component
     */
    private ArtifactMetadataSource artifactMetadataSource;

    /**
     * For determining the best versions of a set of artifacts.
     * 
     * @component
     */
    private ArtifactCollector artifactCollector;

    /**
     * The project's artifact resolver.
     * 
     * @component
     */
    private ArtifactResolver resolver;

    /**
     * A map of symbols to the script resource that they are defined in.
     */
    private final Map<String, String> fileAssignedGlobals = new HashMap<String, String>();

    /**
     * A map of symbols to the script resource that they are defined in from a compile scope perspective.
     */
    private final Map<String, String> compileFileAssignedGlobals = new HashMap<String, String>();

    /**
     * A map of symbols to the script resource that they are declared (but not initialised) in. This map is updated once
     * per run for all of the parsed js files that are processed. For example if there are no files to process given
     * that non have been updated since the last run then this map will be empty. If only one file was processed then
     * this map will contain unassigned globals just for that one file. This approach results in
     * processSourceFilesForUnassignedSymbolDeclarations to run efficiently as it is driven by this map.
     */
    private final Map<String, Set<String>> fileUnassignedGlobals = new HashMap<String, Set<String>>();

    /**
     * A graph of filenames and their dependencies.
     */
    private final Map<String, LinkedHashSet<String>> fileDependencies = new HashMap<String, LinkedHashSet<String>>();

    /**
     * The build context so that we can tell Maven certain files have changed if required.
     * 
     * @component
     */
    private BuildContext buildContext;

    /**
     * Build dependency graph from the source files.
     * 
     * @param fileDependencyGraphModificationTime the time the graph read in was updated. Used for comparing file times.
     * @param sourceJsFolder Where the source JS files live.
     * @param targetFolder Where the target files live.
     * @param processedFiles the files that have been processed as a consequence of this method.
     * @return true if the graph has been updated by this method.
     * @throws MojoExecutionException if something goes wrong.
     */
    private boolean buildDependencyGraphForChangedSourceFiles(long fileDependencyGraphModificationTime,
            File sourceJsFolder, File targetFolder, LinkedHashSet<File> processedFiles)
            throws MojoExecutionException {
        File targetJsFolder = new File(targetFolder, "js");

        boolean fileDependencyGraphUpdated = false;

        FileCollector fileCollector = new FileCollector(buildContext, new String[] { "**/*.js" }, new String[] {});
        for (String source : fileCollector.collectPaths(sourceJsFolder, includes, excludes)) {
            File sourceFile = new File(sourceJsFolder, source);
            if (processFileForImportsAndSymbols(sourceJsFolder, targetJsFolder, sourceFile,
                    fileDependencyGraphModificationTime, null)) {
                processedFiles.add(sourceFile);

                getLog().info("Processed: " + source);

                fileDependencyGraphUpdated = true;
            }
        }

        return fileDependencyGraphUpdated;
    }

    /**
     * Build up the dependency graph and global symbol table by parsing the project's dependencies.
     * 
     * @param scope compile or test.
     * @param fileDependencyGraphModificationTime the time that the dependency graph was updated. Used for file time
     *            comparisons to check the age of them.
     * @param processedFiles an insert-ordered set of files that have been processed.
     * @param targetFolder Where the target files live.
     * @param workFolder Where we can create some long lived information that may be useful to subsequent builds.
     * @param compileWorkFolder Ditto but in the case of testing it points to where the compile working folder is.
     * @return true if the dependency graph has been updated.
     * @throws MojoExecutionException if something bad happens.
     */
    private boolean buildDependencyGraphForDependencies(Scope scope, long fileDependencyGraphModificationTime,
            LinkedHashSet<File> processedFiles, File targetFolder, File workFolder, File compileWorkFolder)
            throws MojoExecutionException {
        File targetJsFolder = new File(targetFolder, "js");

        boolean fileDependencyGraphUpdated = false;

        // Determine how we need to filter things both for direct filtering and transitive filtering.

        String scopeStr = (scope == Scope.COMPILE ? Artifact.SCOPE_COMPILE : Artifact.SCOPE_TEST);

        AndArtifactFilter jsArtifactFilter = new AndArtifactFilter();
        jsArtifactFilter.add(new ScopeArtifactFilter(scopeStr));
        jsArtifactFilter.add(new TypeArtifactFilter("js"));

        AndArtifactFilter wwwZipArtifactFilter = new AndArtifactFilter();
        wwwZipArtifactFilter.add(new ScopeArtifactFilter(scopeStr));
        wwwZipArtifactFilter.add(new TypeArtifactFilter("zip"));
        wwwZipArtifactFilter.add(new ArtifactFilter() {
            public boolean include(Artifact artifact) {
                return artifact.hasClassifier() && artifact.getClassifier().equals("www");
            }
        });

        // Determine the artifacts to resolve and associate their transitive dependencies.

        Map<Artifact, LinkedHashSet<Artifact>> directArtifactWithTransitives = new HashMap<Artifact, LinkedHashSet<Artifact>>(
                dependencies.size());

        Set<Artifact> directArtifacts = new HashSet<Artifact>(dependencies.size());
        LinkedHashSet<Artifact> transitiveArtifacts = new LinkedHashSet<Artifact>();

        for (Dependency dependency : dependencies) {
            // Process imports and symbols of this dependencies' transitives
            // first.
            Artifact directArtifact = artifactFactory.createDependencyArtifact(dependency.getGroupId(),
                    dependency.getArtifactId(), VersionRange.createFromVersion(dependency.getVersion()),
                    dependency.getType(), dependency.getClassifier(), dependency.getScope());

            if (!jsArtifactFilter.include(directArtifact) && !wwwZipArtifactFilter.include(directArtifact)) {
                continue;
            }

            Set<Artifact> artifactsToResolve = new HashSet<Artifact>(1);
            artifactsToResolve.add(directArtifact);

            ArtifactResolutionResult result;
            try {
                result = resolver.resolveTransitively(artifactsToResolve, project.getArtifact(), remoteRepositories,
                        localRepository, artifactMetadataSource);
            } catch (ArtifactResolutionException e) {
                throw new MojoExecutionException("Problem resolving dependencies", e);
            } catch (ArtifactNotFoundException e) {
                throw new MojoExecutionException("Problem resolving dependencies", e);
            }

            // Associate the transitive dependencies with the direct dependency and aggregate all transitives for
            // collection later.

            LinkedHashSet<Artifact> directTransitiveArtifacts = new LinkedHashSet<Artifact>(
                    result.getArtifacts().size());
            for (Object o : result.getArtifacts()) {
                Artifact resolvedArtifact = (Artifact) o;
                if (jsArtifactFilter.include(resolvedArtifact) && //
                        !resolvedArtifact.equals(directArtifact)) {
                    directTransitiveArtifacts.add(resolvedArtifact);
                }
            }

            directArtifacts.add(directArtifact);
            transitiveArtifacts.addAll(directTransitiveArtifacts);
            directArtifactWithTransitives.put(directArtifact, directTransitiveArtifacts);
        }

        // Resolve the best versions of the transitives to use by asking Maven to collect them.

        Set<Artifact> collectedArtifacts = new HashSet<Artifact>(
                directArtifacts.size() + transitiveArtifacts.size());
        Map<ArtifactId, Artifact> indexedCollectedDependencies = new HashMap<ArtifactId, Artifact>(
                collectedArtifacts.size());
        try {
            // Note that we must pass an insert-order set into the collector. The collector appears to assume that order
            // is significant, even though it is undocumented.
            LinkedHashSet<Artifact> collectableArtifacts = new LinkedHashSet<Artifact>(directArtifacts);
            collectableArtifacts.addAll(transitiveArtifacts);

            ArtifactResolutionResult resolutionResult = artifactCollector.collect(collectableArtifacts,
                    project.getArtifact(), localRepository, remoteRepositories, artifactMetadataSource, null, //
                    Collections.EMPTY_LIST);
            for (Object o : resolutionResult.getArtifacts()) {
                Artifact collectedArtifact = (Artifact) o;
                collectedArtifacts.add(collectedArtifact);

                // Build up an index of of collected transitive dependencies so that we can we refer back to them as we
                // process the direct dependencies.
                ArtifactId collectedArtifactId = new ArtifactId(collectedArtifact.getGroupId(),
                        collectedArtifact.getArtifactId());
                indexedCollectedDependencies.put(collectedArtifactId, collectedArtifact);
            }

            if (getLog().isDebugEnabled()) {
                getLog().debug("Dependencies collected: " + collectedArtifacts.toString());
            }
        } catch (ArtifactResolutionException e) {
            throw new MojoExecutionException("Cannot collect dependencies", e);
        }

        // Now go through direct artifacts and process their transitives.

        LocalRepositoryCollector localRepositoryCollector = new LocalRepositoryCollector(project, localRepository,
                new File[] {});

        for (Entry<Artifact, LinkedHashSet<Artifact>> entry : directArtifactWithTransitives.entrySet()) {
            Artifact directArtifact = entry.getKey();
            LinkedHashSet<Artifact> directArtifactTransitives = entry.getValue();

            LinkedHashSet<String> transitivesAsImports = new LinkedHashSet<String>(
                    directArtifactTransitives.size());

            for (Object o : directArtifactTransitives) {
                Artifact directTransitiveArtifact = (Artifact) o;

                // Get the transitive artifact that Maven decided was the best to use.

                ArtifactId directTransitiveArtifactId = new ArtifactId(directTransitiveArtifact.getGroupId(),
                        directTransitiveArtifact.getArtifactId());
                Artifact transitiveArtifact = indexedCollectedDependencies.get(directTransitiveArtifactId);

                List<File> transitiveArtifactFiles = getArtifactFiles(transitiveArtifact, targetFolder, workFolder,
                        compileWorkFolder, localRepositoryCollector);

                // Only process this dependency if we've not done so
                // already.
                for (File transitiveArtifactFile : transitiveArtifactFiles) {
                    if (!processedFiles.contains(transitiveArtifactFile)) {
                        String localRepository = localRepositoryCollector
                                .findLocalRepository(transitiveArtifactFile.getAbsolutePath());
                        if (localRepository != null) {
                            if (processFileForImportsAndSymbols(new File(localRepository), targetJsFolder,
                                    transitiveArtifactFile, fileDependencyGraphModificationTime,
                                    directArtifactTransitives)) {

                                processedFiles.add(transitiveArtifactFile);

                                fileDependencyGraphUpdated = true;
                            }
                        } else {
                            throw new MojoExecutionException(
                                    "Problem determining local repository for transitive file: "
                                            + transitiveArtifactFile);
                        }
                    }

                    // Add transitives to the artifacts set of dependencies -
                    // as if they were @import statements themselves.
                    transitivesAsImports.add(transitiveArtifactFile.getPath());
                }
            }

            // Now deal with the pom specified dependency.
            List<File> artifactFiles = getArtifactFiles(directArtifact, targetFolder, workFolder, compileWorkFolder,
                    localRepositoryCollector);
            for (File artifactFile : artifactFiles) {
                String artifactPath = artifactFile.getAbsolutePath();

                // Process imports and symbols of this dependency if we've not
                // already done so.
                if (!processedFiles.contains(artifactFile)) {
                    String localRepository = localRepositoryCollector
                            .findLocalRepository(artifactFile.getAbsolutePath());
                    if (localRepository != null) {
                        if (processFileForImportsAndSymbols(new File(localRepository), targetJsFolder, artifactFile,
                                fileDependencyGraphModificationTime, null)) {
                            processedFiles.add(artifactFile);

                            fileDependencyGraphUpdated = true;
                        }
                    } else {
                        throw new MojoExecutionException(
                                "Problem determining local repository for file: " + artifactFile);
                    }
                }

                // Add in our transitives to the dependency graph if they're not
                // already there.
                LinkedHashSet<String> existingImports = fileDependencies.get(artifactPath);
                if (existingImports.addAll(transitivesAsImports)) {
                    if (getLog().isDebugEnabled()) {
                        getLog().debug("Using transitives as import: " + transitivesAsImports + " for file: "
                                + artifactPath);
                    }
                    fileDependencyGraphUpdated = true;
                }
            }

        }

        return fileDependencyGraphUpdated;
    }

    /**
     * Convenience method for interacting with a JsFileArtifactHandler object.
     * 
     * @param artifact the artifact to use.
     * @param targetFolder the target folder.
     * @param workFolder the work folder.
     * @param compileWorkFolder Ditto but in the case of test scope this will point to the work folder for compile
     *            artifacts.
     * @param localRepositoryCollector the repository collector.
     * @return the list of files associated with the artifact.
     * @throws MojoExecutionException if something goes wrong.
     */
    private List<File> getArtifactFiles(Artifact artifact, File targetFolder, File workFolder,
            File compileWorkFolder, LocalRepositoryCollector localRepositoryCollector)
            throws MojoExecutionException {
        JsFileArtifactHandler handler;
        try {
            boolean scopeCompile;
            if (artifact.getScope() == null) {
                scopeCompile = true;
            } else if (artifact.getScope().equals(Artifact.SCOPE_COMPILE)) {
                scopeCompile = true;
            } else {
                scopeCompile = false;
            }
            handler = new JsFileArtifactHandler(artifact, targetFolder,
                    scopeCompile ? compileWorkFolder : workFolder);
            File expansionFolder = handler.getExpansionFolder();
            if (expansionFolder != null) {
                localRepositoryCollector.addLocalRepositoryPath(expansionFolder.getAbsolutePath());
            }
        } catch (IOException e) {
            throw new MojoExecutionException("Cannot get js files from transitive artifact", e);
        }
        return handler.getFiles();
    }

    /**
     * Perform the goal of this mojo.
     * 
     * @param sourceJsFolder the folder where the source js files reside.
     * @param targetFolder the folder where the target files reside.
     * @param workFolder the folder where our work files can be found.
     * @param scope scope the scope of the dependencies we are to search for.
     * @throws MojoExecutionException if there is an execution failure.
     */
    protected void doExecute(File sourceJsFolder, File targetFolder, File workFolder, Scope scope)
            throws MojoExecutionException {
        // Load in any existing dependency graph - we only build what we need
        // to.
        long fileDependencyGraphModificationTime = FileDependencyPersistanceUtil.readFileDependencyGraph(workFolder,
                fileDependencies, fileAssignedGlobals);

        // Build hashes of the dependency graph so that we can quickly compare whether they have changed.
        int fileDependencyGraphHashCode = fileDependencies.hashCode();
        int fileAssignedGlobalsHashCode = fileAssignedGlobals.hashCode();

        // If we are in test scope then also load in the compile scoped dependency information as we need to resolve
        // against this also.
        File compileWorkFolder;
        if (scope == Scope.TEST) {
            compileWorkFolder = new File(workFolder.getParentFile(), "main");
            Map<String, LinkedHashSet<String>> compileFileDependencies = new HashMap<String, LinkedHashSet<String>>();
            FileDependencyPersistanceUtil.readFileDependencyGraph(compileWorkFolder, compileFileDependencies, //
                    compileFileAssignedGlobals);
        } else {
            compileWorkFolder = workFolder;
        }

        // Keep a flag to signal whether the graph has been updated.
        boolean fileDependencyGraphUpdated;

        // Clear the dependency graph if it is invalid.
        if (!isDependencyGraphValid()) {
            fileDependencies.clear();
            fileAssignedGlobals.clear();
            fileDependencyGraphModificationTime = 0L;
            fileDependencyGraphUpdated = true;
        } else {
            fileDependencyGraphUpdated = false;
        }

        // Build dependency graph and symbol table against each js file declared
        // as a dependency.

        LinkedHashSet<File> processedFiles = new LinkedHashSet<File>();
        if (buildDependencyGraphForDependencies(scope, fileDependencyGraphModificationTime, processedFiles,
                targetFolder, workFolder, compileWorkFolder)) {
            fileDependencyGraphUpdated = true;
        }

        // Process all of our JS files and build their dependency
        // graphs and symbol tables.
        if (buildDependencyGraphForChangedSourceFiles(fileDependencyGraphModificationTime, sourceJsFolder,
                targetFolder, processedFiles)) {
            fileDependencyGraphUpdated = true;
        }

        // Given that we now have all of the symbols mapped by file we
        // now need to go through our artifacts (dependencies and source files)
        // again looking for those that reference them. We add to the file
        // dependencies as a result.
        processSourceFilesForUnassignedSymbolDeclarations();

        // We have have a complete dependency graph. We will now persist the
        // graph so that other phases can utilise it (if things have truly changed).
        if (fileDependencyGraphUpdated && (fileDependencies.hashCode() != fileDependencyGraphHashCode || //
                fileAssignedGlobals.hashCode() != fileAssignedGlobalsHashCode)) {
            FileDependencyPersistanceUtil.writeFileDependencyGraph(workFolder, fileDependencies,
                    fileAssignedGlobals);
        }
    }

    /**
     * @return property.
     */
    public org.apache.maven.artifact.factory.ArtifactFactory getArtifactFactory() {
        return artifactFactory;
    }

    /**
     * @return property.
     */
    public ArtifactMetadataSource getArtifactMetadataSource() {
        return artifactMetadataSource;
    }

    /**
     * @return property.
     */
    public List<Dependency> getDependencies() {
        return dependencies;
    }

    /**
     * @return property.
     */
    public List<String> getExcludes() {
        return excludes;
    }

    /**
     * @return property.
     */
    public Map<String, String> getFileAssignedGlobals() {
        return fileAssignedGlobals;
    }

    /**
     * @return property.
     */
    public Map<String, LinkedHashSet<String>> getFileDependencies() {
        return fileDependencies;
    }

    /**
     * @return property.
     */
    public Map<String, Set<String>> getFileUnassignedGlobals() {
        return fileUnassignedGlobals;
    }

    /**
     * @return property.
     */
    public List<String> getIncludes() {
        return includes;
    }

    /**
     * @return property.
     */
    public ArtifactRepository getLocalRepository() {
        return localRepository;
    }

    /**
     * @return property.
     */
    public MavenProject getProject() {
        return project;
    }

    /**
     * @return property.
     */
    public List<?> getRemoteRepositories() {
        return remoteRepositories;
    }

    /**
     * @return property.
     */
    public ArtifactResolver getResolver() {
        return resolver;
    }

    /**
     * @return property.
     */
    public boolean isAssumeABrowser() {
        return assumeABrowser;
    }

    /**
     * Check the integrity of the dependency graph. Essentially if any files that were there in a previous run but are
     * no longer there then we must rebuild the graph.
     */
    private boolean isDependencyGraphValid() {
        boolean valid = true;

        for (String fileDependencyFilename : fileDependencies.keySet()) {
            File fileDependencyFile = new File(fileDependencyFilename);
            if (!fileDependencyFile.exists()) {
                if (getLog().isDebugEnabled()) {
                    getLog().debug("Found a file that has been removed so rebuilding graph. File: "
                            + fileDependencyFilename);
                }

                valid = false;
                break;
            }
        }

        return valid;
    }

    /**
     * Match a group and artifact against our list of dependencies.
     * 
     * @param groupId the group id.
     * @param artifactId the artifact id.
     * @return the dependency or null if one cannot be found.
     */
    private Dependency matchDirectDependency(String groupId, String artifactId) {
        Dependency dependencyFound = null;
        for (Dependency dependency : dependencies) {
            if (dependency.getGroupId().equalsIgnoreCase(groupId)
                    && dependency.getArtifactId().equalsIgnoreCase(artifactId)
                    && dependency.getType().equalsIgnoreCase("js")) {
                dependencyFound = dependency;
                break;
            }
        }

        return dependencyFound;
    }

    /**
     * Find a dependency in our set of transitive dependencies.
     * 
     * @param groupId the group to match.
     * @param artifactId the artifact to match.
     * @param transitiveArtifacts artifacts to match against.
     * @return an artifact that has been matched or null if none can be found.
     */
    private Artifact matchTransitiveDependency(String groupId, String artifactId, Set<?> transitiveArtifacts) {
        Artifact artifactFound = null;
        for (Object transitiveArtifactObject : transitiveArtifacts) {
            Artifact transitiveArtifact = (Artifact) transitiveArtifactObject;
            if (transitiveArtifact.getGroupId().equalsIgnoreCase(groupId)
                    && transitiveArtifact.getArtifactId().equalsIgnoreCase(artifactId)
                    && transitiveArtifact.getType().equalsIgnoreCase("js")) {
                artifactFound = transitiveArtifact;
                break;
            }
        }

        return artifactFound;
    }

    /**
     * Process a file for import declarations and for the symbols used.
     * 
     * @param sourceFolder the base directory of the file being processed.
     * @param targetFolder where to write files to.
     * @param sourceFile the file to process.
     * @param fileDependencyGraphModificationTime the last time the dependency graph was updated or 0 if we do not have
     *            one.
     * @param transitiveArtifacts any transititive artifacts to match imports against, or null if no matching is to be
     *            done.
     * @return true if processing occurred.
     * @throws MojoExecutionException if something goes wrong.
     */
    protected boolean processFileForImportsAndSymbols(File sourceFolder, File targetFolder, File sourceFile,
            long fileDependencyGraphModificationTime, Set<?> transitiveArtifacts) throws MojoExecutionException {
        URI sourceFileRelUri = sourceFolder.toURI().relativize(sourceFile.toURI());
        File targetFile = new File(targetFolder, sourceFileRelUri.toString());
        String sourceFilePath = sourceFile.getAbsolutePath();

        // Quickly jump out if this particular artifact has not been updated
        // recently, or we don't have an entry for it in our dependency graph. The latter can happen if we build a multi
        // module project from the parent folder and then build a specific module from its own folder. File dependencies
        // in these scenarios can come from the module's target folder or the local m2 repo respectively.
        if (sourceFile.lastModified() <= fileDependencyGraphModificationTime
                && fileDependencies.containsKey(sourceFilePath)) {
            if (getLog().isDebugEnabled()) {
                getLog().debug("Skipping unchanged JS file: " + sourceFileRelUri);
            }
            return false;
        }

        if (getLog().isDebugEnabled()) {
            getLog().debug("Parsing JS file: " + sourceFileRelUri);
        }

        try {
            // Tokenise the JS file resulting in collections of assigned and
            // unassigned globals, and import statements.
            CharStream cs = new ANTLRFileStream(sourceFilePath);

            ECMAScriptLexer lexer = new ECMAScriptLexer(cs);
            lexer.setSourceFile(sourceFileRelUri, sourceFile.getName());

            CommonTokenStream tokenStream = new CommonTokenStream();
            tokenStream.setTokenSource(lexer);
            writeTokenStream(cs, tokenStream, targetFile);

            if (getLog().isDebugEnabled()) {
                getLog().debug("Assigned globals: " + lexer.getAssignedGlobalVars().toString());
                getLog().debug("Unassigned globals: " + lexer.getUnassignedGlobalVars().toString());
                getLog().debug("Imports: " + lexer.getImportGavs().toString());
            }

            // For each assigned variable map it against the file we're dealing
            // with for later reference. An assigned variable indicates that
            // unassigned declarations of the same variable want this file
            // imported.
            for (String assignedGlobalVar : lexer.getAssignedGlobalVars()) {
                fileAssignedGlobals.put(assignedGlobalVar, sourceFilePath);
            }

            // For each unassigned variable map it against the file we're
            // dealing with for later reference. An unassigned variable
            // indicates that we want to import a file where the variable is
            // assigned.
            Set<String> vars = new HashSet<String>(lexer.getUnassignedGlobalVars());
            if (assumeABrowser) {
                // If we assume a browser then take out all of the ones declared for it. Otherwise we'll be looking for
                // dependencies that do not belong to the project.
                vars.removeAll(Arrays.asList(new String[] { "clearInterval", "clearTimeout", "document", "exports",
                        "event", "frames", "history", "Image", "location", "module", "name", "navigator", "Option",
                        "parent", "require", "screen", "setInterval", "setTimeout", "window", "XMLHttpRequest" }));
            }
            fileUnassignedGlobals.put(sourceFilePath, vars);

            // For each import found resolve its file name and then note it as a
            // dependency of this particular js file. We update any existing
            // dependency edges if they exist given that we've determined this
            // part of the graph needs re-construction.
            LinkedHashSet<String> importedDependencies = new LinkedHashSet<String>(lexer.getImportGavs().size());
            fileDependencies.put(sourceFilePath, importedDependencies);

            for (ECMAScriptLexer.GAV importGav : lexer.getImportGavs()) {
                Artifact artifactFound;
                Dependency dependencyFound = matchDirectDependency(importGav.groupId, importGav.artifactId);
                if (dependencyFound == null) {
                    if (transitiveArtifacts != null) {
                        artifactFound = matchTransitiveDependency(importGav.groupId, importGav.artifactId,
                                transitiveArtifacts);
                    } else {
                        artifactFound = null;
                    }
                    if (artifactFound == null) {
                        getLog().error("Dependency not found: " + importGav.groupId + ":" + importGav.artifactId);
                        throw new MojoExecutionException("Build stopping given dependency issue.");
                    }
                } else {
                    artifactFound = resolveArtifact(dependencyFound);
                }

                /**
                 * Store the dependency as an edge against our dependency graph.
                 */
                importedDependencies.add(artifactFound.getFile().getPath());

                if (getLog().isDebugEnabled()) {
                    getLog().debug("Found import: " + importGav.groupId + ":" + importGav.artifactId + " ("
                            + artifactFound.getFile().getName() + ") for file: " + sourceFileRelUri);
                }
            }

        } catch (IOException e) {
            throw new MojoExecutionException("Problem opening file: " + sourceFileRelUri, e);
        }

        return true;
    }

    /**
     * Go through all of unassigned globals and enhance the file dependencies collection given the file that they are
     * declared in.
     * 
     * @throws MojoExecutionException if something goes wrong.
     */
    protected void processSourceFilesForUnassignedSymbolDeclarations() throws MojoExecutionException {

        // For all of the js files containing unassigned vars...
        Set<Entry<String, Set<String>>> entrySet = fileUnassignedGlobals.entrySet();
        for (Entry<String, Set<String>> entry : entrySet) {

            // For each of the unassigned vars...
            String variableDeclFile = entry.getKey();
            for (String variableName : entry.getValue()) {
                // Resolve the file that contains the var's assignment and throw
                // an exception if it cannot be found.
                String variableAssignedFile = fileAssignedGlobals.get(variableName);
                if (variableAssignedFile == null && compileFileAssignedGlobals != null) {
                    variableAssignedFile = compileFileAssignedGlobals.get(variableName);
                }

                // We've tried pretty hard, but we can't find a dependency. Time to barf.
                if (variableAssignedFile == null) {
                    getLog().error("Dependency not found: " + variableName + " in file: " + variableDeclFile);
                    throw new MojoExecutionException("Build stopping given dependency issue.");

                }

                // Enhance the declaring file's graph of dependencies.
                LinkedHashSet<String> variableDeclFileImports = fileDependencies.get(variableDeclFile);
                if (variableDeclFileImports == null) {
                    variableDeclFileImports = new LinkedHashSet<String>();
                    fileDependencies.put(variableDeclFile, variableDeclFileImports);
                }

                variableDeclFileImports.add(variableAssignedFile);
            }
        }
    }

    /**
     * Resolve an artifact given a dependency.
     * 
     * @param dependency the dependency to resolve.
     * @return the artifact.
     * @throws MojoExecutionException if the dependency cannot be resolved.
     */
    private Artifact resolveArtifact(Dependency dependency) throws MojoExecutionException {
        Artifact artifact = artifactFactory.createArtifactWithClassifier(dependency.getGroupId(),
                dependency.getArtifactId(), dependency.getVersion(), dependency.getType(),
                dependency.getClassifier());
        try {
            resolver.resolve(artifact, remoteRepositories, localRepository);
        } catch (ArtifactResolutionException e) {
            throw new MojoExecutionException(dependency.toString(), e);
        } catch (ArtifactNotFoundException e) {
            throw new MojoExecutionException(dependency.toString(), e);
        }
        return artifact;
    }

    /**
     * @param artifactFactory set property.
     */
    public void setArtifactFactory(ArtifactFactory artifactFactory) {
        this.artifactFactory = artifactFactory;
    }

    /**
     * @param artifactMetadataSource set property.
     */
    public void setArtifactMetadataSource(ArtifactMetadataSource artifactMetadataSource) {
        this.artifactMetadataSource = artifactMetadataSource;
    }

    /**
     * @param assumeABrowser set property.
     */
    public void setAssumeABrowser(boolean assumeABrowser) {
        this.assumeABrowser = assumeABrowser;
    }

    /**
     * @param dependencies set property.
     */
    public void setDependencies(List<Dependency> dependencies) {
        this.dependencies = dependencies;
    }

    /**
     * @param excludes set property.
     */
    public void setExcludes(List<String> excludes) {
        this.excludes = excludes;
    }

    /**
     * @param includes set property.
     */
    public void setIncludes(List<String> includes) {
        this.includes = includes;
    }

    /**
     * @param localRepository set property.
     */
    public void setLocalRepository(ArtifactRepository localRepository) {
        this.localRepository = localRepository;
    }

    /**
     * @param project set property.
     */
    public void setProject(MavenProject project) {
        this.project = project;
    }

    /**
     * @param remoteRepositories set property.
     */
    public void setRemoteRepositories(List<?> remoteRepositories) {
        this.remoteRepositories = remoteRepositories;
    }

    /**
     * @param resolver set property.
     */
    public void setResolver(ArtifactResolver resolver) {
        this.resolver = resolver;
    }

    private void writeTokenStream(CharStream cs, CommonTokenStream tokenStream, File outputFile)
            throws IOException {
        OutputStream os = new BufferedOutputStream(FileUtils.openOutputStream(outputFile));
        try {
            List<?> tokens = tokenStream.getTokens();
            cs.seek(0);
            for (Object tokenObject : tokens) {
                CommonToken token = (CommonToken) tokenObject;
                if (token.getType() == ECMAScriptLexer.MODULE_DECL
                        || token.getType() == ECMAScriptLexer.REQUIRE_DECL) {
                    int startIndex = token.getStartIndex();
                    while (cs.index() < startIndex) {
                        int streamChar = cs.LA(1);
                        if (streamChar == CharStream.EOF) {
                            break;
                        }
                        os.write(streamChar);
                        cs.consume();
                    }

                    CharacterIterator iter = new StringCharacterIterator(token.getText());
                    for (char tokenChar = iter.first(); tokenChar != CharacterIterator.DONE; tokenChar = iter
                            .next()) {
                        os.write(tokenChar);
                    }

                    cs.seek(token.getStopIndex() + 1);
                }
            }

            int streamChar;
            while ((streamChar = cs.LA(1)) != CharStream.EOF) {
                os.write(streamChar);
                cs.consume();
            }
        } finally {
            os.close();
        }
    }
}