All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jszip.maven.OptimizeMojo Maven / Gradle / Ivy

/*
 * Copyright 2011-2012 Stephen Connolly.
 *
 * 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.jszip.maven;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.plugin.MavenPluginManager;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.PluginConfigurationException;
import org.apache.maven.plugin.PluginContainerException;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
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.apache.maven.project.MavenProject;
import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
import org.apache.maven.shared.artifact.filter.collection.ProjectTransitivityFilter;
import org.apache.maven.shared.artifact.filter.collection.ScopeFilter;
import org.apache.maven.shared.artifact.filter.collection.TypeFilter;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.jszip.pseudo.io.PseudoFileSystem;
import org.jszip.rhino.JavaScriptTerminationException;
import org.jszip.rhino.OptimizeContextAction;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.tools.shell.Global;
import org.mozilla.javascript.tools.shell.QuitAction;
import org.mozilla.javascript.tools.shell.ShellContextFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Runs the r.js optimizer over the source
 */
@Mojo(name = "optimize", defaultPhase = LifecyclePhase.PROCESS_CLASSES,
        requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class OptimizeMojo extends AbstractJSZipMojo {

    /**
     * Regex for a quoted string which may include escapes.
     */
    public static final String QUOTED_STRING_WITH_ESCAPES = "'([^\\\\']+|\\\\([btnfr\"'\\\\]|[0-3]?[0-7]{1,"
            + "2}|u[0-9a-fA-F]{4}))*'|\"([^\\\\\"]+|\\\\([btnfr\"'\\\\]|[0-3]?[0-7]{1,2}|u[0-9a-fA-F]{4}))*\"";

    /**
     * Regex for sniffing the version of r.js being used.
     */
    public static final String R_JS_VERSION_REGEX = "\\s+version\\s*=\\s*(" + QUOTED_STRING_WITH_ESCAPES + ")";

    /**
     * The artifact path mappings for unpacking.
     */
    @Parameter(property = "mappings")
    private Mapping[] mappings;

    /**
     * Directory containing the build profiles.
     */
    @Parameter(defaultValue = "src/build/js", required = true)
    private File contentDirectory;

    /**
     * Single directory for extra files to include in the WAR.
     */
    @Parameter(defaultValue = "${basedir}/src/main/webapp", required = true)
    private File warSourceDirectory;

    /**
     * The directory where the webapp is built.
     */
    @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}", required = true)
    private File webappDirectory;

    /**
     * Directory containing the build profiles.
     */
    @Parameter(defaultValue = "src/build/js/r.js")
    private File customRScript;

    /**
     * Skip optimization.
     */
    @Parameter(property = "jszip.optimize.skip", defaultValue = "false")
    private boolean skip;

    /**
     * The reactor projects
     */
    @Parameter(property = "reactorProjects", required = true, readonly = true)
    protected List reactorProjects;

    /**
     * The Maven plugin Manager
     */
    @Component
    private MavenPluginManager mavenPluginManager;

    /**
     * The current build session instance. This is used for plugin manager API calls.
     */
    @Parameter(property = "session", required = true, readonly = true)
    private MavenSession session;

    /**
     * This plugin's descriptor
     */
    @Parameter(property = "plugin", readonly = true)
    private PluginDescriptor pluginDescriptor;

    /**
     * A list of <include> elements specifying the build profiles (by pattern) that should be included in
     * optimization.
     */
    @Parameter(property = "includes")
    private List includes;

    /**
     * A list of <exclude> elements specifying the build profiles (by pattern) that should be excluded from
     * optimization.
     */
    @Parameter(property = "excludes")
    private List excludes;

    /**
     * @see org.apache.maven.plugin.Mojo#execute()
     */
    public void execute() throws MojoExecutionException, MojoFailureException {
        if (skip) {
            getLog().info("Optimization skipped.");
            return;
        }
        if (!contentDirectory.exists()) {
            getLog().info("Nothing to do, no r.js build profiles in " + contentDirectory);
            return;
        }
        if (!contentDirectory.isDirectory()) {
            throw new MojoExecutionException("Build profile directory '" + contentDirectory + "' is not a directory");
        }
        if (webappDirectory.isFile()) {
            throw new MojoExecutionException("Webapp directory '" + webappDirectory + "' is not a directory");
        }
        if (!webappDirectory.isDirectory() && !webappDirectory.mkdirs()) {
            throw new MojoExecutionException("Could not create Webapp directory '" + webappDirectory + "'");
        }
        String source;
        int lineNo = 0;
        InputStream inputStream = null;
        InputStreamReader reader = null;
        try {
            if (customRScript.isFile()) {
                getLog().debug("Using custom r.js from: " + customRScript);
                inputStream = new FileInputStream(customRScript);
            } else {
                getLog().debug("Using bundled r.js");
                inputStream = getClass().getResourceAsStream("/org/jszip/maven/r.js");
            }
            source = IOUtil.toString(inputStream, "UTF-8");
            if (source.startsWith("#!")) {
                int i1 = source.indexOf('\n');
                int i2 = source.indexOf('\r');
                int index = (i1 == -1 || i2 == -1) ? Math.max(i1, i2) : Math.min(i1, i2);
                if (index > 0) {
                    source = source.substring(index);
                    lineNo++;
                }
            }
        } catch (IOException e) {
            throw new MojoExecutionException(e.getMessage(), e);
        } finally {
            IOUtil.close(reader);
            IOUtil.close(inputStream);
        }

        String sourceVersion = "unknown";
        Pattern rJsVersionPattern = Pattern.compile(R_JS_VERSION_REGEX);
        Matcher rJsVersionMatcher = rJsVersionPattern.matcher(source);
        if (rJsVersionMatcher.find()) {
            sourceVersion = rJsVersionMatcher.group(1);
        }

        getLog().info("Using r.js version " + sourceVersion);

        List layers = buildVirtualFileSystemLayers();

        final ContextFactory contextFactory = new ShellContextFactory();
        final Global global = new Global();
        global.initQuitAction(new QuitAction() {
            public void quit(Context context, int exitCode) {
                if (exitCode != 0) {
                    throw new JavaScriptTerminationException("Script exited with exit code of " + exitCode, exitCode);
                }
            }
        });
        if (!global.isInitialized()) {
            global.init(contextFactory);
        }
        DirectoryScanner scanner = new DirectoryScanner();

        scanner.setBasedir(contentDirectory);

        if (includes != null && !includes.isEmpty()) {
            scanner.setIncludes(processIncludesExcludes(includes));
        } else {
            scanner.setIncludes(new String[]{"**/*.js"});
        }

        if (excludes != null && !excludes.isEmpty()) {
            scanner.setExcludes(processIncludesExcludes(excludes));
        } else {
            scanner.setExcludes(new String[]{"r.js"});
        }

        scanner.scan();

        for (String path : scanner.getIncludedFiles()) {
            File profileJs = new File(contentDirectory, path);
            PseudoFileSystem.Layer[] layersArray = layers.toArray(new PseudoFileSystem.Layer[layers.size() + 1]);
            layersArray[layers.size()] = new PseudoFileSystem.FileLayer("build", profileJs.getParentFile());
            try {
                Object rv = contextFactory
                        .call(new OptimizeContextAction(getLog(), global, profileJs, source, lineNo, layersArray));
                if (rv instanceof Number) {
                    if (((Number) rv).intValue() != 0) {
                        throw new MojoExecutionException(
                                "Non-zero exit code of " + ((Number) rv).intValue()
                                        + " when trying to optimize profile " + profileJs);
                    }
                }
            } catch (JavaScriptException e) {
                throw new MojoExecutionException(
                        "Uncaught exception when trying to optimize profile " + profileJs, e);
            } catch (JavaScriptTerminationException e) {
                throw new MojoExecutionException(
                        "Non-zero exit code of " + e.getExitCode() + " when trying to optimize profile " + profileJs);
            }
        }
    }

    private String getPath(Artifact artifact) {
        if (mappings == null) {
            return "/virtual";
        }
        for (Mapping mapping: mappings) {
            if (mapping.isMatch(artifact)) {
                final String path = StringUtils.clean(mapping.getPath());
                if (StringUtils.isBlank(path) || "/".equals(path)) {
                    return "/virtual";
                }
                return "/virtual/" + StringUtils.strip(path, "/");
            }
        }
        return "/virtual";
    }

    private List buildVirtualFileSystemLayers() throws MojoExecutionException {
        List layers = new ArrayList();
        layers.add(new PseudoFileSystem.FileLayer("/target", webappDirectory));
        layers.add(new PseudoFileSystem.FileLayer("/virtual", warSourceDirectory));
        FilterArtifacts filter = new FilterArtifacts();

        filter.addFilter(new ProjectTransitivityFilter(project.getDependencyArtifacts(), false));

        filter.addFilter(new ScopeFilter("runtime", ""));

        filter.addFilter(new TypeFilter(JSZIP_TYPE, ""));

        // start with all artifacts.
        Set artifacts = project.getArtifacts();

        // perform filtering
        try {
            artifacts = filter.filter(artifacts);
        } catch (ArtifactFilterException e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }

        for (Artifact artifact : artifacts) {
            String path = getPath(artifact);
            getLog().info("Adding " + ArtifactUtils.key(artifact) + " to virtual filesystem");
            File file = artifact.getFile();
            if (file.isDirectory()) {
                MavenProject fromReactor = findProject(reactorProjects, artifact);
                if (fromReactor != null) {
                    MavenSession session = this.session.clone();
                    session.setCurrentProject(fromReactor);
                    Plugin plugin = findThisPluginInProject(fromReactor);
                    try {
                        // we cheat here and use our version of the plugin... but this is less of a cheat than the only
                        // other way which is via reflection.
                        MojoDescriptor jszipDescriptor = findMojoDescriptor(this.pluginDescriptor, JSZipMojo.class);

                        for (PluginExecution pluginExecution : plugin.getExecutions()) {
                            if (!pluginExecution.getGoals().contains(jszipDescriptor.getGoal())) {
                                continue;
                            }
                            MojoExecution mojoExecution =
                                    createMojoExecution(plugin, pluginExecution, jszipDescriptor);
                            JSZipMojo mojo = (JSZipMojo) mavenPluginManager
                                    .getConfiguredMojo(org.apache.maven.plugin.Mojo.class, session, mojoExecution);
                            try {
                                File contentDirectory = mojo.getContentDirectory();
                                if (contentDirectory.isDirectory()) {
                                    getLog().debug("Merging directory " + contentDirectory + " into " + path);
                                    layers.add(new PseudoFileSystem.FileLayer(path, contentDirectory));
                                }
                                File resourcesDirectory = mojo.getResourcesDirectory();
                                if (resourcesDirectory.isDirectory()) {
                                    getLog().debug("Merging directory " + contentDirectory + " into " + path);
                                    layers.add(new PseudoFileSystem.FileLayer(path, resourcesDirectory));
                                }
                            } finally {
                                mavenPluginManager.releaseMojo(mojo, mojoExecution);
                            }
                        }
                    } catch (PluginConfigurationException e) {
                        throw new MojoExecutionException(e.getMessage(), e);
                    } catch (PluginContainerException e) {
                        throw new MojoExecutionException(e.getMessage(), e);
                    }
                } else {
                    throw new MojoExecutionException("Cannot find jzsip artifact: " + artifact.getId());
                }
            } else {
                try {
                    getLog().debug("Merging .zip file " + file + " into " + path);
                    layers.add(new PseudoFileSystem.ZipLayer(path, file));
                } catch (IOException e) {
                    throw new MojoExecutionException(e.getMessage(), e);
                }
            }
        }
        return layers;
    }

    private static String[] processIncludesExcludes(List list) {
        List result = new ArrayList();
        for (String entry : list) {
            String[] entries = entry.split(",");
            Collections.addAll(result, entries);
        }
        return result.toArray(new String[result.size()]);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy