View Javadoc
1   package org.codehaus.plexus.compiler.j2objc;
2   
3   /*
4    * Copyright 2005 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import org.codehaus.plexus.compiler.AbstractCompiler;
20  import org.codehaus.plexus.compiler.CompilerConfiguration;
21  import org.codehaus.plexus.compiler.CompilerException;
22  import org.codehaus.plexus.compiler.CompilerMessage;
23  import org.codehaus.plexus.compiler.CompilerMessage.Kind;
24  import org.codehaus.plexus.compiler.CompilerOutputStyle;
25  import org.codehaus.plexus.compiler.CompilerResult;
26  import org.codehaus.plexus.util.StringUtils;
27  import org.codehaus.plexus.util.cli.CommandLineException;
28  import org.codehaus.plexus.util.cli.CommandLineUtils;
29  import org.codehaus.plexus.util.cli.Commandline;
30  import org.codehaus.plexus.util.cli.StreamConsumer;
31  import org.codehaus.plexus.util.cli.WriterStreamConsumer;
32  
33  import java.io.BufferedReader;
34  import java.io.File;
35  import java.io.IOException;
36  import java.io.StringReader;
37  import java.io.StringWriter;
38  import java.io.Writer;
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  import java.util.List;
42  import java.util.Map;
43  
44  /**
45   * A plexus compiler which use J2ObjC . It is derived from the CSharpCompiler to
46   * compile with J2ObjC.
47   *
48   * @author <a href="mailto:ludovic.maitre@effervens.com">Ludovic
49   *         Ma&icirc;tre</a>
50   * @plexus.component role="org.codehaus.plexus.compiler.Compiler" role-hint="j2objc"
51   */
52  public class J2ObjCCompiler
53      extends AbstractCompiler
54  {
55  
56      private static final String X_BOOTCLASSPATH = "Xbootclasspath";
57  
58      /**
59       * -J<flag> Pass Java <flag>, such as -Xmx1G, to the system runtime.
60       */
61      private static final String J_FLAG = "J";
62  
63      /**
64       * --batch-translate-max=<n> The maximum number of source files that are
65       * translated. together. Batching speeds up translation, but requires more
66       * memory.
67       */
68      private static final String BATCH_SIZE = "batch-translate-max";
69  
70      /**
71       * Put the arguments of j2objc who takes one dash inside an array, in order
72       * the check the command line.
73       */
74      private static final List<String> ONE_DASH_ARGS = Arrays.asList(
75          new String[]{ "-pluginpath", "-pluginoptions", "-t", "-Xno-jsni-warnings", "-sourcepath", "-classpath", "-d",
76              "-encoding", "-g", "-q", "-v", "-Werror", "-h", "-use-arc", "-use-reference-counting", "-x" } );
77  
78      /**
79       * Put the command line arguments with 2 dashes inside an array, in order
80       * the check the command line and build it.
81       */
82      private static final List<String> TWO_DASH_ARGS = Arrays.asList(
83          new String[]{ "--build-closure", "--dead-code-report", "--doc-comments", "--no-extract-unsequenced",
84              "--generate-deprecated", "--mapping", "--no-class-methods", "--no-final-methods-functions",
85              "--no-hide-private-members", "--no-package-directories", "--prefix", "--prefixes", "--preserve-full-paths",
86              "--strip-gwt-incompatible", "--strip-reflection", "--segmented-headers", "--timing-info", "--quiet",
87              "--verbose", "--help" } );
88  
89      public J2ObjCCompiler()
90      {
91          super( CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE, ".java", null, null );
92      }
93  
94      // ----------------------------------------------------------------------
95      // Compiler Implementation
96      // ----------------------------------------------------------------------
97  
98      public boolean canUpdateTarget( CompilerConfiguration configuration )
99          throws CompilerException
100     {
101         return false;
102     }
103 
104     public CompilerResult performCompile( CompilerConfiguration config )
105         throws CompilerException
106     {
107         File destinationDir = new File( config.getOutputLocation() );
108         if ( !destinationDir.exists() )
109         {
110             destinationDir.mkdirs();
111         }
112 
113         config.setSourceFiles( null );
114 
115         String[] sourceFiles = J2ObjCCompiler.getSourceFiles( config );
116 
117         if ( sourceFiles.length == 0 )
118         {
119             return new CompilerResult().success( true );
120         }
121 
122         System.out.println(
123             "Compiling " + sourceFiles.length + " " + "source file" + ( sourceFiles.length == 1 ? "" : "s" ) + " to "
124                 + destinationDir.getAbsolutePath() );
125 
126         String[] args = buildCompilerArguments( config, sourceFiles );
127 
128         List<CompilerMessage> messages;
129 
130         if ( config.isFork() )
131         {
132             messages =
133                 compileOutOfProcess( config.getWorkingDirectory(), config.getBuildDirectory(), findExecutable( config ),
134                                      args );
135         }
136         else
137         {
138             throw new CompilerException( "This compiler doesn't support in-process compilation." );
139         }
140 
141         return new CompilerResult().compilerMessages( messages );
142     }
143 
144     public String[] createCommandLine( CompilerConfiguration config )
145         throws CompilerException
146     {
147         return buildCompilerArguments( config, J2ObjCCompiler.getSourceFiles( config ) );
148     }
149 
150     /**
151      * Find the executable given in the configuration or use j2objc from the
152      * PATH.
153      *
154      * @param config
155      * @return the List<String> of args
156      */
157     private String findExecutable( CompilerConfiguration config )
158     {
159         String executable = config.getExecutable();
160 
161         if ( !StringUtils.isEmpty( executable ) )
162         {
163             return executable;
164         }
165 
166         return "j2objc";
167     }
168 
169     /**
170      * Build the compiler arguments :
171      * <li>the output location is used for -d of j2objc)
172      * <li>the classpath entries are added to -classpath
173      * <li>the sourcefiles are listed at the end of the command line
174      * <li>the configuration can contain any of the arguments
175      *
176      * @param config
177      * @param sourceFiles
178      * @return The List<String> to give to the command line tool
179      * @throws CompilerException
180      */
181     private String[] buildCompilerArguments( CompilerConfiguration config, String[] sourceFiles )
182         throws CompilerException
183     {
184         /*
185 		 * j2objc --help Usage: j2objc <options> <source files>
186 		 */
187         List<String> args = new ArrayList<String>();
188         Map<String, String> compilerArguments = config.getCustomCompilerArgumentsAsMap();
189 
190         // Verbose
191         if ( config.isVerbose() )
192         {
193             args.add( "-v" );
194         }
195 
196         // Destination/output directory
197         args.add( "-d" );
198         args.add( config.getOutputLocation() );
199 
200         if ( !config.getClasspathEntries().isEmpty() )
201         {
202             List<String> classpath = new ArrayList<String>();
203             for ( String element : config.getClasspathEntries() )
204             {
205                 File f = new File( element );
206                 classpath.add( f.getAbsolutePath() );
207 
208                 classpath.add( element );
209             }
210             args.add( "-classpath" );
211             args.add( StringUtils.join( classpath.toArray(), File.pathSeparator ) );
212         }
213 
214         if ( config.isVerbose() )
215         {
216             System.out.println( "Args: " );
217         }
218 
219         for ( String k : compilerArguments.keySet() )
220         {
221             if ( config.isVerbose() )
222             {
223                 System.out.println( k + "=" + compilerArguments.get( k ) );
224             }
225             String v = compilerArguments.get( k );
226             if ( J_FLAG.equals( k ) )
227             {
228                 args.add( J_FLAG + v );
229             }
230             else if ( X_BOOTCLASSPATH.equals( k ) )
231             {
232                 args.add( X_BOOTCLASSPATH + ":" + v );
233             }
234             else if ( BATCH_SIZE.equals( k ) )
235             {
236                 args.add( "-" + BATCH_SIZE + "=" + v );
237             }
238             else
239             {
240                 if ( TWO_DASH_ARGS.contains( k ) )
241                 {
242                     args.add( "-" + k );
243                 }
244                 else if ( ONE_DASH_ARGS.contains( k ) )
245                 {
246                     args.add( k );
247                 }
248                 else
249                 {
250                     throw new IllegalArgumentException( "The argument " + k + " isnt't a flag recognized by J2ObjC." );
251                 }
252                 if ( v != null )
253                 {
254                     args.add( v );
255                 }
256             }
257         }
258 
259         for ( String sourceFile : sourceFiles )
260         {
261             args.add( sourceFile );
262         }
263 
264         return args.toArray( new String[args.size()] );
265     }
266 
267     private List<CompilerMessage> compileOutOfProcess( File workingDirectory, File target, String executable,
268                                                        String[] args )
269         throws CompilerException
270     {
271 
272         Commandline cli = new Commandline();
273 
274         cli.setWorkingDirectory( workingDirectory.getAbsolutePath() );
275 
276         cli.setExecutable( executable );
277 
278         cli.addArguments( args );
279 
280         Writer stringWriter = new StringWriter();
281 
282         StreamConsumer out = new WriterStreamConsumer( stringWriter );
283 
284         StreamConsumer err = new WriterStreamConsumer( stringWriter );
285 
286         int returnCode;
287 
288         List<CompilerMessage> messages;
289 
290         try
291         {
292             returnCode = CommandLineUtils.executeCommandLine( cli, out, err );
293 
294             messages = parseCompilerOutput( new BufferedReader( new StringReader( stringWriter.toString() ) ) );
295         }
296         catch ( CommandLineException e )
297         {
298             throw new CompilerException( "Error while executing the external compiler.", e );
299         }
300         catch ( IOException e )
301         {
302             throw new CompilerException( "Error while executing the external compiler.", e );
303         }
304 
305         if ( returnCode != 0 && messages.isEmpty() )
306         {
307             // TODO: exception?
308             messages.add( new CompilerMessage(
309                 "Failure executing the compiler, but could not parse the error:" + EOL + stringWriter.toString(),
310                 Kind.ERROR ) );
311         }
312 
313         return messages;
314     }
315 
316     public static List<CompilerMessage> parseCompilerOutput( BufferedReader bufferedReader )
317         throws IOException
318     {
319         List<CompilerMessage> messages = new ArrayList<CompilerMessage>();
320 
321         String line = bufferedReader.readLine();
322 
323         while ( line != null )
324         {
325             CompilerMessage compilerError = DefaultJ2ObjCCompilerParser.parseLine( line );
326 
327             if ( compilerError != null )
328             {
329                 messages.add( compilerError );
330             }
331 
332             line = bufferedReader.readLine();
333         }
334 
335         return messages;
336     }
337 
338 }