View Javadoc
1   package org.codehaus.plexus.compiler.jikes;
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                     The Apache Software License, Version 1.1
29   ============================================================================
30  
31   Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
32  
33   Redistribution and use in source and binary forms, with or without modifica-
34   tion, are permitted provided that the following conditions are met:
35  
36   1. Redistributions of  source code must  retain the above copyright  notice,
37      this list of conditions and the following disclaimer.
38  
39   2. Redistributions in binary form must reproduce the above copyright notice,
40      this list of conditions and the following disclaimer in the documentation
41      and/or other materials provided with the distribution.
42  
43   3. The end-user documentation included with the redistribution, if any, must
44      include  the following  acknowledgment:  "This product includes  software
45      developed  by the  Apache Software Foundation  (http://www.apache.org/)."
46      Alternately, this  acknowledgment may  appear in the software itself,  if
47      and wherever such third-party acknowledgments normally appear.
48  
49   4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
50      used to  endorse or promote  products derived from  this software without
51      prior written permission. For written permission, please contact
52      apache@apache.org.
53  
54   5. Products  derived from this software may not  be called "Apache", nor may
55      "Apache" appear  in their name,  without prior written permission  of the
56      Apache Software Foundation.
57  
58   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
59   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
60   FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
61   APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
62   INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
63   DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
64   OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
65   ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
66   (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
67   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
68  
69   This software  consists of voluntary contributions made  by many individuals
70   on  behalf of the Apache Software  Foundation and was  originally created by
71   Stefano Mazzocchi  <stefano@apache.org>. For more  information on the Apache
72   Software Foundation, please see <http://www.apache.org/>.
73  
74  */
75  
76  import org.codehaus.plexus.compiler.AbstractCompiler;
77  import org.codehaus.plexus.compiler.CompilerConfiguration;
78  import org.codehaus.plexus.compiler.CompilerException;
79  import org.codehaus.plexus.compiler.CompilerMessage;
80  import org.codehaus.plexus.compiler.CompilerOutputStyle;
81  import org.codehaus.plexus.compiler.CompilerResult;
82  import org.codehaus.plexus.compiler.util.StreamPumper;
83  import org.codehaus.plexus.util.IOUtil;
84  import org.codehaus.plexus.util.Os;
85  import org.codehaus.plexus.util.StringUtils;
86  
87  import java.io.BufferedInputStream;
88  import java.io.BufferedReader;
89  import java.io.BufferedWriter;
90  import java.io.ByteArrayInputStream;
91  import java.io.ByteArrayOutputStream;
92  import java.io.File;
93  import java.io.FileFilter;
94  import java.io.FileWriter;
95  import java.io.IOException;
96  import java.io.InputStreamReader;
97  import java.util.ArrayList;
98  import java.util.List;
99  import java.util.Map;
100 
101 /**
102  * @plexus.component role="org.codehaus.plexus.compiler.Compiler" role-hint="jikes"
103  */
104 public class JikesCompiler
105     extends AbstractCompiler
106 {
107     private static final int OUTPUT_BUFFER_SIZE = 1024;
108 
109     public JikesCompiler()
110     {
111         super( CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE, ".java", ".class", null );
112     }
113 
114     // -----------------------------------------------------------------------
115     // Compiler Implementation
116     // -----------------------------------------------------------------------
117 
118     public CompilerResult performCompile( CompilerConfiguration config )
119         throws CompilerException
120     {
121         // Ensures that the directory exist.
122         getDestinationDir( config );
123 
124         try
125         {
126             // TODO: This should use the CommandLine stuff from plexus-utils.
127 
128             // -----------------------------------------------------------------------
129             // Execute the compiler
130             // -----------------------------------------------------------------------
131 
132             Process p = Runtime.getRuntime().exec( createCommandLine( config ) );
133 
134             BufferedInputStream compilerErr = new BufferedInputStream( p.getErrorStream() );
135 
136             ByteArrayOutputStream tmpErr = new ByteArrayOutputStream( OUTPUT_BUFFER_SIZE );
137             StreamPumper errPumper = new StreamPumper( compilerErr, tmpErr );
138 
139             errPumper.start();
140 
141             p.waitFor();
142 
143             int exitValue = p.exitValue();
144 
145             // Wait until the complete error stream has been read
146             errPumper.join();
147 
148             compilerErr.close();
149 
150             p.destroy();
151 
152             tmpErr.close();
153 
154             // -----------------------------------------------------------------------
155             // Parse the output
156             // -----------------------------------------------------------------------
157 
158             BufferedReader input =
159                 new BufferedReader( new InputStreamReader( new ByteArrayInputStream( tmpErr.toByteArray() ) ) );
160 
161             List<CompilerMessage> messages = new ArrayList<CompilerMessage>();
162 
163             parseStream( input, messages );
164 
165             if ( exitValue != 0 && exitValue != 1 )
166             {
167                 messages.add( new CompilerMessage( "Exit code from jikes was not 0 or 1 ->" + exitValue, true ) );
168             }
169 
170             return new CompilerResult().compilerMessages( messages );
171         }
172         catch ( IOException e )
173         {
174             throw new CompilerException( "Error while compiling.", e );
175         }
176         catch ( InterruptedException e )
177         {
178             throw new CompilerException( "Error while compiling.", e );
179         }
180     }
181 
182     public String[] createCommandLine( CompilerConfiguration config )
183         throws CompilerException
184     {
185         List<String> args = new ArrayList<String>();
186 
187         args.add( "jikes" );
188 
189         String bootClassPath = getPathString( getBootClassPath() );
190         getLogger().debug( "Bootclasspath: " + bootClassPath );
191         if ( !StringUtils.isEmpty( bootClassPath ) )
192         {
193             args.add( "-bootclasspath" );
194             args.add( bootClassPath );
195         }
196 
197         String classPath = getPathString( config.getClasspathEntries() );
198         getLogger().debug( "Classpath: " + classPath );
199         if ( !StringUtils.isEmpty( classPath ) )
200         {
201             args.add( "-classpath" );
202             args.add( classPath );
203         }
204 
205         args.add( "+E" );
206 
207         if ( config.getCustomCompilerArguments().size() > 0 )
208         {
209             for ( Map.Entry<String, String> arg : config.getCustomCompilerArgumentsAsMap().entrySet() )
210             {
211                 args.add( arg.getKey() );
212                 args.add( arg.getValue() );
213             }
214         }
215 
216         args.add( "-target" );
217         if ( StringUtils.isNotEmpty( config.getTargetVersion() ) )
218         {
219             args.add( config.getTargetVersion() );
220         }
221         else
222         {
223             args.add( "1.1" );
224         }
225 
226         args.add( "-source" );
227         if ( StringUtils.isNotEmpty( config.getSourceVersion() ) )
228         {
229             args.add( config.getSourceVersion() );
230         }
231         else
232         {
233             args.add( "1.3" );
234         }
235 
236         if ( !config.isShowWarnings() )
237         {
238             args.add( "-nowarn" );
239         }
240 
241         if ( config.isShowDeprecation() )
242         {
243             args.add( "-deprecation" );
244         }
245 
246         if ( config.isOptimize() )
247         {
248             args.add( "-O" );
249         }
250 
251         if ( config.isVerbose() )
252         {
253             args.add( "-verbose" );
254         }
255 
256         if ( config.isDebug() )
257         {
258             args.add( "-g:lines" );
259         }
260 
261         args.add( "-d" );
262 
263         args.add( getDestinationDir( config ).getAbsolutePath() );
264 
265         String sourcePath = getPathString( config.getSourceLocations() );
266         getLogger().debug( "Source path:" + sourcePath );
267         if ( !StringUtils.isEmpty( sourcePath ) )
268         {
269             args.add( "-sourcepath" );
270             args.add( sourcePath );
271         }
272 
273         String[] sourceFiles = getSourceFiles( config );
274 
275         if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
276         {
277             String tempFileName = null;
278             BufferedWriter fw = null;
279 
280             try
281             {
282                 File tempFile = File.createTempFile( "compList", ".cmp" );
283                 tempFileName = tempFile.getAbsolutePath();
284                 getLogger().debug( "create TempFile" + tempFileName );
285 
286                 tempFile.getParentFile().mkdirs();
287                 fw = new BufferedWriter( new FileWriter( tempFile ) );
288                 for ( int i = 0; i < sourceFiles.length; i++ )
289                 {
290                     fw.write( sourceFiles[i] );
291                     fw.newLine();
292                 }
293                 fw.flush();
294                 tempFile.deleteOnExit();
295             }
296             catch ( IOException e )
297             {
298                 throw new CompilerException( "Could not create temporary file " + tempFileName, e );
299             }
300             finally
301             {
302                 IOUtil.close( fw );
303             }
304 
305             args.add( "@" + tempFileName );
306         }
307         else
308         {
309             for ( int i = 0; i < sourceFiles.length; i++ )
310             {
311                 args.add( sourceFiles[i] );
312             }
313 
314         }
315 
316         return (String[]) args.toArray( new String[args.size()] );
317     }
318 
319     // -----------------------------------------------------------------------
320     // Private
321     // -----------------------------------------------------------------------
322 
323     private File getDestinationDir( CompilerConfiguration config )
324     {
325         File destinationDir = new File( config.getOutputLocation() );
326 
327         if ( !destinationDir.exists() )
328         {
329             destinationDir.mkdirs();
330         }
331 
332         return destinationDir;
333     }
334 
335     private List<String> getBootClassPath()
336     {
337         List<String> bootClassPath = new ArrayList<String>();
338         FileFilter filter = new FileFilter()
339         {
340 
341             public boolean accept( File file )
342             {
343                 String name = file.getName();
344                 return name.endsWith( ".jar" ) || name.endsWith( ".zip" );
345             }
346 
347         };
348 
349         File javaHomeDir = new File( System.getProperty( "java.home" ) );
350 
351         File javaLibDir = new File( javaHomeDir, "lib" );
352         if ( javaLibDir.isDirectory() )
353         {
354             bootClassPath.addAll( asList( javaLibDir.listFiles( filter ) ) );
355         }
356 
357         File javaClassesDir = new File( javaHomeDir, "../Classes" );
358         if ( javaClassesDir.isDirectory() )
359         {
360             bootClassPath.addAll( asList( javaClassesDir.listFiles( filter ) ) );
361         }
362 
363         File javaExtDir = new File( javaLibDir, "ext" );
364         if ( javaExtDir.isDirectory() )
365         {
366             bootClassPath.addAll( asList( javaExtDir.listFiles( filter ) ) );
367         }
368 
369         return bootClassPath;
370     }
371 
372     private List<String> asList( File[] files )
373     {
374         List<String> filenames = new ArrayList<String>( files.length );
375         for ( File file : files )
376         {
377             filenames.add( file.toString() );
378         }
379         return filenames;
380     }
381 
382     /**
383      * Parse the compiler error stream to produce a list of
384      * <code>CompilerMessage</code>s
385      *
386      * @param input The error stream
387      * @return The list of compiler error messages
388      * @throws IOException If an error occurs during message collection
389      */
390     protected List<CompilerMessage> parseStream( BufferedReader input, List<CompilerMessage> messages )
391         throws IOException
392     {
393         String line = null;
394         StringBuilder buffer;
395 
396         while ( true )
397         {
398             // cleanup the buffer
399             buffer = new StringBuilder(); // this is faster than clearing it
400 
401             // first line is not space-starting
402             if ( line == null )
403             {
404                 line = input.readLine();
405             }
406 
407             if ( line == null )
408             {
409                 return messages;
410             }
411 
412             buffer.append( line );
413 
414             // all other space-starting lines are one error
415             while ( true )
416             {
417                 line = input.readLine();
418                 // EOF
419                 if ( line == null )
420                 {
421                     break;
422                 }
423                 // Continuation of previous error starts with ' '
424                 if ( line.length() > 0 && line.charAt( 0 ) != ' ' )
425                 {
426                     break;
427                 }
428                 buffer.append( EOL );
429                 buffer.append( line );
430             }
431 
432             if ( buffer.length() > 0 )
433             {
434                 // add the error bean
435                 messages.add( parseError( buffer.toString() ) );
436             }
437         }
438     }
439 
440     /**
441      * Parse an individual compiler error message
442      *
443      * @param error The error text
444      * @return A mssaged <code>CompilerMessage</code>
445      */
446     private CompilerMessage parseError( String error )
447     {
448         if ( error.startsWith( "Error:" ) )
449         {
450             return new CompilerMessage( error, true );
451         }
452 
453         String[] errorBits = StringUtils.split( error, ":" );
454 
455         int i;
456         String file;
457 
458         if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
459         {
460             file = errorBits[0] + ':' + errorBits[1];
461             i = 2;
462         }
463         else
464         {
465             file = errorBits[0];
466             i = 1;
467         }
468 
469         int startline = Integer.parseInt( errorBits[i++] );
470 
471         int startcolumn = Integer.parseInt( errorBits[i++] );
472 
473         int endline = Integer.parseInt( errorBits[i++] );
474 
475         int endcolumn = Integer.parseInt( errorBits[i++] );
476 
477         String type = errorBits[i++];
478 
479         StringBuilder message = new StringBuilder( errorBits[i++] );
480         while ( i < errorBits.length )
481         {
482             message.append( ':' ).append( errorBits[i++] );
483         }
484 
485         return new CompilerMessage( file, type.indexOf( "Error" ) > -1, startline, startcolumn, endline, endcolumn,
486                                     message.toString() );
487     }
488 }