View Javadoc

1   package net_alchim31_maven_yuicompressor;
2   
3   import java.io.File;
4   import java.io.FileInputStream;
5   import java.io.FileOutputStream;
6   import java.io.IOException;
7   import java.io.InputStreamReader;
8   import java.io.OutputStreamWriter;
9   import java.util.zip.GZIPOutputStream;
10  
11  import org.apache.maven.plugin.MojoExecutionException;
12  import org.codehaus.plexus.util.FileUtils;
13  import org.codehaus.plexus.util.IOUtil;
14  
15  import com.yahoo.platform.yui.compressor.CssCompressor;
16  import com.yahoo.platform.yui.compressor.JavaScriptCompressor;
17  
18  /**
19   * Apply compression on JS and CSS (using YUI Compressor).
20   *
21   * @goal compress
22   * @phase process-resources
23   *
24   * @author David Bernard
25   * @created 2007-08-28
26   */
27  // @SuppressWarnings("unchecked")
28  public class YuiCompressorMojo extends MojoSupport {
29  
30      /**
31       * Read the input file using "encoding".
32       *
33       * @parameter expression="${file.encoding}" default-value="UTF-8"
34       */
35      private String encoding;
36  
37      /**
38       * The output filename suffix.
39       *
40       * @parameter expression="${maven.yuicompressor.suffix}" default-value="-min"
41       */
42      private String suffix;
43  
44      /**
45       * If no "suffix" must be add to output filename (maven's configuration manage empty suffix like default).
46       *
47       * @parameter expression="${maven.yuicompressor.nosuffix}" default-value="false"
48       */
49      private boolean nosuffix;
50  
51      /**
52       * Insert line breaks in output after the specified column number.
53       *
54       * @parameter expression="${maven.yuicompressor.linebreakpos}" default-value="0"
55       */
56      private int linebreakpos;
57  
58      /**
59       * [js only] No compression
60       *
61       * @parameter expression="${maven.yuicompressor.nocompress}" default-value="false"
62       */
63      private boolean nocompress;
64  
65      /**
66       * [js only] Minify only, do not obfuscate.
67       *
68       * @parameter expression="${maven.yuicompressor.nomunge}" default-value="false"
69       */
70      private boolean nomunge;
71  
72      /**
73       * [js only] Preserve unnecessary semicolons.
74       *
75       * @parameter expression="${maven.yuicompressor.preserveAllSemiColons}" default-value="false"
76       */
77      private boolean preserveAllSemiColons;
78  
79      /**
80       * [js only] disable all micro optimizations.
81       *
82       * @parameter expression="${maven.yuicompressor.disableOptimizations}" default-value="false"
83       */
84      private boolean disableOptimizations;
85  
86      /**
87       * force the compression of every files,
88       * else if compressed file already exists and is younger than source file, nothing is done.
89       *
90       * @parameter expression="${maven.yuicompressor.force}" default-value="false"
91       */
92      private boolean force;
93  
94      /**
95       * a list of aggregation/concatenation to do after processing,
96       * for example to create big js files that contain several small js files.
97       * Aggregation could be done on any type of file (js, css, ...).
98       *
99       * @parameter
100      */
101     private Aggregation[] aggregations;
102 
103     /**
104      * request to create a gzipped version of the yuicompressed/aggregation files.
105      *
106      * @parameter expression="${maven.yuicompressor.gzip}" default-value="false"
107      */
108     private boolean gzip;
109 
110     /**
111      * show statistics (compression ratio).
112      *
113      * @parameter expression="${maven.yuicompressor.statistics}" default-value="true"
114      */
115     private boolean statistics;
116 
117     /**
118      * aggregate files before minify
119      * @parameter expression="${maven.yuicompressor.preProcessAggregates}" default-value="false"
120      */
121     private boolean preProcessAggregates;
122 
123     private long inSizeTotal_;
124     private long outSizeTotal_;
125 
126     @Override
127     protected String[] getDefaultIncludes() throws Exception {
128         return new String[]{"**/*.css", "**/*.js"};
129     }
130 
131     @Override
132     public void beforeProcess() throws Exception {
133         if (nosuffix) {
134             suffix = "";
135         }
136 
137         if(preProcessAggregates) aggregate();
138     }
139 
140     @Override
141     protected void afterProcess() throws Exception {
142         if (statistics && (inSizeTotal_ > 0)) {
143             getLog().info(String.format("total input (%db) -> output (%db)[%d%%]", inSizeTotal_, outSizeTotal_, ((outSizeTotal_ * 100)/inSizeTotal_)));
144         }
145 
146         if(!preProcessAggregates) aggregate();
147     }
148 
149     private void aggregate() throws Exception {
150         if (aggregations != null) {
151             for(Aggregation aggregation : aggregations) {
152                 getLog().info("generate aggregation : " + aggregation.output);
153                 aggregation.run();
154                 File gzipped = gzipIfRequested(aggregation.output);
155                 if (statistics) {
156                     if (gzipped != null) {
157                         getLog().info(String.format("%s (%db) -> %s (%db)[%d%%]", aggregation.output.getName(), aggregation.output.length(), gzipped.getName(), gzipped.length(), ratioOfSize(aggregation.output, gzipped)));
158                     } else if (aggregation.output.exists()){
159                         getLog().info(String.format("%s (%db)", aggregation.output.getName(), aggregation.output.length()));
160                     } else {
161                         getLog().warn(String.format("%s not created", aggregation.output.getName()));
162                     }
163                 }
164             }
165         }
166     }
167 
168     @Override
169     protected void processFile(SourceFile src) throws Exception {
170         if (getLog().isDebugEnabled()) {
171             getLog().debug("compress file :" + src.toFile()+ " to " + src.toDestFile(suffix));
172         }
173         File inFile = src.toFile();
174         File outFile = src.toDestFile(suffix);
175 
176         getLog().debug("only compress if input file is younger than existing output file");
177         if (!force && outFile.exists() && (outFile.lastModified() > inFile.lastModified())) {
178             if (getLog().isInfoEnabled()) {
179                 getLog().info("nothing to do, " + outFile + " is younger than original, use 'force' option or clean your target");
180             }
181             return;
182         }
183 
184         InputStreamReader in = null;
185         OutputStreamWriter out = null;
186         File outFileTmp = new File(outFile.getAbsolutePath() + ".tmp");
187         FileUtils.forceDelete(outFileTmp);
188         try {
189             in = new InputStreamReader(new FileInputStream(inFile), encoding);
190             if (!outFile.getParentFile().exists() && !outFile.getParentFile().mkdirs()) {
191                 throw new MojoExecutionException( "Cannot create resource output directory: " + outFile.getParentFile() );
192             }
193             getLog().debug("use a temporary outputfile (in case in == out)");
194 
195             getLog().debug("start compression");
196             out = new OutputStreamWriter(new FileOutputStream(outFileTmp), encoding);
197             if (nocompress) {
198                 getLog().info("No compression is enabled");
199                 IOUtil.copy(in, out);
200             }
201             else if (".js".equalsIgnoreCase(src.getExtension())) {
202                 JavaScriptCompressor compressor = new JavaScriptCompressor(in, jsErrorReporter_);
203                 compressor.compress(out, linebreakpos, !nomunge, jswarn, preserveAllSemiColons, disableOptimizations);
204             } else if (".css".equalsIgnoreCase(src.getExtension())) {
205                 compressCss(in, out);
206             }
207             getLog().debug("end compression");
208         } finally {
209             IOUtil.close(in);
210             IOUtil.close(out);
211         }
212         FileUtils.forceDelete(outFile);
213         FileUtils.rename(outFileTmp, outFile);
214         File gzipped = gzipIfRequested(outFile);
215         if (statistics) {
216             inSizeTotal_ += inFile.length();
217             outSizeTotal_ += outFile.length();
218             getLog().info(String.format("%s (%db) -> %s (%db)[%d%%]", inFile.getName(), inFile.length(), outFile.getName(), outFile.length(), ratioOfSize(inFile, outFile)));
219             if (gzipped != null) {
220                 getLog().info(String.format("%s (%db) -> %s (%db)[%d%%]", inFile.getName(), inFile.length(), gzipped.getName(), gzipped.length(), ratioOfSize(inFile, gzipped)));
221             }
222         }
223     }
224 
225 	private void compressCss(InputStreamReader in, OutputStreamWriter out)
226 			throws IOException {
227 		try{
228 		    CssCompressor compressor = new CssCompressor(in);
229 		    compressor.compress(out, linebreakpos);
230 		}catch(IllegalArgumentException e){
231 			throw new IllegalArgumentException(
232 					"Unexpected characters found in CSS file. Ensure that the CSS file does not contain '$', and try again",e);
233 		}
234 	}
235 
236     protected File gzipIfRequested(File file) throws Exception {
237         if (!gzip || (file == null) || (!file.exists())) {
238             return null;
239         }
240         if (".gz".equalsIgnoreCase(FileUtils.getExtension(file.getName()))) {
241             return null;
242         }
243         File gzipped = new File(file.getAbsolutePath()+".gz");
244         getLog().debug(String.format("create gzip version : %s", gzipped.getName()));
245         GZIPOutputStream out = null;
246         FileInputStream in = null;
247         try {
248             out = new GZIPOutputStream(new FileOutputStream(gzipped));
249             in = new FileInputStream(file);
250             IOUtil.copy(in, out);
251         } finally {
252             IOUtil.close(in);
253             IOUtil.close(out);
254         }
255         return gzipped;
256     }
257 
258     protected long ratioOfSize(File file100, File fileX) throws Exception {
259         long v100 = Math.max(file100.length(), 1);
260         long vX = Math.max(fileX.length(), 1);
261         return (vX * 100)/v100;
262     }
263 }