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