Coverage Report - org.codehaus.plexus.compiler.javac.JavacCompiler
 
Classes in this File Line Coverage Branch Coverage Complexity
JavacCompiler
57%
203/354
53%
133/247
8
JavacCompiler$1
100%
1/1
N/A
8
 
 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  1
     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  1
     private static final String[] NOTE_PREFIXES = { "Note: ", "\u6ce8: ", "\u6ce8\u610f\uff1a " };
 95  
 
 96  
     // see compiler.misc.verbose in compiler.properties of javac sources
 97  1
     private static final String[] MISC_PREFIXES = { "[" };
 98  
 
 99  1
     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  2
     private List<Class<?>> javaccClasses = new CopyOnWriteArrayList<Class<?>>();
 106  
 
 107  
     // ----------------------------------------------------------------------
 108  
     //
 109  
     // ----------------------------------------------------------------------
 110  
 
 111  
     public JavacCompiler()
 112  
     {
 113  2
         super( CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE, ".java", ".class", null );
 114  2
     }
 115  
 
 116  
     // ----------------------------------------------------------------------
 117  
     // Compiler Implementation
 118  
     // ----------------------------------------------------------------------
 119  
 
 120  
     @Override
 121  
     public CompilerResult performCompile( CompilerConfiguration config )
 122  
         throws CompilerException
 123  
     {
 124  14
         File destinationDir = new File( config.getOutputLocation() );
 125  
 
 126  14
         if ( !destinationDir.exists() )
 127  
         {
 128  14
             destinationDir.mkdirs();
 129  
         }
 130  
 
 131  14
         String[] sourceFiles = getSourceFiles( config );
 132  
 
 133  14
         if ( ( sourceFiles == null ) || ( sourceFiles.length == 0 ) )
 134  
         {
 135  0
             return new CompilerResult();
 136  
         }
 137  
 
 138  14
         if ( ( getLogger() != null ) && getLogger().isInfoEnabled() )
 139  
         {
 140  28
             getLogger().info( "Compiling " + sourceFiles.length + " " +
 141  
                                   "source file" + ( sourceFiles.length == 1 ? "" : "s" ) +
 142  14
                                   " to " + destinationDir.getAbsolutePath() );
 143  
         }
 144  
 
 145  14
         String[] args = buildCompilerArguments( config, sourceFiles );
 146  
 
 147  
         CompilerResult result;
 148  
 
 149  14
         if ( config.isFork() )
 150  
         {
 151  0
             String executable = config.getExecutable();
 152  
 
 153  0
             if ( StringUtils.isEmpty( executable ) )
 154  
             {
 155  
                 try
 156  
                 {
 157  0
                     executable = getJavacExecutable();
 158  
                 }
 159  0
                 catch ( IOException e )
 160  
                 {
 161  0
                     getLogger().warn( "Unable to autodetect 'javac' path, using 'javac' from the environment." );
 162  0
                     executable = "javac";
 163  0
                 }
 164  
             }
 165  
 
 166  0
             result = compileOutOfProcess( config, executable, args );
 167  0
         }
 168  
         else
 169  
         {
 170  14
             if ( isJava16() && !config.isForceJavacCompilerUse() )
 171  
             {
 172  
                 // use fqcn to prevent loading of the class on 1.5 environment !
 173  7
                 result =
 174  7
                     org.codehaus.plexus.compiler.javac.JavaxToolsCompiler.compileInProcess( args, config, sourceFiles );
 175  
             }
 176  
             else
 177  
             {
 178  7
                 result = compileInProcess( args, config );
 179  
             }
 180  
         }
 181  
 
 182  14
         return result;
 183  
     }
 184  
 
 185  
     protected static boolean isJava16()
 186  
     {
 187  
         try
 188  
         {
 189  44
             Thread.currentThread().getContextClassLoader().loadClass( "javax.tools.ToolProvider" );
 190  44
             return true;
 191  
         }
 192  0
         catch ( Exception e )
 193  
         {
 194  0
             return false;
 195  
         }
 196  
     }
 197  
 
 198  
     public String[] createCommandLine( CompilerConfiguration config )
 199  
         throws CompilerException
 200  
     {
 201  0
         return buildCompilerArguments( config, getSourceFiles( config ) );
 202  
     }
 203  
 
 204  
     public static String[] buildCompilerArguments( CompilerConfiguration config, String[] sourceFiles )
 205  
     {
 206  30
         List<String> args = new ArrayList<String>();
 207  
 
 208  
         // ----------------------------------------------------------------------
 209  
         // Set output
 210  
         // ----------------------------------------------------------------------
 211  
 
 212  30
         File destinationDir = new File( config.getOutputLocation() );
 213  
 
 214  30
         args.add( "-d" );
 215  
 
 216  30
         args.add( destinationDir.getAbsolutePath() );
 217  
 
 218  
         // ----------------------------------------------------------------------
 219  
         // Set the class and source paths
 220  
         // ----------------------------------------------------------------------
 221  
 
 222  30
         List<String> classpathEntries = config.getClasspathEntries();
 223  30
         if ( classpathEntries != null && !classpathEntries.isEmpty() )
 224  
         {
 225  24
             args.add( "-classpath" );
 226  
 
 227  24
             args.add( getPathString( classpathEntries ) );
 228  
         }
 229  
         
 230  30
         List<String> modulepathEntries = config.getModulepathEntries();
 231  30
         if ( modulepathEntries != null && !modulepathEntries.isEmpty() )
 232  
         {
 233  0
             args.add( "-modulepath" );
 234  
 
 235  0
             args.add( getPathString( modulepathEntries ) );
 236  
         }
 237  
 
 238  30
         List<String> sourceLocations = config.getSourceLocations();
 239  30
         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  24
             args.add( "-sourcepath" );
 244  
 
 245  24
             args.add( getPathString( sourceLocations ) );
 246  
         }
 247  30
         if ( !isJava16() || config.isForceJavacCompilerUse() || config.isFork() )
 248  
         {
 249  7
             args.addAll( Arrays.asList( sourceFiles ) );
 250  
         }
 251  
 
 252  30
         if ( !isPreJava16( config ) )
 253  
         {
 254  
             //now add jdk 1.6 annotation processing related parameters
 255  
 
 256  0
             if ( config.getGeneratedSourcesDirectory() != null )
 257  
             {
 258  0
                 config.getGeneratedSourcesDirectory().mkdirs();
 259  
 
 260  0
                 args.add( "-s" );
 261  0
                 args.add( config.getGeneratedSourcesDirectory().getAbsolutePath() );
 262  
             }
 263  0
             if ( config.getProc() != null )
 264  
             {
 265  0
                 args.add( "-proc:" + config.getProc() );
 266  
             }
 267  0
             if ( config.getAnnotationProcessors() != null )
 268  
             {
 269  0
                 args.add( "-processor" );
 270  0
                 String[] procs = config.getAnnotationProcessors();
 271  0
                 StringBuilder buffer = new StringBuilder();
 272  0
                 for ( int i = 0; i < procs.length; i++ )
 273  
                 {
 274  0
                     if ( i > 0 )
 275  
                     {
 276  0
                         buffer.append( "," );
 277  
                     }
 278  
 
 279  0
                     buffer.append( procs[i] );
 280  
                 }
 281  0
                 args.add( buffer.toString() );
 282  
             }
 283  0
             if ( config.getProcessorPathEntries() != null && !config.getProcessorPathEntries().isEmpty() ) {
 284  0
                 args.add( "-processorpath" );
 285  
 
 286  0
                 args.add( getPathString( config.getProcessorPathEntries() ) );
 287  
             }
 288  
         }
 289  
 
 290  30
         if ( config.isOptimize() )
 291  
         {
 292  0
             args.add( "-O" );
 293  
         }
 294  
 
 295  30
         if ( config.isDebug() )
 296  
         {
 297  24
             if ( StringUtils.isNotEmpty( config.getDebugLevel() ) )
 298  
             {
 299  2
                 args.add( "-g:" + config.getDebugLevel() );
 300  
             }
 301  
             else
 302  
             {
 303  22
                 args.add( "-g" );
 304  
             }
 305  
         }
 306  
 
 307  30
         if ( config.isVerbose() )
 308  
         {
 309  0
             args.add( "-verbose" );
 310  
         }
 311  
 
 312  30
         if ( config.isShowDeprecation() )
 313  
         {
 314  24
             args.add( "-deprecation" );
 315  
 
 316  
             // This is required to actually display the deprecation messages
 317  24
             config.setShowWarnings( true );
 318  
         }
 319  
 
 320  30
         if ( !config.isShowWarnings() )
 321  
         {
 322  0
             args.add( "-nowarn" );
 323  
         }
 324  
         
 325  30
         if ( config.isFailOnWarning() )
 326  
         {
 327  2
             args.add( "-Werror" );
 328  
         }
 329  
 
 330  30
         if ( !StringUtils.isEmpty( config.getReleaseVersion() ) )
 331  
         {
 332  2
             args.add( "-release" );
 333  2
             args.add( config.getReleaseVersion() );
 334  
         }
 335  
         else
 336  
         {
 337  
             // TODO: this could be much improved
 338  28
             if ( StringUtils.isEmpty( config.getTargetVersion() ) )
 339  
             {
 340  
                 // Required, or it defaults to the target of your JDK (eg 1.5)
 341  16
                 args.add( "-target" );
 342  16
                 args.add( "1.1" );
 343  
             }
 344  
             else
 345  
             {
 346  12
                 args.add( "-target" );
 347  12
                 args.add( config.getTargetVersion() );
 348  
             }
 349  
 
 350  28
             if ( !suppressSource( config ) && StringUtils.isEmpty( config.getSourceVersion() ) )
 351  
             {
 352  
                 // If omitted, later JDKs complain about a 1.1 target
 353  16
                 args.add( "-source" );
 354  16
                 args.add( "1.3" );
 355  
             }
 356  12
             else if ( !suppressSource( config ) )
 357  
             {
 358  10
                 args.add( "-source" );
 359  10
                 args.add( config.getSourceVersion() );
 360  
             }            
 361  
         }
 362  
 
 363  
 
 364  30
         if ( !suppressEncoding( config ) && !StringUtils.isEmpty( config.getSourceEncoding() ) )
 365  
         {
 366  8
             args.add( "-encoding" );
 367  8
             args.add( config.getSourceEncoding() );
 368  
         }
 369  
 
 370  30
         for ( Map.Entry<String, String> entry : config.getCustomCompilerArgumentsAsMap().entrySet() )
 371  
         {
 372  22
             String key = entry.getKey();
 373  
 
 374  22
             if ( StringUtils.isEmpty( key ) || key.startsWith( "-J" ) )
 375  
             {
 376  2
                 continue;
 377  
             }
 378  
 
 379  20
             args.add( key );
 380  
 
 381  20
             String value = entry.getValue();
 382  
 
 383  20
             if ( StringUtils.isEmpty( value ) )
 384  
             {
 385  10
                 continue;
 386  
             }
 387  
 
 388  10
             args.add( value );
 389  10
         }
 390  
 
 391  30
         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  70
         String v = config.getCompilerVersion();
 404  
 
 405  70
         if ( v == null )
 406  
         {
 407  52
             return false;
 408  
         }
 409  
 
 410  18
         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  30
         String v = config.getCompilerVersion();
 423  
 
 424  30
         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  24
             String s = config.getSourceVersion();
 429  24
             if ( s == null )
 430  
             {
 431  
                 //now return true, as the 1.6 version is not the default - 1.4 is.
 432  18
                 return true;
 433  
             }
 434  6
             return s.startsWith( "1.5" ) || s.startsWith( "1.4" ) || s.startsWith( "1.3" ) || s.startsWith( "1.2" )
 435  0
                 || s.startsWith( "1.1" ) || s.startsWith( "1.0" );
 436  
         }
 437  
 
 438  6
         return v.startsWith( "1.5" ) || v.startsWith( "1.4" ) || v.startsWith( "1.3" ) || v.startsWith( "1.2" )
 439  0
             || v.startsWith( "1.1" ) || v.startsWith( "1.0" );
 440  
     }
 441  
 
 442  
 
 443  
     private static boolean suppressSource( CompilerConfiguration config )
 444  
     {
 445  40
         return isPreJava14( config );
 446  
     }
 447  
 
 448  
     private static boolean suppressEncoding( CompilerConfiguration config )
 449  
     {
 450  30
         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  0
         Commandline cli = new Commandline();
 467  
 
 468  0
         cli.setWorkingDirectory( config.getWorkingDirectory().getAbsolutePath() );
 469  
 
 470  0
         cli.setExecutable( executable );
 471  
 
 472  
         try
 473  
         {
 474  0
             File argumentsFile = createFileWithArguments( args, config.getOutputLocation() );
 475  0
             cli.addArguments(
 476  0
                 new String[]{ "@" + argumentsFile.getCanonicalPath().replace( File.separatorChar, '/' ) } );
 477  
 
 478  0
             if ( !StringUtils.isEmpty( config.getMaxmem() ) )
 479  
             {
 480  0
                 cli.addArguments( new String[]{ "-J-Xmx" + config.getMaxmem() } );
 481  
             }
 482  
 
 483  0
             if ( !StringUtils.isEmpty( config.getMeminitial() ) )
 484  
             {
 485  0
                 cli.addArguments( new String[]{ "-J-Xms" + config.getMeminitial() } );
 486  
             }
 487  
 
 488  0
             for ( String key : config.getCustomCompilerArgumentsAsMap().keySet() )
 489  
             {
 490  0
                 if ( StringUtils.isNotEmpty( key ) && key.startsWith( "-J" ) )
 491  
                 {
 492  0
                     cli.addArguments( new String[]{ key } );
 493  
                 }
 494  0
             }
 495  
         }
 496  0
         catch ( IOException e )
 497  
         {
 498  0
             throw new CompilerException( "Error creating file with javac arguments", e );
 499  0
         }
 500  
 
 501  0
         CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
 502  
 
 503  
         int returnCode;
 504  
 
 505  
         List<CompilerMessage> messages;
 506  
 
 507  0
         if ( ( getLogger() != null ) && getLogger().isDebugEnabled() )
 508  
         {
 509  0
             File commandLineFile =
 510  0
                 new File( config.getOutputLocation(), "javac." + ( Os.isFamily( Os.FAMILY_WINDOWS ) ? "bat" : "sh" ) );
 511  
             try
 512  
             {
 513  0
                 FileUtils.fileWrite( commandLineFile.getAbsolutePath(), cli.toString().replaceAll( "'", "" ) );
 514  
 
 515  0
                 if ( !Os.isFamily( Os.FAMILY_WINDOWS ) )
 516  
                 {
 517  0
                     Runtime.getRuntime().exec( new String[]{ "chmod", "a+x", commandLineFile.getAbsolutePath() } );
 518  
                 }
 519  
             }
 520  0
             catch ( IOException e )
 521  
             {
 522  0
                 if ( ( getLogger() != null ) && getLogger().isWarnEnabled() )
 523  
                 {
 524  0
                     getLogger().warn( "Unable to write '" + commandLineFile.getName() + "' debug script file", e );
 525  
                 }
 526  0
             }
 527  
         }
 528  
 
 529  
         try
 530  
         {
 531  0
             returnCode = CommandLineUtils.executeCommandLine( cli, out, out );
 532  
 
 533  0
             messages = parseModernStream( returnCode, new BufferedReader( new StringReader( out.getOutput() ) ) );
 534  
         }
 535  0
         catch ( CommandLineException e )
 536  
         {
 537  0
             throw new CompilerException( "Error while executing the external compiler.", e );
 538  
         }
 539  0
         catch ( IOException e )
 540  
         {
 541  0
             throw new CompilerException( "Error while executing the external compiler.", e );
 542  0
         }
 543  
 
 544  0
         boolean success = returnCode == 0;
 545  0
         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  7
         final Class<?> javacClass = getJavacClass( config );
 561  7
         final Thread thread = Thread.currentThread();
 562  7
         final ClassLoader contextClassLoader = thread.getContextClassLoader();
 563  7
         thread.setContextClassLoader( javacClass.getClassLoader() );
 564  7
         getLogger().debug( "ttcl changed run compileInProcessWithProperClassloader" );
 565  
         try
 566  
         {
 567  7
             return compileInProcessWithProperClassloader(javacClass, args);
 568  
         }
 569  
         finally
 570  
         {
 571  7
             releaseJavaccClass( javacClass, config );
 572  7
             thread.setContextClassLoader( contextClassLoader );
 573  
         }
 574  
     }
 575  
 
 576  
     protected CompilerResult compileInProcessWithProperClassloader( Class<?> javacClass, String[] args )
 577  
         throws CompilerException {
 578  7
       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  7
         StringWriter out = new StringWriter();
 588  
 
 589  
         Integer ok;
 590  
 
 591  
         List<CompilerMessage> messages;
 592  
 
 593  
         try
 594  
         {
 595  7
             Method compile = javacClass.getMethod( "compile", new Class[]{ String[].class, PrintWriter.class } );
 596  
 
 597  7
             ok = (Integer) compile.invoke( null, new Object[]{ args, new PrintWriter( out ) } );
 598  
 
 599  7
             messages = parseModernStream( ok.intValue(), new BufferedReader( new StringReader( out.toString() ) ) );
 600  
         }
 601  0
         catch ( NoSuchMethodException e )
 602  
         {
 603  0
             throw new CompilerException( "Error while executing the compiler.", e );
 604  
         }
 605  0
         catch ( IllegalAccessException e )
 606  
         {
 607  0
             throw new CompilerException( "Error while executing the compiler.", e );
 608  
         }
 609  0
         catch ( InvocationTargetException e )
 610  
         {
 611  0
             throw new CompilerException( "Error while executing the compiler.", e );
 612  
         }
 613  0
         catch ( IOException e )
 614  
         {
 615  0
             throw new CompilerException( "Error while executing the compiler.", e );
 616  7
         }
 617  
 
 618  7
         boolean success = ok.intValue() == 0;
 619  7
         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  15
         List<CompilerMessage> errors = new ArrayList<CompilerMessage>();
 634  
 
 635  
         String line;
 636  
 
 637  15
         StringBuilder buffer = new StringBuilder();
 638  
         
 639  15
         boolean hasPointer = false;
 640  
 
 641  
         while ( true )
 642  
         {
 643  319
             line = input.readLine();
 644  
             
 645  319
             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  15
                 if ( buffer.length() > 0 && ( buffer.toString().startsWith( "javac:" )
 650  15
                     || buffer.toString().startsWith( "An exception has occurred in the compiler" ) ) )
 651  
                 {
 652  1
                     errors.add( new CompilerMessage( buffer.toString(), CompilerMessage.Kind.ERROR ) );
 653  
                 }
 654  15
                 return errors;
 655  
             }
 656  
 
 657  
             // new error block?
 658  304
             if ( !line.startsWith( " " ) && hasPointer )
 659  
             {
 660  
                 // add the error bean
 661  19
                 errors.add( parseModernError( exitCode, buffer.toString() ) );
 662  
                 
 663  
                 // reset for next error block
 664  19
                 buffer = new StringBuilder(); // this is quicker than clearing it
 665  
                 
 666  19
                 hasPointer = false;
 667  
             }
 668  
 
 669  
             // TODO: there should be a better way to parse these
 670  304
             if ( ( buffer.length() == 0 ) && line.startsWith( "error: " ) )
 671  
             {
 672  0
                 errors.add( new CompilerMessage( line, true ) );
 673  
             }
 674  304
             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  304
             else if ( ( buffer.length() == 0 ) && isMisc( line ) )
 679  
             {
 680  
                 // verbose output was set
 681  104
                 errors.add( new CompilerMessage( line, CompilerMessage.Kind.OTHER ) );
 682  
             }
 683  
             else
 684  
             {
 685  200
                 buffer.append( line );
 686  
 
 687  200
                 buffer.append( EOL );
 688  
             }
 689  
             
 690  304
             if ( line.endsWith( "^" ) )
 691  
             {
 692  19
                 hasPointer = true;
 693  
             }
 694  
         }
 695  
     }
 696  
     
 697  
     private static boolean isMisc( String line )
 698  
     {
 699  138
         return startsWithPrefix( line, MISC_PREFIXES );
 700  
     }
 701  
 
 702  
     private static boolean isNote( String line )
 703  
     {
 704  138
         return startsWithPrefix( line, NOTE_PREFIXES );
 705  
     }
 706  
     
 707  
     private static boolean startsWithPrefix( String line, String[] prefixes )
 708  
     {
 709  724
         for ( int i = 0; i < prefixes.length; i++ )
 710  
         {
 711  552
             if ( line.startsWith( prefixes[i] ) )
 712  
             {
 713  104
                 return true;
 714  
             }
 715  
         }
 716  172
         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  25
         StringTokenizer tokens = new StringTokenizer( error, ":" );
 729  
 
 730  25
         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  25
             String file = null;
 743  
 
 744  25
             String currentToken = null;
 745  
 
 746  
             do
 747  
             {
 748  238
                 if ( currentToken != null )
 749  
                 {
 750  213
                     if ( file == null )
 751  
                     {
 752  25
                         file = currentToken;
 753  
                     }
 754  
                     else
 755  
                     {
 756  188
                         file = file + ':' + currentToken;
 757  
                     }
 758  
                 }
 759  
 
 760  238
                 currentToken = tokens.nextToken();
 761  
 
 762  
                 // Probably the only backward compatible means of checking if a string is an integer.
 763  
 
 764  238
                 tokenIsAnInteger = true;
 765  
 
 766  
                 try
 767  
                 {
 768  238
                     Integer.parseInt( currentToken );
 769  
                 }
 770  213
                 catch ( NumberFormatException e )
 771  
                 {
 772  213
                     tokenIsAnInteger = false;
 773  25
                 }
 774  
             }
 775  238
             while ( !tokenIsAnInteger );
 776  
 
 777  25
             String lineIndicator = currentToken;
 778  
 
 779  25
             int startOfFileName = file.lastIndexOf( ']' );
 780  
 
 781  25
             if ( startOfFileName > -1 )
 782  
             {
 783  6
                 file = file.substring( startOfFileName + 1 + EOL.length() );
 784  
             }
 785  
 
 786  25
             int line = Integer.parseInt( lineIndicator );
 787  
 
 788  25
             msgBuffer = new StringBuilder();
 789  
 
 790  25
             String msg = tokens.nextToken( EOL ).substring( 2 );
 791  
 
 792  
             // Remove the 'warning: ' prefix
 793  25
             String warnPrefix = getWarnPrefix( msg );
 794  25
             if ( warnPrefix != null )
 795  
             {
 796  7
                 isError = false;
 797  7
                 msg = msg.substring( warnPrefix.length() );
 798  
             }
 799  
             else
 800  
             {
 801  18
                 isError = exitCode != 0;
 802  
             }
 803  
 
 804  25
             msgBuffer.append( msg );
 805  
 
 806  25
             msgBuffer.append( EOL );
 807  
 
 808  25
             String context = tokens.nextToken( EOL );
 809  
             
 810  25
             String pointer = null;
 811  
             
 812  
             do
 813  
             {
 814  43
                 String msgLine = tokens.nextToken( EOL );
 815  
 
 816  43
                 if ( pointer != null )
 817  
                 {
 818  7
                     msgBuffer.append( msgLine );
 819  
 
 820  7
                     msgBuffer.append( EOL );
 821  
                 }
 822  36
                 else if ( msgLine.endsWith( "^" ) )
 823  
                 {
 824  25
                     pointer = msgLine;
 825  
                 }
 826  
                 else
 827  
                 {
 828  11
                     msgBuffer.append( context );
 829  
 
 830  11
                     msgBuffer.append( EOL );
 831  
 
 832  11
                     context = msgLine;
 833  
                 }
 834  
             }
 835  43
             while ( tokens.hasMoreTokens() );
 836  
 
 837  25
             msgBuffer.append( EOL );
 838  
 
 839  25
             String message = msgBuffer.toString();
 840  
 
 841  25
             int startcolumn = pointer.indexOf( "^" );
 842  
 
 843  25
             int endcolumn = context == null ? startcolumn : context.indexOf( " ", startcolumn );
 844  
 
 845  25
             if ( endcolumn == -1 )
 846  
             {
 847  16
                 endcolumn = context.length();
 848  
             }
 849  
 
 850  25
             return new CompilerMessage( file, isError, line, startcolumn, line, endcolumn, message.trim() );
 851  
         }
 852  0
         catch ( NoSuchElementException e )
 853  
         {
 854  0
             return new CompilerMessage( "no more tokens - could not parse error message: " + error, isError );
 855  
         }
 856  0
         catch ( NumberFormatException e )
 857  
         {
 858  0
             return new CompilerMessage( "could not parse error message: " + error, isError );
 859  
         }
 860  0
         catch ( Exception e )
 861  
         {
 862  0
             return new CompilerMessage( "could not parse error message: " + error, isError );
 863  
         }
 864  
     }
 865  
 
 866  
     private static String getWarnPrefix( String msg )
 867  
     {
 868  79
         for ( int i = 0; i < WARNING_PREFIXES.length; i++ )
 869  
         {
 870  61
             if ( msg.startsWith( WARNING_PREFIXES[i] ) )
 871  
             {
 872  7
                 return WARNING_PREFIXES[i];
 873  
             }
 874  
         }
 875  18
         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  0
         PrintWriter writer = null;
 889  
         try
 890  
         {
 891  
             File tempFile;
 892  0
             if ( ( getLogger() != null ) && getLogger().isDebugEnabled() )
 893  
             {
 894  0
                 tempFile =
 895  0
                     File.createTempFile( JavacCompiler.class.getName(), "arguments", new File( outputDirectory ) );
 896  
             }
 897  
             else
 898  
             {
 899  0
                 tempFile = File.createTempFile( JavacCompiler.class.getName(), "arguments" );
 900  0
                 tempFile.deleteOnExit();
 901  
             }
 902  
 
 903  0
             writer = new PrintWriter( new FileWriter( tempFile ) );
 904  
 
 905  0
             for ( int i = 0; i < args.length; i++ )
 906  
             {
 907  0
                 String argValue = args[i].replace( File.separatorChar, '/' );
 908  
 
 909  0
                 writer.write( "\"" + argValue + "\"" );
 910  
 
 911  0
                 writer.println();
 912  
             }
 913  
 
 914  0
             writer.flush();
 915  
 
 916  0
             return tempFile;
 917  
 
 918  
         }
 919  
         finally
 920  
         {
 921  0
             if ( writer != null )
 922  
             {
 923  0
                 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  0
         String javacCommand = "javac" + ( Os.isFamily( Os.FAMILY_WINDOWS ) ? ".exe" : "" );
 939  
 
 940  0
         String javaHome = System.getProperty( "java.home" );
 941  
         File javacExe;
 942  0
         if ( Os.isName( "AIX" ) )
 943  
         {
 944  0
             javacExe = new File( javaHome + File.separator + ".." + File.separator + "sh", javacCommand );
 945  
         }
 946  0
         else if ( Os.isName( "Mac OS X" ) )
 947  
         {
 948  0
             javacExe = new File( javaHome + File.separator + "bin", javacCommand );
 949  
         }
 950  
         else
 951  
         {
 952  0
             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  0
         if ( !javacExe.isFile() )
 959  
         {
 960  0
             Properties env = CommandLineUtils.getSystemEnvVars();
 961  0
             javaHome = env.getProperty( "JAVA_HOME" );
 962  0
             if ( StringUtils.isEmpty( javaHome ) )
 963  
             {
 964  0
                 throw new IOException( "The environment variable JAVA_HOME is not correctly set." );
 965  
             }
 966  0
             if ( !new File( javaHome ).isDirectory() )
 967  
             {
 968  0
                 throw new IOException(
 969  
                     "The environment variable JAVA_HOME=" + javaHome + " doesn't exist or is not a valid directory." );
 970  
             }
 971  
 
 972  0
             javacExe = new File( env.getProperty( "JAVA_HOME" ) + File.separator + "bin", javacCommand );
 973  
         }
 974  
 
 975  0
         if ( !javacExe.isFile() )
 976  
         {
 977  0
             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  0
         return javacExe.getAbsolutePath();
 982  
     }
 983  
 
 984  
     private void releaseJavaccClass( Class<?> javaccClass, CompilerConfiguration compilerConfiguration )
 985  
     {
 986  7
         if ( compilerConfiguration.getCompilerReuseStrategy()
 987  
             == CompilerConfiguration.CompilerReuseStrategy.ReuseCreated )
 988  
         {
 989  7
             javaccClasses.add( javaccClass );
 990  
         }
 991  
 
 992  7
     }
 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  7
         Class<?> c = null;
 1004  7
         switch ( compilerConfiguration.getCompilerReuseStrategy() )
 1005  
         {
 1006  
             case AlwaysNew:
 1007  0
                 return createJavacClass();
 1008  
             case ReuseCreated:
 1009  7
                 synchronized ( javaccClasses )
 1010  
                 {
 1011  7
                     if ( javaccClasses.size() > 0 )
 1012  
                     {
 1013  6
                         c = javaccClasses.get( 0 );
 1014  6
                         javaccClasses.remove( c );
 1015  6
                         return c;
 1016  
                     }
 1017  1
                 }
 1018  1
                 c = createJavacClass();
 1019  1
                 return c;
 1020  
             case ReuseSame:
 1021  
             default:
 1022  0
                 c = JavacCompiler.JAVAC_CLASS;
 1023  0
                 if ( c != null )
 1024  
                 {
 1025  0
                     return c;
 1026  
                 }
 1027  0
                 synchronized ( JavacCompiler.LOCK )
 1028  
                 {
 1029  0
                     if ( c == null )
 1030  
                     {
 1031  0
                         JavacCompiler.JAVAC_CLASS = c = createJavacClass();
 1032  
                     }
 1033  0
                     return c;
 1034  0
                 }
 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  1
             return JavacCompiler.class.getClassLoader().loadClass( JavacCompiler.JAVAC_CLASSNAME );
 1052  
         }
 1053  0
         catch ( ClassNotFoundException ex )
 1054  
         {
 1055  
             // ok
 1056  
         }
 1057  
 
 1058  0
         final File toolsJar = new File( System.getProperty( "java.home" ), "../lib/tools.jar" );
 1059  0
         if ( !toolsJar.exists() )
 1060  
         {
 1061  0
             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  0
             URL[] originalUrls = ((URLClassLoader) JavacCompiler.class.getClassLoader()).getURLs();
 1069  0
             URL[] urls = new URL[originalUrls.length + 1];
 1070  0
             urls[0] = toolsJar.toURI().toURL();
 1071  0
             System.arraycopy(originalUrls, 0, urls, 1, originalUrls.length);
 1072  0
             ClassLoader javacClassLoader = new URLClassLoader(urls);
 1073  
 
 1074  0
             final Thread thread = Thread.currentThread();
 1075  0
             final ClassLoader contextClassLoader = thread.getContextClassLoader();
 1076  0
             thread.setContextClassLoader( javacClassLoader );
 1077  
             try
 1078  
             {
 1079  
                 //return Class.forName( JavacCompiler.JAVAC_CLASSNAME, true, javacClassLoader );
 1080  0
                 return javacClassLoader.loadClass( JavacCompiler.JAVAC_CLASSNAME );
 1081  
             }
 1082  
             finally
 1083  
             {
 1084  0
                 thread.setContextClassLoader( contextClassLoader );
 1085  
             }
 1086  
         }
 1087  0
         catch ( MalformedURLException ex )
 1088  
         {
 1089  0
             throw new CompilerException(
 1090  
                 "Could not convert the file reference to tools.jar to a URL, path to tools.jar: '"
 1091  0
                     + toolsJar.getAbsolutePath() + "'.", ex );
 1092  
         }
 1093  0
         catch ( ClassNotFoundException ex )
 1094  
         {
 1095  0
             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  
 }