1 /***************************************************************************************
2 * Copyright (c) Jonas Bonér, Alexandre Vasseur. All rights reserved. *
3 * http://aspectwerkz.codehaus.org *
4 * ---------------------------------------------------------------------------------- *
5 * The software in this package is published under the terms of the LGPL license *
6 * a copy of which has been included with this distribution in the license.txt file. *
7 **************************************************************************************/
8 package org.codehaus.aspectwerkz.compiler;
9
10 import org.codehaus.aspectwerkz.definition.DefinitionLoader;
11 import org.codehaus.aspectwerkz.definition.SystemDefinitionContainer;
12 import org.codehaus.aspectwerkz.hook.ClassPreProcessor;
13
14 import java.io.ByteArrayInputStream;
15 import java.io.ByteArrayOutputStream;
16 import java.io.File;
17 import java.io.FileInputStream;
18 import java.io.FileOutputStream;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.net.URL;
22 import java.net.URLClassLoader;
23 import java.text.SimpleDateFormat;
24 import java.util.ArrayList;
25 import java.util.Date;
26 import java.util.Enumeration;
27 import java.util.HashMap;
28 import java.util.Hashtable;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.jar.Attributes;
33 import java.util.jar.Manifest;
34 import java.util.zip.CRC32;
35 import java.util.zip.ZipEntry;
36 import java.util.zip.ZipFile;
37 import java.util.zip.ZipOutputStream;
38
39 /***
40 * AspectWerkzC allow for precompilation of class / jar / zip given a class preprocessor. <p/>
41 * <h2>Usage</h2>
42 * <p/>
43 * <pre>
44 * java [-Daspectwerkz.classloader.preprocessor={ClassPreProcessorImpl}] -cp [...]
45 * org.codehaus.aspectwerkz.compiler.AspectWerkzC [-verbose] [-haltOnError] [-verify] [-cp {additional cp i}]* {target
46 * 1} .. {target n}
47 * {ClassPreProcessorImpl} : full qualified name of the ClassPreProcessor implementation (must be in classpath)
48 * defaults to org.codehaus.aspectwerkz.transform.AspectWerkzPreProcessor
49 * {additional cp i} : additionnal classpath needed at compile time (eg: myaspect.jar)
50 * use as many -cp options as needed
51 * supports java classpath syntax for classpath separator: ; on windows, : on others
52 * {target i} : exploded dir, jar, zip files to compile
53 * Ant 1.5 must be in the classpath
54 * </pre>
55 * <p/>
56 * <h2>Classpath note</h2>
57 * At the beginning of the compilation, all {target i} are added to the classpath automatically. <br/>This is required
58 * to support caller side advices. <p/>
59 * <h2>Error handling</h2>
60 * For each target i, a backup copy is written in ./_aspectwerkzc/i/target <br/>Transformation occurs on original target
61 * class/dir/jar/zip file <br/>On failure, target backup is restored and stacktrace is given <br/><br/>If
62 * <i>-haltOnError </i> was set, compilations ends and a <b>complete </b> rollback occurs on all targets, else a status
63 * report is printed at the end of the compilation, indicating SUCCESS or ERROR for each given target. <br/>If
64 * <i>-verify </i> was set, all compiled class are verified during the compilation and an error is generated if the
65 * compiled class bytecode is corrupted. The error is then handled according to the <i>-haltOnError </i> option. <br/>
66 * <p/>
67 * <h2>Manifest.mf update</h2>
68 * The Manifest.mf if present is updated wit the following:
69 * <ul>
70 * <li>AspectWerkzC-created: date of the compilation</li>
71 * <li>AspectWerkzC-preprocessor: full qualified classname of the preprocessor used</li>
72 * <li>AspectWerkzC-comment: comments</li>
73 * </ul>
74 *
75 * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
76 */
77 public class AspectWerkzC {
78
79 private static final String COMMAND_LINE_OPTION_DASH = "-";
80 private static final String COMMAND_LINE_OPTION_VERBOSE = "-verbose";
81 private static final String COMMAND_LINE_OPTION_HALT = "-haltOnError";
82 private static final String COMMAND_LINE_OPTION_VERIFY = "-verify";
83 private static final String COMMAND_LINE_OPTION_CLASSPATH = "-cp";
84 private static final String COMMAND_LINE_OPTION_TARGETS = "compile.targets";
85
86 /***
87 * option used to defined the class preprocessor
88 */
89 private static final String PRE_PROCESSOR_CLASSNAME_PROPERTY = "aspectwerkz.classloader.preprocessor";
90
91 /***
92 * default class preprocessor
93 */
94 private static final String PRE_PROCESSOR_CLASSNAME_DEFAULT = "org.codehaus.aspectwerkz.transform.AspectWerkzPreProcessor";
95
96 private final static String MF_CUSTOM_DATE = "X-AspectWerkzC-created";
97
98 private final static String MF_CUSTOM_PP = "X-AspectWerkzC-preprocessor";
99
100 private final static String MF_CUSTOM_COMMENT = "X-AspectWerkzC-comment";
101
102 private final static String MF_CUSTOM_COMMENT_VALUE = "AspectWerkzC - AspectWerkz compiler, aspectwerkz.codehaus.org";
103
104 private final static SimpleDateFormat DF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
105
106 private final static String BACKUP_DIR = "_aspectwerkzc";
107
108 private boolean verify = false;
109
110 private boolean haltOnError = false;
111
112 private String backupDir = BACKUP_DIR;
113
114 /***
115 * class loader in which the effective compilation occurs, child of system classloader
116 */
117 private URLClassLoader compilationLoader = null;
118
119 /***
120 * class preprocessor instance used to compile targets
121 */
122 private ClassPreProcessor preprocessor = null;
123
124 /***
125 * index to keep track of {target i} backups
126 */
127 private int sourceIndex;
128
129 /***
130 * Maps the target file to the target backup file
131 */
132 private Map backupMap = new HashMap();
133
134 /***
135 * Maps the target file to a status indicating compilation was successfull
136 */
137 private Map successMap = new HashMap();
138
139 private long timer;
140
141 /***
142 * Utility for file manipulation
143 */
144 private Utility utility;
145
146 /***
147 * Construct a new Utility, restore the index for backup
148 */
149 public AspectWerkzC() {
150
151 sourceIndex = 0;
152 utility = new Utility();
153 timer = System.currentTimeMillis();
154 }
155
156
157
158
159
160 public void setVerbose(boolean verbose) {
161 utility.setVerbose(verbose);
162 }
163
164 public void setHaltOnError(boolean haltOnError) {
165 this.haltOnError = haltOnError;
166 }
167
168 public void setVerify(boolean verify) {
169 this.verify = verify;
170 }
171
172 public void setBackupDir(String backup) {
173 this.backupDir = backup;
174 }
175
176 public Utility getUtility() {
177 return utility;
178 }
179
180 /***
181 * Sets the ClassPreProcessor implementation to use. <p/>The ClassLoader will be set to System ClassLoader when
182 * transform(className, byteCode, callerClassLoader) will be called to compile a class.
183 */
184 public void setPreprocessor(String preprocessor) throws CompileException {
185 try {
186 Class pp = Class.forName(preprocessor);
187 this.preprocessor = (ClassPreProcessor) pp.newInstance();
188 this.preprocessor.initialize(new Hashtable());
189 } catch (Exception e) {
190 throw new CompileException("failed to instantiate preprocessor " + preprocessor, e);
191 }
192 }
193
194 /***
195 * Backup source file in backup_dir/index/file. The backupMap is updated for further rollback
196 */
197 public void backup(File source, int index) {
198
199 File dest = new File(this.backupDir + File.separator + index + File.separator + source.getName());
200 utility.backupFile(source, dest);
201
202
203 backupMap.put(source, dest);
204 }
205
206 /***
207 * Restore the backup registered
208 */
209 public void restoreBackup() {
210 for (Iterator i = backupMap.keySet().iterator(); i.hasNext();) {
211 File source = (File) i.next();
212 if (!successMap.containsKey(source)) {
213 File dest = (File) backupMap.get(source);
214 utility.backupFile(dest, source);
215 }
216 }
217 }
218
219 /***
220 * Delete backup dir at the end of all compilation
221 */
222 public void postCompile(String message) {
223 restoreBackup();
224 utility.log(" [backup] removing backup");
225 utility.deleteDir(new File(this.backupDir));
226 long ms = Math.max(System.currentTimeMillis() - timer, 1 * 1000);
227 System.out.println("( " + (int) (ms / 1000) + " s ) " + message);
228 if (!haltOnError) {
229 for (Iterator i = backupMap.keySet().iterator(); i.hasNext();) {
230 File source = (File) i.next();
231 if (successMap.containsKey(source)) {
232 System.out.println("SUCCESS: " + source);
233 } else {
234 System.out.println("FAILED : " + source);
235 }
236 }
237 }
238 }
239
240 /***
241 * Compile sourceFile. If prefixPackage is not null, assumes it is the class package information. <p/>Handles :
242 * <ul>
243 * <li>directory recursively (exploded jar)</li>
244 * <li>jar / zip file</li>
245 * </ul>
246 */
247 public void doCompile(File sourceFile, String prefixPackage) throws CompileException {
248 if (sourceFile.isDirectory()) {
249 File[] classes = sourceFile.listFiles();
250 for (int i = 0; i < classes.length; i++) {
251 if (classes[i].isDirectory() && !(this.backupDir.equals(classes[i].getName()))) {
252 String packaging = (prefixPackage != null) ? (prefixPackage + "." + classes[i]
253 .getName()) : classes[i].getName();
254 doCompile(classes[i], packaging);
255 } else if (classes[i].getName().toLowerCase().endsWith(".class")) {
256 compileClass(classes[i], prefixPackage);
257 } else if (isJarFile(classes[i])) {
258
259 compileJar(classes[i]);
260 }
261 }
262 } else if (sourceFile.getName().toLowerCase().endsWith(".class")) {
263 compileClass(sourceFile, null);
264 } else if (isJarFile(sourceFile)) {
265 compileJar(sourceFile);
266 }
267 }
268
269 /***
270 * Compiles .class file using fileName as className and given packaging as package name
271 */
272 public void compileClass(File file, String packaging) throws CompileException {
273 InputStream in = null;
274 FileOutputStream fos = null;
275 try {
276 utility.log(" [compile] " + file.getCanonicalPath());
277
278
279 ByteArrayOutputStream bos = new ByteArrayOutputStream();
280 in = new FileInputStream(file);
281 byte[] buffer = new byte[1024];
282 while (in.available() > 0) {
283 int length = in.read(buffer);
284 if (length == -1) {
285 break;
286 }
287 bos.write(buffer, 0, length);
288 }
289
290
291 String className = file.getName().substring(0, file.getName().length() - 6);
292 if (packaging != null) {
293 className = packaging + '.' + className;
294 }
295
296
297 byte[] transformed = null;
298 try {
299 transformed = preprocessor.preProcess(className, bos.toByteArray(), compilationLoader);
300 } catch (Throwable t) {
301 throw new CompileException("weaver failed for class: " + className, t);
302 }
303
304
305 fos = new FileOutputStream(file);
306 fos.write(transformed);
307 fos.close();
308
309
310 if (verify) {
311 URLClassLoader verifier = new VerifierClassLoader(
312 compilationLoader.getURLs(),
313 ClassLoader.getSystemClassLoader()
314 );
315 try {
316 utility.log(" [verify] " + className);
317 Class.forName(className, false, verifier);
318 } catch (Throwable t) {
319 utility.log(" [verify] corrupted class: " + className);
320 throw new CompileException("corrupted class: " + className, t);
321 }
322 }
323 } catch (IOException e) {
324 throw new CompileException("compile " + file.getAbsolutePath() + " failed", e);
325 } finally {
326 try {
327 in.close();
328 } catch (Throwable e) {
329 ;
330 }
331 try {
332 fos.close();
333 } catch (Throwable e) {
334 ;
335 }
336 }
337 }
338
339 /***
340 * Compile all .class encountered in the .jar/.zip file. <p/>The target.jar is compiled in the
341 * target.jar.aspectwerkzc and the target.jar.aspectwerkzc then overrides target.jar on success.
342 */
343 public void compileJar(File file) throws CompileException {
344 utility.log(" [compilejar] " + file.getAbsolutePath());
345
346
347 File workingFile = new File(file.getAbsolutePath() + ".aspectwerkzc");
348 if (workingFile.exists()) {
349 workingFile.delete();
350 }
351 ZipFile zip = null;
352 ZipOutputStream zos = null;
353 try {
354 zip = new ZipFile(file);
355 zos = new ZipOutputStream(new FileOutputStream(workingFile));
356 for (Enumeration e = zip.entries(); e.hasMoreElements();) {
357 ZipEntry ze = (ZipEntry) e.nextElement();
358
359
360 InputStream in = zip.getInputStream(ze);
361 ByteArrayOutputStream bos = new ByteArrayOutputStream();
362 byte[] buffer = new byte[1024];
363 while (in.available() > 0) {
364 int length = in.read(buffer);
365 if (length == -1) {
366 break;
367 }
368 bos.write(buffer, 0, length);
369 }
370 in.close();
371
372
373 byte[] transformed = null;
374 if (ze.getName().toLowerCase().endsWith(".class")) {
375 utility.log(" [compilejar] compile " + file.getName() + ":" + ze.getName());
376 String className = ze.getName().substring(0, ze.getName().length() - 6);
377 try {
378 transformed = preprocessor.preProcess(
379 className, bos.toByteArray(),
380 compilationLoader
381 );
382 } catch (Throwable t) {
383 throw new CompileException("weaver failed for class: " + className, t);
384 }
385 } else {
386 transformed = bos.toByteArray();
387 }
388
389
390 if (ze.getName().toLowerCase().equals("meta-inf/manifest.mf")) {
391 try {
392 Manifest mf = new Manifest(new ByteArrayInputStream(transformed));
393 Attributes at = mf.getMainAttributes();
394 at.putValue(MF_CUSTOM_DATE, DF.format(new Date()));
395 at.putValue(MF_CUSTOM_PP, preprocessor.getClass().getName());
396 at.putValue(MF_CUSTOM_COMMENT, MF_CUSTOM_COMMENT_VALUE);
397
398
399 bos.reset();
400 mf.write(bos);
401 transformed = bos.toByteArray();
402 } catch (Exception emf) {
403 emf.printStackTrace();
404 }
405 }
406
407
408 ZipEntry transformedZe = new ZipEntry(ze.getName());
409 transformedZe.setSize(transformed.length);
410 CRC32 crc = new CRC32();
411 crc.update(transformed);
412 transformedZe.setCrc(crc.getValue());
413 transformedZe.setMethod(ze.getMethod());
414 zos.putNextEntry(transformedZe);
415 zos.write(transformed, 0, transformed.length);
416 }
417 zip.close();
418 zos.close();
419
420
421 File swap = new File(file.getAbsolutePath() + ".swap.aspectwerkzc");
422 utility.backupFile(file, swap);
423 try {
424 utility.backupFile(workingFile, new File(file.getAbsolutePath()));
425 workingFile.delete();
426 swap.delete();
427 } catch (Exception e) {
428
429 utility.backupFile(swap, new File(file.getAbsolutePath()));
430 workingFile.delete();
431 throw new CompileException("compile " + file.getAbsolutePath() + " failed", e);
432 }
433 } catch (IOException e) {
434 throw new CompileException("compile " + file.getAbsolutePath() + " failed", e);
435 } finally {
436 try {
437 zos.close();
438 } catch (Throwable e) {
439 ;
440 }
441 try {
442 zip.close();
443 } catch (Throwable e) {
444 ;
445 }
446 }
447 }
448
449 /***
450 * Compile given target.
451 *
452 * @return false if process should stop
453 */
454 public boolean compile(File source) {
455 sourceIndex++;
456 backup(source, sourceIndex);
457 try {
458 doCompile(source, null);
459 } catch (CompileException e) {
460 utility.log(" [aspectwerkzc] compilation encountered an error");
461 e.printStackTrace();
462 return (!haltOnError);
463 }
464
465
466 successMap.put(source, Boolean.TRUE);
467 return true;
468 }
469
470 /***
471 * Set up the compilation path by building a URLClassLoader with all targets in
472 *
473 * @param targets to add to compilationLoader classpath
474 * @param parentLoader the parent ClassLoader used by the new one
475 */
476 public void setCompilationPath(File[] targets, ClassLoader parentLoader) {
477 URL[] urls = new URL[targets.length];
478 int j = 0;
479 for (int i = 0; i < targets.length; i++) {
480 try {
481 urls[j] = targets[i].getCanonicalFile().toURL();
482 j++;
483 } catch (IOException e) {
484 System.err.println("bad target " + targets[i]);
485 }
486 }
487
488 compilationLoader = new URLClassLoader(urls, parentLoader);
489 }
490
491 /***
492 * Test if file is a zip/jar file
493 */
494 public static boolean isJarFile(File source) {
495 return (source.isFile() && (source.getName().toLowerCase().endsWith(".jar") || source
496 .getName().toLowerCase().endsWith(".zip")));
497 }
498
499 /***
500 * Usage message
501 */
502 public static void doHelp() {
503 System.out.println("--- AspectWerkzC compiler ---");
504 System.out.println("Usage:");
505 System.out
506 .println(
507 "java -cp ... org.codehaus.aspectwerkz.compiler.AspectWerkzC [-verbose] [-haltOnError] [-verify] <target 1> .. <target n>"
508 );
509 System.out.println(" <target i> : exploded dir, jar, zip files to compile");
510 }
511
512 /***
513 * Creates and configures an AspectWerkzC compiler.
514 *
515 * @param params a map containing the compiler parameters
516 * @return a new and configured <CODE>AspectWerkzC</CODE>
517 */
518 private static AspectWerkzC createCompiler(Map params) {
519 AspectWerkzC compiler = new AspectWerkzC();
520
521 for (Iterator it = params.entrySet().iterator(); it.hasNext();) {
522 Map.Entry param = (Map.Entry) it.next();
523
524 if (COMMAND_LINE_OPTION_VERBOSE.equals(param.getKey())) {
525 compiler.setVerbose(Boolean.TRUE.equals(param.getValue()));
526 } else if (COMMAND_LINE_OPTION_HALT.equals(param.getKey())) {
527 compiler.setHaltOnError(Boolean.TRUE.equals(param.getValue()));
528 } else if (COMMAND_LINE_OPTION_VERIFY.equals(param.getKey())) {
529 compiler.setVerify(Boolean.TRUE.equals(param.getValue()));
530 }
531 }
532
533 return compiler;
534 }
535
536 /***
537 * Runs the AspectWerkzC compiler for the <tt>targets</tt> files.
538 *
539 * @param compiler a configured <CODE>AspectWerkzC</CODE>
540 * @param classLoader the class loader to be used
541 * @param preProcessor fully qualified name of the preprocessor class.
542 * If <tt>null</tt> than the default is used
543 * (<CODE>org.codehaus.aspectwerkz.transform.AspectWerkzPreProcessor</CODE>)
544 * @param classpath list of Files representing the classpath (List<File>)
545 * @param targets the list of target files (List<File>)
546 */
547 public static void compile(AspectWerkzC compiler,
548 ClassLoader classLoader,
549 String preProcessor,
550 List classpath,
551 List targets) {
552
553 List fullPath = new ArrayList();
554 if (classpath != null) {
555 fullPath.addAll(classpath);
556 }
557
558 fullPath.addAll(targets);
559
560 compiler.setCompilationPath((File[]) fullPath.toArray(new File[fullPath.size()]), classLoader);
561
562 Thread.currentThread().setContextClassLoader(compiler.compilationLoader);
563
564
565
566
567 SystemDefinitionContainer.disableSystemWideDefinition();
568 SystemDefinitionContainer.deploySystemDefinitions(
569 compiler.compilationLoader,
570 DefinitionLoader.getDefaultDefinition(compiler.compilationLoader)
571 );
572
573 String preprocessorFqn = preProcessor == null ? PRE_PROCESSOR_CLASSNAME_DEFAULT
574 : preProcessor;
575
576 try {
577 compiler.setPreprocessor(preprocessorFqn);
578 } catch (CompileException e) {
579 System.err.println("Cannot instantiate ClassPreProcessor: " + preprocessorFqn);
580 e.printStackTrace();
581 System.exit(-1);
582 }
583
584 cleanBackupDir(compiler);
585
586 for (Iterator i = targets.iterator(); i.hasNext();) {
587 if (!compiler.compile((File) i.next())) {
588 compiler.postCompile("*** An error occured ***");
589 System.exit(-1);
590 }
591 }
592 compiler.postCompile("");
593 }
594
595 private static void cleanBackupDir(AspectWerkzC compiler) {
596
597 try {
598 File temp = new File(compiler.backupDir);
599 if (temp.exists()) {
600 compiler.getUtility().deleteDir(temp);
601 }
602 temp.mkdir();
603 (new File(temp, "" + System.currentTimeMillis() + ".timestamp")).createNewFile();
604 } catch (Exception e) {
605 System.err.println("failed to prepare backup dir: " + compiler.backupDir);
606 e.printStackTrace();
607 System.exit(-1);
608 }
609 }
610
611 public static void main(String[] args) {
612 if (args.length <= 0) {
613 doHelp();
614 return;
615 }
616
617 Map options = parseOptions(args);
618 AspectWerkzC compiler = createCompiler(options);
619
620 compiler.setBackupDir(BACKUP_DIR);
621
622 compile(
623 compiler,
624 ClassLoader.getSystemClassLoader(),
625 System.getProperty(
626 PRE_PROCESSOR_CLASSNAME_PROPERTY,
627 PRE_PROCESSOR_CLASSNAME_DEFAULT
628 ),
629 (List) options.get(COMMAND_LINE_OPTION_CLASSPATH),
630 (List) options.get(COMMAND_LINE_OPTION_TARGETS)
631 );
632 }
633
634 private static Map parseOptions(String[] args) {
635 Map options = new HashMap();
636 List targets = new ArrayList();
637
638 for (int i = 0; i < args.length; i++) {
639 if (COMMAND_LINE_OPTION_VERBOSE.equals(args[i])) {
640 options.put(COMMAND_LINE_OPTION_VERBOSE, Boolean.TRUE);
641 } else if (COMMAND_LINE_OPTION_HALT.equals(args[i])) {
642 options.put(COMMAND_LINE_OPTION_HALT, Boolean.TRUE);
643 } else if (COMMAND_LINE_OPTION_VERIFY.equals(args[i])) {
644 options.put(COMMAND_LINE_OPTION_VERIFY, Boolean.TRUE);
645 } else if (COMMAND_LINE_OPTION_CLASSPATH.equals(args[i])) {
646 if (i == (args.length - 1)) {
647 continue;
648 } else {
649 options.put(
650 COMMAND_LINE_OPTION_CLASSPATH,
651 toFileArray(args[++i], File.pathSeparator)
652 );
653 }
654 } else if (args[i].startsWith(COMMAND_LINE_OPTION_DASH)) {
655 ;
656 } else {
657 File file = toFile(args[i]);
658 if (file == null) {
659 System.err.println("Ignoring inexistant target: " + args[i]);
660 } else {
661 targets.add(file);
662 }
663 }
664 }
665
666 options.put(COMMAND_LINE_OPTION_TARGETS, targets);
667
668 return options;
669 }
670
671 private static List toFileArray(String str, String sep) {
672 if (str == null || str.length() == 0) {
673 return new ArrayList();
674 }
675
676 List files = new ArrayList();
677 int start = 0;
678 int idx = str.indexOf(sep, start);
679 int len = sep.length();
680
681 while (idx != -1) {
682 files.add(new File(str.substring(start, idx)));
683 start = idx + len;
684 idx = str.indexOf(sep, start);
685 }
686
687 files.add(new File(str.substring(start)));
688
689 return files;
690 }
691
692 private static File toFile(String path) {
693 File file = new File(path);
694
695 return file.exists() ? file : null;
696 }
697 }