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