View Javadoc
1   package org.codehaus.plexus.compiler.ajc;
2   
3   import javax.inject.Named;
4   
5   import java.io.File;
6   import java.io.IOException;
7   import java.net.MalformedURLException;
8   import java.net.URL;
9   import java.net.URLClassLoader;
10  import java.util.ArrayList;
11  import java.util.Arrays;
12  import java.util.HashSet;
13  import java.util.LinkedList;
14  import java.util.List;
15  import java.util.Map;
16  import java.util.Set;
17  
18  import org.aspectj.ajdt.ajc.BuildArgParser;
19  import org.aspectj.ajdt.internal.core.builder.AjBuildConfig;
20  import org.aspectj.ajdt.internal.core.builder.AjBuildManager;
21  import org.aspectj.bridge.AbortException;
22  import org.aspectj.bridge.IMessage;
23  import org.aspectj.bridge.ISourceLocation;
24  import org.aspectj.bridge.MessageHandler;
25  import org.aspectj.org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
26  import org.aspectj.tools.ajc.Main;
27  import org.codehaus.plexus.compiler.AbstractCompiler;
28  import org.codehaus.plexus.compiler.CompilerConfiguration;
29  import org.codehaus.plexus.compiler.CompilerException;
30  import org.codehaus.plexus.compiler.CompilerMessage;
31  import org.codehaus.plexus.compiler.CompilerOutputStyle;
32  import org.codehaus.plexus.compiler.CompilerResult;
33  import org.codehaus.plexus.util.DirectoryScanner;
34  
35  /**
36   * <p>
37   * Options
38   * </p>
39   * <p>
40   * -injars JarList
41   * </p>
42   * <p>
43   * Accept as source bytecode any .class files inside the specified .jar files. The output will include these
44   * classes, possibly as woven with any applicable aspects. JarList, like classpath, is a single argument
45   * containing a list of paths to jar files, delimited by the platform- specific classpath delimiter.
46   * </p>
47   * <p>
48   * -aspectpath JarList
49   * </p>
50   * <p>
51   * Weave binary aspects from JarList zip files into all sources. The aspects should have been output by
52   * the same version of the compiler. To run the output classes requires putting all the aspectpath entries on
53   * the run classpath. JarList, like classpath, is a single argument containing a list of paths to jar files,
54   * delimited by the platform- specific classpath delimiter.
55   * </p>
56   * <p>
57   * -argfile File
58   * </p>
59   * <p>
60   * The file is a line-delimited list of arguments. These arguments are inserted into the argument list.
61   * </p>
62   * <p>
63   * -outjar output.jar
64   * </p>
65   * <p>
66   * Put output classes in zip file output.jar.
67   * </p>
68   * <p>
69   * -incremental
70   * </p>
71   * <p>
72   * Run the compiler continuously. After the initial compilation, the compiler will wait to recompile until it
73   * reads a newline from the standard input, and will quit when it reads a 'q'. It will only recompile necessary
74   * components, so a recompile should be much faster than doing a second compile. This requires -sourceroots.
75   * </p>
76   * <p>
77   * -sourceroots DirPaths
78   * </p>
79   * <p>
80   * Find and build all .java or .aj source files under any directory listed in DirPaths. DirPaths, like
81   * classpath, is a single argument containing a list of paths to directories, delimited by the platform-
82   * specific classpath delimiter. Required by -incremental.
83   * </p>
84   * <p>
85   * -emacssym
86   * </p>
87   * <p>
88   * Generate .ajesym symbol files for emacs support
89   * </p>
90   * <p>
91   * -Xlint
92   * </p>
93   * <p>
94   * Same as -Xlint:warning (enabled by default)
95   * </p>
96   * <p>
97   * -Xlint:{level}
98   * </p>
99   * <p>
100  * Set default level for messages about potential programming mistakes in crosscutting code. {level} may be
101  * ignore, warning, or error. This overrides entries in org/aspectj/weaver/XlintDefault.properties from
102  * aspectjtools.jar, but does not override levels set using the -Xlintfile option.
103  * </p>
104  * <p>
105  * -Xlintfile PropertyFile
106  * </p>
107  * <p>
108  * Specify properties file to set levels for specific crosscutting messages. PropertyFile is a path to a
109  * Java .properties file that takes the same property names and values as
110  * org/aspectj/weaver/XlintDefault.properties from aspectjtools.jar, which it also overrides.
111  * -help
112  * </p>
113  * <p>
114  * Emit information on compiler options and usage
115  * </p>
116  * <p>
117  * -version
118  * </p>
119  * <p>
120  * Emit the version of the AspectJ compiler
121  * </p>
122  * <p>
123  * -classpath Path
124  * </p>
125  * <p>
126  * Specify where to find user class files. Path is a single argument containing a list of paths to zip files
127  * or directories, delimited by the platform-specific path delimiter.
128  * </p>
129  * <p>
130  * -bootclasspath Path
131  * </p>
132  * <p>
133  * Override location of VM's bootclasspath for purposes of evaluating types when compiling. Path is a single
134  * argument containing a list of paths to zip files or directories, delimited by the platform-specific path
135  * delimiter.
136  * </p>
137  * <p>
138  * -extdirs Path
139  * </p>
140  * <p>
141  * Override location of VM's extension directories for purposes of evaluating types when compiling. Path is
142  * a single argument containing a list of paths to directories, delimited by the platform-specific path
143  * delimiter.
144  * </p>
145  * <p>
146  * -d Directory
147  * </p>
148  * <p>
149  * Specify where to place generated .class files. If not specified, Directory defaults to the current
150  * working dir.
151  * </p>
152  * <p>
153  * -target [1.1|1.2]
154  * </p>
155  * <p>
156  * Specify classfile target setting (1.1 or 1.2, default is 1.1)
157  * </p>
158  * <p>
159  * -1.3
160  * </p>
161  * <p>
162  * Set compliance level to 1.3 (default)
163  * -1.4
164  * </p>
165  * <p>
166  * Set compliance level to 1.4
167  * -source [1.3|1.4]
168  * </p>
169  * <p>
170  * Toggle assertions (1.3 or 1.4, default is 1.3 in -1.3 mode and 1.4 in -1.4 mode). When using -source 1.3,
171  * an assert() statement valid under Java 1.4 will result in a compiler error. When using -source 1.4, treat
172  * assert as a keyword and implement assertions according to the 1.4 language spec.
173  * </p>
174  * <p>
175  * -nowarn
176  * </p>
177  * <p>
178  * Emit no warnings (equivalent to '-warn:none') This does not suppress messages generated by declare warning
179  * or Xlint.
180  * </p>
181  * <p>
182  * -warn: items
183  * </p>
184  * <p>
185  * Emit warnings for any instances of the comma-delimited list of questionable code
186  * (eg '-warn:unusedLocals,deprecation'):
187  * </p>
188  * <p>
189  * constructorName        method with constructor name
190  * packageDefaultMethod   attempt to override package-default method
191  * deprecation            usage of deprecated type or member
192  * maskedCatchBlocks      hidden catch block
193  * unusedLocals           local variable never read
194  * unusedArguments        method argument never read
195  * unusedImports          import statement not used by code in file
196  * none                   suppress all compiler warnings
197  * </p>
198  * <p>
199  * -warn:none does not suppress messages generated by declare warning or Xlint.
200  * </p>
201  * <p>
202  * -deprecation
203  * </p>
204  * <p>
205  * Same as -warn:deprecation
206  * </p>
207  * <p>
208  * -noImportError
209  * </p>
210  * <p>
211  * Emit no errors for unresolved imports
212  * </p>
213  * <p>
214  * -proceedOnError
215  * </p>
216  * <p>
217  * Keep compiling after error, dumping class files with problem methods
218  * </p>
219  * <p>
220  * -g:[lines,vars,source]
221  * </p>
222  * <p>
223  * debug attributes level, that may take three forms:
224  * </p>
225  * <p>
226  * -g         all debug info ('-g:lines,vars,source')
227  * -g:none    no debug info
228  * -g:{items} debug info for any/all of [lines, vars, source], e.g.,
229  * -g:lines,source
230  * </p>
231  * <p>
232  * -preserveAllLocals
233  * </p>
234  * <p>
235  * Preserve all local variables during code generation (to facilitate debugging).
236  * </p>
237  * <p>
238  * -referenceInfo
239  * </p>
240  * <p>
241  * Compute reference information.
242  * </p>
243  * <p>
244  * -encoding format
245  * </p>
246  * <p>
247  * Specify default source encoding format. Specify custom encoding on a per file basis by suffixing each
248  * input source file/folder name with '[encoding]'.
249  * </p>
250  * <p>
251  * -verbose
252  * </p>
253  * <p>
254  * Emit messages about accessed/processed compilation units
255  * </p>
256  * <p>
257  * -log file Specify a log file for compiler messages.
258  * </p>
259  * <p>
260  * -progress Show progress (requires -log mode).
261  * </p>
262  * <p>
263  * -time Display speed information.
264  * </p>
265  * <p>
266  * -noExit Do not call System.exit(n) at end of compilation (n=0 if no error)
267  * </p>
268  * <p>
269  * -repeat N Repeat compilation process N times (typically to do performance analysis).
270  * </p>
271  * <p>
272  * -Xnoweave (Experimental) produce unwoven class files for input using -injars.
273  * </p>
274  * <p>
275  * -Xnoinline (Experimental) do not inline around advice
276  * </p>
277  * <p>
278  * -XincrementalFile file
279  * </p>
280  * <p>
281  * (Experimental) This works like incremental mode, but using a file rather than standard input to control
282  * the compiler. It will recompile each time file is changed and and halt when file is deleted.
283  * </p>
284  * <p>
285  * -XserializableAspects (Experimental) Normally it is an error to declare aspects Serializable. This option removes that restriction.
286  * </p>
287  *
288  * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
289  */
290 @Named("aspectj")
291 public class AspectJCompiler extends AbstractCompiler {
292 
293     // ----------------------------------------------------------------------
294     //
295     // ----------------------------------------------------------------------
296 
297     public AspectJCompiler() {
298         // Input file ending "" means: Give me all files, I am going to filter them myself later. We are doing this,
299         // because in method 'getSourceFiles' we need to search for both ".java" and ".aj" files.
300         super(CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE, "", ".class", null);
301     }
302 
303     @Override
304     public String getCompilerId() {
305         return "aspectj";
306     }
307 
308     public CompilerResult performCompile(CompilerConfiguration config) throws CompilerException {
309         File destinationDir = new File(config.getOutputLocation());
310 
311         if (!destinationDir.exists()) {
312             destinationDir.mkdirs();
313         }
314 
315         String[] sourceFiles = getSourceFiles(config);
316 
317         if (sourceFiles.length == 0) {
318             return new CompilerResult();
319         }
320 
321         logCompiling(sourceFiles, config);
322 
323         //        String[] args = buildCompilerArguments( config, sourceFiles );
324         AjBuildConfig buildConfig = buildCompilerConfig(config);
325         return new CompilerResult().compilerMessages(compileInProcess(buildConfig));
326     }
327 
328     private static class AspectJMessagePrinter extends Main.MessagePrinter {
329         public AspectJMessagePrinter(boolean verbose) {
330             super(verbose);
331         }
332     }
333 
334     private AjBuildConfig buildCompilerConfig(CompilerConfiguration config) throws CompilerException {
335         BuildArgParser buildArgParser = new BuildArgParser(new AspectJMessagePrinter(config.isVerbose()));
336         AjBuildConfig buildConfig = new AjBuildConfig(buildArgParser);
337         // Avoid NPE when AjBuildConfig.getCheckedClasspaths() is called later during compilation
338         buildArgParser.populateBuildConfig(buildConfig, new String[0], true, null);
339         buildConfig.setIncrementalMode(false);
340 
341         String[] files = getSourceFiles(config);
342         if (files != null) {
343             buildConfig.setFiles(buildFileList(Arrays.asList(files)));
344         }
345 
346         String releaseVersion = config.getReleaseVersion();
347         setSourceVersion(buildConfig, releaseVersion == null ? config.getSourceVersion() : releaseVersion);
348         setTargetVersion(buildConfig, releaseVersion == null ? config.getTargetVersion() : releaseVersion);
349 
350         if (config.isDebug()) {
351             buildConfig.getOptions().produceDebugAttributes =
352                     ClassFileConstants.ATTR_SOURCE + ClassFileConstants.ATTR_LINES + ClassFileConstants.ATTR_VARS;
353         }
354 
355         Map<String, String> javaOpts = config.getCustomCompilerArgumentsAsMap();
356         if (javaOpts != null && !javaOpts.isEmpty()) {
357             // TODO support customCompilerArguments
358             // buildConfig.setJavaOptions( javaOpts );
359         }
360 
361         List<String> cp = new LinkedList<>(config.getClasspathEntries());
362 
363         File javaHomeDir = new File(System.getProperty("java.home"));
364         File[] jars = new File(javaHomeDir, "lib").listFiles();
365         if (jars != null) {
366             for (File jar : jars) {
367                 if (jar.getName().endsWith(".jar") || jar.getName().endsWith(".zip")) {
368                     cp.add(0, jar.getAbsolutePath());
369                 }
370             }
371         }
372         jars = new File(javaHomeDir, "../Classes").listFiles();
373         if (jars != null) {
374             for (File jar : jars) {
375                 if (jar.getName().endsWith(".jar") || jar.getName().endsWith(".zip")) {
376                     cp.add(0, jar.getAbsolutePath());
377                 }
378             }
379         }
380 
381         checkForAspectJRT(cp);
382         if (cp != null && !cp.isEmpty()) {
383             List<String> elements = new ArrayList<>(cp.size());
384             for (String path : cp) {
385                 elements.add((new File(path)).getAbsolutePath());
386             }
387 
388             buildConfig.setClasspath(elements);
389         }
390 
391         String outputLocation = config.getOutputLocation();
392         if (outputLocation != null) {
393             File outDir = new File(outputLocation);
394             if (!outDir.exists()) {
395                 outDir.mkdirs();
396             }
397 
398             buildConfig.setOutputDir(outDir);
399         }
400 
401         if (config instanceof AspectJCompilerConfiguration) {
402             AspectJCompilerConfiguration ajCfg = (AspectJCompilerConfiguration) config;
403 
404             Map<String, File> sourcePathResources = ajCfg.getSourcePathResources();
405             if (sourcePathResources != null && !sourcePathResources.isEmpty()) {
406                 buildConfig.setSourcePathResources(sourcePathResources);
407             }
408 
409             Map<String, String> ajOptions = ajCfg.getAJOptions();
410             if (ajOptions != null && !ajOptions.isEmpty()) {
411                 // TODO not supported
412                 // buildConfig.setAjOptions( ajCfg.getAJOptions() );
413             }
414 
415             List<File> aspectPath = buildFileList(ajCfg.getAspectPath());
416             if (aspectPath != null && !aspectPath.isEmpty()) {
417                 buildConfig.setAspectpath(buildFileList(ajCfg.getAspectPath()));
418             }
419 
420             List<File> inJars = buildFileList(ajCfg.getInJars());
421             if (inJars != null && !inJars.isEmpty()) {
422                 buildConfig.setInJars(buildFileList(ajCfg.getInJars()));
423             }
424 
425             List<File> inPaths = buildFileList(ajCfg.getInPath());
426             if (inPaths != null && !inPaths.isEmpty()) {
427                 buildConfig.setInPath(buildFileList(ajCfg.getInPath()));
428             }
429 
430             String outJar = ajCfg.getOutputJar();
431             if (outJar != null) {
432                 buildConfig.setOutputJar(new File(ajCfg.getOutputJar()));
433             }
434         }
435 
436         return buildConfig;
437     }
438 
439     private List<CompilerMessage> compileInProcess(AjBuildConfig buildConfig) throws CompilerException {
440 
441         MessageHandler messageHandler = new MessageHandler();
442 
443         AjBuildManager manager = new AjBuildManager(messageHandler);
444 
445         try {
446             manager.batchBuild(buildConfig, messageHandler);
447         } catch (AbortException | IOException e) {
448             throw new CompilerException("Unknown error while compiling", e);
449         }
450 
451         // We need the location of the maven so we have a couple of options
452         // here.
453         //
454         // The aspectjrt jar is something this component needs to function so we
455         // can either
456         // bake it into the plugin and retrieve it somehow or use a system
457         // property or we
458         // could pass in a set of parameters in a Map.
459 
460         boolean errors = messageHandler.hasAnyMessage(IMessage.ERROR, true);
461 
462         List<CompilerMessage> messages = new ArrayList<>();
463         if (errors) {
464             IMessage[] errorMessages = messageHandler.getMessages(IMessage.ERROR, true);
465 
466             for (IMessage m : errorMessages) {
467                 ISourceLocation sourceLocation = m.getSourceLocation();
468                 CompilerMessage error;
469 
470                 if (sourceLocation == null) {
471                     error = new CompilerMessage(m.getMessage(), true);
472                 } else {
473                     error = new CompilerMessage(
474                             sourceLocation.getSourceFile().getPath(),
475                             true,
476                             sourceLocation.getLine(),
477                             sourceLocation.getColumn(),
478                             sourceLocation.getEndLine(),
479                             sourceLocation.getColumn(),
480                             m.getMessage());
481                 }
482                 messages.add(error);
483             }
484         }
485 
486         return messages;
487     }
488 
489     private void checkForAspectJRT(List<String> cp) {
490         if (cp == null || cp.isEmpty()) {
491             throw new IllegalStateException("AspectJ Runtime not found in supplied classpath");
492         } else {
493             try {
494                 URL[] urls = new URL[cp.size()];
495                 for (int i = 0; i < urls.length; i++) {
496                     urls[i] = (new File(cp.get(i))).toURL();
497                 }
498 
499                 URLClassLoader cloader = new URLClassLoader(urls);
500 
501                 cloader.loadClass("org.aspectj.lang.JoinPoint");
502             } catch (MalformedURLException e) {
503                 throw new IllegalArgumentException("Invalid classpath entry");
504             } catch (ClassNotFoundException e) {
505                 throw new IllegalStateException("AspectJ Runtime not found in supplied classpath");
506             }
507         }
508     }
509 
510     private List<File> buildFileList(List<String> locations) {
511         List<File> fileList = new LinkedList<>();
512         for (String location : locations) {
513             fileList.add(new File(location));
514         }
515 
516         return fileList;
517     }
518 
519     /**
520      * Set the source version in AspectJ compiler
521      *
522      * @param buildConfig
523      * @param sourceVersion
524      */
525     private void setSourceVersion(AjBuildConfig buildConfig, String sourceVersion) throws CompilerException {
526         buildConfig.getOptions().sourceLevel = versionStringToMajorMinor(sourceVersion);
527     }
528 
529     /**
530      * Set the target version in AspectJ compiler
531      *
532      * @param buildConfig
533      * @param targetVersion
534      */
535     private void setTargetVersion(AjBuildConfig buildConfig, String targetVersion) throws CompilerException {
536         buildConfig.getOptions().targetJDK = versionStringToMajorMinor(targetVersion);
537     }
538 
539     private static long versionStringToMajorMinor(String version) throws CompilerException {
540         if (version == null) {
541             version = "";
542         }
543         // Note: We avoid using org.codehaus.plexus:plexus-java here on purpose, because Maven Compiler might depend on
544         // a different (older) versionm, e.g. not having the 'asMajor' method yet. This can cause problems for users
545         // trying to compile their AspectJ code using Plexus.
546 
547         version = version.trim()
548                 // Cut off leading "1.", focusing on the Java major
549                 .replaceFirst("^1[.]", "")
550                 // Accept, but cut off trailing ".0", as ECJ/ACJ explicitly support versions like 5.0, 8.0, 11.0
551                 .replaceFirst("[.]0$", "");
552 
553         switch (version) {
554                 // Java 1.6 as a default source/target seems to make sense. Maven Compiler should set its own default
555                 // anyway, so this probably never needs to be used. But not having a default feels bad, too.
556             case "":
557                 return ClassFileConstants.JDK1_6;
558             case "1":
559                 return ClassFileConstants.JDK1_1;
560             case "2":
561                 return ClassFileConstants.JDK1_2;
562             case "3":
563                 return ClassFileConstants.JDK1_3;
564             case "4":
565                 return ClassFileConstants.JDK1_4;
566             case "5":
567                 return ClassFileConstants.JDK1_5;
568             case "6":
569                 return ClassFileConstants.JDK1_6;
570             case "7":
571                 return ClassFileConstants.JDK1_7;
572             case "8":
573                 return ClassFileConstants.JDK1_8;
574             case "9":
575                 return ClassFileConstants.JDK9;
576             case "10":
577                 return ClassFileConstants.JDK10;
578             case "11":
579                 return ClassFileConstants.JDK11;
580             case "12":
581                 return ClassFileConstants.JDK12;
582             case "13":
583                 return ClassFileConstants.JDK13;
584             case "14":
585                 return ClassFileConstants.JDK14;
586             case "15":
587                 return ClassFileConstants.JDK15;
588             case "16":
589                 return ClassFileConstants.JDK16;
590         }
591         throw new CompilerException("Unknown Java source/target version number: " + version);
592     }
593 
594     /**
595      * @return null
596      */
597     public String[] createCommandLine(CompilerConfiguration config) throws CompilerException {
598         return null;
599     }
600 
601     protected static String[] getSourceFiles(CompilerConfiguration config) {
602         Set<String> sources = new HashSet<>();
603 
604         Set<File> sourceFiles = config.getSourceFiles();
605 
606         if (sourceFiles != null && !sourceFiles.isEmpty()) {
607             for (File sourceFile : sourceFiles) {
608                 if (sourceFile.getName().endsWith(".java")
609                         || sourceFile.getName().endsWith(".aj")) {
610                     sources.add(sourceFile.getAbsolutePath());
611                 }
612             }
613         } else {
614             for (String sourceLocation : config.getSourceLocations()) {
615                 sources.addAll(getSourceFilesForSourceRoot(config, sourceLocation));
616             }
617         }
618 
619         String[] result;
620 
621         if (sources.isEmpty()) {
622             result = new String[0];
623         } else {
624             result = sources.toArray(new String[sources.size()]);
625         }
626 
627         return result;
628     }
629 
630     protected static Set<String> getSourceFilesForSourceRoot(CompilerConfiguration config, String sourceLocation) {
631         DirectoryScanner scanner = new DirectoryScanner();
632 
633         scanner.setBasedir(sourceLocation);
634 
635         Set<String> includes = config.getIncludes();
636 
637         if (includes != null && !includes.isEmpty()) {
638             String[] inclStrs = includes.toArray(new String[includes.size()]);
639             scanner.setIncludes(inclStrs);
640         } else {
641             scanner.setIncludes(new String[] {"**/*.java", "**/*.aj"});
642         }
643 
644         Set<String> excludes = config.getExcludes();
645 
646         if (excludes != null && !excludes.isEmpty()) {
647             String[] exclStrs = excludes.toArray(new String[excludes.size()]);
648             scanner.setExcludes(exclStrs);
649         }
650 
651         scanner.scan();
652 
653         String[] sourceDirectorySources = scanner.getIncludedFiles();
654 
655         Set<String> sources = new HashSet<>();
656 
657         for (String sourceDirectorySource : sourceDirectorySources) {
658             File f = new File(sourceLocation, sourceDirectorySource);
659 
660             sources.add(f.getPath());
661         }
662 
663         return sources;
664     }
665 }