View Javadoc
1   package org.codehaus.plexus.compiler.javac;
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  /**
28   *
29   * Copyright 2004 The Apache Software Foundation
30   *
31   *  Licensed under the Apache License, Version 2.0 (the "License");
32   *  you may not use this file except in compliance with the License.
33   *  You may obtain a copy of the License at
34   *
35   *     http://www.apache.org/licenses/LICENSE-2.0
36   *
37   *  Unless required by applicable law or agreed to in writing, software
38   *  distributed under the License is distributed on an "AS IS" BASIS,
39   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
40   *  See the License for the specific language governing permissions and
41   *  limitations under the License.
42   */
43  
44  import java.io.BufferedReader;
45  import java.io.File;
46  import java.io.FileWriter;
47  import java.io.IOException;
48  import java.io.PrintWriter;
49  import java.io.StringReader;
50  import java.io.StringWriter;
51  import java.lang.reflect.InvocationTargetException;
52  import java.lang.reflect.Method;
53  import java.net.MalformedURLException;
54  import java.net.URL;
55  import java.net.URLClassLoader;
56  import java.util.ArrayList;
57  import java.util.Arrays;
58  import java.util.List;
59  import java.util.Map;
60  import java.util.NoSuchElementException;
61  import java.util.Properties;
62  import java.util.StringTokenizer;
63  import java.util.concurrent.CopyOnWriteArrayList;
64  
65  import org.codehaus.plexus.compiler.AbstractCompiler;
66  import org.codehaus.plexus.compiler.CompilerConfiguration;
67  import org.codehaus.plexus.compiler.CompilerException;
68  import org.codehaus.plexus.compiler.CompilerMessage;
69  import org.codehaus.plexus.compiler.CompilerOutputStyle;
70  import org.codehaus.plexus.compiler.CompilerResult;
71  import org.codehaus.plexus.util.FileUtils;
72  import org.codehaus.plexus.util.Os;
73  import org.codehaus.plexus.util.StringUtils;
74  import org.codehaus.plexus.util.cli.CommandLineException;
75  import org.codehaus.plexus.util.cli.CommandLineUtils;
76  import org.codehaus.plexus.util.cli.Commandline;
77  
78  /**
79   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
80   * @author <a href="mailto:matthew.pocock@ncl.ac.uk">Matthew Pocock</a>
81   * @author <a href="mailto:joerg.wassmer@web.de">J&ouml;rg Wa&szlig;mer</a>
82   * @author Others
83   * @plexus.component role="org.codehaus.plexus.compiler.Compiler"
84   * role-hint="javac"
85   */
86  public class JavacCompiler
87      extends AbstractCompiler
88  {
89  
90      // see compiler.warn.warning in compiler.properties of javac sources
91      private static final String[] WARNING_PREFIXES = { "warning: ", "\u8b66\u544a: ", "\u8b66\u544a\uff1a " };
92  
93      // see compiler.note.note in compiler.properties of javac sources
94      private static final String[] NOTE_PREFIXES = { "Note: ", "\u6ce8: ", "\u6ce8\u610f\uff1a " };
95  
96      // see compiler.misc.verbose in compiler.properties of javac sources
97      private static final String[] MISC_PREFIXES = { "[" };
98  
99      private static final Object LOCK = new Object();
100 
101     private static final String JAVAC_CLASSNAME = "com.sun.tools.javac.Main";
102 
103     private static volatile Class<?> JAVAC_CLASS;
104 
105     private List<Class<?>> javaccClasses = new CopyOnWriteArrayList<Class<?>>();
106 
107     // ----------------------------------------------------------------------
108     //
109     // ----------------------------------------------------------------------
110 
111     public JavacCompiler()
112     {
113         super( CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE, ".java", ".class", null );
114     }
115 
116     // ----------------------------------------------------------------------
117     // Compiler Implementation
118     // ----------------------------------------------------------------------
119 
120     @Override
121     public CompilerResult performCompile( CompilerConfiguration config )
122         throws CompilerException
123     {
124         File destinationDir = new File( config.getOutputLocation() );
125 
126         if ( !destinationDir.exists() )
127         {
128             destinationDir.mkdirs();
129         }
130 
131         String[] sourceFiles = getSourceFiles( config );
132 
133         if ( ( sourceFiles == null ) || ( sourceFiles.length == 0 ) )
134         {
135             return new CompilerResult();
136         }
137 
138         if ( ( getLogger() != null ) && getLogger().isInfoEnabled() )
139         {
140             getLogger().info( "Compiling " + sourceFiles.length + " " +
141                                   "source file" + ( sourceFiles.length == 1 ? "" : "s" ) +
142                                   " to " + destinationDir.getAbsolutePath() );
143         }
144 
145         String[] args = buildCompilerArguments( config, sourceFiles );
146 
147         CompilerResult result;
148 
149         if ( config.isFork() )
150         {
151             String executable = config.getExecutable();
152 
153             if ( StringUtils.isEmpty( executable ) )
154             {
155                 try
156                 {
157                     executable = getJavacExecutable();
158                 }
159                 catch ( IOException e )
160                 {
161                     getLogger().warn( "Unable to autodetect 'javac' path, using 'javac' from the environment." );
162                     executable = "javac";
163                 }
164             }
165 
166             result = compileOutOfProcess( config, executable, args );
167         }
168         else
169         {
170             if ( isJava16() && !config.isForceJavacCompilerUse() )
171             {
172                 // use fqcn to prevent loading of the class on 1.5 environment !
173                 result =
174                     org.codehaus.plexus.compiler.javac.JavaxToolsCompiler.compileInProcess( args, config, sourceFiles );
175             }
176             else
177             {
178                 result = compileInProcess( args, config );
179             }
180         }
181 
182         return result;
183     }
184 
185     protected static boolean isJava16()
186     {
187         try
188         {
189             Thread.currentThread().getContextClassLoader().loadClass( "javax.tools.ToolProvider" );
190             return true;
191         }
192         catch ( Exception e )
193         {
194             return false;
195         }
196     }
197 
198     public String[] createCommandLine( CompilerConfiguration config )
199         throws CompilerException
200     {
201         return buildCompilerArguments( config, getSourceFiles( config ) );
202     }
203 
204     public static String[] buildCompilerArguments( CompilerConfiguration config, String[] sourceFiles )
205     {
206         List<String> args = new ArrayList<String>();
207 
208         // ----------------------------------------------------------------------
209         // Set output
210         // ----------------------------------------------------------------------
211 
212         File destinationDir = new File( config.getOutputLocation() );
213 
214         args.add( "-d" );
215 
216         args.add( destinationDir.getAbsolutePath() );
217 
218         // ----------------------------------------------------------------------
219         // Set the class and source paths
220         // ----------------------------------------------------------------------
221 
222         List<String> classpathEntries = config.getClasspathEntries();
223         if ( classpathEntries != null && !classpathEntries.isEmpty() )
224         {
225             args.add( "-classpath" );
226 
227             args.add( getPathString( classpathEntries ) );
228         }
229         
230         List<String> modulepathEntries = config.getModulepathEntries();
231         if ( modulepathEntries != null && !modulepathEntries.isEmpty() )
232         {
233             args.add( "--module-path" );
234 
235             args.add( getPathString( modulepathEntries ) );
236         }
237 
238         List<String> sourceLocations = config.getSourceLocations();
239         if ( sourceLocations != null && !sourceLocations.isEmpty() )
240         {
241             //always pass source path, even if sourceFiles are declared,
242             //needed for jsr269 annotation processing, see MCOMPILER-98
243             args.add( "-sourcepath" );
244 
245             args.add( getPathString( sourceLocations ) );
246         }
247         if ( !isJava16() || config.isForceJavacCompilerUse() || config.isFork() )
248         {
249             args.addAll( Arrays.asList( sourceFiles ) );
250         }
251 
252         if ( !isPreJava16( config ) )
253         {
254             //now add jdk 1.6 annotation processing related parameters
255 
256             if ( config.getGeneratedSourcesDirectory() != null )
257             {
258                 config.getGeneratedSourcesDirectory().mkdirs();
259 
260                 args.add( "-s" );
261                 args.add( config.getGeneratedSourcesDirectory().getAbsolutePath() );
262             }
263             if ( config.getProc() != null )
264             {
265                 args.add( "-proc:" + config.getProc() );
266             }
267             if ( config.getAnnotationProcessors() != null )
268             {
269                 args.add( "-processor" );
270                 String[] procs = config.getAnnotationProcessors();
271                 StringBuilder buffer = new StringBuilder();
272                 for ( int i = 0; i < procs.length; i++ )
273                 {
274                     if ( i > 0 )
275                     {
276                         buffer.append( "," );
277                     }
278 
279                     buffer.append( procs[i] );
280                 }
281                 args.add( buffer.toString() );
282             }
283             if ( config.getProcessorPathEntries() != null && !config.getProcessorPathEntries().isEmpty() ) {
284                 args.add( "-processorpath" );
285 
286                 args.add( getPathString( config.getProcessorPathEntries() ) );
287             }
288         }
289 
290         if ( config.isOptimize() )
291         {
292             args.add( "-O" );
293         }
294 
295         if ( config.isDebug() )
296         {
297             if ( StringUtils.isNotEmpty( config.getDebugLevel() ) )
298             {
299                 args.add( "-g:" + config.getDebugLevel() );
300             }
301             else
302             {
303                 args.add( "-g" );
304             }
305         }
306 
307         if ( config.isVerbose() )
308         {
309             args.add( "-verbose" );
310         }
311 
312         if ( !isPreJava18(config) && config.isParameters() )
313         {
314             args.add( "-parameters" );
315         }
316 
317         if ( config.isShowDeprecation() )
318         {
319             args.add( "-deprecation" );
320 
321             // This is required to actually display the deprecation messages
322             config.setShowWarnings( true );
323         }
324 
325         if ( !config.isShowWarnings() )
326         {
327             args.add( "-nowarn" );
328         }
329         
330         if ( config.isFailOnWarning() )
331         {
332             args.add( "-Werror" );
333         }
334 
335         if ( !StringUtils.isEmpty( config.getReleaseVersion() ) )
336         {
337             args.add( "--release" );
338             args.add( config.getReleaseVersion() );
339         }
340         else
341         {
342             // TODO: this could be much improved
343             if ( StringUtils.isEmpty( config.getTargetVersion() ) )
344             {
345                 // Required, or it defaults to the target of your JDK (eg 1.5)
346                 args.add( "-target" );
347                 args.add( "1.1" );
348             }
349             else
350             {
351                 args.add( "-target" );
352                 args.add( config.getTargetVersion() );
353             }
354 
355             if ( !suppressSource( config ) && StringUtils.isEmpty( config.getSourceVersion() ) )
356             {
357                 // If omitted, later JDKs complain about a 1.1 target
358                 args.add( "-source" );
359                 args.add( "1.3" );
360             }
361             else if ( !suppressSource( config ) )
362             {
363                 args.add( "-source" );
364                 args.add( config.getSourceVersion() );
365             }            
366         }
367 
368 
369         if ( !suppressEncoding( config ) && !StringUtils.isEmpty( config.getSourceEncoding() ) )
370         {
371             args.add( "-encoding" );
372             args.add( config.getSourceEncoding() );
373         }
374 
375         if ( !StringUtils.isEmpty( config.getModuleVersion() ) )
376         {
377             args.add( "--module-version" );
378             args.add( config.getModuleVersion() );
379         }
380 
381         for ( Map.Entry<String, String> entry : config.getCustomCompilerArgumentsEntries() )
382         {
383             String key = entry.getKey();
384 
385             if ( StringUtils.isEmpty( key ) || key.startsWith( "-J" ) )
386             {
387                 continue;
388             }
389 
390             args.add( key );
391 
392             String value = entry.getValue();
393 
394             if ( StringUtils.isEmpty( value ) )
395             {
396                 continue;
397             }
398 
399             args.add( value );
400         }
401 
402         return args.toArray( new String[args.size()] );
403     }
404 
405     /**
406      * Determine if the compiler is a version prior to 1.4.
407      * This is needed as 1.3 and earlier did not support -source or -encoding parameters
408      *
409      * @param config The compiler configuration to test.
410      * @return true if the compiler configuration represents a Java 1.4 compiler or later, false otherwise
411      */
412     private static boolean isPreJava14( CompilerConfiguration config )
413     {
414         String v = config.getCompilerVersion();
415 
416         if ( v == null )
417         {
418             return false;
419         }
420 
421         return v.startsWith( "1.3" ) || v.startsWith( "1.2" ) || v.startsWith( "1.1" ) || v.startsWith( "1.0" );
422     }
423 
424     /**
425      * Determine if the compiler is a version prior to 1.6.
426      * This is needed for annotation processing parameters.
427      *
428      * @param config The compiler configuration to test.
429      * @return true if the compiler configuration represents a Java 1.6 compiler or later, false otherwise
430      */
431     private static boolean isPreJava16( CompilerConfiguration config )
432     {
433         String v = config.getCompilerVersion();
434 
435         if ( v == null )
436         {
437             //mkleint: i haven't completely understood the reason for the
438             //compiler version parameter, checking source as well, as most projects will have this one set, not the compiler
439             String s = config.getSourceVersion();
440             if ( s == null )
441             {
442                 //now return true, as the 1.6 version is not the default - 1.4 is.
443                 return true;
444             }
445             return s.startsWith( "1.5" ) || s.startsWith( "1.4" ) || s.startsWith( "1.3" ) || s.startsWith( "1.2" )
446                 || s.startsWith( "1.1" ) || s.startsWith( "1.0" );
447         }
448 
449         return v.startsWith( "1.5" ) || v.startsWith( "1.4" ) || v.startsWith( "1.3" ) || v.startsWith( "1.2" )
450             || v.startsWith( "1.1" ) || v.startsWith( "1.0" );
451     }
452 
453     private static boolean isPreJava18( CompilerConfiguration config )
454     {
455         String v = config.getCompilerVersion();
456 
457         if ( v == null )
458         {
459             v = config.getSourceVersion();
460         }
461 
462         if ( v == null )
463         {
464             return true;
465         }
466 
467         return v.startsWith( "1.7" ) || v.startsWith( "1.6" ) || v.startsWith( "1.5" ) || v.startsWith( "1.4" )
468                 || v.startsWith( "1.3" ) || v.startsWith( "1.2" ) || v.startsWith( "1.1" ) || v.startsWith( "1.0" );
469     }
470 
471 
472     private static boolean suppressSource( CompilerConfiguration config )
473     {
474         return isPreJava14( config );
475     }
476 
477     private static boolean suppressEncoding( CompilerConfiguration config )
478     {
479         return isPreJava14( config );
480     }
481 
482     /**
483      * Compile the java sources in a external process, calling an external executable,
484      * like javac.
485      *
486      * @param config     compiler configuration
487      * @param executable name of the executable to launch
488      * @param args       arguments for the executable launched
489      * @return a CompilerResult object encapsulating the result of the compilation and any compiler messages
490      * @throws CompilerException
491      */
492     protected CompilerResult compileOutOfProcess( CompilerConfiguration config, String executable, String[] args )
493         throws CompilerException
494     {
495         Commandline cli = new Commandline();
496 
497         cli.setWorkingDirectory( config.getWorkingDirectory().getAbsolutePath() );
498 
499         cli.setExecutable( executable );
500 
501         try
502         {
503             File argumentsFile = createFileWithArguments( args, config.getOutputLocation() );
504             cli.addArguments(
505                 new String[]{ "@" + argumentsFile.getCanonicalPath().replace( File.separatorChar, '/' ) } );
506 
507             if ( !StringUtils.isEmpty( config.getMaxmem() ) )
508             {
509                 cli.addArguments( new String[]{ "-J-Xmx" + config.getMaxmem() } );
510             }
511 
512             if ( !StringUtils.isEmpty( config.getMeminitial() ) )
513             {
514                 cli.addArguments( new String[]{ "-J-Xms" + config.getMeminitial() } );
515             }
516 
517             for ( String key : config.getCustomCompilerArgumentsAsMap().keySet() )
518             {
519                 if ( StringUtils.isNotEmpty( key ) && key.startsWith( "-J" ) )
520                 {
521                     cli.addArguments( new String[]{ key } );
522                 }
523             }
524         }
525         catch ( IOException e )
526         {
527             throw new CompilerException( "Error creating file with javac arguments", e );
528         }
529 
530         CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
531 
532         int returnCode;
533 
534         List<CompilerMessage> messages;
535 
536         if ( ( getLogger() != null ) && getLogger().isDebugEnabled() )
537         {
538             File commandLineFile =
539                 new File( config.getOutputLocation(), "javac." + ( Os.isFamily( Os.FAMILY_WINDOWS ) ? "bat" : "sh" ) );
540             try
541             {
542                 FileUtils.fileWrite( commandLineFile.getAbsolutePath(), cli.toString().replaceAll( "'", "" ) );
543 
544                 if ( !Os.isFamily( Os.FAMILY_WINDOWS ) )
545                 {
546                     Runtime.getRuntime().exec( new String[]{ "chmod", "a+x", commandLineFile.getAbsolutePath() } );
547                 }
548             }
549             catch ( IOException e )
550             {
551                 if ( ( getLogger() != null ) && getLogger().isWarnEnabled() )
552                 {
553                     getLogger().warn( "Unable to write '" + commandLineFile.getName() + "' debug script file", e );
554                 }
555             }
556         }
557 
558         try
559         {
560             returnCode = CommandLineUtils.executeCommandLine( cli, out, out );
561 
562             messages = parseModernStream( returnCode, new BufferedReader( new StringReader( out.getOutput() ) ) );
563         }
564         catch ( CommandLineException e )
565         {
566             throw new CompilerException( "Error while executing the external compiler.", e );
567         }
568         catch ( IOException e )
569         {
570             throw new CompilerException( "Error while executing the external compiler.", e );
571         }
572 
573         boolean success = returnCode == 0;
574         return new CompilerResult( success, messages );
575     }
576 
577     /**
578      * Compile the java sources in the current JVM, without calling an external executable,
579      * using <code>com.sun.tools.javac.Main</code> class
580      *
581      * @param args   arguments for the compiler as they would be used in the command line javac
582      * @param config compiler configuration
583      * @return a CompilerResult object encapsulating the result of the compilation and any compiler messages
584      * @throws CompilerException
585      */
586     CompilerResult compileInProcess( String[] args, CompilerConfiguration config )
587         throws CompilerException
588     {
589         final Class<?> javacClass = getJavacClass( config );
590         final Thread thread = Thread.currentThread();
591         final ClassLoader contextClassLoader = thread.getContextClassLoader();
592         thread.setContextClassLoader( javacClass.getClassLoader() );
593         getLogger().debug( "ttcl changed run compileInProcessWithProperClassloader" );
594         try
595         {
596             return compileInProcessWithProperClassloader(javacClass, args);
597         }
598         finally
599         {
600             releaseJavaccClass( javacClass, config );
601             thread.setContextClassLoader( contextClassLoader );
602         }
603     }
604 
605     protected CompilerResult compileInProcessWithProperClassloader( Class<?> javacClass, String[] args )
606         throws CompilerException {
607       return compileInProcess0(javacClass, args);
608     }
609 
610     /**
611      * Helper method for compileInProcess()
612      */
613     private static CompilerResult compileInProcess0( Class<?> javacClass, String[] args )
614         throws CompilerException
615     {
616         StringWriter out = new StringWriter();
617 
618         Integer ok;
619 
620         List<CompilerMessage> messages;
621 
622         try
623         {
624             Method compile = javacClass.getMethod( "compile", new Class[]{ String[].class, PrintWriter.class } );
625 
626             ok = (Integer) compile.invoke( null, new Object[]{ args, new PrintWriter( out ) } );
627 
628             messages = parseModernStream( ok.intValue(), new BufferedReader( new StringReader( out.toString() ) ) );
629         }
630         catch ( NoSuchMethodException e )
631         {
632             throw new CompilerException( "Error while executing the compiler.", e );
633         }
634         catch ( IllegalAccessException e )
635         {
636             throw new CompilerException( "Error while executing the compiler.", e );
637         }
638         catch ( InvocationTargetException e )
639         {
640             throw new CompilerException( "Error while executing the compiler.", e );
641         }
642         catch ( IOException e )
643         {
644             throw new CompilerException( "Error while executing the compiler.", e );
645         }
646 
647         boolean success = ok.intValue() == 0;
648         return new CompilerResult( success, messages );
649     }
650 
651     /**
652      * Parse the output from the compiler into a list of CompilerMessage objects
653      *
654      * @param exitCode The exit code of javac.
655      * @param input    The output of the compiler
656      * @return List of CompilerMessage objects
657      * @throws IOException
658      */
659     static List<CompilerMessage> parseModernStream( int exitCode, BufferedReader input )
660         throws IOException
661     {
662         List<CompilerMessage> errors = new ArrayList<CompilerMessage>();
663 
664         String line;
665 
666         StringBuilder buffer = new StringBuilder();
667         
668         boolean hasPointer = false;
669 
670         while ( true )
671         {
672             line = input.readLine();
673             
674             if ( line == null )
675             {
676                 // javac output not detected by other parsing
677                 // maybe better to ignore only the summary an mark the rest as error
678                 if (buffer.length() > 0 && buffer.toString().startsWith("javac:"))
679                 {
680                     errors.add( new CompilerMessage( buffer.toString(), CompilerMessage.Kind.ERROR ) );
681                 }
682                 return errors;
683             }
684 
685             // A compiler error occurred, treat everything that follows as part of the error.
686             if (line.startsWith( "An exception has occurred in the compiler") ) {
687                 buffer = new StringBuilder();
688 
689                 while (line != null) {
690                     buffer.append(line);
691                     buffer.append(EOL);
692                     line = input.readLine();
693                 }
694 
695                 errors.add( new CompilerMessage( buffer.toString(), CompilerMessage.Kind.ERROR ) );
696                 return errors;
697             }
698 
699             // new error block?
700             if ( !line.startsWith( " " ) && hasPointer )
701             {
702                 // add the error bean
703                 errors.add( parseModernError( exitCode, buffer.toString() ) );
704                 
705                 // reset for next error block
706                 buffer = new StringBuilder(); // this is quicker than clearing it
707                 
708                 hasPointer = false;
709             }
710 
711             // TODO: there should be a better way to parse these
712             if ( ( buffer.length() == 0 ) && line.startsWith( "error: " ) )
713             {
714                 errors.add( new CompilerMessage( line, CompilerMessage.Kind.ERROR ) );
715             }
716             else if ( ( buffer.length() == 0 ) && line.startsWith( "warning: " ) )
717             {
718                 errors.add( new CompilerMessage( line, CompilerMessage.Kind.WARNING ) );
719             }
720             else if ( ( buffer.length() == 0 ) && isNote( line ) )
721             {
722                 // skip, JDK 1.5 telling us deprecated APIs are used but -Xlint:deprecation isn't set
723             }
724             else if ( ( buffer.length() == 0 ) && isMisc( line ) )
725             {
726                 // verbose output was set
727                 errors.add( new CompilerMessage( line, CompilerMessage.Kind.OTHER ) );
728             }
729             else
730             {
731                 buffer.append( line );
732 
733                 buffer.append( EOL );
734             }
735             
736             if ( line.endsWith( "^" ) )
737             {
738                 hasPointer = true;
739             }
740         }
741     }
742     
743     private static boolean isMisc( String line )
744     {
745         return startsWithPrefix( line, MISC_PREFIXES );
746     }
747 
748     private static boolean isNote( String line )
749     {
750         return startsWithPrefix( line, NOTE_PREFIXES );
751     }
752     
753     private static boolean startsWithPrefix( String line, String[] prefixes )
754     {
755         for ( int i = 0; i < prefixes.length; i++ )
756         {
757             if ( line.startsWith( prefixes[i] ) )
758             {
759                 return true;
760             }
761         }
762         return false;
763     }
764 
765     /**
766      * Construct a CompilerMessage object from a line of the compiler output
767      *
768      * @param exitCode The exit code from javac.
769      * @param error    output line from the compiler
770      * @return the CompilerMessage object
771      */
772     static CompilerMessage parseModernError( int exitCode, String error )
773     {
774         final StringTokenizer tokens = new StringTokenizer( error, ":" );
775 
776         boolean isError = exitCode != 0;
777 
778         try
779         {
780             // With Java 6 error output lines from the compiler got longer. For backward compatibility
781             // .. and the time being, we eat up all (if any) tokens up to the erroneous file and source
782             // .. line indicator tokens.
783 
784             boolean tokenIsAnInteger;
785 
786             StringBuilder file = null;
787 
788             String currentToken = null;
789 
790             do
791             {
792                 if ( currentToken != null )
793                 {
794                     if ( file == null )
795                     {
796                         file = new StringBuilder(currentToken);
797                     }
798                     else
799                     {
800                         file.append(':').append(currentToken);
801                     }
802                 }
803 
804                 currentToken = tokens.nextToken();
805 
806                 // Probably the only backward compatible means of checking if a string is an integer.
807 
808                 tokenIsAnInteger = true;
809 
810                 try
811                 {
812                     Integer.parseInt( currentToken );
813                 }
814                 catch ( NumberFormatException e )
815                 {
816                     tokenIsAnInteger = false;
817                 }
818             }
819             while ( !tokenIsAnInteger );
820 
821             final String lineIndicator = currentToken;
822 
823             final int startOfFileName = file.toString().lastIndexOf( ']' );
824 
825             if ( startOfFileName > -1 )
826             {
827                 file = new StringBuilder(file.substring(startOfFileName + 1 + EOL.length()));
828             }
829 
830             final int line = Integer.parseInt( lineIndicator );
831 
832             final StringBuilder msgBuffer = new StringBuilder();
833 
834             String msg = tokens.nextToken( EOL ).substring( 2 );
835 
836             // Remove the 'warning: ' prefix
837             final String warnPrefix = getWarnPrefix( msg );
838             if ( warnPrefix != null )
839             {
840                 isError = false;
841                 msg = msg.substring( warnPrefix.length() );
842             }
843             else
844             {
845                 isError = exitCode != 0;
846             }
847 
848             msgBuffer.append( msg );
849 
850             msgBuffer.append( EOL );
851 
852             String context = tokens.nextToken( EOL );
853             
854             String pointer = null;
855             
856             do
857             {
858                 final String msgLine = tokens.nextToken( EOL );
859 
860                 if ( pointer != null )
861                 {
862                     msgBuffer.append( msgLine );
863 
864                     msgBuffer.append( EOL );
865                 }
866                 else if ( msgLine.endsWith( "^" ) )
867                 {
868                     pointer = msgLine;
869                 }
870                 else
871                 {
872                     msgBuffer.append( context );
873 
874                     msgBuffer.append( EOL );
875 
876                     context = msgLine;
877                 }
878             }
879             while ( tokens.hasMoreTokens() );
880 
881             msgBuffer.append( EOL );
882 
883             final String message = msgBuffer.toString();
884 
885             final int startcolumn = pointer.indexOf( "^" );
886 
887             int endcolumn = (context == null) ? startcolumn : context.indexOf(" ", startcolumn);
888 
889             if ( endcolumn == -1 )
890             {
891                 endcolumn = context.length();
892             }
893 
894             return new CompilerMessage(file.toString(), isError, line, startcolumn, line, endcolumn, message.trim() );
895         }
896         catch ( NoSuchElementException e )
897         {
898             return new CompilerMessage( "no more tokens - could not parse error message: " + error, isError );
899         }
900         catch ( NumberFormatException e )
901         {
902             return new CompilerMessage( "could not parse error message: " + error, isError );
903         }
904         catch ( Exception e )
905         {
906             return new CompilerMessage( "could not parse error message: " + error, isError );
907         }
908     }
909 
910     private static String getWarnPrefix( String msg )
911     {
912         for ( int i = 0; i < WARNING_PREFIXES.length; i++ )
913         {
914             if ( msg.startsWith( WARNING_PREFIXES[i] ) )
915             {
916                 return WARNING_PREFIXES[i];
917             }
918         }
919         return null;
920     }
921 
922     /**
923      * put args into a temp file to be referenced using the @ option in javac command line
924      *
925      * @param args
926      * @return the temporary file wth the arguments
927      * @throws IOException
928      */
929     private File createFileWithArguments( String[] args, String outputDirectory )
930         throws IOException
931     {
932         PrintWriter writer = null;
933         try
934         {
935             File tempFile;
936             if ( ( getLogger() != null ) && getLogger().isDebugEnabled() )
937             {
938                 tempFile =
939                     File.createTempFile( JavacCompiler.class.getName(), "arguments", new File( outputDirectory ) );
940             }
941             else
942             {
943                 tempFile = File.createTempFile( JavacCompiler.class.getName(), "arguments" );
944                 tempFile.deleteOnExit();
945             }
946 
947             writer = new PrintWriter( new FileWriter( tempFile ) );
948 
949             for ( int i = 0; i < args.length; i++ )
950             {
951                 String argValue = args[i].replace( File.separatorChar, '/' );
952 
953                 writer.write( "\"" + argValue + "\"" );
954 
955                 writer.println();
956             }
957 
958             writer.flush();
959 
960             return tempFile;
961 
962         }
963         finally
964         {
965             if ( writer != null )
966             {
967                 writer.close();
968             }
969         }
970     }
971 
972     /**
973      * Get the path of the javac tool executable: try to find it depending the OS or the <code>java.home</code>
974      * system property or the <code>JAVA_HOME</code> environment variable.
975      *
976      * @return the path of the Javadoc tool
977      * @throws IOException if not found
978      */
979     private static String getJavacExecutable()
980         throws IOException
981     {
982         String javacCommand = "javac" + ( Os.isFamily( Os.FAMILY_WINDOWS ) ? ".exe" : "" );
983 
984         String javaHome = System.getProperty( "java.home" );
985         File javacExe;
986         if ( Os.isName( "AIX" ) )
987         {
988             javacExe = new File( javaHome + File.separator + ".." + File.separator + "sh", javacCommand );
989         }
990         else if ( Os.isName( "Mac OS X" ) )
991         {
992             javacExe = new File( javaHome + File.separator + "bin", javacCommand );
993         }
994         else
995         {
996             javacExe = new File( javaHome + File.separator + ".." + File.separator + "bin", javacCommand );
997         }
998 
999         // ----------------------------------------------------------------------
1000         // Try to find javacExe from JAVA_HOME environment variable
1001         // ----------------------------------------------------------------------
1002         if ( !javacExe.isFile() )
1003         {
1004             Properties env = CommandLineUtils.getSystemEnvVars();
1005             javaHome = env.getProperty( "JAVA_HOME" );
1006             if ( StringUtils.isEmpty( javaHome ) )
1007             {
1008                 throw new IOException( "The environment variable JAVA_HOME is not correctly set." );
1009             }
1010             if ( !new File( javaHome ).isDirectory() )
1011             {
1012                 throw new IOException(
1013                     "The environment variable JAVA_HOME=" + javaHome + " doesn't exist or is not a valid directory." );
1014             }
1015 
1016             javacExe = new File( env.getProperty( "JAVA_HOME" ) + File.separator + "bin", javacCommand );
1017         }
1018 
1019         if ( !javacExe.isFile() )
1020         {
1021             throw new IOException( "The javadoc executable '" + javacExe
1022                                        + "' doesn't exist or is not a file. Verify the JAVA_HOME environment variable." );
1023         }
1024 
1025         return javacExe.getAbsolutePath();
1026     }
1027 
1028     private void releaseJavaccClass( Class<?> javaccClass, CompilerConfiguration compilerConfiguration )
1029     {
1030         if ( compilerConfiguration.getCompilerReuseStrategy()
1031             == CompilerConfiguration.CompilerReuseStrategy.ReuseCreated )
1032         {
1033             javaccClasses.add( javaccClass );
1034         }
1035 
1036     }
1037 
1038     /**
1039      * Find the main class of JavaC. Return the same class for subsequent calls.
1040      *
1041      * @return the non-null class.
1042      * @throws CompilerException if the class has not been found.
1043      */
1044     private Class<?> getJavacClass( CompilerConfiguration compilerConfiguration )
1045         throws CompilerException
1046     {
1047         Class<?> c = null;
1048         switch ( compilerConfiguration.getCompilerReuseStrategy() )
1049         {
1050             case AlwaysNew:
1051                 return createJavacClass();
1052             case ReuseCreated:
1053                 synchronized ( javaccClasses )
1054                 {
1055                     if ( javaccClasses.size() > 0 )
1056                     {
1057                         c = javaccClasses.get( 0 );
1058                         javaccClasses.remove( c );
1059                         return c;
1060                     }
1061                 }
1062                 c = createJavacClass();
1063                 return c;
1064             case ReuseSame:
1065             default:
1066                 c = JavacCompiler.JAVAC_CLASS;
1067                 if ( c != null )
1068                 {
1069                     return c;
1070                 }
1071                 synchronized ( JavacCompiler.LOCK )
1072                 {
1073                     if ( c == null )
1074                     {
1075                         JavacCompiler.JAVAC_CLASS = c = createJavacClass();
1076                     }
1077                     return c;
1078                 }
1079 
1080 
1081         }
1082     }
1083 
1084 
1085     /**
1086      * Helper method for create Javac class
1087      */
1088     protected Class<?> createJavacClass()
1089         throws CompilerException
1090     {
1091         try
1092         {
1093             // look whether JavaC is on Maven's classpath
1094             //return Class.forName( JavacCompiler.JAVAC_CLASSNAME, true, JavacCompiler.class.getClassLoader() );
1095             return JavacCompiler.class.getClassLoader().loadClass( JavacCompiler.JAVAC_CLASSNAME );
1096         }
1097         catch ( ClassNotFoundException ex )
1098         {
1099             // ok
1100         }
1101 
1102         final File toolsJar = new File( System.getProperty( "java.home" ), "../lib/tools.jar" );
1103         if ( !toolsJar.exists() )
1104         {
1105             throw new CompilerException( "tools.jar not found: " + toolsJar );
1106         }
1107 
1108         try
1109         {
1110             // Combined classloader with no parent/child relationship, so classes in our classloader
1111             // can reference classes in tools.jar
1112             URL[] originalUrls = ((URLClassLoader) JavacCompiler.class.getClassLoader()).getURLs();
1113             URL[] urls = new URL[originalUrls.length + 1];
1114             urls[0] = toolsJar.toURI().toURL();
1115             System.arraycopy(originalUrls, 0, urls, 1, originalUrls.length);
1116             ClassLoader javacClassLoader = new URLClassLoader(urls);
1117 
1118             final Thread thread = Thread.currentThread();
1119             final ClassLoader contextClassLoader = thread.getContextClassLoader();
1120             thread.setContextClassLoader( javacClassLoader );
1121             try
1122             {
1123                 //return Class.forName( JavacCompiler.JAVAC_CLASSNAME, true, javacClassLoader );
1124                 return javacClassLoader.loadClass( JavacCompiler.JAVAC_CLASSNAME );
1125             }
1126             finally
1127             {
1128                 thread.setContextClassLoader( contextClassLoader );
1129             }
1130         }
1131         catch ( MalformedURLException ex )
1132         {
1133             throw new CompilerException(
1134                 "Could not convert the file reference to tools.jar to a URL, path to tools.jar: '"
1135                     + toolsJar.getAbsolutePath() + "'.", ex );
1136         }
1137         catch ( ClassNotFoundException ex )
1138         {
1139             throw new CompilerException( "Unable to locate the Javac Compiler in:" + EOL + "  " + toolsJar + EOL
1140                                              + "Please ensure you are using JDK 1.4 or above and" + EOL
1141                                              + "not a JRE (the com.sun.tools.javac.Main class is required)." + EOL
1142                                              + "In most cases you can change the location of your Java" + EOL
1143                                              + "installation by setting the JAVA_HOME environment variable.", ex );
1144         }
1145     }
1146 
1147 }