001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.commons.jci.compilers;
019:
020: import java.io.BufferedReader;
021: import java.io.IOException;
022: import java.io.PrintWriter;
023: import java.io.StringReader;
024: import java.io.StringWriter;
025: import java.lang.reflect.Method;
026: import java.util.ArrayList;
027: import java.util.List;
028: import java.util.NoSuchElementException;
029: import java.util.StringTokenizer;
030:
031: import org.apache.commons.jci.problems.CompilationProblem;
032: import org.apache.commons.jci.readers.ResourceReader;
033: import org.apache.commons.jci.stores.ResourceStore;
034:
035: /**
036: * Compiler leveraging the javac from tools.jar. Using byte code rewriting
037: * it is tricked not to read/write from/to disk but use the ResourceReader and
038: * ResourceStore provided instead.
039: *
040: * NOTE: (As of now) this compiler only works up until java5. Java6 comes with
041: * a new API based on jsr199. So please use that jsr199 compiler instead.
042: *
043: * @author tcurdt
044: * @todo classpath and settings support
045: */
046: public final class JavacJavaCompiler extends AbstractJavaCompiler {
047:
048: private static final String EOL = System
049: .getProperty("line.separator");
050: private static final String WARNING_PREFIX = "warning: ";
051: private static final String NOTE_PREFIX = "Note: ";
052: private static final String ERROR_PREFIX = "error: ";
053:
054: private final JavacJavaCompilerSettings settings;
055:
056: public JavacJavaCompiler() {
057: settings = new JavacJavaCompilerSettings();
058: }
059:
060: public JavacJavaCompiler(final JavacJavaCompilerSettings pSettings) {
061: settings = pSettings;
062: }
063:
064: public CompilationResult compile(final String[] pSourcePaths,
065: final ResourceReader pReader, ResourceStore pStore,
066: final ClassLoader pClasspathClassLoader,
067: final JavaCompilerSettings pSettings) {
068:
069: try {
070: final ClassLoader cl = new JavacClassLoader(
071: pClasspathClassLoader);
072: final Class renamedClass = cl
073: .loadClass("com.sun.tools.javac.Main");
074:
075: FileInputStreamProxy.setResourceReader(pReader);
076: FileOutputStreamProxy.setResourceStore(pStore);
077:
078: final Method compile = renamedClass.getMethod("compile",
079: new Class[] { String[].class, PrintWriter.class });
080: final StringWriter out = new StringWriter();
081: final Integer ok = (Integer) compile.invoke(null,
082: new Object[] {
083: buildCompilerArguments(pSourcePaths,
084: pClasspathClassLoader),
085: new PrintWriter(out) });
086:
087: final CompilationResult result = parseModernStream(new BufferedReader(
088: new StringReader(out.toString())));
089:
090: if (result.getErrors().length == 0 && ok.intValue() != 0) {
091: return new CompilationResult(
092: new CompilationProblem[] { new JavacCompilationProblem(
093: "Failure executing javac, but could not parse the error: "
094: + out.toString(), true) });
095: }
096:
097: return result;
098:
099: } catch (Exception e) {
100: return new CompilationResult(
101: new CompilationProblem[] { new JavacCompilationProblem(
102: "Error while executing the compiler: "
103: + e.toString(), true) });
104: } finally {
105: // help GC
106: FileInputStreamProxy.setResourceReader(null);
107: FileOutputStreamProxy.setResourceStore(null);
108: }
109: }
110:
111: private CompilationResult parseModernStream(
112: final BufferedReader pReader) throws IOException {
113: final List problems = new ArrayList();
114: String line;
115:
116: while (true) {
117: // cleanup the buffer
118: final StringBuffer buffer = new StringBuffer();
119:
120: // most errors terminate with the '^' char
121: do {
122: line = pReader.readLine();
123: if (line == null) {
124: return new CompilationResult(
125: (CompilationProblem[]) problems
126: .toArray(new CompilationProblem[problems
127: .size()]));
128: }
129:
130: // TODO: there should be a better way to parse these
131: if (buffer.length() == 0
132: && line.startsWith(ERROR_PREFIX)) {
133: problems
134: .add(new JavacCompilationProblem(line, true));
135: } else if (buffer.length() == 0
136: && line.startsWith(NOTE_PREFIX)) {
137: // skip this one - it is JDK 1.5 telling us that the
138: // interface is deprecated.
139: } else {
140: buffer.append(line);
141: buffer.append(EOL);
142: }
143: } while (!line.endsWith("^"));
144:
145: // add the error
146: problems.add(parseModernError(buffer.toString()));
147: }
148: }
149:
150: private CompilationProblem parseModernError(final String pError) {
151: final StringTokenizer tokens = new StringTokenizer(pError, ":");
152: boolean isError = true;
153: try {
154: String file = tokens.nextToken();
155: // When will this happen?
156: if (file.length() == 1) {
157: file = new StringBuffer(file).append(":").append(
158: tokens.nextToken()).toString();
159: }
160: final int line = Integer.parseInt(tokens.nextToken());
161: final StringBuffer msgBuffer = new StringBuffer();
162:
163: String msg = tokens.nextToken(EOL).substring(2);
164: isError = !msg.startsWith(WARNING_PREFIX);
165:
166: // Remove the 'warning: ' prefix
167: if (!isError) {
168: msg = msg.substring(WARNING_PREFIX.length());
169: }
170: msgBuffer.append(msg);
171:
172: String context = tokens.nextToken(EOL);
173: String pointer = tokens.nextToken(EOL);
174:
175: if (tokens.hasMoreTokens()) {
176: msgBuffer.append(EOL);
177: msgBuffer.append(context); // 'symbol' line
178: msgBuffer.append(EOL);
179: msgBuffer.append(pointer); // 'location' line
180: msgBuffer.append(EOL);
181:
182: context = tokens.nextToken(EOL);
183:
184: try {
185: pointer = tokens.nextToken(EOL);
186: } catch (NoSuchElementException e) {
187: pointer = context;
188: context = null;
189: }
190: }
191: final String message = msgBuffer.toString();
192: int startcolumn = pointer.indexOf("^");
193: int endcolumn = context == null ? startcolumn : context
194: .indexOf(" ", startcolumn);
195: if (endcolumn == -1) {
196: endcolumn = context.length();
197: }
198: return new JavacCompilationProblem(file, isError, line,
199: startcolumn, line, endcolumn, message);
200: } catch (NoSuchElementException e) {
201: return new JavacCompilationProblem(
202: "no more tokens - could not parse error message: "
203: + pError, isError);
204: } catch (NumberFormatException e) {
205: return new JavacCompilationProblem(
206: "could not parse error message: " + pError, isError);
207: } catch (Exception e) {
208: return new JavacCompilationProblem(
209: "could not parse error message: " + pError, isError);
210: }
211: }
212:
213: public JavaCompilerSettings createDefaultSettings() {
214: return settings;
215: }
216:
217: private String[] buildCompilerArguments(
218: final String[] resourcePaths, final ClassLoader classloader) {
219:
220: // FIXME: build classpath from classloader information
221: return resourcePaths;
222:
223: // {
224: // final List args = new ArrayList();
225: // for (int i = 0; i < resourcePaths.length; i++) {
226: // args.add(resourcePaths[i]);
227: // }
228: //
229: // if (settings != null) {
230: // if (settings.isOptimize()) {
231: // args.add("-O");
232: // }
233: //
234: // if (settings.isDebug()) {
235: // args.add("-g");
236: // }
237: //
238: // if (settings.isVerbose()) {
239: // args.add("-verbose");
240: // }
241: //
242: // if (settings.isShowDeprecation()) {
243: // args.add("-deprecation");
244: // // This is required to actually display the deprecation messages
245: // settings.setShowWarnings(true);
246: // }
247: //
248: // if (settings.getMaxmem() != null) {
249: // args.add("-J-Xmx" + settings.getMaxmem());
250: // }
251: //
252: // if (settings.getMeminitial() != null) {
253: // args.add("-J-Xms" + settings.getMeminitial());
254: // }
255: //
256: // if (!settings.isShowWarnings()) {
257: // args.add("-nowarn");
258: // }
259: //
260: // // TODO: this could be much improved
261: // if (settings.getTargetVersion() != null) {
262: // // Required, or it defaults to the target of your JDK (eg 1.5)
263: // args.add("-target");
264: // args.add("1.1");
265: // } else {
266: // args.add("-target");
267: // args.add(settings.getTargetVersion());
268: // }
269: //
270: // // TODO suppressSource
271: // if (settings.getSourceVersion() != null) {
272: // // If omitted, later JDKs complain about a 1.1 target
273: // args.add("-source");
274: // args.add("1.3");
275: // } else {
276: // args.add("-source");
277: // args.add(settings.getSourceVersion());
278: // }
279: //
280: // // TODO suppressEncoding
281: // if (settings.getSourceEncoding() != null) {
282: // args.add("-encoding");
283: // args.add(settings.getSourceEncoding());
284: // }
285: //
286: // // TODO CustomCompilerArguments
287: // }
288: //
289: // return (String[]) args.toArray(new String[args.size()]);
290: }
291: }
|