org.codehaus.mojo.javascript.AbstractCompressMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.codehaus.mojo.javascript.AbstractCompressMojo.java

Source

package org.codehaus.mojo.javascript;

/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * 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.
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
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.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.codehaus.mojo.javascript.compress.CompressionException;
import org.codehaus.mojo.javascript.compress.IsolatedClassLoader;
import org.codehaus.mojo.javascript.compress.JSCompressor;
import org.codehaus.mojo.javascript.compress.JSCompressorProxy;
import org.codehaus.mojo.javascript.compress.JSMinCompressor;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;

/**
 * Abstact mojo for compressing JavaScripts.
 * 
 * @author <a href="mailto:nicolas@apache.org">nicolas De Loof</a>
 */
public abstract class AbstractCompressMojo extends AbstractMojo {

    /**
     * Resolves the artifacts and dependencies.
     * 
     * @component
     */
    private ArtifactResolver artifactResolver;

    /**
     * Create Artifact references
     * 
     * @component
     */
    private ArtifactFactory artifactFactory;

    /**
     * The local repository
     * 
     * @parameter expression="${localRepository}"
     * @required
     */
    private ArtifactRepository localRepository;

    /**
     * For retrieval of artifact's metadata.
     * 
     * @component
     */
    private ArtifactMetadataSource metadataSource;

    /**
     * The remote repositories declared in the pom.
     * 
     * @parameter expression="${project.pluginArtifactRepositories}"
     */
    private List remoteRepositories;

    /**
     *
     */
    private static final NumberFormat INTEGER = NumberFormat.getIntegerInstance();

    private static final String HR = StringUtils.rightPad("", 78, "-");

    /**
     * The maven project we are working on.
     * 
     * @parameter default-value="${project}"
     * @required
     * @readonly
     */
    MavenProject project;

    /**
     * The available compressors
     */
    private Map compressors = new HashMap();
    {
        compressors.put("jsmin", new JSMinCompressor());
    }

    /**
     * Optimization level, from 0 to 9
     * 
     * @parameter default-value="9"
     */
    private int optimizationLevel;

    /**
     * JS Language version (130 for JS 1.3)
     * 
     * @parameter default-value="130"
     */
    private int languageVersion;

    /**
     * The compressor to used. Either "shrinksafe", "yahooui" or "jsmin" for default compressor, 
    * or a custom one provided as an artifact in repo org.codehaus.mojo.javascript:<xxx>-compressor.
     * 
     * @parameter default-value="jsmin"
     */
    private String compressor;

    /**
     * Don't display compression stats
     * 
     * @parameter
     */
    private boolean skipStats;

    private static final String[] DEFAULT_INCLUDES = new String[] { "**/*.js" };

    /**
     * Inclusion patterns
     * 
     * @parameter
     */
    private String[] includes;

    /**
     * Exclusion patterns
     * 
     * @parameter
     */
    private String[] excludes;

    /**
     * A special token to recognize lines to be removed from scripts (debugging
     * code).
     * 
     * @parameter
     */
    private String strip;

    /**
     * {@inheritDoc}
     * 
     * @see org.apache.maven.plugin.AbstractMojo#execute()
     */
    public void execute() throws MojoExecutionException {
        DirectoryScanner scanner = new DirectoryScanner();
        scanner.setBasedir(getSourceDirectory());
        if (includes == null) {
            includes = DEFAULT_INCLUDES;
        }
        scanner.setIncludes(includes);
        scanner.addDefaultExcludes();
        if (excludes != null) {
            scanner.setExcludes(excludes);
        }
        scanner.scan();
        String[] files = scanner.getIncludedFiles();

        // if ( !Context.isValidOptimizationLevel( optimizationLevel ) )
        // {
        // throw new MojoExecutionException( "optimizationLevel is invalid" );
        // }
        // if ( !Context.isValidLanguageVersion( languageVersion ) )
        // {
        // throw new MojoExecutionException( "languageVersion is invalid" );
        // }

        JSCompressor jscompressor = getCompressor();

        logStats(HR);
        getOutputDirectory().mkdirs();
        long saved = 0;
        for (int i = 0; i < files.length; i++) {
            String file = files[i];
            saved = compress(jscompressor, file);
        }
        logStats(HR);
        logStats("compression saved " + INTEGER.format(saved) + " bytes");
    }

    private File stripDebugs(File file) throws MojoExecutionException {
        if (strip == null) {
            return file;
        }
        try {

            File stripped = File.createTempFile("stripped", ".js");

            BufferedReader reader = new BufferedReader(new FileReader(file));
            PrintWriter writer = new PrintWriter(stripped);
            String line;
            while ((line = reader.readLine()) != null) {
                if (!line.trim().startsWith(strip)) {
                    writer.println(line);
                }
            }
            IOUtil.close(reader);
            IOUtil.close(writer);

            FileUtils.copyFile(stripped, file);
            stripped.delete();
        } catch (IOException e) {
            throw new MojoExecutionException("Failed to strip debug code in " + file, e);
        }
        return file;
    }

