1   /*
2    *
3    *  Copyright (c) 1998-2005, The University of Sheffield.
4    *
5    *  This file is part of GATE (see http://gate.ac.uk/), and is free
6    *  software, licenced under the GNU Library General Public License,
7    *  Version 2, June 1991 (in the distribution as file licence.html,
8    *  and also available at http://gate.ac.uk/gate/licence.html).
9    *
10   *  This class is based on code from the Jasper 2 JSP compiler from Jakarta
11   *  Tomcat 5.5, produced by the Apache project.
12   *
13   *  Ian Roberts, 13/Dec/2004
14   *
15   *  $Id: Eclipse.java,v 1.4 2005/01/12 16:37:58 valyt Exp $
16   */
17  package gate.util.compilers;
18  
19  import java.io.*;
20  import java.util.*;
21  
22  import org.eclipse.jdt.core.compiler.IProblem;
23  import org.eclipse.jdt.internal.compiler.ClassFile;
24  import org.eclipse.jdt.internal.compiler.CompilationResult;
25  import org.eclipse.jdt.internal.compiler.Compiler;
26  import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
27  import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
28  import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
29  import org.eclipse.jdt.internal.compiler.IProblemFactory;
30  import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
31  import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
32  import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
33  import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
34  import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
35  import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
36  
37  import gate.util.*;
38  import gate.Gate;
39  import gate.GateConstants;
40  import gate.creole.ExecutionException;
41  
42  /**
43   * This class copiles a set of java sources using the JDT compiler from the
44   * Eclipse project.  Unlike the Sun compiler, this compiler can load
45   * dependencies directly from the GATE class loader, which (a) makes it faster,
46   * (b) means the compiler will work when GATE is loaded from a classloader
47   * other than the system classpath (for example within a Tomcat web
48   * application), and (c) allows it to compile code that depends on classes
49   * defined in CREOLE plugins, as well as in the GATE core.  This is the default
50   * compiler for GATE version 3.0.
51   *
52   * @author Ian Roberts
53   */
54  public class Eclipse extends gate.util.Javac {
55  
56    /**
57     * Compiles a set of java sources using the Eclipse Java compiler and loads
58     * the compiled classes in the gate class loader.
59     * 
60     * @param sources a map from fully qualified classname to java source
61     * @throws GateException in case of a compilation error or warning.
62     * In the case of warnings the compiled classes are loaded before the error is
63     * raised.
64     */
65    public void compile(final Map sources) throws GateException {
66      if(classLoader == null) classLoader = Gate.getClassLoader();
67  
68      // store any problems that occur douring compilation
69      final Map problems = new HashMap();
70  
71      // A class representing a file to be compiled.  An instance of this class
72      // is returned by the name environment when one of the classes given in the
73      // sources map is requested.
74      class CompilationUnit implements ICompilationUnit {
75        String className;
76  
77        CompilationUnit(String className) {
78          this.className = className;
79        }
80  
81        public char[] getFileName() {
82          return className.toCharArray();
83        }
84        
85        public char[] getContents() {
86          return ((String)sources.get(className)).toCharArray();
87        }
88        
89        /**
90         * Returns the unqualified name of the class defined by this
91         * compilation unit.
92         */
93        public char[] getMainTypeName() {
94          int dot = className.lastIndexOf('.');
95          if (dot > 0) {
96            return className.substring(dot + 1).toCharArray();
97          }
98          return className.toCharArray();
99        }
100       
101       /**
102        * Returns the package name for the class defined by this compilation
103        * unit.  For example, if this unit defines java.lang.String,
104        * ["java".toCharArray(), "lang".toCharArray()] would be returned.
105        */
106       public char[][] getPackageName() {
107         StringTokenizer izer = 
108           new StringTokenizer(className, ".");
109         char[][] result = new char[izer.countTokens()-1][];
110         for (int i = 0; i < result.length; i++) {
111           String tok = izer.nextToken();
112           result[i] = tok.toCharArray();
113         }
114         return result;
115       }
116     }
117     
118     // Name enviroment - maps class names to eclipse objects.  If the class
119     // name is one of those given in the sources map, the appropriate
120     // CompilationUnit is created.  Otherwise, we try to load the requested
121     // .class file from the GATE classloader and return a ClassFileReader for
122     // that class.
123     final INameEnvironment env = new INameEnvironment() {
124 
125       /**
126        * Tries to find the class or source file defined by the given type
127        * name.  We construct a string from the compound name (e.g. ["java",
128        * "lang", "String"] becomes "java.lang.String") and search using that.
129        */
130       public NameEnvironmentAnswer findType(char[][] compoundTypeName) {
131         String result = "";
132         String sep = "";
133         for (int i = 0; i < compoundTypeName.length; i++) {
134           result += sep;
135           result += new String(compoundTypeName[i]);
136           sep = ".";
137         }
138         return findType(result);
139       }
140 
141       /**
142        * Tries to find the class or source file defined by the given type
143        * name.  We construct a string from the compound name (e.g. "String",
144        * ["java", "lang"] becomes "java.lang.String") and search using that.
145        */
146       public NameEnvironmentAnswer findType(char[] typeName, 
147                                             char[][] packageName) {
148         String result = "";
149         String sep = "";
150         for (int i = 0; i < packageName.length; i++) {
151           result += sep;
152           result += new String(packageName[i]);
153           sep = ".";
154         }
155         result += sep;
156         result += new String(typeName);
157         return findType(result);
158       }
159       
160       /**
161        * Find the type referenced by the given name.
162        */
163       private NameEnvironmentAnswer findType(String className) {
164         try {
165           if (sources.containsKey(className)) {
166             // if it's one of the sources we were given to compile,
167             // return that as a CompilationUnit.
168             ICompilationUnit compilationUnit = new CompilationUnit(className);
169             return new NameEnvironmentAnswer(compilationUnit);
170           }
171 
172           // otherwise, try and load the class from the GATE classloader.
173           String resourceName = className.replace('.', '/') + ".class";
174           InputStream is = classLoader.getResourceAsStream(resourceName);
175           if (is != null) {
176             byte[] classBytes;
177             byte[] buf = new byte[8192];
178             ByteArrayOutputStream baos = 
179               new ByteArrayOutputStream(buf.length);
180             int count;
181             while ((count = is.read(buf, 0, buf.length)) > 0) {
182               baos.write(buf, 0, count);
183             }
184             baos.flush();
185             classBytes = baos.toByteArray();
186             char[] fileName = className.toCharArray();
187             ClassFileReader classFileReader = 
188               new ClassFileReader(classBytes, fileName, 
189                                   true);
190             return new NameEnvironmentAnswer(classFileReader);
191           }
192         }
193         catch (IOException exc) {
194           System.err.println("Compilation error");
195           exc.printStackTrace();
196         }
197         catch (org.eclipse.jdt.internal.compiler
198                     .classfmt.ClassFormatException exc) {
199           System.err.println("Compilation error");
200           exc.printStackTrace();
201         }
202         // if no class found by that name, either as a source of in the
203         // GATE classloader, return null.  This will cause a compiler
204         // error.
205         return null;
206       }
207 
208       /**
209        * Is the requested name a package?  We assume yes if it's not a class.
210        */
211       private boolean isPackage(String result) {
212         if (sources.containsKey(result)) {
213           return false;
214         }
215 //        String resourceName = result.replace('.', '/') + ".class";
216         Class theClass = null;
217         try{
218           theClass = classLoader.loadClass(result);
219         }catch(Throwable e){};
220         return theClass == null;
221       }
222 
223       /**
224        * Checks whether the given name refers to a package rather than a
225        * class.
226        */
227       public boolean isPackage(char[][] parentPackageName, 
228                                char[] packageName) {
229         String result = "";
230         String sep = "";
231         if (parentPackageName != null) {
232           for (int i = 0; i < parentPackageName.length; i++) {
233             result += sep;
234             String str = new String(parentPackageName[i]);
235             result += str;
236             sep = ".";
237           }
238         }
239         String str = new String(packageName);
240         if (Character.isUpperCase(str.charAt(0))) {
241           if (!isPackage(result)) {
242             return false;
243           }
244         }
245         result += sep;
246         result += str;
247         return isPackage(result);
248       }
249 
250       public void cleanup() {
251       }
252 
253     };
254 
255     // Error handling policy - try the best we can
256     final IErrorHandlingPolicy policy = 
257         DefaultErrorHandlingPolicies.proceedWithAllProblems();
258 
259     final Map settings = new HashMap();
260     settings.put(CompilerOptions.OPTION_LineNumberAttribute,
261                  CompilerOptions.GENERATE);
262     settings.put(CompilerOptions.OPTION_SourceFileAttribute,
263                  CompilerOptions.GENERATE);
264     settings.put(CompilerOptions.OPTION_ReportDeprecation,
265                  CompilerOptions.IGNORE);
266     // ignore unused imports - otherwise every JAPE action class would generate
267     // warnings...
268     settings.put(CompilerOptions.OPTION_ReportUnusedImport,
269                  CompilerOptions.IGNORE);
270 
271     final IProblemFactory problemFactory = 
272       new DefaultProblemFactory(Locale.getDefault());
273 
274     // CompilerRequestor defines what to do with the result of a compilation.
275     final ICompilerRequestor requestor = new ICompilerRequestor() {
276       public void acceptResult(CompilationResult result) {
277         boolean errors = false;
278         if (result.hasProblems()) {
279           IProblem[] problems = result.getProblems();
280           for (int i = 0; i < problems.length; i++) {
281             // store all the errors and warnings from this result
282             IProblem problem = problems[i];
283             if (problem.isError()) {
284               errors = true;
285             }
286             addProblem(problem);
287           }
288         }
289         // if there were no errors (there may have been warnings), load the
290         // compiled classes into the GATE classloader
291         if (!errors) {
292           ClassFile[] classFiles = result.getClassFiles();
293           for (int i = 0; i < classFiles.length; i++) {
294             ClassFile classFile = classFiles[i];
295             char[][] compoundName = classFile.getCompoundName();
296             String className = "";
297             String sep = "";
298             for (int j = 0; j < compoundName.length; j++) {
299               className += sep;
300               className += new String(compoundName[j]);
301               sep = ".";
302             }
303             byte[] bytes = classFile.getBytes();
304             classLoader.defineGateClass(className, bytes,
305                                         0, bytes.length);
306           }
307         }
308       }
309 
310       private void addProblem(IProblem problem) {
311         String name = new String(problem.getOriginatingFileName());
312         List problemsForName = (List)problems.get(name);
313         if(problemsForName == null) {
314           problemsForName = new ArrayList();
315           problems.put(name, problemsForName);
316         }
317         problemsForName.add(problem);
318       }
319     };
320 
321     // Define the list of things to compile
322     ICompilationUnit[] compilationUnits = new ICompilationUnit[sources.size()];
323     int i = 0;
324     Iterator sourcesIt = sources.keySet().iterator();
325     while(sourcesIt.hasNext()) {
326       compilationUnits[i++] =
327         new CompilationUnit((String)sourcesIt.next());
328     }
329 
330     // create the compiler
331     Compiler compiler = new Compiler(env,
332                                      policy,
333                                      settings,
334                                      requestor,
335                                      problemFactory);
336 
337     // and compile the classes
338     compiler.compile(compilationUnits);
339 
340     if(!problems.isEmpty()) {
341       Iterator problemsIt = problems.entrySet().iterator();
342       while(problemsIt.hasNext()) {
343         Map.Entry prob = (Map.Entry)problemsIt.next();
344         String name = (String)prob.getKey();
345         List probsForName = (List)prob.getValue();
346         Iterator probsForNameIt = probsForName.iterator();
347         while(probsForNameIt.hasNext()) {
348           IProblem problem = (IProblem)probsForNameIt.next();
349           if(problem.isError()) {
350             Err.pr("Error: ");
351           }
352           else if(problem.isWarning()) {
353             Err.pr("Warning: ");
354           }
355           Err.prln(problem.getMessage()
356                 + " at line " 
357                 + problem.getSourceLineNumber() + " in " + name);
358         }
359         // print the source for this class, to help the user debug.
360         Err.prln("\nThe offending input was:\n");
361         Err.prln(Strings.addLineNumbers((String)sources.get(name)));
362       }
363       throw new GateException(
364         "There were problems; see error log for details!");
365     }
366   }
367 
368   private static GateClassLoader classLoader;
369 }
370