Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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 }