    private JSCompressor getCompressor() throws MojoExecutionException {
        if (compressors.containsKey(compressor)) {
            return (JSCompressor) compressors.get(compressor);
        }

        // Inspired by the surefire plugin
        // allows to use multiple compressor that rely on modifier Rhino engines
        // without dependencies/classpath conflicts

        String id = compressor.toLowerCase() + "-compressor";

        // TODO don't have version hardcoded
        Artifact compressorArtifact = artifactFactory.createDependencyArtifact("org.codehaus.mojo.javascript", id,
                VersionRange.createFromVersion("1.0-alpha-1-SNAPSHOT"), "jar", null, Artifact.SCOPE_RUNTIME);

        Artifact originatingArtifact = artifactFactory.createBuildArtifact("dummy", "dummy", "1.0", "jar");

        ArtifactResolutionResult dependencies;
        try {
            dependencies = artifactResolver.resolveTransitively(Collections.singleton(compressorArtifact),
                    originatingArtifact, localRepository, remoteRepositories, metadataSource, null);
        } catch (Exception e) {
            getLog().info("Failed to load compressor artifact " + compressorArtifact.toString());
            throw new MojoExecutionException("Failed to load compressor artifact" + compressorArtifact.toString(),
                    e);
        }

        IsolatedClassLoader classLoader = new IsolatedClassLoader(dependencies.getArtifacts());

        compressor = StringUtils.capitalize(compressor);
        String compressorClassName = "org.codehaus.mojo.javascript.compress." + compressor + "Compressor";
        Class compressorClass;
        try {
            compressorClass = classLoader.loadClass(compressorClassName);
        } catch (ClassNotFoundException e) {
            getLog().info("Failed to load compressor class " + compressorClassName);
            throw new MojoExecutionException("Failed to load compressor class" + compressorClassName, e);
        }

        JSCompressor jscompressor;
        try {
            jscompressor = new JSCompressorProxy(compressorClass.newInstance());
        } catch (Exception e) {
            getLog().info("Failed to create a isolated-classloader proxy for " + compressorClassName);
            throw new MojoExecutionException(
                    "Failed to create a isolated-classloader proxy for " + compressorClassName, e);
        }
        getLog().info("Compressing javascript using " + compressor);

        compressors.put(compressor, jscompressor);
        return jscompressor;
    }

    private long compress(JSCompressor jscompressor, String file) throws MojoExecutionException {
        String name = file;
        if (getExtension() != null) {
            int ext = file.lastIndexOf('.');
            name = file.substring(0, ext) + "-" + getExtension() + file.substring(ext);
        }
        File compressed = new File(getOutputDirectory(), name);
        compressed.getParentFile().mkdirs();
        File in = new File(getSourceDirectory(), file);
        if (in.equals(compressed)) {
            try {
                File temp = File.createTempFile("compress", ".js");
                long size = compress(in, temp, jscompressor);
                FileUtils.copyFile(temp, compressed);
                temp.delete();
                return size;
            } catch (IOException e) {
                throw new MojoExecutionException("Error creating temp file for compression", e);
            }
        } else {
            return compress(in, compressed, jscompressor);
        }
    }

    private long compress(File in, File compressed, JSCompressor jscompressor) throws MojoExecutionException {
        if (in.length() > 0) {
            File stripped = stripDebugs(in);
            try {
                jscompressor.compress(stripped, compressed, optimizationLevel, languageVersion);
            } catch (CompressionException e) {
                throw new MojoExecutionException("Failed to compress Javascript file " + e.getScript(), e);
            }
            String describe = in.getName() + " (" + INTEGER.format(in.length()) + " bytes) ";
            String title = StringUtils.rightPad(describe, 60, ".");
            logStats(title + " compressed at " + ratio(compressed, in) + "%");
            return in.length() - compressed.length();
        } else {
            try {
                compressed.createNewFile();
                getLog().info(in.getName() + " was zero length; not compressed.");
                return 0;
            } catch (IOException e) {
                throw new MojoExecutionException("Error handling zero length file.", e);
            }
        }
    }

    private long ratio(File compressed, File in) {
        long length = in.length();
        if (length == 0) {
            return 0;
        }
        return (((length - compressed.length()) * 100) / length);
    }

    private void logStats(String line) {
        if (skipStats) {
            return;
        }
        getLog().info(line);
    }

    /**
     * @return the extension to append to compressed scripts.
     */
    public abstract String getExtension();

    /**
     * @return the outputDirectory
     */
    protected abstract File getOutputDirectory();

    /**
     * @return the sourceDirectory
     */
    protected abstract File getSourceDirectory();

    protected void setLocalRepository(ArtifactRepository localRepository) {
        this.localRepository = localRepository;
    }
}