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 defaultSettings;
54  
55      public JavacJavaCompiler() {
56          defaultSettings = new JavacJavaCompilerSettings();
57      }
58  
59      public JavacJavaCompiler( final JavacJavaCompilerSettings pSettings ) {
60          defaultSettings = 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  
75              final String[] compilerArguments = buildCompilerArguments(new JavacJavaCompilerSettings(pSettings), pSourcePaths, pClasspathClassLoader);
76                          
77              final Integer ok = (Integer) compile.invoke(null, new Object[] { compilerArguments, new PrintWriter(out) });
78  
79              final CompilationResult result = parseModernStream(new BufferedReader(new StringReader(out.toString())));
80  
81              if (result.getErrors().length == 0 && ok.intValue() != 0) {
82                  return new CompilationResult(new CompilationProblem[] {
83                          new JavacCompilationProblem("Failure executing javac, but could not parse the error: " + out.toString(), true) });
84              }
85  
86              return result;
87  
88          } catch(Exception e) {
89              return new CompilationResult(new CompilationProblem[] {
90                      new JavacCompilationProblem("Error while executing the compiler: " + e.toString(), true) });
91          } finally {
92              // help GC
93              FileInputStreamProxy.setResourceReader(null);
94              FileOutputStreamProxy.setResourceStore(null);
95          }
96      }
97  
98      private CompilationResult parseModernStream( final BufferedReader pReader ) throws IOException {
99          final List problems = new ArrayList();
100         String line;
101 
102         while (true) {
103             // cleanup the buffer
104             final StringBuffer buffer = new StringBuffer();
105 
106             // most errors terminate with the '^' char
107             do {
108                 line = pReader.readLine();
109                 if (line == null) {
110                     return new CompilationResult((CompilationProblem[]) problems.toArray(new CompilationProblem[problems.size()]));
111                 }
112 
113                 // TODO: there should be a better way to parse these
114                 if (buffer.length() == 0 && line.startsWith(ERROR_PREFIX)) {
115                     problems.add(new JavacCompilationProblem(line, true));
116                 }
117                 else if (buffer.length() == 0 && line.startsWith(NOTE_PREFIX)) {
118                     // skip this one - it is JDK 1.5 telling us that the
119                     // interface is deprecated.
120                 } else {
121                     buffer.append(line);
122                     buffer.append(EOL);
123                 }
124             } while (!line.endsWith("^"));
125 
126             // add the error
127             problems.add(parseModernError(buffer.toString()));
128         }
129     }
130 
131     private CompilationProblem parseModernError( final String pError ) {
132         final StringTokenizer tokens = new StringTokenizer(pError, ":");
133         boolean isError = true;
134         try {
135             String file = tokens.nextToken();
136             // When will this happen?
137             if (file.length() == 1) {
138                 file = new StringBuffer(file).append(":").append(
139                         tokens.nextToken()).toString();
140             }
141             final int line = Integer.parseInt(tokens.nextToken());
142             final StringBuffer msgBuffer = new StringBuffer();
143 
144             String msg = tokens.nextToken(EOL).substring(2);
145             isError = !msg.startsWith(WARNING_PREFIX);
146 
147             // Remove the 'warning: ' prefix
148             if (!isError) {
149                 msg = msg.substring(WARNING_PREFIX.length());
150             }
151             msgBuffer.append(msg);
152 
153             String context = tokens.nextToken(EOL);
154             String pointer = tokens.nextToken(EOL);
155 
156             if (tokens.hasMoreTokens()) {
157                 msgBuffer.append(EOL);
158                 msgBuffer.append(context); // 'symbol' line
159                 msgBuffer.append(EOL);
160                 msgBuffer.append(pointer); // 'location' line
161                 msgBuffer.append(EOL);
162 
163                 context = tokens.nextToken(EOL);
164 
165                 try {
166                     pointer = tokens.nextToken(EOL);
167                 } catch (NoSuchElementException e) {
168                     pointer = context;
169                     context = null;
170                 }
171             }
172             final String message = msgBuffer.toString();
173             int startcolumn = pointer.indexOf("^");
174             int endcolumn = context == null ? startcolumn : context.indexOf(" ", startcolumn);
175             if (endcolumn == -1) {
176                 endcolumn = context.length();
177             }
178             return new JavacCompilationProblem(file, isError, line, startcolumn, line, endcolumn, message);
179         }
180         catch (NoSuchElementException e) {
181             return new JavacCompilationProblem("no more tokens - could not parse error message: " + pError, isError);
182         }
183         catch (NumberFormatException e) {
184             return new JavacCompilationProblem("could not parse error message: " + pError, isError);
185         }
186         catch (Exception e) {
187             return new JavacCompilationProblem("could not parse error message: " + pError, isError);
188         }
189     }
190 
191     public JavaCompilerSettings createDefaultSettings() {
192         return new JavacJavaCompilerSettings(defaultSettings);
193     }
194 
195     private String[] buildCompilerArguments( final JavacJavaCompilerSettings pSettings, final String[] pResourcePaths, final ClassLoader pClassloader ) {
196 
197         // FIXME: build classpath from classloader information
198     	final String[] classpath = new String[0];
199     	final String[] resources = pResourcePaths;    	
200     	final String[] args = pSettings.toNativeSettings();
201     	
202     	final String[] result = new String[classpath.length + resources.length + args.length];
203     	
204     	System.arraycopy(classpath, 0, result, 0, classpath.length);
205     	System.arraycopy(resources, 0, result, classpath.length, resources.length);
206     	System.arraycopy(args, 0, result, classpath.length + resources.length, args.length);
207     	
208     	return result;
209     }
210     
211 }