View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jci.compilers;
19  
20  import java.io.BufferedReader;
21  import java.io.IOException;
22  import java.io.PrintWriter;
23  import java.io.StringReader;
24  import java.io.StringWriter;
25  import java.lang.reflect.Method;
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.NoSuchElementException;
29  import java.util.StringTokenizer;
30  
31  import org.apache.commons.jci.problems.CompilationProblem;
32  import org.apache.commons.jci.readers.ResourceReader;
33  import org.apache.commons.jci.stores.ResourceStore;
34  
35  /**
36   * Compiler leveraging the javac from tools.jar. Using byte code rewriting
37   * it is tricked not to read/write from/to disk but use the ResourceReader and
38   * ResourceStore provided instead.
39   * 
40   * NOTE: (As of now) this compiler only works up until java5. Java6 comes with
41   * a new API based on jsr199. So please use that jsr199 compiler instead.
42   *   
43   * @author tcurdt
44   * @todo classpath and settings support
45   */
46  public final class JavacJavaCompiler extends AbstractJavaCompiler {
47  
48      private static final String EOL = System.getProperty("line.separator");
49      private static final String WARNING_PREFIX = "warning: ";
50      private static final String NOTE_PREFIX = "Note: ";
51      private static final String ERROR_PREFIX = "error: ";
52  
53      private final JavacJavaCompilerSettings settings;
54  
55      public JavacJavaCompiler() {
56          settings = new JavacJavaCompilerSettings();
57      }
58  
59      public JavacJavaCompiler( final JavacJavaCompilerSettings pSettings ) {
60          settings = pSettings;
61      }
62  
63      public CompilationResult compile( final String[] pSourcePaths, final ResourceReader pReader, ResourceStore pStore, final ClassLoader pClasspathClassLoader, final JavaCompilerSettings pSettings ) {
64  
65          try {
66              final ClassLoader cl = new JavacClassLoader(pClasspathClassLoader);
67              final Class renamedClass = cl.loadClass("com.sun.tools.javac.Main");
68  
69              FileInputStreamProxy.setResourceReader(pReader);
70              FileOutputStreamProxy.setResourceStore(pStore);
71  
72              final Method compile = renamedClass.getMethod("compile", new Class[] { String[].class, PrintWriter.class });
73              final StringWriter out = new StringWriter();
74              final Integer ok = (Integer) compile.invoke(null, new Object[] { buildCompilerArguments(pSourcePaths, pClasspathClassLoader), new PrintWriter(out) });
75  
76              final CompilationResult result = parseModernStream(new BufferedReader(new StringReader(out.toString())));
77  
78              if (result.getErrors().length == 0 && ok.intValue() != 0) {
79                  return new CompilationResult(new CompilationProblem[] {
80                          new JavacCompilationProblem("Failure executing javac, but could not parse the error: " + out.toString(), true) });
81              }
82  
83              return result;
84  
85          } catch(Exception e) {
86              return new CompilationResult(new CompilationProblem[] {
87                      new JavacCompilationProblem("Error while executing the compiler: " + e.toString(), true) });
88          } finally {
89              // help GC
90              FileInputStreamProxy.setResourceReader(null);
91              FileOutputStreamProxy.setResourceStore(null);
92          }
93      }
94  
95      private CompilationResult parseModernStream( final BufferedReader pReader ) throws IOException {
96          final List problems = new ArrayList();
97          String line;
98  
99          while (true) {
100             // cleanup the buffer
101             final StringBuffer buffer = new StringBuffer();
102 
103             // most errors terminate with the '^' char
104             do {
105                 line = pReader.readLine();
106                 if (line == null) {
107                     return new CompilationResult((CompilationProblem[]) problems.toArray(new CompilationProblem[problems.size()]));
108                 }
109 
110                 // TODO: there should be a better way to parse these
111                 if (buffer.length() == 0 && line.startsWith(ERROR_PREFIX)) {
112                     problems.add(new JavacCompilationProblem(line, true));
113                 }
114                 else if (buffer.length() == 0 && line.startsWith(NOTE_PREFIX)) {
115                     // skip this one - it is JDK 1.5 telling us that the
116                     // interface is deprecated.
117                 } else {
118                     buffer.append(line);
119                     buffer.append(EOL);
120                 }
121             } while (!line.endsWith("^"));
122 
123             // add the error
124             problems.add(parseModernError(buffer.toString()));
125         }
126     }
127 
128     private CompilationProblem parseModernError( final String pError ) {
129         final StringTokenizer tokens = new StringTokenizer(pError, ":");
130         boolean isError = true;
131         try {
132             String file = tokens.nextToken();
133             // When will this happen?
134             if (file.length() == 1) {
135                 file = new StringBuffer(file).append(":").append(
136                         tokens.nextToken()).toString();
137             }
138             final int line = Integer.parseInt(tokens.nextToken());
139             final StringBuffer msgBuffer = new StringBuffer();
140 
141             String msg = tokens.nextToken(EOL).substring(2);
142             isError = !msg.startsWith(WARNING_PREFIX);
143 
144             // Remove the 'warning: ' prefix
145             if (!isError) {
146                 msg = msg.substring(WARNING_PREFIX.length());
147             }
148             msgBuffer.append(msg);
149 
150             String context = tokens.nextToken(EOL);
151             String pointer = tokens.nextToken(EOL);
152 
153             if (tokens.hasMoreTokens()) {
154                 msgBuffer.append(EOL);
155                 msgBuffer.append(context); // 'symbol' line
156                 msgBuffer.append(EOL);
157                 msgBuffer.append(pointer); // 'location' line
158                 msgBuffer.append(EOL);
159 
160                 context = tokens.nextToken(EOL);
161 
162                 try {
163                     pointer = tokens.nextToken(EOL);
164                 } catch (NoSuchElementException e) {
165                     pointer = context;
166                     context = null;
167                 }
168             }
169             final String message = msgBuffer.toString();
170             int startcolumn = pointer.indexOf("^");
171             int endcolumn = context == null ? startcolumn : context.indexOf(" ", startcolumn);
172             if (endcolumn == -1) {
173                 endcolumn = context.length();
174             }
175             return new JavacCompilationProblem(file, isError, line, startcolumn, line, endcolumn, message);
176         }
177         catch (NoSuchElementException e) {
178             return new JavacCompilationProblem("no more tokens - could not parse error message: " + pError, isError);
179         }
180         catch (NumberFormatException e) {
181             return new JavacCompilationProblem("could not parse error message: " + pError, isError);
182         }
183         catch (Exception e) {
184             return new JavacCompilationProblem("could not parse error message: " + pError, isError);
185         }
186     }
187 
188     public JavaCompilerSettings createDefaultSettings() {
189         return settings;
190     }
191 
192     private String[] buildCompilerArguments( final String[] resourcePaths, final ClassLoader classloader ) {
193 
194         // FIXME: build classpath from classloader information
195         return resourcePaths;
196 
197 //    {
198 //        final List args = new ArrayList();
199 //        for (int i = 0; i < resourcePaths.length; i++) {
200 //            args.add(resourcePaths[i]);
201 //        }
202 //
203 //        if (settings != null) {
204 //            if (settings.isOptimize()) {
205 //                args.add("-O");
206 //            }
207 //
208 //            if (settings.isDebug()) {
209 //                args.add("-g");
210 //            }
211 //
212 //            if (settings.isVerbose()) {
213 //                args.add("-verbose");
214 //            }
215 //
216 //            if (settings.isShowDeprecation()) {
217 //                args.add("-deprecation");
218 //                // This is required to actually display the deprecation messages
219 //                settings.setShowWarnings(true);
220 //            }
221 //
222 //            if (settings.getMaxmem() != null) {
223 //                args.add("-J-Xmx" + settings.getMaxmem());
224 //            }
225 //
226 //            if (settings.getMeminitial() != null) {
227 //                args.add("-J-Xms" + settings.getMeminitial());
228 //            }
229 //
230 //            if (!settings.isShowWarnings()) {
231 //                args.add("-nowarn");
232 //            }
233 //
234 //            // TODO: this could be much improved
235 //            if (settings.getTargetVersion() != null) {
236 //                // Required, or it defaults to the target of your JDK (eg 1.5)
237 //                args.add("-target");
238 //                args.add("1.1");
239 //            } else {
240 //                args.add("-target");
241 //                args.add(settings.getTargetVersion());
242 //            }
243 //
244 //            // TODO suppressSource
245 //            if (settings.getSourceVersion() != null) {
246 //                // If omitted, later JDKs complain about a 1.1 target
247 //                args.add("-source");
248 //                args.add("1.3");
249 //            } else {
250 //                args.add("-source");
251 //                args.add(settings.getSourceVersion());
252 //            }
253 //
254 //            // TODO suppressEncoding
255 //            if (settings.getSourceEncoding() != null) {
256 //                args.add("-encoding");
257 //                args.add(settings.getSourceEncoding());
258 //            }
259 //
260 //            // TODO CustomCompilerArguments
261 //        }
262 //
263 //        return (String[]) args.toArray(new String[args.size()]);
264     }
265 }