Source code: mijava/JavaCompiler.java
1 /*
2 @(#) $Id: JavaCompiler.java,v 1.16 2002/03/27 18:50:29 hobb0001 Exp $
3 Copyright 2002 Michael Hobbs
4
5 This file is part of MIJava.
6
7 MIJava is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 MIJava is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with MIJava; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21
22 package mijava;
23
24 import java.io.InputStream;
25 import java.io.PrintStream;
26 import java.io.File;
27 import java.io.IOException;
28 import java.util.Collection;
29 import java.util.Map;
30 import java.util.Iterator;
31 import java.util.HashMap;
32 import java.util.regex.Pattern;
33 import java.util.regex.Matcher;
34
35
36
37 class JavaCompiler
38 {
39 public static final class FileInfo
40 {
41 private final String givenFileName;
42 private final String tempFileName;
43 private final DiffString contents;
44 private final int type;
45 public final static int TYPE_NORMAL_CLASS = 0;
46 public final static int TYPE_MICLASS_INTERFACE = 1;
47 public final static int TYPE_EMPTY_CLASS = 2;
48 public final static int TYPE_MICLASS = 3;
49 public final static int TYPE_ERROR_FILE = 4;
50 public FileInfo
51 (String givenFileName, String tempFileName, DiffString contents,
52 int type)
53 {
54 this.givenFileName = givenFileName;
55 this.tempFileName = tempFileName;
56 this.contents = contents;
57 this.type = type;
58 }
59 }
60
61 public static void compile
62 (Collection compilerFlags, Collection files, File buildDir,
63 MIJavaCompiler.Result result)
64 throws IOException
65 {
66 if (files.size() == 0) return;
67 StringBuffer command = new StringBuffer("javac");
68 for (Iterator i=compilerFlags.iterator(); i.hasNext(); ) {
69 String flag = (String) i.next();
70 command.append(" " + flag);
71 }
72 command.append(" " + MIJava.getClassPathArgs());
73 command.append(" -d " + buildDir.getCanonicalPath());
74 Map fileNameInfoMap = new HashMap();
75 for (Iterator i=files.iterator(); i.hasNext(); ) {
76 FileInfo fileInfo = (FileInfo) i.next();
77 command.append(" " + fileInfo.tempFileName);
78 fileNameInfoMap.put(fileInfo.tempFileName, fileInfo);
79 }
80 Process javac = Runtime.getRuntime().exec(command.toString());
81 ConvertOutput convertStdOut =
82 new ConvertOutput(javac.getInputStream(), System.out, fileNameInfoMap,
83 result);
84 ConvertOutput convertStdErr =
85 new ConvertOutput(javac.getErrorStream(), System.err, fileNameInfoMap,
86 result);
87 try {
88 javac.waitFor();
89 convertStdOut.print();
90 convertStdErr.print();
91 } catch (InterruptedException e) {
92 result.success = false;
93 javac.destroy();
94 }
95 }
96
97 private static final class ConvertOutput
98 {
99 private static final String EOL =
100 "(?:\\r\\n|\\r|\\n|\\u0085|\\u2028|\\u2029)";
101 private static final Pattern errStmtP = Pattern.compile
102 ("(.+?):([0-9]+): (.*)" + EOL +
103 "(?:symbol : (.*)" + EOL +
104 "location: (.*)" + EOL + ")?" +
105 "(?:found : .*" + EOL +
106 "required: .*" + EOL + ")?" +
107 "(.*)" + EOL +
108 "( *\\^)" + EOL);
109 private static final String javaLetter = "[$_A-Za-z]";
110 private static final String javaDigit = "[0-9]";
111 private static final String identifier =
112 "(?:" + javaLetter + "(?:" + javaLetter + "|" + javaDigit + ")*)";
113 private static final Pattern miClassNameP =
114 Pattern.compile("(?<=\\s|\\.)\\$(" + identifier + ")");
115
116 private final InputStream in;
117 private final PrintStream out;
118 // Map(String [fileName], FileInfo)
119 private final Map fileNameInfoMap;
120 private final MIJavaCompiler.Result compileResult;
121
122 public ConvertOutput
123 (InputStream in, PrintStream out, Map fileNameInfoMap,
124 MIJavaCompiler.Result compileResult)
125 {
126 this.in = in;
127 this.out = out;
128 this.fileNameInfoMap = fileNameInfoMap;
129 this.compileResult = compileResult;
130 }
131
132 public void print()
133 throws IOException
134 {
135 StringBuffer outputBuffer = new StringBuffer();
136 byte[] buffer = new byte[4096];
137 for (;;) {
138 int bytesRead = in.read(buffer);
139 if (bytesRead <= 0) break;
140 String output = new String(buffer, 0, bytesRead);
141 compileResult.appendJavaCompilerOutput(output);
142 outputBuffer.append(output);
143 for (;;) {
144 Matcher matcher = errStmtP.matcher(outputBuffer);
145 if (!matcher.lookingAt()) break;
146 CharSequence orig = convertToOrig(matcher);
147 if (orig != null && orig.length() > 0) {
148 compileResult.incrementErrorCount();
149 compileResult.appendOutput(orig);
150 out.print(orig);
151 }
152 outputBuffer.delete(0, matcher.end());
153 }
154 }
155 in.close();
156 }
157
158 private CharSequence convertToOrig(Matcher m)
159 {
160 CharSequence result;
161 result = convertExtendClass(m);
162 if (result != null) return result;
163 result = convertUnresolvedSymbols(m);
164 if (result != null) return result;
165 result = ignoreNonSyntaxErrors(m);
166 if (result != null) return result;
167 return defaultConvert(m, false, null);
168 }
169
170 private CharSequence convertExtendClass(Matcher m)
171 {
172 String message = m.group(3);
173 if (!message.equals("interface expected here")) return null;
174 return defaultConvert(m, false,
175 "a miclass can only extend another miclass");
176 }
177
178 private CharSequence convertUnresolvedSymbols(Matcher m)
179 {
180 String message = m.group(3);
181 String symbol = m.group(4);
182 String location = m.group(5);
183 if (!message.equals("cannot resolve symbol") ||
184 symbol == null || location == null) {
185 return null;
186 }
187 return defaultConvert(m, true, null);
188 }
189
190 private CharSequence ignoreNonSyntaxErrors(Matcher m)
191 {
192 String fileName = m.group(1);
193 FileInfo fileInfo = (FileInfo) fileNameInfoMap.get(fileName);
194 if (fileInfo == null || fileInfo.type != FileInfo.TYPE_ERROR_FILE) {
195 return null;
196 }
197 String message = m.group(3);
198 // Returning 'null' will cause the error to be displayed.
199 if (message.startsWith("unclosed")) return null;
200 if (message.indexOf("expected") >= 0) return null;
201 if (message.equals("illegal start of expression")) return null;
202 // Returning "" will cause the error to be skipped.
203 return "";
204 }
205
206 private CharSequence defaultConvert
207 (Matcher m, boolean force, String alternateMessage)
208 {
209 DiffString errStmt = new DiffString(m.group());
210 String fileName = m.group(1);
211 String lineNum = m.group(2);
212 String line = m.group(6);
213 String pointer = m.group(7);
214 FileInfo fileInfo = (FileInfo) fileNameInfoMap.get(fileName);
215 if (fileInfo == null || fileInfo.contents == null) return "";
216 DiffString.LineInfo lineInfo =
217 getOrigLineInfo(fileInfo.contents, Integer.parseInt(lineNum),
218 toLineOffset(pointer), force);
219 if (lineInfo == null) return null;
220 errStmt.replace(m.start(1), m.end(1), fileInfo.givenFileName);
221 errStmt.replace(m.start(2), m.end(2),
222 Integer.toString(lineInfo.lineNumber));
223 if (alternateMessage != null) {
224 errStmt.replace(m.start(3), m.end(3), alternateMessage);
225 }
226 errStmt.replace(m.start(6), m.end(6), lineInfo.line);
227 errStmt.replace(m.start(7), m.end(7), toPointer(lineInfo.lineOffset));
228 if (fileInfo.type == FileInfo.TYPE_MICLASS_INTERFACE &&
229 m.group(5) != null) {
230 // Convert the word 'interface' that shows up in the "location:"
231 // to 'miclass', so as not to confuse the reader.
232 int idx = m.group(5).indexOf("interface");
233 if (idx >= 0) {
234 errStmt.replace(m.start(5) + idx, m.start(5) + idx + 9, "miclass");
235 }
236 }
237 // Convert mangled class names that show up in the error statements back
238 // to their original name.
239 Matcher matcher = miClassNameP.matcher(errStmt);
240 return matcher.replaceAll("$1");
241 }
242
243 private static int toLineOffset(String pointer)
244 {
245 return pointer.indexOf('^');
246 }
247
248 private static CharSequence toPointer(int lineOffset)
249 {
250 StringBuffer result = new StringBuffer();
251 for (int i=0; i<lineOffset; ++i) {
252 result.append(' ');
253 }
254 result.append('^');
255 return result;
256 }
257
258 private static DiffString.LineInfo getOrigLineInfo
259 (DiffString ds, int valueLineNumber, int valueLineOffset, boolean force)
260 {
261 DiffString.LineInfo lineInfo =
262 ds.getOrigLineInfo(valueLineNumber, valueLineOffset, force);
263 if (lineInfo == null) return null;
264 CharSequence orig = ds.getOriginal();
265 if (orig instanceof DiffString) {
266 return getOrigLineInfo((DiffString) orig, lineInfo.lineNumber,
267 lineInfo.lineOffset, force);
268 }
269 return lineInfo;
270 }
271
272 }
273
274 private static final String _ID_ =
275 "@(#) $Id: JavaCompiler.java,v 1.16 2002/03/27 18:50:29 hobb0001 Exp $";
276 }
277
278