Source code: mijava/MIJavaCompiler.java
1 /*
2 @(#) $Id: MIJavaCompiler.java,v 1.24 2002/03/28 20:07:25 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.File;
25 import java.io.Writer;
26 import java.io.FileWriter;
27 import java.io.FilenameFilter;
28 import java.io.IOException;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.Map;
32 import java.util.Set;
33 import java.util.Iterator;
34 import java.util.ArrayList;
35 import java.util.HashMap;
36 import java.util.HashSet;
37 import java.util.regex.Pattern;
38 import java.util.regex.Matcher;
39
40
41
42 class MIJavaCompiler
43 {
44 public static final class Result
45 {
46 StringBuffer output = new StringBuffer();
47 StringBuffer javaCompilerOutput = new StringBuffer();
48 boolean success = true;
49 int errorCount = 0;
50 public synchronized void appendOutput(CharSequence s)
51 {
52 output.append(s.toString());
53 }
54 public synchronized void appendJavaCompilerOutput(CharSequence s)
55 {
56 javaCompilerOutput.append(s.toString());
57 }
58 public synchronized void incrementErrorCount()
59 {
60 success = false;
61 ++errorCount;
62 }
63 }
64
65
66 public static Result compile
67 (Collection compilerFlags, Collection sourceFiles)
68 throws IOException
69 {
70 MIJavaCompiler compiler = new MIJavaCompiler(sourceFiles);
71 Result result = new Result();
72 compiler.compile(compilerFlags, result);
73 return result;
74 }
75
76 // Map(String [fileName], Parser.Result)
77 private Map parseResults = new HashMap();
78 // Map(String [packageName], Set(String [className]))
79 private Map checkedClasses = new HashMap();
80 // Set(String)
81 private static final Collection miClassNames = new HashSet();
82 // Map(String [packageName], Map(String [className], Parser.ClassInfo))
83 private static final Map miClasses = new HashMap();
84
85 private MIJavaCompiler(Collection sourceFiles)
86 throws IOException
87 {
88 for (Iterator i=sourceFiles.iterator(); i.hasNext(); ) {
89 String sourceFile = (String) i.next();
90 CharSequence sourceFileText = MIJava.readFile(sourceFile);
91 Parser.Result parseResult = null;
92 if (sourceFileText != null) {
93 parseResult = Parser.parse(sourceFileText);
94 }
95 parseResults.put(sourceFile, parseResult);
96 if (parseResult != null) {
97 addMIClasses(parseResult);
98 }
99 }
100 }
101
102 public static final Parser.ClassInfo findClass
103 (String packageName, String className)
104 {
105 if (packageName == null) packageName = "";
106 Map packageClasses = (Map) miClasses.get(packageName);
107 if (packageClasses == null) return null;
108 return (Parser.ClassInfo) packageClasses.get(className);
109 }
110
111 private void compile(Collection compilerFlags, Result result)
112 throws IOException
113 {
114 File tempDir = createTempDir();
115 File ifaceDir = createInterfaceDir(tempDir);
116 File buildDir;
117 buildDir = compileInterfaces(ifaceDir, compilerFlags, result);
118 if (result.success) {
119 copyClassFiles(buildDir);
120 MIJava.prependToClassPath(buildDir);
121 File classDir = createClassDir(tempDir);
122 buildDir = compileClasses(classDir, compilerFlags, result);
123 if (result.success) {
124 copyClassFiles(buildDir);
125 }
126 }
127 deleteDir(tempDir);
128 }
129
130 private File compileInterfaces
131 (File tempDir, Collection compilerFlags, Result result)
132 throws IOException
133 {
134 // Collection(JavaCompiler.FileInfo)
135 Collection files = new ArrayList();
136 boolean okay = true;
137 for (Iterator i=parseResults.entrySet().iterator(); i.hasNext(); ) {
138 Map.Entry entry = (Map.Entry) i.next();
139 String sourceFile = (String) entry.getKey();
140 if (!Validator.validateSourceFile(sourceFile, result)) {
141 okay = false;
142 continue;
143 }
144 Parser.Result parseResult = (Parser.Result) entry.getValue();
145 if (parseResult == null) {
146 compileForErrors(sourceFile, tempDir, compilerFlags, result);
147 okay = false;
148 continue;
149 }
150 for (Iterator j=parseResult.classes.iterator(); j.hasNext(); ) {
151 Parser.ClassInfo clazz = (Parser.ClassInfo) j.next();
152 JavaCompiler.FileInfo fileInfo;
153 if (!Validator.validateInterface(clazz, parseResult, sourceFile,
154 result)) {
155 okay = false;
156 continue;
157 }
158 if (clazz.isMIClass) {
159 DiffString iface = Interface.generate(clazz, parseResult);
160 String tempFileName =
161 MIJava.writeToFile(tempDir, interfaceFileName(clazz, parseResult),
162 iface);
163 fileInfo = new JavaCompiler.FileInfo
164 (sourceFile, tempFileName, iface,
165 JavaCompiler.FileInfo.TYPE_MICLASS_INTERFACE);
166 } else {
167 DiffString emptyClass = EmptyClass.generate(clazz, parseResult);
168 String tempFileName =
169 MIJava.writeToFile(tempDir, emptyClassFileName(clazz, parseResult),
170 emptyClass);
171 fileInfo = new JavaCompiler.FileInfo
172 (sourceFile, tempFileName, emptyClass,
173 JavaCompiler.FileInfo.TYPE_EMPTY_CLASS);
174 }
175 files.add(fileInfo);
176 }
177 }
178 File buildDir = getBuildDir(tempDir);
179 if (okay) JavaCompiler.compile(compilerFlags, files, buildDir, result);
180 return buildDir;
181 }
182
183 private File compileClasses
184 (File tempDir, Collection compilerFlags, Result result)
185 throws IOException
186 {
187 // Collection(JavaCompiler.FileInfo)
188 Collection files = new ArrayList();
189 boolean okay = true;
190 for (Iterator i=parseResults.entrySet().iterator(); i.hasNext(); ) {
191 Map.Entry entry = (Map.Entry) i.next();
192 String sourceFile = (String) entry.getKey();
193 Parser.Result parseResult = (Parser.Result) entry.getValue();
194 if (parseResult == null) {
195 // This shouldn't ever happen, but if an error message accidentally
196 // slips by the filters, it could happen. Continue on and let the top
197 // level loop handle the error reporting.
198 okay = false;
199 continue;
200 }
201 for (Iterator j=parseResult.classes.iterator(); j.hasNext(); ) {
202 Parser.ClassInfo clazz = (Parser.ClassInfo) j.next();
203 DiffString classText;
204 int fileType;
205 if (clazz.isMIClass) {
206 if (!Validator.validateMIClass(clazz, parseResult, sourceFile,
207 result)) {
208 okay = false;
209 continue;
210 }
211 classText = MIClass.generate(clazz, parseResult);
212 fileType = JavaCompiler.FileInfo.TYPE_MICLASS;
213 } else {
214 classText = StandardClass.generate(clazz, parseResult);
215 fileType = JavaCompiler.FileInfo.TYPE_NORMAL_CLASS;
216 }
217 classText = replaceReferences(classText);
218 String tempFileName =
219 MIJava.writeToFile(tempDir, classFileName(clazz, parseResult),
220 classText);
221 JavaCompiler.FileInfo fileInfo =
222 new JavaCompiler.FileInfo(sourceFile, tempFileName, classText,
223 fileType);
224 files.add(fileInfo);
225 }
226 }
227 File buildDir = getBuildDir(tempDir);
228 if (okay) JavaCompiler.compile(compilerFlags, files, buildDir, result);
229 return buildDir;
230 }
231
232 private static void compileForErrors
233 (String sourceFileName, File tempDir, Collection compilerFlags,
234 Result result)
235 throws IOException
236 {
237 Collection errorFile = new ArrayList(1);
238 String sourceFileText = MIJava.readFile(sourceFileName).toString();
239 DiffString ds = new DiffString(sourceFileText);
240 // Clear out the usage of the 'miclass' keyword, so that the Java compiler
241 // doesn't get tripped up on it and fail to report the real problem.
242 for (int start = 0;;) {
243 int idx = sourceFileText.indexOf("miclass", start);
244 if (idx < 0) break;
245 ds.delete(idx, idx+2);
246 start = idx + 7;
247 }
248 String tempFileName =
249 MIJava.writeToFile(tempDir, conventionalFileName(sourceFileName), ds);
250 JavaCompiler.FileInfo fileInfo = new JavaCompiler.FileInfo
251 (sourceFileName, tempFileName, ds,
252 JavaCompiler.FileInfo.TYPE_ERROR_FILE);
253 errorFile.add(fileInfo);
254 File buildDir = getBuildDir(tempDir);
255 JavaCompiler.compile(compilerFlags, errorFile, buildDir, result);
256 }
257
258 private void copyClassFiles(File buildDir)
259 throws IOException
260 {
261 String destDir = MIJava.getDestDir();
262 if (destDir.length() != 0) {
263 File dir = new File(destDir);
264 dir.mkdirs();
265 MIJava.deepCopy(buildDir, dir);
266 return;
267 }
268 copyToSourceDir(buildDir);
269 }
270
271 private void copyToSourceDir(File buildDir)
272 throws IOException
273 {
274 for (Iterator i=parseResults.entrySet().iterator(); i.hasNext(); ) {
275 Map.Entry entry = (Map.Entry) i.next();
276 String sourceFileName = (String) entry.getKey();
277 Parser.Result parseResult = (Parser.Result) entry.getValue();
278 if (parseResult == null) {
279 continue;
280 }
281 File sourceFile = new File(sourceFileName);
282 String subDirName =
283 parseResult.packageName.replace('.', File.pathSeparatorChar);
284 File specificBuildDir = new File(buildDir, subDirName);
285 for (Iterator j=parseResult.classes.iterator(); j.hasNext(); ) {
286 Parser.ClassInfo clazz = (Parser.ClassInfo) j.next();
287 copyClass(clazz, "", specificBuildDir, sourceFile.getParentFile());
288 }
289 }
290 }
291
292 private static void copyClass
293 (Parser.ClassInfo clazz, String enclosingClassName,
294 File buildDir, File destDir)
295 throws IOException
296 {
297 File classFile;
298 classFile =
299 new File(buildDir, enclosingClassName + clazz.name + ".class");
300 if (classFile.exists()) {
301 MIJava.copyFile(classFile, destDir);
302 }
303 classFile =
304 new File(buildDir, enclosingClassName + "$" + clazz.name + ".class");
305 if (classFile.exists()) {
306 MIJava.copyFile(classFile, destDir);
307 }
308 for (Iterator i=clazz.members.iterator(); i.hasNext(); ) {
309 Object member = i.next();
310 if (member instanceof Parser.ClassInfo) {
311 Parser.ClassInfo innerClass = (Parser.ClassInfo) member;
312 if (enclosingClassName.length() > 0) {
313 enclosingClassName += "$";
314 }
315 enclosingClassName += clazz.name;
316 copyClass(innerClass, enclosingClassName, buildDir, destDir);
317 }
318 }
319 }
320
321 private void deleteEmptyClasses(File buildDir)
322 {
323 for (Iterator i=parseResults.entrySet().iterator(); i.hasNext(); ) {
324 Map.Entry entry = (Map.Entry) i.next();
325 String sourceFileName = (String) entry.getKey();
326 Parser.Result parseResult = (Parser.Result) entry.getValue();
327 if (parseResult == null) {
328 continue;
329 }
330 String subDirName =
331 parseResult.packageName.replace('.', File.separatorChar);
332 File specificBuildDir = new File(buildDir, subDirName);
333 for (Iterator j=parseResult.classes.iterator(); j.hasNext(); ) {
334 final Parser.ClassInfo clazz = (Parser.ClassInfo) j.next();
335 if (!clazz.isMIClass) {
336 File[] files = specificBuildDir.listFiles(new FilenameFilter() {
337 public boolean accept(File dir, String name) {
338 return name.startsWith(clazz.name);
339 }
340 });
341 for (int k=0; files != null && k<files.length; ++k) {
342 files[k].delete();
343 }
344 }
345 }
346 }
347 }
348
349 private void addMIClasses(Parser.Result parseResult)
350 {
351 for (Iterator i=parseResult.classes.iterator(); i.hasNext(); ) {
352 Parser.ClassInfo clazz = (Parser.ClassInfo) i.next();
353 addDefinedMIClass(parseResult.packageName, clazz);
354 Map packageClasses = (Map) miClasses.get(parseResult.packageName);
355 if (packageClasses == null) {
356 packageClasses = new HashMap();
357 miClasses.put(parseResult.packageName, packageClasses);
358 }
359 packageClasses.put(clazz.name, clazz);
360 }
361 addImportedMIClasses(parseResult);
362 }
363
364 private void addDefinedMIClass(String packageName, Parser.ClassInfo clazz)
365 {
366 String className = MIClass.genInterfaceName(clazz).toString();
367 Set classes = getCheckedPackageClasses(packageName);
368 boolean alreadyChecked = classes.contains(className);
369 classes.add(className);
370 if (clazz.isMIClass && !alreadyChecked) {
371 recordMIClass(clazz, packageName, className);
372 }
373 }
374
375 private void addImportedMIClasses(Parser.Result parseResult)
376 {
377 for (Iterator i=parseResult.imports.iterator(); i.hasNext(); ) {
378 Parser.ImportInfo importInfo = (Parser.ImportInfo) i.next();
379 if (importInfo.allClasses) {
380 scanPackage(importInfo.packageName);
381 } else {
382 try {
383 Class clazz = PackageScanner.getClass(importInfo);
384 addImportedMIClass(importInfo.packageName, clazz);
385 } catch (ClassNotFoundException e) {}
386 }
387 }
388 }
389
390 private void addImportedMIClass(String packageName, Class clazz)
391 {
392 String className = convertName(clazz.getName());
393 Set classes = getCheckedPackageClasses(packageName);
394 if (classes.contains(className)) return;
395 classes.add(className);
396 if (MIJava.isMIClass(clazz)) {
397 recordMIClass(clazz, packageName, className);
398 }
399 }
400
401 // Set(String [packageName])
402 private Set scannedPackages = new HashSet();
403
404 private void scanPackage(String packageName)
405 {
406 if (scannedPackages.contains(packageName)) return;
407 scannedPackages.add(packageName);
408 Collection classes = PackageScanner.getClasses(packageName);
409 for (Iterator i=classes.iterator(); i.hasNext(); ) {
410 Class clazz = (Class) i.next();
411 String className = convertName(clazz.getName());
412 Set checkedPackageClasses = getCheckedPackageClasses(packageName);
413 if (checkedPackageClasses.contains(className)) continue;
414 checkedPackageClasses.add(className);
415 if (MIJava.isMIClass(clazz)) {
416 recordMIClass(clazz, packageName, className);
417 }
418 }
419 }
420
421 private void recordMIClass
422 (Object clazz, String packageName, String className)
423 {
424 miClassNames.add(className);
425 // We may deal with the package name sometime later.
426 }
427
428 private static final Pattern spaceP = Pattern.compile("\\s*");
429
430 private DiffString replaceReferences(CharSequence s)
431 {
432 DiffString result = new DiffString(s);
433 if (miClassNames.size() == 0) return result;
434 StringBuffer classRefs = new StringBuffer();
435 StringBuffer newDecls = new StringBuffer();
436 boolean first = true;
437 for (Iterator i=miClassNames.iterator(); i.hasNext(); ) {
438 String className = (String) i.next();
439 if (!first) {
440 classRefs.append("|");
441 newDecls.append("|");
442 }
443 first = false;
444 classRefs.append("(?<![a-zA-Z0-9_\\$])" + className + ".(?!class)");
445 // We purposely array creation alone. (e.g. "new Foo[13];")
446 newDecls.append("new\\s+("+className + ")\\s*\\((.*?)\\)");
447 }
448 Pattern classRefsP =
449 Pattern.compile(classRefs.toString().replaceAll("\\.", "\\\\\\."));
450 Matcher classRefsM = classRefsP.matcher(s);
451 for (int start=0; classRefsM.find(start); start = classRefsM.end()) {
452 result.insert(classRefsM.start(), "$");
453 }
454 Pattern newDeclsP =
455 Pattern.compile(newDecls.toString().replaceAll("\\.(?!\\*)", "\\\\\\."),
456 Pattern.DOTALL);
457 Matcher newDeclsM = newDeclsP.matcher(s);
458 for (int start=0; newDeclsM.find(start); start = newDeclsM.end()) {
459 int count = newDeclsM.groupCount();
460 for (int i=1; i<=count*2; i += 2) {
461 if (newDeclsM.start(i) >= 0) {
462 result.insert(newDeclsM.start(i), "$");
463 String firstParam = "null";
464 Matcher spaceM = spaceP.matcher(newDeclsM.group(i+1));
465 if (!spaceM.matches()) firstParam += ",";
466 result.insert(newDeclsM.start(i+1), firstParam);
467 break;
468 }
469 }
470 }
471 return result;
472 }
473
474 private Set getCheckedPackageClasses(String packageName)
475 {
476 Set classes = (Set) checkedClasses.get(packageName);
477 if (classes == null) {
478 classes = new HashSet();
479 checkedClasses.put(packageName, classes);
480 }
481 return classes;
482 }
483
484 private static String convertName(String s)
485 {
486 // Strip out package name
487 int dotIdx = s.lastIndexOf('.');
488 if (dotIdx >= 0) {
489 s = s.substring(dotIdx + 1);
490 }
491 char[] in = s.toCharArray();
492 char[] out = new char[in.length];
493 int o = 0;
494 // A class name such as "$Foo" should be converted to "Foo".
495 for (int i=0; i<in.length; ++i) {
496 // An embedded 'miclass' will have a name like "$Foo$$Bar", which should
497 // be converted to "Foo.Bar". But an embedded 'class' will have a name
498 // like "$Foo$Bar", which should also be converted to "Foo.Bar".
499 if (in[i] == '$') {
500 if (o == 0) continue;
501 if (out[o-1] == '.') continue;
502 out[o] = '.';
503 } else {
504 out[o] = in[i];
505 }
506 ++o;
507 }
508 return new String(out, 0, o);
509 }
510
511 private static String classFileName
512 (Parser.ClassInfo clazz, Parser.Result parseResult)
513 {
514 String className = clazz.name;
515 if (clazz.isMIClass) {
516 className = "$" + className;
517 }
518 return (parseResult.packageName.replace('.', File.separatorChar) +
519 File.separatorChar + className + ".java");
520 }
521
522 private static String interfaceFileName
523 (Parser.ClassInfo clazz, Parser.Result parseResult)
524 {
525 return (parseResult.packageName.replace('.', File.separatorChar) +
526 File.separatorChar + clazz.name + ".java");
527 }
528
529 private static String emptyClassFileName
530 (Parser.ClassInfo clazz, Parser.Result parseResult)
531 {
532 return (parseResult.packageName.replace('.', File.separatorChar) +
533 File.separatorChar + clazz.name + ".java");
534 }
535
536 private static String conventionalFileName(String fileName)
537 {
538 return fileName.replaceAll("\\.[^\\.]+$", ".java");
539 }
540
541 private static File getBuildDir(File parent)
542 {
543 File buildDir = new File(parent, "build");
544 buildDir.mkdirs();
545 return buildDir;
546 }
547
548 private static File createTempDir()
549 throws IOException
550 {
551 File tempFile = File.createTempFile("mijava", null);
552 String path = tempFile.getCanonicalPath();
553 tempFile.delete();
554 File tempDir = new File(path);
555 tempDir.mkdir();
556 return tempDir;
557 }
558
559 private static File createInterfaceDir(File parent)
560 throws IOException
561 {
562 File dir = new File(parent, "interfaces");
563 dir.mkdir();
564 return dir;
565 }
566
567 private static File createClassDir(File parent)
568 throws IOException
569 {
570 File dir = new File(parent, "classes");
571 dir.mkdir();
572 return dir;
573 }
574
575 private static void deleteDir(File dir)
576 {
577 File[] files = dir.listFiles();
578 for (int i=0; i<files.length; ++i) {
579 if (files[i].isDirectory()) {
580 deleteDir(files[i]);
581 } else {
582 files[i].delete();
583 }
584 }
585 dir.delete();
586 }
587
588 private static final String _ID_ =
589 "@(#) $Id: MIJavaCompiler.java,v 1.24 2002/03/28 20:07:25 hobb0001 Exp $";
590 }