1 package net_alchim31_maven_yuicompressor;
2
3 import java.io.File;
4 import java.io.FileInputStream;
5 import java.io.IOException;
6 import java.io.InputStreamReader;
7 import java.io.OutputStreamWriter;
8 import java.util.Collection;
9 import java.util.HashSet;
10 import java.util.Set;
11 import java.util.zip.GZIPOutputStream;
12
13 import org.apache.maven.plugin.MojoExecutionException;
14 import org.codehaus.plexus.util.FileUtils;
15 import org.codehaus.plexus.util.IOUtil;
16
17 import com.yahoo.platform.yui.compressor.CssCompressor;
18 import com.yahoo.platform.yui.compressor.JavaScriptCompressor;
19
20
21
22
23
24
25
26
27
28
29
30
31 public class YuiCompressorMojo extends MojoSupport {
32
33
34
35
36
37
38 private String encoding;
39
40
41
42
43
44
45 private String suffix;
46
47
48
49
50
51
52 private boolean nosuffix;
53
54
55
56
57
58
59 private int linebreakpos;
60
61
62
63
64
65
66 private boolean nocompress;
67
68
69
70
71
72
73 private boolean nomunge;
74
75
76
77
78
79
80 private boolean preserveAllSemiColons;
81
82
83
84
85
86
87 private boolean disableOptimizations;
88
89
90
91
92
93
94
95 private boolean force;
96
97
98
99
100
101
102
103
104 private Aggregation[] aggregations;
105
106
107
108
109
110
111 private boolean gzip;
112
113
114
115
116
117
118 private boolean statistics;
119
120
121
122
123
124 private boolean preProcessAggregates;
125
126
127
128
129
130 private boolean useSmallestFile;
131
132 private long inSizeTotal_;
133 private long outSizeTotal_;
134
135 @Override
136 protected String[] getDefaultIncludes() throws Exception {
137 return new String[]{"**/*.css", "**/*.js"};
138 }
139
140 @Override
141 public void beforeProcess() throws Exception {
142 if (nosuffix) {
143 suffix = "";
144 }
145
146 if(preProcessAggregates) aggregate();
147 }
148
149 @Override
150 protected void afterProcess() throws Exception {
151 if (statistics && (inSizeTotal_ > 0)) {
152 getLog().info(String.format("total input (%db) -> output (%db)[%d%%]", inSizeTotal_, outSizeTotal_, ((outSizeTotal_ * 100)/inSizeTotal_)));
153 }
154
155 if(!preProcessAggregates) aggregate();
156 }
157
158 private void aggregate() throws Exception {
159 if (aggregations != null) {
160 Set<File> previouslyIncludedFiles = new HashSet<File>();
161 for(Aggregation aggregation : aggregations) {
162 getLog().info("generate aggregation : " + aggregation.output);
163 Collection<File> aggregatedFiles = aggregation.run(previouslyIncludedFiles,buildContext);
164 previouslyIncludedFiles.addAll(aggregatedFiles);
165
166 File gzipped = gzipIfRequested(aggregation.output);
167 if (statistics) {
168 if (gzipped != null) {
169 getLog().info(String.format("%s (%db) -> %s (%db)[%d%%]", aggregation.output.getName(), aggregation.output.length(), gzipped.getName(), gzipped.length(), ratioOfSize(aggregation.output, gzipped)));
170 } else if (aggregation.output.exists()){
171 getLog().info(String.format("%s (%db)", aggregation.output.getName(), aggregation.output.length()));
172 } else {
173 getLog().warn(String.format("%s not created", aggregation.output.getName()));
174 }
175 }
176 }
177 }
178 }
179
180 @Override
181 protected void processFile(SourceFile src) throws Exception {
182 if (getLog().isDebugEnabled()) {
183 getLog().debug("compress file :" + src.toFile()+ " to " + src.toDestFile(suffix));
184 }
185 File inFile = src.toFile();
186 File outFile = src.toDestFile(suffix);
187
188 getLog().debug("only compress if input file is younger than existing output file");
189 if (!force && outFile.exists() && (outFile.lastModified() > inFile.lastModified())) {
190 if (getLog().isInfoEnabled()) {
191 getLog().info("nothing to do, " + outFile + " is younger than original, use 'force' option or clean your target");
192 }
193 return;
194 }
195
196 InputStreamReader in = null;
197 OutputStreamWriter out = null;
198 File outFileTmp = new File(outFile.getAbsolutePath() + ".tmp");
199 FileUtils.forceDelete(outFileTmp);
200 try {
201 in = new InputStreamReader(new FileInputStream(inFile), encoding);
202 if (!outFile.getParentFile().exists() && !outFile.getParentFile().mkdirs()) {
203 throw new MojoExecutionException( "Cannot create resource output directory: " + outFile.getParentFile() );
204 }
205 getLog().debug("use a temporary outputfile (in case in == out)");
206
207 getLog().debug("start compression");
208 out = new OutputStreamWriter(buildContext.newFileOutputStream(outFileTmp), encoding);
209 if (nocompress) {
210 getLog().info("No compression is enabled");
211 IOUtil.copy(in, out);
212 } else if (".js".equalsIgnoreCase(src.getExtension())) {
213 JavaScriptCompressor compressor = new JavaScriptCompressor(in, jsErrorReporter_);
214 compressor.compress(out, linebreakpos, !nomunge, jswarn, preserveAllSemiColons, disableOptimizations);
215 } else if (".css".equalsIgnoreCase(src.getExtension())) {
216 compressCss(in, out);
217 }
218 getLog().debug("end compression");
219 } finally {
220 IOUtil.close(in);
221 IOUtil.close(out);
222 }
223
224 boolean outputIgnored = useSmallestFile && inFile.length() < outFile.length();
225 if (outputIgnored) {
226 FileUtils.forceDelete(outFileTmp);
227 FileUtils.copyFile(inFile, outFile);
228 getLog().debug("output greater than input, using original instead");
229 } else {
230 FileUtils.forceDelete(outFile);
231 FileUtils.rename(outFileTmp, outFile);
232 buildContext.refresh(outFile);
233 buildContext.refresh(outFileTmp);
234 }
235
236 File gzipped = gzipIfRequested(outFile);
237 if (statistics) {
238 inSizeTotal_ += inFile.length();
239 outSizeTotal_ += outFile.length();
240
241 String fileStatistics;
242 if (outputIgnored) {
243 fileStatistics = String.format("%s (%db) -> %s (%db)[compressed output discarded (exceeded input size)]", inFile.getName(), inFile.length(), outFile.getName(), outFile.length());
244 } else {
245 fileStatistics = String.format("%s (%db) -> %s (%db)[%d%%]", inFile.getName(), inFile.length(), outFile.getName(), outFile.length(), ratioOfSize(inFile, outFile));
246 }
247
248 if (gzipped != null) {
249 fileStatistics = fileStatistics + String.format(" -> %s (%db)[%d%%]", gzipped.getName(), gzipped.length(), ratioOfSize(inFile, gzipped));
250 }
251 getLog().info(fileStatistics);
252 }
253 }
254
255 private void compressCss(InputStreamReader in, OutputStreamWriter out)
256 throws IOException {
257 try{
258 CssCompressor compressor = new CssCompressor(in);
259 compressor.compress(out, linebreakpos);
260 }catch(IllegalArgumentException e){
261 throw new IllegalArgumentException(
262 "Unexpected characters found in CSS file. Ensure that the CSS file does not contain '$', and try again",e);
263 }
264 }
265
266 protected File gzipIfRequested(File file) throws Exception {
267 if (!gzip || (file == null) || (!file.exists())) {
268 return null;
269 }
270 if (".gz".equalsIgnoreCase(FileUtils.getExtension(file.getName()))) {
271 return null;
272 }
273 File gzipped = new File(file.getAbsolutePath()+".gz");
274 getLog().debug(String.format("create gzip version : %s", gzipped.getName()));
275 GZIPOutputStream out = null;
276 FileInputStream in = null;
277 try {
278 out = new GZIPOutputStream(buildContext.newFileOutputStream(gzipped));
279 in = new FileInputStream(file);
280 IOUtil.copy(in, out);
281 } finally {
282 IOUtil.close(in);
283 IOUtil.close(out);
284 }
285 return gzipped;
286 }
287
288 protected long ratioOfSize(File file100, File fileX) throws Exception {
289 long v100 = Math.max(file100.length(), 1);
290 long vX = Math.max(fileX.length(), 1);
291 return (vX * 100)/v100;
292 }
293 }