Coverage Report - org.codehaus.plexus.compiler.javac.JavacCompiler
 
Classes in this File Line Coverage Branch Coverage Complexity
JavacCompiler
61%
229/375
60%
168/279
8,4
JavacCompiler$1
100%
1/1
N/A
8,4
 
 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  52
             Thread.currentThread().getContextClassLoader().loadClass( "javax.tools.ToolProvider" );
 190  52
             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  38
         List<String> args = new ArrayList<String>();
 207  
 
 208  
         // ----------------------------------------------------------------------
 209  
         // Set output
 210  
         // ----------------------------------------------------------------------
 211  
 
 212  38
         File destinationDir = new File( config.getOutputLocation() );
 213  
 
 214  38
         args.add( "-d" );
 215  
 
 216  38
         args.add( destinationDir.getAbsolutePath() );
 217  
 
 218  
         // ----------------------------------------------------------------------
 219  
         // Set the class and source paths
 220  
         // ----------------------------------------------------------------------
 221  
 
 222  38
         List<String> classpathEntries = config.getClasspathEntries();
 223  38
         if ( classpathEntries != null && !classpathEntries.isEmpty() )
 224  
         {
 225  26
             args.add( "-classpath" );
 226  
 
 227  26
             args.add( getPathString( classpathEntries ) );
 228  
         }
 229  
         
 230  38
         List<String> modulepathEntries = config.getModulepathEntries();
 231  38
         if ( modulepathEntries != null && !modulepathEntries.isEmpty() )
 232  
         {
 233  2
             args.add( "--module-path" );
 234  
 
 235  2
             args.add( getPathString( modulepathEntries ) );
 236  
         }
 237  
 
 238  38
         List<String> sourceLocations = config.getSourceLocations();
 239  38
         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  26
             args.add( "-sourcepath" );
 244  
 
 245  26
             args.add( getPathString( sourceLocations ) );
 246  
         }
 247  38
         if ( !isJava16() || config.isForceJavacCompilerUse() || config.isFork() )
 248  
         {
 249  7
             args.addAll( Arrays.asList( sourceFiles ) );
 250  
         }
 251  
 
 252  38
         if ( !isPreJava16( config ) )
 253  
         {
 254  
             //now add jdk 1.6 annotation processing related parameters
 255  
 
 256  2
             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  2
             if ( config.getProc() != null )
 264  
             {
 265  0
                 args.add( "-proc:" + config.getProc() );
 266  
             }
 267  2
             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  2
             if ( config.getProcessorPathEntries() != null && !config.getProcessorPathEntries().isEmpty() ) {
 284  0
                 args.add( "-processorpath" );
 285  
 
 286  0
                 args.add( getPathString( config.getProcessorPathEntries() ) );
 287  
             }
 288  
         }
 289  
 
 290  38
         if ( config.isOptimize() )
 291  
         {
 292  0
             args.add( "-O" );
 293  
         }
 294  
 
 295  38
         if ( config.isDebug() )
 296  
         {
 297  26
             if ( StringUtils.isNotEmpty( config.getDebugLevel() ) )
 298  
             {
 299  2
                 args.add( "-g:" + config.getDebugLevel() );
 300  
             }
 301  
             else
 302  
             {
 303  24
                 args.add( "-g" );
 304  
             }
 305  
         }
 306  
 
 307  38
         if ( config.isVerbose() )
 308  
         {
 309  0
             args.add( "-verbose" );
 310  
         }
 311  
 
 312  38
         if ( !isPreJava18(config) && config.isParameters() )
 313  
         {
 314  2
             args.add( "-parameters" );
 315  
         }
 316  
 
 317  38
         if ( config.isShowDeprecation() )
 318  
         {
 319  26
             args.add( "-deprecation" );
 320  
 
 321  
             // This is required to actually display the deprecation messages
 322  26
             config.setShowWarnings( true );
 323  
         }
 324  
 
 325  38
         if ( !config.isShowWarnings() )
 326  
         {
 327  0
             args.add( "-nowarn" );
 328  
         }
 329  
         
 330  38
         if ( config.isFailOnWarning() )
 331  
         {
 332  2
             args.add( "-Werror" );
 333  
         }
 334  
 
 335  38
         if ( !StringUtils.isEmpty( config.getReleaseVersion() ) )
 336  
         {
 337  2
             args.add( "--release" );
 338  2
             args.add( config.getReleaseVersion() );
 339  
         }
 340  
         else
 341  
         {
 342  
             // TODO: this could be much improved
 343  36
             if ( StringUtils.isEmpty( config.getTargetVersion() ) )
 344  
             {
 345  
                 // Required, or it defaults to the target of your JDK (eg 1.5)
 346  22
                 args.add( "-target" );
 347  22
                 args.add( "1.1" );
 348  
             }
 349  
             else
 350  
             {
 351  14
                 args.add( "-target" );
 352  14
                 args.add( config.getTargetVersion() );
 353  
             }
 354  
 
 355  36
             if ( !suppressSource( config ) && StringUtils.isEmpty( config.getSourceVersion() ) )
 356  
             {
 357  
                 // If omitted, later JDKs complain about a 1.1 target
 358  22
                 args.add( "-source" );
 359  22
                 args.add( "1.3" );
 360  
             }
 361  14
             else if ( !suppressSource( config ) )
 362  
             {
 363  12
                 args.add( "-source" );
 364  12
                 args.add( config.getSourceVersion() );
 365  
             }            
 366  
         }
 367  
 
 368  
 
 369  38
         if ( !suppressEncoding( config ) && !StringUtils.isEmpty( config.getSourceEncoding() ) )
 370  
         {
 371  10
             args.add( "-encoding" );
 372  10
             args.add( config.getSourceEncoding() );
 373  
         }
 374  
 
 375  38
         if ( !StringUtils.isEmpty( config.getModuleVersion() ) )
 376  
         {
 377  2
             args.add( "--module-version" );
 378  2
             args.add( config.getModuleVersion() );
 379  
         }
 380  
 
 381  38
         for ( Map.Entry<String, String> entry : config.getCustomCompilerArgumentsEntries() )
 382  
         {
 383  30
             String key = entry.getKey();
 384  
 
 385  30
             if ( StringUtils.isEmpty( key ) || key.startsWith( "-J" ) )
 386  
             {
 387  2
                 continue;
 388  
             }
 389  
 
 390  28
             args.add( key );
 391  
 
 392  28
             String value = entry.getValue();
 393  
 
 394  28
             if ( StringUtils.isEmpty( value ) )
 395  
             {
 396  12
                 continue;
 397  
             }
 398  
 
 399  16
             args.add( value );
 400  16
         }
 401  
 
 402  38
         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  88
         String v = config.getCompilerVersion();
 415  
 
 416  88
         if ( v == null )
 417  
         {
 418  64
             return false;
 419  
         }
 420  
 
 421  24
         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  38
         String v = config.getCompilerVersion();
 434  
 
 435  38
         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  30
             String s = config.getSourceVersion();
 440  30
             if ( s == null )
 441  
             {
 442  
                 //now return true, as the 1.6 version is not the default - 1.4 is.
 443  24
                 return true;
 444  
             }
 445  12
             return s.startsWith( "1.5" ) || s.startsWith( "1.4" ) || s.startsWith( "1.3" ) || s.startsWith( "1.2" )
 446  0
                 || s.startsWith( "1.1" ) || s.startsWith( "1.0" );
 447  
         }
 448  
 
 449  16
         return v.startsWith( "1.5" ) || v.startsWith( "1.4" ) || v.startsWith( "1.3" ) || v.startsWith( "1.2" )
 450  2
             || v.startsWith( "1.1" ) || v.startsWith( "1.0" );
 451  
     }
 452  
 
 453  
     private static boolean isPreJava18( CompilerConfiguration config )
 454  
     {
 455  38
         String v = config.getCompilerVersion();
 456  
 
 457  38
         if ( v == null )
 458  
         {
 459  30
             v = config.getSourceVersion();
 460  
         }
 461  
 
 462  38
         if ( v == null )
 463  
         {
 464  24
             return true;
 465  
         }
 466  
 
 467  28
         return v.startsWith( "1.7" ) || v.startsWith( "1.6" ) || v.startsWith( "1.5" ) || v.startsWith( "1.4" )
 468  10
                 || 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  50
         return isPreJava14( config );
 475  
     }
 476  
 
 477  
     private static boolean suppressEncoding( CompilerConfiguration config )
 478  
     {
 479  38
         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  0
         Commandline cli = new Commandline();
 496  
 
 497  0
         cli.setWorkingDirectory( config.getWorkingDirectory().getAbsolutePath() );
 498  
 
 499  0
         cli.setExecutable( executable );
 500  
 
 501  
         try
 502  
         {
 503  0
             File argumentsFile = createFileWithArguments( args, config.getOutputLocation() );
 504  0
             cli.addArguments(
 505  0
                 new String[]{ "@" + argumentsFile.getCanonicalPath().replace( File.separatorChar, '/' ) } );
 506  
 
 507  0
             if ( !StringUtils.isEmpty( config.getMaxmem() ) )
 508  
             {
 509  0
                 cli.addArguments( new String[]{ "-J-Xmx" + config.getMaxmem() } );
 510  
             }
 511  
 
 512  0
             if ( !StringUtils.isEmpty( config.getMeminitial() ) )
 513  
             {
 514  0
                 cli.addArguments( new String[]{ "-J-Xms" + config.getMeminitial() } );
 515  
             }
 516  
 
 517  0
             for ( String key : config.getCustomCompilerArgumentsAsMap().keySet() )
 518  
             {
 519  0
                 if ( StringUtils.isNotEmpty( key ) && key.startsWith( "-J" ) )
 520  
                 {
 521  0
                     cli.addArguments( new String[]{ key } );
 522  
                 }
 523  0
             }
 524  
         }
 525  0
         catch ( IOException e )
 526  
         {
 527  0
             throw new CompilerException( "Error creating file with javac arguments", e );
 528  0
         }
 529  
 
 530  0
         CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
 531  
 
 532  
         int returnCode;
 533  
 
 534  
         List<CompilerMessage> messages;
 535  
 
 536  0
         if ( ( getLogger() != null ) && getLogger().isDebugEnabled() )
 537  
         {
 538  0
             File commandLineFile =
 539  0
                 new File( config.getOutputLocation(), "javac." + ( Os.isFamily( Os.FAMILY_WINDOWS ) ? "bat" : "sh" ) );
 540  
             try
 541  
             {
 542  0
                 FileUtils.fileWrite( commandLineFile.getAbsolutePath(), cli.toString().replaceAll( "'", "" ) );
 543  
 
 544  0
                 if ( !Os.isFamily( Os.FAMILY_WINDOWS ) )
 545  
                 {
 546  0
                     Runtime.getRuntime().exec( new String[]{ "chmod", "a+x", commandLineFile.getAbsolutePath() } );
 547  
                 }
 548  
             }
 549  0
             catch ( IOException e )
 550  
             {
 551  0
                 if ( ( getLogger() != null ) && getLogger().isWarnEnabled() )
 552  
                 {
 553  0
                     getLogger().warn( "Unable to write '" + commandLineFile.getName() + "' debug script file", e );
 554  
                 }
 555  0
             }
 556  
         }
 557  
 
 558  
         try
 559  
         {
 560  0
             returnCode = CommandLineUtils.executeCommandLine( cli, out, out );
 561  
 
 562  0
             messages = parseModernStream( returnCode, new BufferedReader( new StringReader( out.getOutput() ) ) );
 563  
         }
 564  0
         catch ( CommandLineException e )
 565  
         {
 566  0
             throw new CompilerException( "Error while executing the external compiler.", e );
 567  
         }
 568  0
         catch ( IOException e )
 569  
         {
 570  0
             throw new CompilerException( "Error while executing the external compiler.", e );
 571  0
         }
 572  
 
 573  0
         boolean success = returnCode == 0;
 574  0
         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  7
         final Class<?> javacClass = getJavacClass( config );
 590  7
         final Thread thread = Thread.currentThread();
 591  7
         final ClassLoader contextClassLoader = thread.getContextClassLoader();
 592  7
         thread.setContextClassLoader( javacClass.getClassLoader() );
 593  7
         getLogger().debug( "ttcl changed run compileInProcessWithProperClassloader" );
 594  
         try
 595  
         {
 596  14
             return compileInProcessWithProperClassloader(javacClass, args);
 597  
         }
 598  
         finally
 599  
         {
 600  7
             releaseJavaccClass( javacClass, config );
 601  7
             thread.setContextClassLoader( contextClassLoader );
 602  
         }
 603  
     }
 604  
 
 605  
     protected CompilerResult compileInProcessWithProperClassloader( Class<?> javacClass, String[] args )
 606  
         throws CompilerException {
 607  7
       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  7
         StringWriter out = new StringWriter();
 617  
 
 618  
         Integer ok;
 619  
 
 620  
         List<CompilerMessage> messages;
 621  
 
 622  
         try
 623  
         {
 624  7
             Method compile = javacClass.getMethod( "compile", new Class[]{ String[].class, PrintWriter.class } );
 625  
 
 626  7
             ok = (Integer) compile.invoke( null, new Object[]{ args, new PrintWriter( out ) } );
 627  
 
 628  7
             messages = parseModernStream( ok.intValue(), new BufferedReader( new StringReader( out.toString() ) ) );
 629  
         }
 630  0
         catch ( NoSuchMethodException e )
 631  
         {
 632  0
             throw new CompilerException( "Error while executing the compiler.", e );
 633  
         }
 634  0
         catch ( IllegalAccessException e )
 635  
         {
 636  0
             throw new CompilerException( "Error while executing the compiler.", e );
 637  
         }
 638  0
         catch ( InvocationTargetException e )
 639  
         {
 640  0
             throw new CompilerException( "Error while executing the compiler.", e );
 641  
         }
 642  0
         catch ( IOException e )
 643  
         {
 644  0
             throw new CompilerException( "Error while executing the compiler.", e );
 645  7
         }
 646  
 
 647  7
         boolean success = ok.intValue() == 0;
 648  7
         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  19
         List<CompilerMessage> errors = new ArrayList<CompilerMessage>();
 663  
 
 664  
         String line;
 665  
 
 666  19
         StringBuilder buffer = new StringBuilder();
 667  
         
 668  19
         boolean hasPointer = false;
 669  
 
 670  
         while ( true )
 671  
         {
 672  336
             line = input.readLine();
 673  
             
 674  336
             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  17
                 if (buffer.length() > 0 && buffer.toString().startsWith("javac:"))
 679  
                 {
 680  0
                     errors.add( new CompilerMessage( buffer.toString(), CompilerMessage.Kind.ERROR ) );
 681  
                 }
 682  17
                 return errors;
 683  
             }
 684  
 
 685  
             // A compiler error occurred, treat everything that follows as part of the error.
 686  319
             if (line.startsWith( "An exception has occurred in the compiler") ) {
 687  2
                 buffer = new StringBuilder();
 688  
 
 689  32
                 while (line != null) {
 690  30
                     buffer.append(line);
 691  30
                     buffer.append(EOL);
 692  30
                     line = input.readLine();
 693  
                 }
 694  
 
 695  2
                 errors.add( new CompilerMessage( buffer.toString(), CompilerMessage.Kind.ERROR ) );
 696  2
                 return errors;
 697  
             }
 698  
 
 699  
             // new error block?
 700  317
             if ( !line.startsWith( " " ) && hasPointer )
 701  
             {
 702  
                 // add the error bean
 703  21
                 errors.add( parseModernError( exitCode, buffer.toString() ) );
 704  
                 
 705  
                 // reset for next error block
 706  21
                 buffer = new StringBuilder(); // this is quicker than clearing it
 707  
                 
 708  21
                 hasPointer = false;
 709  
             }
 710  
 
 711  
             // TODO: there should be a better way to parse these
 712  317
             if ( ( buffer.length() == 0 ) && line.startsWith( "error: " ) )
 713  
             {
 714  0
                 errors.add( new CompilerMessage( line, CompilerMessage.Kind.ERROR ) );
 715  
             }
 716  317
             else if ( ( buffer.length() == 0 ) && line.startsWith( "warning: " ) )
 717  
             {
 718  34
                 errors.add( new CompilerMessage( line, CompilerMessage.Kind.WARNING ) );
 719  
             }
 720  283
             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  283
             else if ( ( buffer.length() == 0 ) && isMisc( line ) )
 725  
             {
 726  
                 // verbose output was set
 727  183
                 errors.add( new CompilerMessage( line, CompilerMessage.Kind.OTHER ) );
 728  
             }
 729  
             else
 730  
             {
 731  100
                 buffer.append( line );
 732  
 
 733  100
                 buffer.append( EOL );
 734  
             }
 735  
             
 736  317
             if ( line.endsWith( "^" ) )
 737  
             {
 738  21
                 hasPointer = true;
 739  
             }
 740  
         }
 741  
     }
 742  
     
 743  
     private static boolean isMisc( String line )
 744  
     {
 745  222
         return startsWithPrefix( line, MISC_PREFIXES );
 746  
     }
 747  
 
 748  
     private static boolean isNote( String line )
 749  
     {
 750  222
         return startsWithPrefix( line, NOTE_PREFIXES );
 751  
     }
 752  
     
 753  
     private static boolean startsWithPrefix( String line, String[] prefixes )
 754  
     {
 755  1149
         for ( int i = 0; i < prefixes.length; i++ )
 756  
         {
 757  888
             if ( line.startsWith( prefixes[i] ) )
 758  
             {
 759  183
                 return true;
 760  
             }
 761  
         }
 762  261
         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  27
         final StringTokenizer tokens = new StringTokenizer( error, ":" );
 775  
 
 776  27
         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  27
             StringBuilder file = null;
 787  
 
 788  27
             String currentToken = null;
 789  
 
 790  
             do
 791  
             {
 792  66
                 if ( currentToken != null )
 793  
                 {
 794  39
                     if ( file == null )
 795  
                     {
 796  27
                         file = new StringBuilder(currentToken);
 797  
                     }
 798  
                     else
 799  
                     {
 800  12
                         file.append(':').append(currentToken);
 801  
                     }
 802  
                 }
 803  
 
 804  66
                 currentToken = tokens.nextToken();
 805  
 
 806  
                 // Probably the only backward compatible means of checking if a string is an integer.
 807  
 
 808  66
                 tokenIsAnInteger = true;
 809  
 
 810  
                 try
 811  
                 {
 812  66
                     Integer.parseInt( currentToken );
 813  
                 }
 814  39
                 catch ( NumberFormatException e )
 815  
                 {
 816  39
                     tokenIsAnInteger = false;
 817  27
                 }
 818  
             }
 819  66
             while ( !tokenIsAnInteger );
 820  
 
 821  27
             final String lineIndicator = currentToken;
 822  
 
 823  27
             final int startOfFileName = file.toString().lastIndexOf( ']' );
 824  
 
 825  27
             if ( startOfFileName > -1 )
 826  
             {
 827  0
                 file = new StringBuilder(file.substring(startOfFileName + 1 + EOL.length()));
 828  
             }
 829  
 
 830  27
             final int line = Integer.parseInt( lineIndicator );
 831  
 
 832  27
             final StringBuilder msgBuffer = new StringBuilder();
 833  
 
 834  27
             String msg = tokens.nextToken( EOL ).substring( 2 );
 835  
 
 836  
             // Remove the 'warning: ' prefix
 837  27
             final String warnPrefix = getWarnPrefix( msg );
 838  27
             if ( warnPrefix != null )
 839  
             {
 840  9
                 isError = false;
 841  9
                 msg = msg.substring( warnPrefix.length() );
 842  
             }
 843  
             else
 844  
             {
 845  18
                 isError = exitCode != 0;
 846  
             }
 847  
 
 848  27
             msgBuffer.append( msg );
 849  
 
 850  27
             msgBuffer.append( EOL );
 851  
 
 852  27
             String context = tokens.nextToken( EOL );
 853  
             
 854  27
             String pointer = null;
 855  
             
 856  
             do
 857  
             {
 858  45
                 final String msgLine = tokens.nextToken( EOL );
 859  
 
 860  45
                 if ( pointer != null )
 861  
                 {
 862  7
                     msgBuffer.append( msgLine );
 863  
 
 864  7
                     msgBuffer.append( EOL );
 865  
                 }
 866  38
                 else if ( msgLine.endsWith( "^" ) )
 867  
                 {
 868  27
                     pointer = msgLine;
 869  
                 }
 870  
                 else
 871  
                 {
 872  11
                     msgBuffer.append( context );
 873  
 
 874  11
                     msgBuffer.append( EOL );
 875  
 
 876  11
                     context = msgLine;
 877  
                 }
 878  
             }
 879  45
             while ( tokens.hasMoreTokens() );
 880  
 
 881  27
             msgBuffer.append( EOL );
 882  
 
 883  27
             final String message = msgBuffer.toString();
 884  
 
 885  27
             final int startcolumn = pointer.indexOf( "^" );
 886  
 
 887  27
             int endcolumn = (context == null) ? startcolumn : context.indexOf(" ", startcolumn);
 888  
 
 889  27
             if ( endcolumn == -1 )
 890  
             {
 891  18
                 endcolumn = context.length();
 892  
             }
 893  
 
 894  27
             return new CompilerMessage(file.toString(), isError, line, startcolumn, line, endcolumn, message.trim() );
 895  
         }
 896  0
         catch ( NoSuchElementException e )
 897  
         {
 898  0
             return new CompilerMessage( "no more tokens - could not parse error message: " + error, isError );
 899  
         }
 900  0
         catch ( NumberFormatException e )
 901  
         {
 902  0
             return new CompilerMessage( "could not parse error message: " + error, isError );
 903  
         }
 904  0
         catch ( Exception e )
 905  
         {
 906  0
             return new CompilerMessage( "could not parse error message: " + error, isError );
 907  
         }
 908  
     }
 909  
 
 910  
     private static String getWarnPrefix( String msg )
 911  
     {
 912  81
         for ( int i = 0; i < WARNING_PREFIXES.length; i++ )
 913  
         {
 914  63
             if ( msg.startsWith( WARNING_PREFIXES[i] ) )
 915  
             {
 916  9
                 return WARNING_PREFIXES[i];
 917  
             }
 918  
         }
 919  18
         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  0
         PrintWriter writer = null;
 933  
         try
 934  
         {
 935  
             File tempFile;
 936  0
             if ( ( getLogger() != null ) && getLogger().isDebugEnabled() )
 937  
             {
 938  0
                 tempFile =
 939  0
                     File.createTempFile( JavacCompiler.class.getName(), "arguments", new File( outputDirectory ) );
 940  
             }
 941  
             else
 942  
             {
 943  0
                 tempFile = File.createTempFile( JavacCompiler.class.getName(), "arguments" );
 944  0
                 tempFile.deleteOnExit();
 945  
             }
 946  
 
 947  0
             writer = new PrintWriter( new FileWriter( tempFile ) );
 948  
 
 949  0
             for ( int i = 0; i < args.length; i++ )
 950  
             {
 951  0
                 String argValue = args[i].replace( File.separatorChar, '/' );
 952  
 
 953  0
                 writer.write( "\"" + argValue + "\"" );
 954  
 
 955  0
                 writer.println();
 956  
             }
 957  
 
 958  0
             writer.flush();
 959  
 
 960  0
             return tempFile;
 961  
 
 962  
         }
 963  
         finally
 964  
         {
 965  0
             if ( writer != null )
 966  
             {
 967  0
                 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  0
         String javacCommand = "javac" + ( Os.isFamily( Os.FAMILY_WINDOWS ) ? ".exe" : "" );
 983  
 
 984  0
         String javaHome = System.getProperty( "java.home" );
 985  
         File javacExe;
 986  0
         if ( Os.isName( "AIX" ) )
 987  
         {
 988  0
             javacExe = new File( javaHome + File.separator + ".." + File.separator + "sh", javacCommand );
 989  
         }
 990  0
         else if ( Os.isName( "Mac OS X" ) )
 991  
         {
 992  0
             javacExe = new File( javaHome + File.separator + "bin", javacCommand );
 993  
         }
 994  
         else
 995  
         {
 996  0
             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  0
         if ( !javacExe.isFile() )
 1003  
         {
 1004  0
             Properties env = CommandLineUtils.getSystemEnvVars();
 1005  0
             javaHome = env.getProperty( "JAVA_HOME" );
 1006  0
             if ( StringUtils.isEmpty( javaHome ) )
 1007  
             {
 1008  0
                 throw new IOException( "The environment variable JAVA_HOME is not correctly set." );
 1009  
             }
 1010  0
             if ( !new File( javaHome ).isDirectory() )
 1011  
             {
 1012  0
                 throw new IOException(
 1013  
                     "The environment variable JAVA_HOME=" + javaHome + " doesn't exist or is not a valid directory." );
 1014  
             }
 1015  
 
 1016  0
             javacExe = new File( env.getProperty( "JAVA_HOME" ) + File.separator + "bin", javacCommand );
 1017  
         }
 1018  
 
 1019  0
         if ( !javacExe.isFile() )
 1020  
         {
 1021  0
             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  0
         return javacExe.getAbsolutePath();
 1026  
     }
 1027  
 
 1028  
     private void releaseJavaccClass( Class<?> javaccClass, CompilerConfiguration compilerConfiguration )
 1029  
     {
 1030  7
         if ( compilerConfiguration.getCompilerReuseStrategy()
 1031  
             == CompilerConfiguration.CompilerReuseStrategy.ReuseCreated )
 1032  
         {
 1033  7
             javaccClasses.add( javaccClass );
 1034  
         }
 1035  
 
 1036  7
     }
 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  7
         Class<?> c = null;
 1048  7
         switch ( compilerConfiguration.getCompilerReuseStrategy() )
 1049  
         {
 1050  
             case AlwaysNew:
 1051  0
                 return createJavacClass();
 1052  
             case ReuseCreated:
 1053  7
                 synchronized ( javaccClasses )
 1054  
                 {
 1055  7
                     if ( javaccClasses.size() > 0 )
 1056  
                     {
 1057  6
                         c = javaccClasses.get( 0 );
 1058  6
                         javaccClasses.remove( c );
 1059  6
                         return c;
 1060  
                     }
 1061  1
                 }
 1062  1
                 c = createJavacClass();
 1063  1
                 return c;
 1064  
             case ReuseSame:
 1065  
             default:
 1066  0
                 c = JavacCompiler.JAVAC_CLASS;
 1067  0
                 if ( c != null )
 1068  
                 {
 1069  0
                     return c;
 1070  
                 }
 1071  0
                 synchronized ( JavacCompiler.LOCK )
 1072  
                 {
 1073  0
                     if ( c == null )
 1074  
                     {
 1075  0
                         JavacCompiler.JAVAC_CLASS = c = createJavacClass();
 1076  
                     }
 1077  0
                     return c;
 1078  0
                 }
 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  1
             return JavacCompiler.class.getClassLoader().loadClass( JavacCompiler.JAVAC_CLASSNAME );
 1096  
         }
 1097  0
         catch ( ClassNotFoundException ex )
 1098  
         {
 1099  
             // ok
 1100  
         }
 1101  
 
 1102  0
         final File toolsJar = new File( System.getProperty( "java.home" ), "../lib/tools.jar" );
 1103  0
         if ( !toolsJar.exists() )
 1104  
         {
 1105  0
             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  0
             URL[] originalUrls = ((URLClassLoader) JavacCompiler.class.getClassLoader()).getURLs();
 1113  0
             URL[] urls = new URL[originalUrls.length + 1];
 1114  0
             urls[0] = toolsJar.toURI().toURL();
 1115  0
             System.arraycopy(originalUrls, 0, urls, 1, originalUrls.length);
 1116  0
             ClassLoader javacClassLoader = new URLClassLoader(urls);
 1117  
 
 1118  0
             final Thread thread = Thread.currentThread();
 1119  0
             final ClassLoader contextClassLoader = thread.getContextClassLoader();
 1120  0
             thread.setContextClassLoader( javacClassLoader );
 1121  
             try
 1122  
             {
 1123  
                 //return Class.forName( JavacCompiler.JAVAC_CLASSNAME, true, javacClassLoader );
 1124  0
                 return javacClassLoader.loadClass( JavacCompiler.JAVAC_CLASSNAME );
 1125  
             }
 1126  
             finally
 1127  
             {
 1128  0
                 thread.setContextClassLoader( contextClassLoader );
 1129  
             }
 1130  
         }
 1131  0
         catch ( MalformedURLException ex )
 1132  
         {
 1133  0
             throw new CompilerException(
 1134  
                 "Could not convert the file reference to tools.jar to a URL, path to tools.jar: '"
 1135  0
                     + toolsJar.getAbsolutePath() + "'.", ex );
 1136  
         }
 1137  0
         catch ( ClassNotFoundException ex )
 1138  
         {
 1139  0
             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  
 }