package org.zkoss.maven.yuicompressor;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.zip.GZIPOutputStream;
import net_alchim31_maven_yuicompressor.Aggregation;
import net_alchim31_maven_yuicompressor.MojoSupport;
import net_alchim31_maven_yuicompressor.SourceFile;
import org.apache.maven.plugin.MojoExecutionException;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.zkoss.maven.yuicompressor.util.Comments;
import org.zkoss.maven.yuicompressor.util.UnicodeReader;
import com.yahoo.platform.yui.compressor.JavaScriptCompressor;
/**
* Apply compression on JS and CSS (using YUI Compressor).
*
* we modify from net_alchim31_maven_yuicompressor.YuiCompressorMojo , To do
* some customize for zk with modifying the source suffix , and use our
* customize css compressor.
*
*
* from ZK:
* phase can decide when the compressor called ,
* we use the package phase for default.
*
*
* @goal compress
*
* @phase process-resources
*
* @author TonyQ
* @created 2010-9-24
*/
// @SuppressWarnings("unchecked")
public class YuiCompressorMojo extends MojoSupport {
/**
* Read the input file using "encoding".
*
* @parameter expression="${file.encoding}" default-value="UTF-8"
*/
private String encoding;
/**
* The output filename suffix.
*
* @parameter expression="${maven.yuicompressor.suffix}"
* default-value=""
*/
private String suffix;
/**
* The output source filename suffix.
*
* @parameter expression="${maven.yuicompressor.sourcesuffix}"
* default-value=".src"
*/
private String sourcesuffix;
/**
* when you set it , it will not generate the source code.
*
* @parameter expression="${maven.yuicompressor.nosource}"
* default-value="false"
*/
private boolean nosource;
/**
* If no "suffix" must be add to output filename (maven's configuration
* manage empty suffix like default).
*
* @parameter expression="${maven.yuicompressor.nosuffix}"
* default-value="true"
*/
private boolean nosuffix;
/**
* Insert line breaks in output after the specified column number.
*
* @parameter expression="${maven.yuicompressor.linebreakpos}"
* default-value="-1"
*/
private int linebreakpos ;
/**
* [js only] Minify only, do not obfuscate.
*
* @parameter expression="${maven.yuicompressor.nomunge}"
* default-value="false"
*/
private boolean nomunge;
/**
* [js only] Preserve unnecessary semicolons.
*
* @parameter expression="${maven.yuicompressor.preserveAllSemiColons}"
* default-value="false"
*/
private boolean preserveAllSemiColons;
/**
* [js only] disable all micro optimizations.
*
* @parameter expression="${maven.yuicompressor.disableOptimizations}"
* default-value="false"
*/
private boolean disableOptimizations;
/**
* force the compression of every files, else if compressed file already
* exists and is younger than source file, nothing is done.
* @parameter expression="${maven.yuicompressor.force}"
* default-value="false"
*/
private boolean force;
/**
* zk paramter.
* a option to set remove the source's javascript comment or not.
* @parameter expression="${maven.yuicompressor.removeSourceComment}"
* default-value="true"
*/
private boolean removeSourceComment;
/**
* a list of aggregation/concatenation to do after processing, for example
* to create big js files that contain several small js files. Aggregation
* could be done on any type of file (js, css, ...).
*
* @parameter
*/
private Aggregation[] aggregations;
/**
* request to create a gzipped version of the yuicompressed/aggregation
* files.
*
* @parameter expression="${maven.yuicompressor.gzip}" default-value="false"
*/
private boolean gzip;
/**
* show statistics (compression ratio).
*
* @parameter expression="${maven.yuicompressor.statistics}"
* default-value="true"
*/
private boolean statistics;
private long inSizeTotal_;
private long outSizeTotal_;
@Override
protected String[] getDefaultIncludes() throws Exception {
//zk modified here
return new String[] { "**/*.css.dsp", "**/*.css", "**/*.js" };
}
@Override
public void beforeProcess() throws Exception {
if (nosuffix) {
suffix = "";
}
}
@Override
protected void afterProcess() throws Exception {
if (statistics && (inSizeTotal_ > 0)) {
getLog().info(
String.format("total input (%db) -> output (%db)[%d%%]", inSizeTotal_, outSizeTotal_,
((outSizeTotal_ * 100) / inSizeTotal_)));
}
if (aggregations != null) {
for (Aggregation aggregation : aggregations) {
getLog().info("generate aggregation : " + aggregation.output);
aggregation.run();
File gzipped = gzipIfRequested(aggregation.output);
if (statistics) {
if (gzipped != null) {
getLog().info(
String.format("%s (%db) -> %s (%db)[%d%%]", aggregation.output.getName(),
aggregation.output.length(), gzipped.getName(), gzipped.length(),
ratioOfSize(aggregation.output, gzipped)));
} else if (aggregation.output.exists()) {
getLog().info(
String.format("%s (%db)", aggregation.output.getName(), aggregation.output.length()));
} else {
getLog().warn(String.format("%s not created", aggregation.output.getName()));
}
}
}
}
}
@Override
protected void processFile(SourceFile src) throws Exception {
if (getLog().isDebugEnabled()) {
getLog().debug("compress file :" + src.toFile() + " to " + src.toDestFile(suffix));
}
File inFile = src.toFile();
File outFile = src.toDestFile(suffix);
File copyToFile = null;
getLog().debug("only compress if input file is younger than existing output file");
if (!force && outFile.exists() && (outFile.lastModified() > inFile.lastModified())) {
if (getLog().isInfoEnabled()) {
getLog().info(
"nothing to do, " + outFile
+ " is younger than original, use 'force' option or clean your target");
}
return;
}
//zk modified here
//2011/1/7 TonyQ:
//Because user can't set sourcesuffix as empty string because we give it default string,
//so we add a nosource flag here.
if (!"".equals(sourcesuffix) && !nosource) {
getLog().info("compress source :["+sourcesuffix+"]");
if (!(".css".equalsIgnoreCase(src.getExtension()) || src.toFile().getName().endsWith(".css.dsp"))){
copyToFile = src.toDestFile(sourcesuffix);
if(copyToFile.exists() && copyToFile.lastModified() > inFile.lastModified()) {
if (getLog().isInfoEnabled()) {
getLog().info(
"nothing to do, " + copyToFile
+ " is younger than original, clean your target instead.");
}
return;
}
if (getLog().isDebugEnabled()) {
getLog().debug("copyFile inFile from: " + inFile.getAbsolutePath()
+ " to: " + copyToFile.getAbsolutePath());
}
if(removeSourceComment){
getLog().info("remove js comment: "+copyToFile.getName());
//TonyQ 2010/12/14 remvoe js file comment
String fileContent = FileUtils.fileRead(inFile, encoding);
try{
fileContent = Comments.removeComment(fileContent);
}catch(IllegalStateException ex){
getLog().error("clear comment failed:"+copyToFile.getName()+":"+ex.getMessage()+":skip clear comment step");
}
FileUtils.fileWrite(copyToFile.getAbsolutePath(), encoding, fileContent);
}else
FileUtils.copyFile(inFile, copyToFile);
}
}
InputStreamReader in = null;
OutputStreamWriter out = null;
File outFileTmp = new File(outFile.getAbsolutePath() + ".tmp");
FileUtils.forceDelete(outFileTmp);
try {
//bug fix for UTF8 BOM issue by TonyQ
in = new UnicodeReader(new FileInputStream(inFile), encoding);
if (!outFile.getParentFile().exists() && !outFile.getParentFile().mkdirs()) {
throw new MojoExecutionException("Cannot create resource output directory: " + outFile.getParentFile());
}
getLog().debug("use a temporary outputfile (in case in == out)");
getLog().debug("start compression");
out = new OutputStreamWriter(new FileOutputStream(outFileTmp), encoding);
if (".js".equalsIgnoreCase(src.getExtension())) {
JavaScriptCompressor compressor = new JavaScriptCompressor(in, jsErrorReporter_);
compressor.compress(out, linebreakpos, !nomunge, jswarn, preserveAllSemiColons, disableOptimizations);
//zk modified here
} else if (".css".equalsIgnoreCase(src.getExtension()) || src.toFile().getName().endsWith(".css.dsp")) {
//zk modified here
// here we use css compressor for zk version
CssCompressor compressor = new CssCompressor(in);
compressor.compress(out, linebreakpos);
}
getLog().debug("end compression");
} finally {
IOUtil.close(in);
IOUtil.close(out);
}
FileUtils.forceDelete(outFile);
if (getLog().isDebugEnabled()) {
getLog().debug("rename outFile from: " + outFileTmp.getAbsolutePath() + " to: " + outFile.getAbsolutePath());
}
FileUtils.rename(outFileTmp, outFile);
// update lastModifyTime
if (copyToFile != null)
copyToFile.setLastModified(outFile.lastModified() + 500);
File gzipped = gzipIfRequested(outFile);
if (statistics) {
inSizeTotal_ += inFile.length();
outSizeTotal_ += outFile.length();
getLog().info(
String.format("%s (%db) -> %s (%db)[%d%%]", inFile.getName(), inFile.length(), outFile.getName(),
outFile.length(), ratioOfSize(inFile, outFile)));
if (gzipped != null) {
getLog().info(
String.format("%s (%db) -> %s (%db)[%d%%]", inFile.getName(), inFile.length(),
gzipped.getName(), gzipped.length(), ratioOfSize(inFile, gzipped)));
}
}
}
protected File gzipIfRequested(File file) throws Exception {
if (!gzip || (file == null) || (!file.exists())) {
return null;
}
if (".gz".equalsIgnoreCase(FileUtils.getExtension(file.getName()))) {
return null;
}
File gzipped = new File(file.getAbsolutePath() + ".gz");
getLog().debug(String.format("create gzip version : %s", gzipped.getName()));
GZIPOutputStream out = null;
FileInputStream in = null;
try {
out = new GZIPOutputStream(new FileOutputStream(gzipped));
in = new FileInputStream(file);
IOUtil.copy(in, out);
} finally {
IOUtil.close(in);
IOUtil.close(out);
}
return gzipped;
}
protected long ratioOfSize(File file100, File fileX) throws Exception {
long v100 = Math.max(file100.length(), 1);
long vX = Math.max(fileX.length(), 1);
return (vX * 100) / v100;
}
}