/* | |
* 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. | |
*/ | |
package org.apache.myfaces.buildtools.maven2.plugin.javascript.jmt; | |
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.apache.myfaces.buildtools.maven2.plugin.javascript.jmt.compress.CompressionException; | |
import org.apache.myfaces.buildtools.maven2.plugin.javascript.jmt.compress.IsolatedClassLoader; | |
import org.apache.myfaces.buildtools.maven2.plugin.javascript.jmt.compress.JSCompressor; | |
import org.apache.myfaces.buildtools.maven2.plugin.javascript.jmt.compress.JSCompressorProxy; | |
import org.apache.myfaces.buildtools.maven2.plugin.javascript.jmt.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"; | |
/* | |
String id = "myfaces-javascript-plugin"; | |
// TODO don't have version hardcoded | |
Artifact compressorArtifact = | |
artifactFactory.createDependencyArtifact( "org.apache.myfaces.buildtools", id, | |
VersionRange.createFromVersion( "1.0.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.apache.myfaces.buildtools.maven2.plugin.javascript.jmt.compress." + compressor + "Compressor"; | |
Class compressorClass; | |
try | |
{ | |
compressorClass = getClass().getClassLoader().loadClass( compressorClassName ); | |
//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; | |
} | |
} |