1 package org.codehaus.mojo.javascript;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import org.apache.commons.lang.StringUtils;
24 import org.apache.maven.artifact.Artifact;
25 import org.apache.maven.artifact.factory.ArtifactFactory;
26 import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
27 import org.apache.maven.artifact.repository.ArtifactRepository;
28 import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
29 import org.apache.maven.artifact.resolver.ArtifactResolver;
30 import org.apache.maven.artifact.versioning.VersionRange;
31 import org.apache.maven.plugin.AbstractMojo;
32 import org.apache.maven.plugin.MojoExecutionException;
33 import org.apache.maven.project.MavenProject;
34 import org.codehaus.mojo.javascript.compress.CompressionException;
35 import org.codehaus.mojo.javascript.compress.IsolatedClassLoader;
36 import org.codehaus.mojo.javascript.compress.JSCompressor;
37 import org.codehaus.mojo.javascript.compress.JSMinCompressor;
38 import org.codehaus.plexus.util.DirectoryScanner;
39 import org.codehaus.plexus.util.FileUtils;
40 import org.codehaus.plexus.util.IOUtil;
41
42 import java.io.*;
43 import java.text.NumberFormat;
44 import java.util.Collections;
45 import java.util.HashMap;
46 import java.util.List;
47 import java.util.Map;
48
49
50
51
52
53
54 public abstract class AbstractCompressMojo
55 extends AbstractMojo
56 {
57
58
59
60
61
62
63 private ArtifactResolver artifactResolver;
64
65
66
67
68
69
70 private ArtifactFactory artifactFactory;
71
72
73
74
75
76
77
78 private ArtifactRepository localRepository;
79
80
81
82
83
84
85 private ArtifactMetadataSource metadataSource;
86
87
88
89
90
91
92 private List remoteRepositories;
93
94
95
96
97 private static final NumberFormat INTEGER = NumberFormat.getIntegerInstance();
98
99 private static final String HR = StringUtils.rightPad( "", 78, "-" );
100
101
102
103
104
105
106
107
108 MavenProject project;
109
110
111
112
113 private Map compressors = new HashMap();
114 {
115 compressors.put( "jsmin", new JSMinCompressor() );
116 }
117
118
119
120
121
122
123 private int optimizationLevel;
124
125
126
127
128
129
130 private int languageVersion;
131
132
133
134
135
136
137
138 private String compressor;
139
140
141
142
143
144
145 private boolean skipStats;
146
147 private static final String[] DEFAULT_INCLUDES = new String[] { "**/*.js" };
148
149
150
151
152
153
154 private String[] includes;
155
156
157
158
159
160
161 private String[] excludes;
162
163
164
165
166
167
168
169 private String[] strips;
170
171
172
173
174
175
176
177 private String strip;
178
179
180
181
182
183
184 public void execute()
185 throws MojoExecutionException
186 {
187 DirectoryScanner scanner = getDirectoryScanner();
188 scanner.scan();
189 String[] files = scanner.getIncludedFiles();
190
191
192
193
194
195
196
197
198
199
200 JSCompressor jscompressor = getCompressor();
201
202 logStats( HR );
203 getOutputDirectory().mkdirs();
204 long saved = 0;
205 for ( int i = 0; i < files.length; i++ )
206 {
207 String file = files[i];
208 saved = compress( jscompressor, file );
209 }
210 logStats( HR );
211 logStats( "compression saved " + INTEGER.format( saved ) + " bytes" );
212 }
213
214 protected DirectoryScanner getDirectoryScanner() {
215 DirectoryScanner scanner = new DirectoryScanner();
216 scanner.setBasedir( getSourceDirectory() );
217 if ( includes == null )
218 {
219 includes = DEFAULT_INCLUDES;
220 }
221 scanner.setIncludes( includes );
222 scanner.addDefaultExcludes();
223 if ( excludes != null )
224 {
225 scanner.setExcludes( excludes );
226 }
227 return scanner;
228 }
229
230 private File stripDebugs( String name, File file )
231 throws MojoExecutionException
232 {
233 if ( strip == null && (strips == null || strips.length == 0))
234 {
235 return file;
236 }
237
238 File stripped = new File( getStrippedDirectory(), name );
239 stripped.getParentFile().mkdirs();
240 if ( file.equals( stripped ) )
241 {
242 try
243 {
244 File temp = File.createTempFile( "stripped", ".js" );
245 stripDebugs(file, temp);
246 FileUtils.copyFile( temp, file );
247 temp.delete();
248 return file;
249 }
250 catch ( IOException e )
251 {
252 throw new MojoExecutionException( "Error creating temp file for stripping", e );
253 }
254 }
255 else
256 {
257 stripDebugs(file, stripped);
258 return stripped;
259 }
260 }
261
262 private void stripDebugs( File file, File stripped )
263 throws MojoExecutionException
264 {
265 try
266 {
267 BufferedReader reader = new BufferedReader( new FileReader( file ) );
268 PrintWriter writer = new PrintWriter( stripped );
269 String line;
270 while ( ( line = reader.readLine() ) != null )
271 {
272 String trimmed = line.trim();
273 boolean stripLine = (strip != null && trimmed.startsWith( strip ));
274 if( strips != null ) {
275 for( int i=0, len = strips.length; !stripLine && i < len; i++ ) {
276 stripLine = trimmed.startsWith( strips[i] );
277 }
278 }
279 if ( !stripLine )
280 {
281 writer.println( line );
282 }
283 }
284 IOUtil.close( reader );
285 IOUtil.close( writer );
286 }
287 catch ( IOException e )
288 {
289 throw new MojoExecutionException( "Failed to strip debug code in " + file, e );
290 }
291 }
292
293 private JSCompressor getCompressor()
294 throws MojoExecutionException
295 {
296 if ( compressors.containsKey( compressor ) )
297 {
298 return (JSCompressor) compressors.get( compressor );
299 }
300
301
302
303
304
305 String id = compressor.toLowerCase() + "-compressor";
306
307
308 Artifact compressorArtifact =
309 artifactFactory.createDependencyArtifact( "org.codehaus.mojo.javascript", id,
310 VersionRange.createFromVersion( "1.0-softec" ), "jar", null,
311 Artifact.SCOPE_RUNTIME );
312
313 Artifact originatingArtifact =
314 artifactFactory.createBuildArtifact( "dummy", "dummy", "1.0", "jar" );
315
316 ArtifactResolutionResult dependencies;
317 try
318 {
319 dependencies =
320 artifactResolver.resolveTransitively( Collections.singleton( compressorArtifact ),
321 originatingArtifact, localRepository, remoteRepositories, metadataSource, null );
322 }
323 catch ( Exception e )
324 {
325 getLog().info( "Failed to load compressor artifact " + compressorArtifact.toString() );
326 throw new MojoExecutionException( "Failed to load compressor artifact"
327 + compressorArtifact.toString(), e );
328 }
329
330 IsolatedClassLoader classLoader = new IsolatedClassLoader( dependencies.getArtifacts(),
331 this.getClass().getClassLoader() );
332
333 compressor = StringUtils.capitalize( compressor );
334 String compressorClassName =
335 "org.codehaus.mojo.javascript.compress." + compressor + "Compressor";
336 Class compressorClass;
337 try
338 {
339 compressorClass = classLoader.loadClass( compressorClassName );
340 }
341 catch ( ClassNotFoundException e )
342 {
343 getLog().info( "Failed to load compressor class " + compressorClassName );
344 throw new MojoExecutionException( "Failed to load compressor class"
345 + compressorClassName, e );
346 }
347
348 JSCompressor jscompressor;
349 try
350 {
351 jscompressor = (JSCompressor) compressorClass.newInstance();
352 jscompressor.setLogger(new MojoJSCompressorLogger(getLog()));
353 }
354 catch ( Exception e )
355 {
356 getLog().info(
357 "Failed to create a isolated-classloader proxy for " + compressorClassName );
358 throw new MojoExecutionException( "Failed to create a isolated-classloader proxy for "
359 + compressorClassName, e );
360 }
361 getLog().info( "Compressing javascript using " + compressor );
362
363 compressors.put( compressor, jscompressor );
364 return jscompressor;
365 }
366
367 private long compress( JSCompressor jscompressor, String file )
368 throws MojoExecutionException
369 {
370 String name = file;
371 if ( getExtension() != null )
372 {
373 int ext = file.lastIndexOf( '.' );
374 name = file.substring( 0, ext ) + "-" + getExtension() + file.substring( ext );
375 }
376 File compressed = new File( getOutputDirectory(), name );
377 compressed.getParentFile().mkdirs();
378 File in = new File( getSourceDirectory(), file );
379 if ( in.equals( compressed ) )
380 {
381 try
382 {
383 File temp = File.createTempFile( "compress", ".js" );
384 long size = compress( name, in, temp, jscompressor );
385 FileUtils.copyFile( temp, compressed );
386 temp.delete();
387 return size;
388 }
389 catch ( IOException e )
390 {
391 throw new MojoExecutionException( "Error creating temp file for compression", e );
392 }
393 }
394 else
395 {
396 return compress( name, in, compressed, jscompressor );
397 }
398 }
399
400 private long compress( String name, File in, File compressed, JSCompressor jscompressor )
401 throws MojoExecutionException
402 {
403 if ( in.length() > 0 )
404 {
405 File stripped = stripDebugs( name, in );
406 try
407 {
408 jscompressor.compress( stripped, compressed, optimizationLevel, languageVersion );
409 }
410 catch ( CompressionException e )
411 {
412 throw new MojoExecutionException( "Failed to compress Javascript file "
413 + e.getScript(), e );
414 }
415 String describe = in.getName() + " (" + INTEGER.format( in.length() ) + " bytes) ";
416 String title = StringUtils.rightPad( describe, 50, "." );
417 logStats( title + " compressed at " + ratio( compressed, in ) + "%" );
418 return in.length() - compressed.length();
419 }
420 else
421 {
422 try
423 {
424 compressed.createNewFile();
425 getLog().info( in.getName() + " was zero length; not compressed." );
426 return 0;
427 }
428 catch ( IOException e )
429 {
430 throw new MojoExecutionException( "Error handling zero length file.", e );
431 }
432 }
433 }
434
435 private long ratio( File compressed, File in )
436 {
437 long length = in.length();
438 if ( length == 0 )
439 {
440 return 0;
441 }
442 return ( ( ( length - compressed.length() ) * 100 ) / length );
443 }
444
445 private void logStats( String line )
446 {
447 if ( skipStats )
448 {
449 return;
450 }
451 getLog().info( line );
452 }
453
454
455
456
457 public abstract String getExtension();
458
459
460
461
462 protected abstract File getOutputDirectory();
463
464
465
466
467 protected abstract File getStrippedDirectory();
468
469
470
471
472 protected abstract File getSourceDirectory();
473
474 protected void setLocalRepository( ArtifactRepository localRepository )
475 {
476 this.localRepository = localRepository;
477 }
478 }