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 java.io.BufferedReader;
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.StringReader;
23  import java.io.StringWriter;
24  import java.io.Writer;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.codehaus.plexus.compiler.AbstractCompiler;
31  import org.codehaus.plexus.compiler.CompilerConfiguration;
32  import org.codehaus.plexus.compiler.CompilerException;
33  import org.codehaus.plexus.compiler.CompilerMessage;
34  import org.codehaus.plexus.compiler.CompilerMessage.Kind;
35  import org.codehaus.plexus.compiler.CompilerOutputStyle;
36  import org.codehaus.plexus.compiler.CompilerResult;
37  import org.codehaus.plexus.util.StringUtils;
38  import org.codehaus.plexus.util.cli.CommandLineException;
39  import org.codehaus.plexus.util.cli.CommandLineUtils;
40  import org.codehaus.plexus.util.cli.Commandline;
41  import org.codehaus.plexus.util.cli.StreamConsumer;
42  import org.codehaus.plexus.util.cli.WriterStreamConsumer;
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   * @see CSharpCompiler
51   * @plexus.component role="org.codehaus.plexus.compiler.Compiler"
52   *                   role-hint="j2objc"
53   */
54  public class J2ObjCCompiler extends AbstractCompiler {
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
75  			.asList(new String[] { "-pluginpath", "-pluginoptions", "-t",
76  					"-Xno-jsni-warnings", "-sourcepath", "-classpath", "-d",
77  					"-encoding", "-g", "-q", "-v", "-Werror", "-h", "-use-arc",
78  					"-use-reference-counting", "-x" });
79  
80  	/**
81  	 * Put the command line arguments with 2 dashes inside an array, in order
82  	 * the check the command line and build it.
83  	 */
84  	private static final List<String> TWO_DASH_ARGS = Arrays
85  			.asList(new String[] { "--build-closure", "--dead-code-report",
86  					"--doc-comments", "--no-extract-unsequenced",
87  					"--generate-deprecated", "--mapping", "--no-class-methods",
88  					"--no-final-methods-functions",
89  					"--no-hide-private-members", "--no-package-directories",
90  					"--prefix", "--prefixes", "--preserve-full-paths",
91  					"--strip-gwt-incompatible", "--strip-reflection",
92  					"--segmented-headers", "--timing-info", "--quiet",
93  					"--verbose", "--help" });
94  
95  	public J2ObjCCompiler() {
96  		super(CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE, ".java",
97  				null, null);
98  	}
99  
100 	// ----------------------------------------------------------------------
101 	// Compiler Implementation
102 	// ----------------------------------------------------------------------
103 
104 	public boolean canUpdateTarget(CompilerConfiguration configuration)
105 			throws CompilerException {
106 		return false;
107 	}
108 
109 	public CompilerResult performCompile(CompilerConfiguration config)
110 			throws CompilerException {
111 		File destinationDir = new File(config.getOutputLocation());
112 		if (!destinationDir.exists()) {
113 			destinationDir.mkdirs();
114 		}
115 
116 		config.setSourceFiles(null);
117 
118 		String[] sourceFiles = J2ObjCCompiler.getSourceFiles(config);
119 
120 		if (sourceFiles.length == 0) {
121 			return new CompilerResult().success(true);
122 		}
123 
124 		System.out.println("Compiling " + sourceFiles.length + " "
125 				+ "source file" + (sourceFiles.length == 1 ? "" : "s") + " to "
126 				+ destinationDir.getAbsolutePath());
127 
128 		String[] args = buildCompilerArguments(config, sourceFiles);
129 
130 		List<CompilerMessage> messages;
131 
132 		if (config.isFork()) {
133 			messages = compileOutOfProcess(config.getWorkingDirectory(),
134 					config.getBuildDirectory(), findExecutable(config), args);
135 		} else {
136 			throw new CompilerException(
137 					"This compiler doesn't support in-process compilation.");
138 		}
139 
140 		return new CompilerResult().compilerMessages(messages);
141 	}
142 
143 	public String[] createCommandLine(CompilerConfiguration config)
144 			throws CompilerException {
145 		return buildCompilerArguments(config,
146 				J2ObjCCompiler.getSourceFiles(config));
147 	}
148 
149 	/**
150 	 * Find the executable given in the configuration or use j2objc from the
151 	 * PATH.
152 	 * 
153 	 * @param config
154 	 * @return the List<String> of args
155 	 */
156 	private String findExecutable(CompilerConfiguration config) {
157 		String executable = config.getExecutable();
158 
159 		if (!StringUtils.isEmpty(executable)) {
160 			return executable;
161 		}
162 
163 		return "j2objc";
164 	}
165 
166 	/**
167 	 * Build the compiler arguments : 
168 	 * <li>the output location is used for -d of j2objc) 
169 	 * <li>the classpath entries are added to -classpath 
170 	 * <li>the sourcefiles are listed at the end of the command line 
171 	 * <li>the configuration can contain any of the arguments
172 	 * 
173 	 * @param config
174 	 * @param sourceFiles
175 	 * @return The List<String> to give to the command line tool
176 	 * @throws CompilerException
177 	 */
178 	private String[] buildCompilerArguments(CompilerConfiguration config,
179 			String[] sourceFiles) throws CompilerException {
180 		/*
181 		 * j2objc --help Usage: j2objc <options> <source files>
182 		 */
183 		List<String> args = new ArrayList<String>();
184 		Map<String, String> compilerArguments = config
185 				.getCustomCompilerArgumentsAsMap();
186 
187 		// Verbose
188 		if (config.isVerbose()) {
189 			args.add("-v");
190 		}
191 
192 		// Destination/output directory
193 		args.add("-d");
194 		args.add(config.getOutputLocation());
195 
196 		if (!config.getClasspathEntries().isEmpty()) {
197 			List<String> classpath = new ArrayList<String>();
198 			for (String element : config.getClasspathEntries()) {
199 				File f = new File(element);
200 				classpath.add(f.getAbsolutePath());
201 
202 				classpath.add(element);
203 			}
204 			args.add("-classpath");
205 			args.add(StringUtils.join(classpath.toArray(), File.pathSeparator));
206 		}
207 
208 		if (config.isVerbose()) {
209 			System.out.println("Args: ");
210 		}
211 
212 		for (String k : compilerArguments.keySet()) {
213 			if (config.isVerbose()) {
214 				System.out.println(k + "=" + compilerArguments.get(k));
215 			}
216 			String v = compilerArguments.get(k);
217 			if (J_FLAG.equals(k)) {
218 				args.add(J_FLAG + v);
219 			} else if (X_BOOTCLASSPATH.equals(k)) {
220 				args.add(X_BOOTCLASSPATH + ":" + v);
221 			} else if (BATCH_SIZE.equals(k)) {
222 				args.add("-" + BATCH_SIZE + "=" + v);
223 			} else {
224 				if (TWO_DASH_ARGS.contains(k)) {
225 					args.add("-" + k);
226 				} else if (ONE_DASH_ARGS.contains(k)) {
227 					args.add(k);
228 				} else {
229 					throw new IllegalArgumentException("The argument " + k
230 							+ " isnt't a flag recognized by J2ObjC.");
231 				}
232 				if (v != null) {
233 					args.add(v);
234 				}
235 			}
236 		}
237 
238 		for (String sourceFile : sourceFiles) {
239 			args.add(sourceFile);
240 		}
241 
242 		return args.toArray(new String[args.size()]);
243 	}
244 
245 	private List<CompilerMessage> compileOutOfProcess(File workingDirectory,
246 			File target, String executable, String[] args)
247 			throws CompilerException {
248 
249 		Commandline cli = new Commandline();
250 
251 		cli.setWorkingDirectory(workingDirectory.getAbsolutePath());
252 
253 		cli.setExecutable(executable);
254 
255 		cli.addArguments(args);
256 
257 		Writer stringWriter = new StringWriter();
258 
259 		StreamConsumer out = new WriterStreamConsumer(stringWriter);
260 
261 		StreamConsumer err = new WriterStreamConsumer(stringWriter);
262 
263 		int returnCode;
264 
265 		List<CompilerMessage> messages;
266 
267 		try {
268 			returnCode = CommandLineUtils.executeCommandLine(cli, out, err);
269 
270 			messages = parseCompilerOutput(new BufferedReader(new StringReader(
271 					stringWriter.toString())));
272 		} catch (CommandLineException e) {
273 			throw new CompilerException(
274 					"Error while executing the external compiler.", e);
275 		} catch (IOException e) {
276 			throw new CompilerException(
277 					"Error while executing the external compiler.", e);
278 		}
279 
280 		if (returnCode != 0 && messages.isEmpty()) {
281 			// TODO: exception?
282 			messages.add(new CompilerMessage(
283 					"Failure executing the compiler, but could not parse the error:"
284 							+ EOL + stringWriter.toString(), Kind.ERROR));
285 		}
286 
287 		return messages;
288 	}
289 
290 	public static List<CompilerMessage> parseCompilerOutput(
291 			BufferedReader bufferedReader) throws IOException {
292 		List<CompilerMessage> messages = new ArrayList<CompilerMessage>();
293 
294 		String line = bufferedReader.readLine();
295 
296 		while (line != null) {
297 			CompilerMessage compilerError = DefaultJ2ObjCCompilerParser
298 					.parseLine(line);
299 
300 			if (compilerError != null) {
301 				messages.add(compilerError);
302 			}
303 
304 			line = bufferedReader.readLine();
305 		}
306 
307 		return messages;
308 	}
309 
310 }