View Javadoc
1   package org.codehaus.plexus.compiler.eclipse;
2   
3   /**
4    * The MIT License
5    *
6    * Copyright (c) 2005, The Codehaus
7    *
8    * Permission is hereby granted, free of charge, to any person obtaining a copy of
9    * this software and associated documentation files (the "Software"), to deal in
10   * the Software without restriction, including without limitation the rights to
11   * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12   * of the Software, and to permit persons to whom the Software is furnished to do
13   * so, subject to the following conditions:
14   *
15   * The above copyright notice and this permission notice shall be included in all
16   * copies or substantial portions of the Software.
17   *
18   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24   * SOFTWARE.
25   */
26  
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.FileUtils;
34  import org.codehaus.plexus.util.IOUtil;
35  import org.codehaus.plexus.util.StringUtils;
36  import org.eclipse.jdt.core.compiler.IProblem;
37  import org.eclipse.jdt.internal.compiler.ClassFile;
38  import org.eclipse.jdt.internal.compiler.CompilationResult;
39  import org.eclipse.jdt.internal.compiler.Compiler;
40  import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
41  import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
42  import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
43  import org.eclipse.jdt.internal.compiler.IProblemFactory;
44  import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
45  import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
46  import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
47  import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
48  import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
49  import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
50  import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
51  
52  import java.io.BufferedInputStream;
53  import java.io.File;
54  import java.io.FileInputStream;
55  import java.io.FileNotFoundException;
56  import java.io.FileOutputStream;
57  import java.io.IOException;
58  import java.io.InputStream;
59  import java.net.MalformedURLException;
60  import java.net.URL;
61  import java.net.URLClassLoader;
62  import java.util.ArrayList;
63  import java.util.HashMap;
64  import java.util.Iterator;
65  import java.util.LinkedList;
66  import java.util.List;
67  import java.util.Locale;
68  import java.util.LinkedHashMap;
69  import java.util.Map;
70  import java.util.Properties;
71  import java.util.Set;
72  import java.util.StringTokenizer;
73  
74  /**
75   * @plexus.component role="org.codehaus.plexus.compiler.Compiler" role-hint="eclipse"
76   */
77  public class EclipseJavaCompiler
78      extends AbstractCompiler
79  {
80      public EclipseJavaCompiler()
81      {
82          super( CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE, ".java", ".class", null );
83      }
84  
85      // ----------------------------------------------------------------------
86      // Compiler Implementation
87      // ----------------------------------------------------------------------
88      boolean errorsAsWarnings = false;
89  
90      public CompilerResult performCompile( CompilerConfiguration config )
91          throws CompilerException
92      {
93          List<CompilerMessage> errors = new LinkedList<CompilerMessage>();
94  
95          List<String> classpathEntries = config.getClasspathEntries();
96  
97          URL[] urls = new URL[1 + classpathEntries.size()];
98  
99          int i = 0;
100 
101         try
102         {
103             urls[i++] = new File( config.getOutputLocation() ).toURL();
104 
105             for ( String entry : classpathEntries )
106             {
107                 urls[i++] = new File( entry ).toURL();
108             }
109         }
110         catch ( MalformedURLException e )
111         {
112             throw new CompilerException( "Error while converting the classpath entries to URLs.", e );
113         }
114 
115         ClassLoader classLoader = new URLClassLoader( urls );
116 
117         SourceCodeLocator sourceCodeLocator = new SourceCodeLocator( config.getSourceLocations() );
118 
119         INameEnvironment env = new EclipseCompilerINameEnvironment( sourceCodeLocator, classLoader, errors );
120 
121         IErrorHandlingPolicy policy = DefaultErrorHandlingPolicies.proceedWithAllProblems();
122 
123         // ----------------------------------------------------------------------
124         // Build settings from configuration
125         // ----------------------------------------------------------------------
126 
127         Map<String, String> settings = new HashMap<String, String>();
128 
129         if ( config.isDebug() )
130         {
131             settings.put( CompilerOptions.OPTION_LocalVariableAttribute, CompilerOptions.GENERATE );
132             settings.put( CompilerOptions.OPTION_LineNumberAttribute, CompilerOptions.GENERATE );
133             settings.put( CompilerOptions.OPTION_SourceFileAttribute, CompilerOptions.GENERATE );
134         }
135 
136         if ( !config.isShowWarnings() )
137         {
138             Map opts = new CompilerOptions().getMap();
139             for (Object optKey : opts.keySet()) {
140                 if (opts.get(optKey).equals(CompilerOptions.WARNING)) {
141                     settings.put((String) optKey, CompilerOptions.IGNORE);
142                 }
143             }
144         }
145 
146         String sourceVersion = decodeVersion( config.getSourceVersion() );
147 
148         if ( sourceVersion != null )
149         {
150             settings.put( CompilerOptions.OPTION_Source, sourceVersion );
151         }
152 
153         String targetVersion = decodeVersion( config.getTargetVersion() );
154 
155         if ( targetVersion != null )
156         {
157             settings.put( CompilerOptions.OPTION_TargetPlatform, targetVersion );
158             settings.put( CompilerOptions.OPTION_Compliance, targetVersion );
159         }
160 
161         if ( StringUtils.isNotEmpty( config.getSourceEncoding() ) )
162         {
163             settings.put( CompilerOptions.OPTION_Encoding, config.getSourceEncoding() );
164         }
165 
166         if ( config.isShowDeprecation() )
167         {
168             settings.put( CompilerOptions.OPTION_ReportDeprecation, CompilerOptions.WARNING );
169         }
170         else
171         {
172             settings.put( CompilerOptions.OPTION_ReportDeprecation, CompilerOptions.IGNORE );
173         }
174 
175         if ( config.isParameters() )
176         {
177             settings.put( CompilerOptions.OPTION_MethodParametersAttribute, CompilerOptions.GENERATE );
178         }
179 
180         // ----------------------------------------------------------------------
181         // Set Eclipse-specific options
182         // ----------------------------------------------------------------------
183 
184         settings.put( CompilerOptions.OPTION_LineNumberAttribute, CompilerOptions.GENERATE );
185         settings.put( CompilerOptions.OPTION_SourceFileAttribute, CompilerOptions.GENERATE );
186 
187         // compiler-specific extra options override anything else in the config object...
188         Map<String, String> extras = cleanKeyNames( config.getCustomCompilerArgumentsAsMap() );
189         if( extras.containsKey( "errorsAsWarnings" ) )
190         {
191         	extras.remove( "errorsAsWarnings" );
192         	this.errorsAsWarnings = true;
193         }
194 
195         settings.putAll( extras );
196 
197         if ( settings.containsKey( "properties" ) )
198         {
199             initializeWarnings( settings.get( "properties" ), settings );
200             settings.remove( "properties" );
201         }
202 
203         IProblemFactory problemFactory = new DefaultProblemFactory( Locale.getDefault() );
204 
205         ICompilerRequestor requestor = new EclipseCompilerICompilerRequestor( config.getOutputLocation(), errors );
206 
207         List<CompilationUnit> compilationUnits = new ArrayList<CompilationUnit>();
208 
209         for ( String sourceRoot : config.getSourceLocations() )
210         {
211             // annotations directory does not always exist and the below scanner fails on non existing directories
212             File potentialSourceDirectory = new File( sourceRoot );
213             if ( potentialSourceDirectory.exists() )
214             {
215                 Set<String> sources = getSourceFilesForSourceRoot( config, sourceRoot );
216 
217                 for ( String source : sources )
218                 {
219                     CompilationUnit unit = new CompilationUnit( source, makeClassName( source, sourceRoot ), errors,
220                                                                 config.getSourceEncoding() );
221 
222                     compilationUnits.add( unit );
223                 }
224             }
225         }
226 
227         // ----------------------------------------------------------------------
228         // Compile!
229         // ----------------------------------------------------------------------
230 
231         CompilerOptions options = new CompilerOptions( settings );
232         Compiler compiler = new Compiler( env, policy, options, requestor, problemFactory );
233 
234         ICompilationUnit[] units = compilationUnits.toArray( new ICompilationUnit[compilationUnits.size()] );
235 
236         compiler.compile( units );
237 
238         CompilerResult compilerResult = new CompilerResult().compilerMessages( errors );
239 
240         for ( CompilerMessage compilerMessage : errors )
241         {
242             if ( compilerMessage.isError() )
243             {
244                 compilerResult.setSuccess( false );
245                 continue;
246             }
247         }
248 
249         return compilerResult;
250     }
251 
252     // The compiler mojo adds a dash to all keys which does not make sense for the eclipse compiler
253     Map<String, String> cleanKeyNames( Map<String, String> customCompilerArgumentsAsMap )
254     {
255         LinkedHashMap<String, String> cleanedMap = new LinkedHashMap<String, String>();
256 
257         for ( Map.Entry<String, String> entry : customCompilerArgumentsAsMap.entrySet() )
258         {
259             String key = entry.getKey();
260             if ( key.startsWith( "-" ) )
261             {
262                 key = key.substring( 1 );
263             }
264             cleanedMap.put( key, entry.getValue() );
265         }
266 
267         return cleanedMap;
268     }
269 
270     public String[] createCommandLine( CompilerConfiguration config )
271         throws CompilerException
272     {
273         return null;
274     }
275 
276     private CompilerMessage handleError( String className, int line, int column, Object errorMessage )
277     {
278         if ( className.endsWith( ".java" ) )
279         {
280             className = className.substring( 0, className.lastIndexOf( '.' ) );
281         }
282         String fileName = className.replace( '.', File.separatorChar ) + ".java";
283 
284         if ( column < 0 )
285         {
286             column = 0;
287         }
288 
289         String message;
290 
291         if ( errorMessage != null )
292         {
293             message = errorMessage.toString();
294         }
295         else
296         {
297             message = "No message";
298         }
299 
300         return new CompilerMessage( fileName, CompilerMessage.Kind.ERROR, line, column, line, column, message );
301 
302     }
303 
304     private CompilerMessage handleWarning( String fileName, IProblem warning )
305     {
306         return new CompilerMessage( fileName, CompilerMessage.Kind.WARNING,
307                                     warning.getSourceLineNumber(), warning.getSourceStart(),
308                                     warning.getSourceLineNumber(), warning.getSourceEnd(), warning.getMessage() );
309     }
310 
311     private String decodeVersion( String versionSpec )
312     {
313         if ( StringUtils.isEmpty( versionSpec ) )
314         {
315             return null;
316         }
317         else if ( "1.1".equals( versionSpec ) )
318         {
319             return CompilerOptions.VERSION_1_1;
320         }
321         else if ( "1.2".equals( versionSpec ) )
322         {
323             return CompilerOptions.VERSION_1_2;
324         }
325         else if ( "1.3".equals( versionSpec ) )
326         {
327             return CompilerOptions.VERSION_1_3;
328         }
329         else if ( "1.4".equals( versionSpec ) )
330         {
331             return CompilerOptions.VERSION_1_4;
332         }
333         else if ( "1.5".equals( versionSpec ) )
334         {
335             return CompilerOptions.VERSION_1_5;
336         }
337         else if ( "1.6".equals( versionSpec ) )
338         {
339             return CompilerOptions.VERSION_1_6;
340         }
341         else if ( "1.7".equals( versionSpec ) )
342         {
343             return CompilerOptions.VERSION_1_7;
344         }
345         else if ( "1.8".equals( versionSpec ) )
346         {
347             return CompilerOptions.VERSION_1_8;
348         }
349         else if ( "9".equals( versionSpec ) )
350         {
351         	return CompilerOptions.VERSION_9;
352         }
353         else
354         {
355             getLogger().warn(
356                 "Unknown version '" + versionSpec + "', no version setting will be given to the compiler." );
357 
358             return null;
359         }
360     }
361 
362     // ----------------------------------------------------------------------
363     // Classes
364     // ----------------------------------------------------------------------
365 
366     private class CompilationUnit
367         implements ICompilationUnit
368     {
369         private final String className;
370 
371         private final String sourceFile;
372 
373         private final String sourceEncoding;
374 
375         private final List<CompilerMessage> errors;
376 
377         CompilationUnit( String sourceFile, String className, List<CompilerMessage> errors )
378         {
379             this( sourceFile, className, errors, null );
380         }
381 
382         CompilationUnit( String sourceFile, String className, List<CompilerMessage> errors, String sourceEncoding )
383         {
384             this.className = className;
385             this.sourceFile = sourceFile;
386             this.errors = errors;
387             this.sourceEncoding = sourceEncoding;
388         }
389 
390         public char[] getFileName()
391         {
392             String fileName = sourceFile;
393 
394             int lastSeparator = fileName.lastIndexOf( File.separatorChar );
395 
396             if ( lastSeparator > 0 )
397             {
398                 fileName = fileName.substring( lastSeparator + 1 );
399             }
400 
401             return fileName.toCharArray();
402         }
403 
404         String getAbsolutePath()
405         {
406             return sourceFile;
407         }
408 
409         public char[] getContents()
410         {
411             try
412             {
413                 return FileUtils.fileRead( sourceFile, sourceEncoding ).toCharArray();
414             }
415             catch ( FileNotFoundException e )
416             {
417                 errors.add( handleError( className, -1, -1, e.getMessage() ) );
418 
419                 return null;
420             }
421             catch ( IOException e )
422             {
423                 errors.add( handleError( className, -1, -1, e.getMessage() ) );
424 
425                 return null;
426             }
427         }
428 
429         public char[] getMainTypeName()
430         {
431             int dot = className.lastIndexOf( '.' );
432 
433             if ( dot > 0 )
434             {
435                 return className.substring( dot + 1 ).toCharArray();
436             }
437 
438             return className.toCharArray();
439         }
440 
441         public char[][] getPackageName()
442         {
443             StringTokenizer izer = new StringTokenizer( className, "." );
444 
445             char[][] result = new char[izer.countTokens() - 1][];
446 
447             for ( int i = 0; i < result.length; i++ )
448             {
449                 String tok = izer.nextToken();
450 
451                 result[i] = tok.toCharArray();
452             }
453 
454             return result;
455         }
456 
457         public boolean ignoreOptionalProblems()
458         {
459             return false;
460         }
461     }
462 
463     private class EclipseCompilerINameEnvironment
464         implements INameEnvironment
465     {
466         private SourceCodeLocator sourceCodeLocator;
467 
468         private ClassLoader classLoader;
469 
470         private List<CompilerMessage> errors;
471 
472         public EclipseCompilerINameEnvironment( SourceCodeLocator sourceCodeLocator, ClassLoader classLoader,
473                                                 List<CompilerMessage> errors )
474         {
475             this.sourceCodeLocator = sourceCodeLocator;
476             this.classLoader = classLoader;
477             this.errors = errors;
478         }
479 
480         public NameEnvironmentAnswer findType( char[][] compoundTypeName )
481         {
482             String result = "";
483 
484             String sep = "";
485 
486             for ( int i = 0; i < compoundTypeName.length; i++ )
487             {
488                 result += sep;
489                 result += new String( compoundTypeName[i] );
490                 sep = ".";
491             }
492 
493             return findType( result );
494         }
495 
496         public NameEnvironmentAnswer findType( char[] typeName, char[][] packageName )
497         {
498             String result = "";
499 
500             String sep = "";
501 
502             for ( int i = 0; i < packageName.length; i++ )
503             {
504                 result += sep;
505                 result += new String( packageName[i] );
506                 sep = ".";
507             }
508 
509             result += sep;
510             result += new String( typeName );
511             return findType( result );
512         }
513 
514         private NameEnvironmentAnswer findType( String className )
515         {
516             try
517             {
518                 File f = sourceCodeLocator.findSourceCodeForClass( className );
519 
520                 if ( f != null )
521                 {
522                     ICompilationUnit compilationUnit = new CompilationUnit( f.getAbsolutePath(), className, errors );
523 
524                     return new NameEnvironmentAnswer( compilationUnit, null );
525                 }
526 
527                 String resourceName = className.replace( '.', '/' ) + ".class";
528 
529                 InputStream is = classLoader.getResourceAsStream( resourceName );
530 
531                 if ( is == null )
532                 {
533                     return null;
534                 }
535 
536                 byte[] classBytes = IOUtil.toByteArray( is );
537 
538                 char[] fileName = className.toCharArray();
539 
540                 ClassFileReader classFileReader = new ClassFileReader( classBytes, fileName, true );
541 
542                 return new NameEnvironmentAnswer( classFileReader, null );
543             }
544             catch ( IOException e )
545             {
546                 errors.add( handleError( className, -1, -1, e.getMessage() ) );
547 
548                 return null;
549             }
550             catch ( ClassFormatException e )
551             {
552                 errors.add( handleError( className, -1, -1, e.getMessage() ) );
553 
554                 return null;
555             }
556         }
557 
558         private boolean isPackage( String result )
559         {
560             if ( sourceCodeLocator.findSourceCodeForClass( result ) != null )
561             {
562                 return false;
563             }
564 
565             String resourceName = "/" + result.replace( '.', '/' ) + ".class";
566 
567             InputStream is = classLoader.getResourceAsStream( resourceName );
568 
569             return is == null;
570         }
571 
572         public boolean isPackage( char[][] parentPackageName, char[] packageName )
573         {
574             String result = "";
575 
576             String sep = "";
577 
578             if ( parentPackageName != null )
579             {
580                 for ( int i = 0; i < parentPackageName.length; i++ )
581                 {
582                     result += sep;
583                     result += new String( parentPackageName[i] );
584                     sep = ".";
585                 }
586             }
587 
588             if ( Character.isUpperCase( packageName[0] ) )
589             {
590                 return false;
591             }
592 
593             String str = new String( packageName );
594 
595             result += sep;
596 
597             result += str;
598 
599             return isPackage( result );
600         }
601 
602         public void cleanup()
603         {
604             // nothing to do
605         }
606     }
607 
608     private class EclipseCompilerICompilerRequestor
609         implements ICompilerRequestor
610     {
611         private String destinationDirectory;
612 
613         private List<CompilerMessage> errors;
614 
615         public EclipseCompilerICompilerRequestor( String destinationDirectory, List<CompilerMessage> errors )
616         {
617             this.destinationDirectory = destinationDirectory;
618             this.errors = errors;
619         }
620 
621         public void acceptResult( CompilationResult result )
622         {
623             boolean hasErrors = false;
624 
625             if ( result.hasProblems() )
626             {
627                 IProblem[] problems = result.getProblems();
628 
629                 for ( IProblem problem : problems )
630                 {
631                     String name = getFileName( result.getCompilationUnit(), problem.getOriginatingFileName() );
632 
633                     if ( problem.isWarning() )
634                     {
635                         errors.add( handleWarning( name, problem ) );
636                     }
637                     else
638                     {
639                     	if( errorsAsWarnings )
640                     	{
641                     		errors.add( handleWarning( name, problem ) );
642                     	}
643                     	else
644                     	{
645                     		hasErrors = true;
646                     		errors.add( handleError( name, problem.getSourceLineNumber(), -1, problem.getMessage() ) );
647                     	}
648                     }
649                 }
650             }
651 
652             if ( !hasErrors )
653             {
654                 ClassFile[] classFiles = result.getClassFiles();
655 
656                 for ( ClassFile classFile : classFiles )
657                 {
658                     char[][] compoundName = classFile.getCompoundName();
659                     String className = "";
660                     String sep = "";
661 
662                     for ( int j = 0; j < compoundName.length; j++ )
663                     {
664                         className += sep;
665                         className += new String( compoundName[j] );
666                         sep = ".";
667                     }
668 
669                     byte[] bytes = classFile.getBytes();
670 
671                     File outFile = new File( destinationDirectory, className.replace( '.', '/' ) + ".class" );
672 
673                     if ( !outFile.getParentFile().exists() )
674                     {
675                         outFile.getParentFile().mkdirs();
676                     }
677 
678                     FileOutputStream fout = null;
679 
680                     try
681                     {
682                         fout = new FileOutputStream( outFile );
683 
684                         fout.write( bytes );
685                     }
686                     catch ( FileNotFoundException e )
687                     {
688                         errors.add( handleError( className, -1, -1, e.getMessage() ) );
689                     }
690                     catch ( IOException e )
691                     {
692                         errors.add( handleError( className, -1, -1, e.getMessage() ) );
693                     }
694                     finally
695                     {
696                         IOUtil.close( fout );
697                     }
698                 }
699             }
700         }
701 
702         private String getFileName( ICompilationUnit compilationUnit, char[] originalFileName )
703         {
704             if ( compilationUnit instanceof CompilationUnit )
705             {
706                 return ( (CompilationUnit) compilationUnit ).getAbsolutePath();
707             }
708             else
709             {
710                 return String.valueOf( originalFileName );
711             }
712         }
713     }
714 
715     private void initializeWarnings( String propertiesFile, Map<String, String> setting )
716     {
717         File file = new File( propertiesFile );
718         if ( !file.exists() )
719         {
720             throw new IllegalArgumentException( "Properties file not exist" );
721         }
722         BufferedInputStream stream = null;
723         Properties properties = null;
724         try
725         {
726             stream = new BufferedInputStream( new FileInputStream( propertiesFile ) );
727             properties = new Properties();
728             properties.load( stream );
729         }
730         catch ( IOException e )
731         {
732             throw new IllegalArgumentException( "Properties file load error" );
733         }
734         finally
735         {
736             if ( stream != null )
737             {
738                 try
739                 {
740                     stream.close();
741                 }
742                 catch ( IOException e )
743                 {
744                     // ignore
745                 }
746             }
747         }
748         for ( Iterator iterator = properties.entrySet().iterator(); iterator.hasNext(); )
749         {
750             Map.Entry entry = (Map.Entry) iterator.next();
751             final String key = (String) entry.getKey();
752             setting.put( key, entry.getValue().toString() );
753         }
754     }
755 }