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( "-modulepath" );
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 ( config.isShowDeprecation() )
313         {
314             args.add( "-deprecation" );
315 
316             // This is required to actually display the deprecation messages
317             config.setShowWarnings( true );
318         }
319 
320         if ( !config.isShowWarnings() )
321         {
322             args.add( "-nowarn" );
323         }
324         
325         if ( config.isFailOnWarning() )
326         {
327             args.add( "-Werror" );
328         }
329 
330         if ( !StringUtils.isEmpty( config.getReleaseVersion() ) )
331         {
332             args.add( "-release" );
333             args.add( config.getReleaseVersion() );
334         }
335         else
336         {
337             // TODO: this could be much improved
338             if ( StringUtils.isEmpty( config.getTargetVersion() ) )
339             {
340                 // Required, or it defaults to the target of your JDK (eg 1.5)
341                 args.add( "-target" );
342                 args.add( "1.1" );
343             }
344             else
345             {
346                 args.add( "-target" );
347                 args.add( config.getTargetVersion() );
348             }
349 
350             if ( !suppressSource( config ) && StringUtils.isEmpty( config.getSourceVersion() ) )
351             {
352                 // If omitted, later JDKs complain about a 1.1 target
353                 args.add( "-source" );
354                 args.add( "1.3" );
355             }
356             else if ( !suppressSource( config ) )
357             {
358                 args.add( "-source" );
359                 args.add( config.getSourceVersion() );
360             }            
361         }
362 
363 
364         if ( !suppressEncoding( config ) && !StringUtils.isEmpty( config.getSourceEncoding() ) )
365         {
366             args.add( "-encoding" );
367             args.add( config.getSourceEncoding() );
368         }
369 
370         for ( Map.Entry<String, String> entry : config.getCustomCompilerArgumentsAsMap().entrySet() )
371         {
372             String key = entry.getKey();
373 
374             if ( StringUtils.isEmpty( key ) || key.startsWith( "-J" ) )
375             {
376                 continue;
377             }
378 
379             args.add( key );
380 
381             String value = entry.getValue();
382 
383             if ( StringUtils.isEmpty( value ) )
384             {
385                 continue;
386             }
387 
388             args.add( value );
389         }
390 
391         return args.toArray( new String[args.size()] );
392     }
393 
394     /**
395      * Determine if the compiler is a version prior to 1.4.
396      * This is needed as 1.3 and earlier did not support -source or -encoding parameters
397      *
398      * @param config The compiler configuration to test.
399      * @return true if the compiler configuration represents a Java 1.4 compiler or later, false otherwise
400      */
401     private static boolean isPreJava14( CompilerConfiguration config )
402     {
403         String v = config.getCompilerVersion();
404 
405         if ( v == null )
406         {
407             return false;
408         }
409 
410         return v.startsWith( "1.3" ) || v.startsWith( "1.2" ) || v.startsWith( "1.1" ) || v.startsWith( "1.0" );
411     }
412 
413     /**
414      * Determine if the compiler is a version prior to 1.6.
415      * This is needed for annotation processing parameters.
416      *
417      * @param config The compiler configuration to test.
418      * @return true if the compiler configuration represents a Java 1.6 compiler or later, false otherwise
419      */
420     private static boolean isPreJava16( CompilerConfiguration config )
421     {
422         String v = config.getCompilerVersion();
423 
424         if ( v == null )
425         {
426             //mkleint: i haven't completely understood the reason for the
427             //compiler version parameter, checking source as well, as most projects will have this one set, not the compiler
428             String s = config.getSourceVersion();
429             if ( s == null )
430             {
431                 //now return true, as the 1.6 version is not the default - 1.4 is.
432                 return true;
433             }
434             return s.startsWith( "1.5" ) || s.startsWith( "1.4" ) || s.startsWith( "1.3" ) || s.startsWith( "1.2" )
435                 || s.startsWith( "1.1" ) || s.startsWith( "1.0" );
436         }
437 
438         return v.startsWith( "1.5" ) || v.startsWith( "1.4" ) || v.startsWith( "1.3" ) || v.startsWith( "1.2" )
439             || v.startsWith( "1.1" ) || v.startsWith( "1.0" );
440     }
441 
442 
443     private static boolean suppressSource( CompilerConfiguration config )
444     {
445         return isPreJava14( config );
446     }
447 
448     private static boolean suppressEncoding( CompilerConfiguration config )
449     {
450         return isPreJava14( config );
451     }
452 
453     /**
454      * Compile the java sources in a external process, calling an external executable,
455      * like javac.
456      *
457      * @param config     compiler configuration
458      * @param executable name of the executable to launch
459      * @param args       arguments for the executable launched
460      * @return a CompilerResult object encapsulating the result of the compilation and any compiler messages
461      * @throws CompilerException
462      */
463     protected CompilerResult compileOutOfProcess( CompilerConfiguration config, String executable, String[] args )
464         throws CompilerException
465     {
466         Commandline cli = new Commandline();
467 
468         cli.setWorkingDirectory( config.getWorkingDirectory().getAbsolutePath() );
469 
470         cli.setExecutable( executable );
471 
472         try
473         {
474             File argumentsFile = createFileWithArguments( args, config.getOutputLocation() );
475             cli.addArguments(
476                 new String[]{ "@" + argumentsFile.getCanonicalPath().replace( File.separatorChar, '/' ) } );
477 
478             if ( !StringUtils.isEmpty( config.getMaxmem() ) )
479             {
480                 cli.addArguments( new String[]{ "-J-Xmx" + config.getMaxmem() } );
481             }
482 
483             if ( !StringUtils.isEmpty( config.getMeminitial() ) )
484             {
485                 cli.addArguments( new String[]{ "-J-Xms" + config.getMeminitial() } );
486             }
487 
488             for ( String key : config.getCustomCompilerArgumentsAsMap().keySet() )
489             {
490                 if ( StringUtils.isNotEmpty( key ) && key.startsWith( "-J" ) )
491                 {
492                     cli.addArguments( new String[]{ key } );
493                 }
494             }
495         }
496         catch ( IOException e )
497         {
498             throw new CompilerException( "Error creating file with javac arguments", e );
499         }
500 
501         CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
502 
503         int returnCode;
504 
505         List<CompilerMessage> messages;
506 
507         if ( ( getLogger() != null ) && getLogger().isDebugEnabled() )
508         {
509             File commandLineFile =
510                 new File( config.getOutputLocation(), "javac." + ( Os.isFamily( Os.FAMILY_WINDOWS ) ? "bat" : "sh" ) );
511             try
512             {
513                 FileUtils.fileWrite( commandLineFile.getAbsolutePath(), cli.toString().replaceAll( "'", "" ) );
514 
515                 if ( !Os.isFamily( Os.FAMILY_WINDOWS ) )
516                 {
517                     Runtime.getRuntime().exec( new String[]{ "chmod", "a+x", commandLineFile.getAbsolutePath() } );
518                 }
519             }
520             catch ( IOException e )
521             {
522                 if ( ( getLogger() != null ) && getLogger().isWarnEnabled() )
523                 {
524                     getLogger().warn( "Unable to write '" + commandLineFile.getName() + "' debug script file", e );
525                 }
526             }
527         }
528 
529         try
530         {
531             returnCode = CommandLineUtils.executeCommandLine( cli, out, out );
532 
533             messages = parseModernStream( returnCode, new BufferedReader( new StringReader( out.getOutput() ) ) );
534         }
535         catch ( CommandLineException e )
536         {
537             throw new CompilerException( "Error while executing the external compiler.", e );
538         }
539         catch ( IOException e )
540         {
541             throw new CompilerException( "Error while executing the external compiler.", e );
542         }
543 
544         boolean success = returnCode == 0;
545         return new CompilerResult( success, messages );
546     }
547 
548     /**
549      * Compile the java sources in the current JVM, without calling an external executable,
550      * using <code>com.sun.tools.javac.Main</code> class
551      *
552      * @param args   arguments for the compiler as they would be used in the command line javac
553      * @param config compiler configuration
554      * @return a CompilerResult object encapsulating the result of the compilation and any compiler messages
555      * @throws CompilerException
556      */
557     CompilerResult compileInProcess( String[] args, CompilerConfiguration config )
558         throws CompilerException
559     {
560         final Class<?> javacClass = getJavacClass( config );
561         final Thread thread = Thread.currentThread();
562         final ClassLoader contextClassLoader = thread.getContextClassLoader();
563         thread.setContextClassLoader( javacClass.getClassLoader() );
564         getLogger().debug( "ttcl changed run compileInProcessWithProperClassloader" );
565         try
566         {
567             return compileInProcessWithProperClassloader(javacClass, args);
568         }
569         finally
570         {
571             releaseJavaccClass( javacClass, config );
572             thread.setContextClassLoader( contextClassLoader );
573         }
574     }
575 
576     protected CompilerResult compileInProcessWithProperClassloader( Class<?> javacClass, String[] args )
577         throws CompilerException {
578       return compileInProcess0(javacClass, args);
579     }
580 
581     /**
582      * Helper method for compileInProcess()
583      */
584     private static CompilerResult compileInProcess0( Class<?> javacClass, String[] args )
585         throws CompilerException
586     {
587         StringWriter out = new StringWriter();
588 
589         Integer ok;
590 
591         List<CompilerMessage> messages;
592 
593         try
594         {
595             Method compile = javacClass.getMethod( "compile", new Class[]{ String[].class, PrintWriter.class } );
596 
597             ok = (Integer) compile.invoke( null, new Object[]{ args, new PrintWriter( out ) } );
598 
599             messages = parseModernStream( ok.intValue(), new BufferedReader( new StringReader( out.toString() ) ) );
600         }
601         catch ( NoSuchMethodException e )
602         {
603             throw new CompilerException( "Error while executing the compiler.", e );
604         }
605         catch ( IllegalAccessException e )
606         {
607             throw new CompilerException( "Error while executing the compiler.", e );
608         }
609         catch ( InvocationTargetException e )
610         {
611             throw new CompilerException( "Error while executing the compiler.", e );
612         }
613         catch ( IOException e )
614         {
615             throw new CompilerException( "Error while executing the compiler.", e );
616         }
617 
618         boolean success = ok.intValue() == 0;
619         return new CompilerResult( success, messages );
620     }
621 
622     /**
623      * Parse the output from the compiler into a list of CompilerMessage objects
624      *
625      * @param exitCode The exit code of javac.
626      * @param input    The output of the compiler
627      * @return List of CompilerMessage objects
628      * @throws IOException
629      */
630     static List<CompilerMessage> parseModernStream( int exitCode, BufferedReader input )
631         throws IOException
632     {
633         List<CompilerMessage> errors = new ArrayList<CompilerMessage>();
634 
635         String line;
636 
637         StringBuilder buffer = new StringBuilder();
638         
639         boolean hasPointer = false;
640 
641         while ( true )
642         {
643             line = input.readLine();
644             
645             if ( line == null )
646             {
647                 // javac output not detected by other parsing
648                 // maybe better to ignore only the summary an mark the rest as error
649                 if ( buffer.length() > 0 && ( buffer.toString().startsWith( "javac:" )
650                     || buffer.toString().startsWith( "An exception has occurred in the compiler" ) ) )
651                 {
652                     errors.add( new CompilerMessage( buffer.toString(), CompilerMessage.Kind.ERROR ) );
653                 }
654                 return errors;
655             }
656 
657             // new error block?
658             if ( !line.startsWith( " " ) && hasPointer )
659             {
660                 // add the error bean
661                 errors.add( parseModernError( exitCode, buffer.toString() ) );
662                 
663                 // reset for next error block
664                 buffer = new StringBuilder(); // this is quicker than clearing it
665                 
666                 hasPointer = false;
667             }
668 
669             // TODO: there should be a better way to parse these
670             if ( ( buffer.length() == 0 ) && line.startsWith( "error: " ) )
671             {
672                 errors.add( new CompilerMessage( line, true ) );
673             }
674             else if ( ( buffer.length() == 0 ) && isNote( line ) )
675             {
676                 // skip, JDK 1.5 telling us deprecated APIs are used but -Xlint:deprecation isn't set
677             }
678             else if ( ( buffer.length() == 0 ) && isMisc( line ) )
679             {
680                 // verbose output was set
681                 errors.add( new CompilerMessage( line, CompilerMessage.Kind.OTHER ) );
682             }
683             else
684             {
685                 buffer.append( line );
686 
687                 buffer.append( EOL );
688             }
689             
690             if ( line.endsWith( "^" ) )
691             {
692                 hasPointer = true;
693             }
694         }
695     }
696     
697     private static boolean isMisc( String line )
698     {
699         return startsWithPrefix( line, MISC_PREFIXES );
700     }
701 
702     private static boolean isNote( String line )
703     {
704         return startsWithPrefix( line, NOTE_PREFIXES );
705     }
706     
707     private static boolean startsWithPrefix( String line, String[] prefixes )
708     {
709         for ( int i = 0; i < prefixes.length; i++ )
710         {
711             if ( line.startsWith( prefixes[i] ) )
712             {
713                 return true;
714             }
715         }
716         return false;
717     }
718 
719     /**
720      * Construct a CompilerMessage object from a line of the compiler output
721      *
722      * @param exitCode The exit code from javac.
723      * @param error    output line from the compiler
724      * @return the CompilerMessage object
725      */
726     static CompilerMessage parseModernError( int exitCode, String error )
727     {
728         StringTokenizer tokens = new StringTokenizer( error, ":" );
729 
730         boolean isError = exitCode != 0;
731 
732         StringBuilder msgBuffer;
733 
734         try
735         {
736             // With Java 6 error output lines from the compiler got longer. For backward compatibility
737             // .. and the time being, we eat up all (if any) tokens up to the erroneous file and source
738             // .. line indicator tokens.
739 
740             boolean tokenIsAnInteger;
741 
742             String file = null;
743 
744             String currentToken = null;
745 
746             do
747             {
748                 if ( currentToken != null )
749                 {
750                     if ( file == null )
751                     {
752                         file = currentToken;
753                     }
754                     else
755                     {
756                         file = file + ':' + currentToken;
757                     }
758                 }
759 
760                 currentToken = tokens.nextToken();
761 
762                 // Probably the only backward compatible means of checking if a string is an integer.
763 
764                 tokenIsAnInteger = true;
765 
766                 try
767                 {
768                     Integer.parseInt( currentToken );
769                 }
770                 catch ( NumberFormatException e )
771                 {
772                     tokenIsAnInteger = false;
773                 }
774             }
775             while ( !tokenIsAnInteger );
776 
777             String lineIndicator = currentToken;
778 
779             int startOfFileName = file.lastIndexOf( ']' );
780 
781             if ( startOfFileName > -1 )
782             {
783                 file = file.substring( startOfFileName + 1 + EOL.length() );
784             }
785 
786             int line = Integer.parseInt( lineIndicator );
787 
788             msgBuffer = new StringBuilder();
789 
790             String msg = tokens.nextToken( EOL ).substring( 2 );
791 
792             // Remove the 'warning: ' prefix
793             String warnPrefix = getWarnPrefix( msg );
794             if ( warnPrefix != null )
795             {
796                 isError = false;
797                 msg = msg.substring( warnPrefix.length() );
798             }
799             else
800             {
801                 isError = exitCode != 0;
802             }
803 
804             msgBuffer.append( msg );
805 
806             msgBuffer.append( EOL );
807 
808             String context = tokens.nextToken( EOL );
809             
810             String pointer = null;
811             
812             do
813             {
814                 String msgLine = tokens.nextToken( EOL );
815 
816                 if ( pointer != null )
817                 {
818                     msgBuffer.append( msgLine );
819 
820                     msgBuffer.append( EOL );
821                 }
822                 else if ( msgLine.endsWith( "^" ) )
823                 {
824                     pointer = msgLine;
825                 }
826                 else
827                 {
828                     msgBuffer.append( context );
829 
830                     msgBuffer.append( EOL );
831 
832                     context = msgLine;
833                 }
834             }
835             while ( tokens.hasMoreTokens() );
836 
837             msgBuffer.append( EOL );
838 
839             String message = msgBuffer.toString();
840 
841             int startcolumn = pointer.indexOf( "^" );
842 
843             int endcolumn = context == null ? startcolumn : context.indexOf( " ", startcolumn );
844 
845             if ( endcolumn == -1 )
846             {
847                 endcolumn = context.length();
848             }
849 
850             return new CompilerMessage( file, isError, line, startcolumn, line, endcolumn, message.trim() );
851         }
852         catch ( NoSuchElementException e )
853         {
854             return new CompilerMessage( "no more tokens - could not parse error message: " + error, isError );
855         }
856         catch ( NumberFormatException e )
857         {
858             return new CompilerMessage( "could not parse error message: " + error, isError );
859         }
860         catch ( Exception e )
861         {
862             return new CompilerMessage( "could not parse error message: " + error, isError );
863         }
864     }
865 
866     private static String getWarnPrefix( String msg )
867     {
868         for ( int i = 0; i < WARNING_PREFIXES.length; i++ )
869         {
870             if ( msg.startsWith( WARNING_PREFIXES[i] ) )
871             {
872                 return WARNING_PREFIXES[i];
873             }
874         }
875         return null;
876     }
877 
878     /**
879      * put args into a temp file to be referenced using the @ option in javac command line
880      *
881      * @param args
882      * @return the temporary file wth the arguments
883      * @throws IOException
884      */
885     private File createFileWithArguments( String[] args, String outputDirectory )
886         throws IOException
887     {
888         PrintWriter writer = null;
889         try
890         {
891             File tempFile;
892             if ( ( getLogger() != null ) && getLogger().isDebugEnabled() )
893             {
894                 tempFile =
895                     File.createTempFile( JavacCompiler.class.getName(), "arguments", new File( outputDirectory ) );
896             }
897             else
898             {
899                 tempFile = File.createTempFile( JavacCompiler.class.getName(), "arguments" );
900                 tempFile.deleteOnExit();
901             }
902 
903             writer = new PrintWriter( new FileWriter( tempFile ) );
904 
905             for ( int i = 0; i < args.length; i++ )
906             {
907                 String argValue = args[i].replace( File.separatorChar, '/' );
908 
909                 writer.write( "\"" + argValue + "\"" );
910 
911                 writer.println();
912             }
913 
914             writer.flush();
915 
916             return tempFile;
917 
918         }
919         finally
920         {
921             if ( writer != null )
922             {
923                 writer.close();
924             }
925         }
926     }
927 
928     /**
929      * Get the path of the javac tool executable: try to find it depending the OS or the <code>java.home</code>
930      * system property or the <code>JAVA_HOME</code> environment variable.
931      *
932      * @return the path of the Javadoc tool
933      * @throws IOException if not found
934      */
935     private static String getJavacExecutable()
936         throws IOException
937     {
938         String javacCommand = "javac" + ( Os.isFamily( Os.FAMILY_WINDOWS ) ? ".exe" : "" );
939 
940         String javaHome = System.getProperty( "java.home" );
941         File javacExe;
942         if ( Os.isName( "AIX" ) )
943         {
944             javacExe = new File( javaHome + File.separator + ".." + File.separator + "sh", javacCommand );
945         }
946         else if ( Os.isName( "Mac OS X" ) )
947         {
948             javacExe = new File( javaHome + File.separator + "bin", javacCommand );
949         }
950         else
951         {
952             javacExe = new File( javaHome + File.separator + ".." + File.separator + "bin", javacCommand );
953         }
954 
955         // ----------------------------------------------------------------------
956         // Try to find javacExe from JAVA_HOME environment variable
957         // ----------------------------------------------------------------------
958         if ( !javacExe.isFile() )
959         {
960             Properties env = CommandLineUtils.getSystemEnvVars();
961             javaHome = env.getProperty( "JAVA_HOME" );
962             if ( StringUtils.isEmpty( javaHome ) )
963             {
964                 throw new IOException( "The environment variable JAVA_HOME is not correctly set." );
965             }
966             if ( !new File( javaHome ).isDirectory() )
967             {
968                 throw new IOException(
969                     "The environment variable JAVA_HOME=" + javaHome + " doesn't exist or is not a valid directory." );
970             }
971 
972             javacExe = new File( env.getProperty( "JAVA_HOME" ) + File.separator + "bin", javacCommand );
973         }
974 
975         if ( !javacExe.isFile() )
976         {
977             throw new IOException( "The javadoc executable '" + javacExe
978                                        + "' doesn't exist or is not a file. Verify the JAVA_HOME environment variable." );
979         }
980 
981         return javacExe.getAbsolutePath();
982     }
983 
984     private void releaseJavaccClass( Class<?> javaccClass, CompilerConfiguration compilerConfiguration )
985     {
986         if ( compilerConfiguration.getCompilerReuseStrategy()
987             == CompilerConfiguration.CompilerReuseStrategy.ReuseCreated )
988         {
989             javaccClasses.add( javaccClass );
990         }
991 
992     }
993 
994     /**
995      * Find the main class of JavaC. Return the same class for subsequent calls.
996      *
997      * @return the non-null class.
998      * @throws CompilerException if the class has not been found.
999      */
1000     private Class<?> getJavacClass( CompilerConfiguration compilerConfiguration )
1001         throws CompilerException
1002     {
1003         Class<?> c = null;
1004         switch ( compilerConfiguration.getCompilerReuseStrategy() )
1005         {
1006             case AlwaysNew:
1007                 return createJavacClass();
1008             case ReuseCreated:
1009                 synchronized ( javaccClasses )
1010                 {
1011                     if ( javaccClasses.size() > 0 )
1012                     {
1013                         c = javaccClasses.get( 0 );
1014                         javaccClasses.remove( c );
1015                         return c;
1016                     }
1017                 }
1018                 c = createJavacClass();
1019                 return c;
1020             case ReuseSame:
1021             default:
1022                 c = JavacCompiler.JAVAC_CLASS;
1023                 if ( c != null )
1024                 {
1025                     return c;
1026                 }
1027                 synchronized ( JavacCompiler.LOCK )
1028                 {
1029                     if ( c == null )
1030                     {
1031                         JavacCompiler.JAVAC_CLASS = c = createJavacClass();
1032                     }
1033                     return c;
1034                 }
1035 
1036 
1037         }
1038     }
1039 
1040 
1041     /**
1042      * Helper method for create Javac class
1043      */
1044     protected Class<?> createJavacClass()
1045         throws CompilerException
1046     {
1047         try
1048         {
1049             // look whether JavaC is on Maven's classpath
1050             //return Class.forName( JavacCompiler.JAVAC_CLASSNAME, true, JavacCompiler.class.getClassLoader() );
1051             return JavacCompiler.class.getClassLoader().loadClass( JavacCompiler.JAVAC_CLASSNAME );
1052         }
1053         catch ( ClassNotFoundException ex )
1054         {
1055             // ok
1056         }
1057 
1058         final File toolsJar = new File( System.getProperty( "java.home" ), "../lib/tools.jar" );
1059         if ( !toolsJar.exists() )
1060         {
1061             throw new CompilerException( "tools.jar not found: " + toolsJar );
1062         }
1063 
1064         try
1065         {
1066             // Combined classloader with no parent/child relationship, so classes in our classloader
1067             // can reference classes in tools.jar
1068             URL[] originalUrls = ((URLClassLoader) JavacCompiler.class.getClassLoader()).getURLs();
1069             URL[] urls = new URL[originalUrls.length + 1];
1070             urls[0] = toolsJar.toURI().toURL();
1071             System.arraycopy(originalUrls, 0, urls, 1, originalUrls.length);
1072             ClassLoader javacClassLoader = new URLClassLoader(urls);
1073 
1074             final Thread thread = Thread.currentThread();
1075             final ClassLoader contextClassLoader = thread.getContextClassLoader();
1076             thread.setContextClassLoader( javacClassLoader );
1077             try
1078             {
1079                 //return Class.forName( JavacCompiler.JAVAC_CLASSNAME, true, javacClassLoader );
1080                 return javacClassLoader.loadClass( JavacCompiler.JAVAC_CLASSNAME );
1081             }
1082             finally
1083             {
1084                 thread.setContextClassLoader( contextClassLoader );
1085             }
1086         }
1087         catch ( MalformedURLException ex )
1088         {
1089             throw new CompilerException(
1090                 "Could not convert the file reference to tools.jar to a URL, path to tools.jar: '"
1091                     + toolsJar.getAbsolutePath() + "'.", ex );
1092         }
1093         catch ( ClassNotFoundException ex )
1094         {
1095             throw new CompilerException( "Unable to locate the Javac Compiler in:" + EOL + "  " + toolsJar + EOL
1096                                              + "Please ensure you are using JDK 1.4 or above and" + EOL
1097                                              + "not a JRE (the com.sun.tools.javac.Main class is required)." + EOL
1098                                              + "In most cases you can change the location of your Java" + EOL
1099                                              + "installation by setting the JAVA_HOME environment variable.", ex );
1100         }
1101     }
1102 
1103 }