Eclipse.java
001 /*
002  *
003  *  Copyright (c) 1995-2010, The University of Sheffield. See the file
004  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
005  *
006  *  This file is part of GATE (see http://gate.ac.uk/), and is free
007  *  software, licenced under the GNU Library General Public License,
008  *  Version 2, June 1991 (in the distribution as file licence.html,
009  *  and also available at http://gate.ac.uk/gate/licence.html).
010  *
011  *  This class is based on code from the Jasper 2 JSP compiler from Jakarta
012  *  Tomcat 5.5, produced by the Apache project.
013  *
014  *  Ian Roberts, 13/Dec/2004
015  *
016  *  $Id: Eclipse.java 12006 2009-12-01 17:24:28Z thomas_heitz $
017  */
018 package gate.util.compilers;
019 
020 import java.io.*;
021 import java.util.*;
022 
023 // note that we re-package the org.eclipse.jdt classes into an alternative
024 // package using JarJar links (http://code.google.com/p/jarjar/) to avoid
025 // version conflicts
026 import gate.util.compilers.eclipse.jdt.core.compiler.IProblem;
027 import gate.util.compilers.eclipse.jdt.internal.compiler.ClassFile;
028 import gate.util.compilers.eclipse.jdt.internal.compiler.CompilationResult;
029 import gate.util.compilers.eclipse.jdt.internal.compiler.Compiler;
030 import gate.util.compilers.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
031 import gate.util.compilers.eclipse.jdt.internal.compiler.ICompilerRequestor;
032 import gate.util.compilers.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
033 import gate.util.compilers.eclipse.jdt.internal.compiler.IProblemFactory;
034 import gate.util.compilers.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
035 import gate.util.compilers.eclipse.jdt.internal.compiler.env.ICompilationUnit;
036 import gate.util.compilers.eclipse.jdt.internal.compiler.env.INameEnvironment;
037 import gate.util.compilers.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
038 import gate.util.compilers.eclipse.jdt.internal.compiler.impl.CompilerOptions;
039 import gate.util.compilers.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
040 
041 import gate.util.*;
042 import gate.Gate;
043 
044 /**
045  * This class copiles a set of java sources using the JDT compiler from the
046  * Eclipse project.  Unlike the Sun compiler, this compiler can load
047  * dependencies directly from the GATE class loader, which (a) makes it faster,
048  * (b) means the compiler will work when GATE is loaded from a classloader
049  * other than the system classpath (for example within a Tomcat web
050  * application), and (c) allows it to compile code that depends on classes
051  * defined in CREOLE plugins, as well as in the GATE core.  This is the default
052  * compiler for GATE version 3.0.
053  *
054  @author Ian Roberts
055  */
056 public class Eclipse extends gate.util.Javac {
057 
058   public static final boolean DEBUG = false;
059 
060   /**
061    * Compiles a set of java sources using the Eclipse Java compiler and loads
062    * the compiled classes in the gate class loader.
063    
064    @param sources a map from fully qualified classname to java source
065    @throws GateException in case of a compilation error or warning.
066    * In the case of warnings the compiled classes are loaded before the error is
067    * raised.
068    */
069   public void compile(final Map sourcesthrows GateException {
070     if(classLoader == nullclassLoader = Gate.getClassLoader();
071 
072     // store any problems that occur douring compilation
073     final Map problems = new HashMap();
074 
075     // A class representing a file to be compiled.  An instance of this class
076     // is returned by the name environment when one of the classes given in the
077     // sources map is requested.
078     class CompilationUnit implements ICompilationUnit {
079       String className;
080 
081       CompilationUnit(String className) {
082         this.className = className;
083       }
084 
085       public char[] getFileName() {
086         return className.toCharArray();
087       }
088       
089       public char[] getContents() {
090         return ((String)sources.get(className)).toCharArray();
091       }
092       
093       /**
094        * Returns the unqualified name of the class defined by this
095        * compilation unit.
096        */
097       public char[] getMainTypeName() {
098         int dot = className.lastIndexOf('.');
099         if (dot > 0) {
100           return className.substring(dot + 1).toCharArray();
101         }
102         return className.toCharArray();
103       }
104       
105       /**
106        * Returns the package name for the class defined by this compilation
107        * unit.  For example, if this unit defines java.lang.String,
108        * ["java".toCharArray(), "lang".toCharArray()] would be returned.
109        */
110       public char[][] getPackageName() {
111         StringTokenizer izer = 
112           new StringTokenizer(className, ".");
113         char[][] result = new char[izer.countTokens()-1][];
114         for (int i = 0; i < result.length; i++) {
115           String tok = izer.nextToken();
116           result[i= tok.toCharArray();
117         }
118         return result;
119       }
120     }
121     
122     // Name enviroment - maps class names to eclipse objects.  If the class
123     // name is one of those given in the sources map, the appropriate
124     // CompilationUnit is created.  Otherwise, we try to load the requested
125     // .class file from the GATE classloader and return a ClassFileReader for
126     // that class.
127     final INameEnvironment env = new INameEnvironment() {
128 
129       /**
130        * Tries to find the class or source file defined by the given type
131        * name.  We construct a string from the compound name (e.g. ["java",
132        * "lang", "String"] becomes "java.lang.String") and search using that.
133        */
134       public NameEnvironmentAnswer findType(char[][] compoundTypeName) {
135         String result = "";
136         String sep = "";
137         for (int i = 0; i < compoundTypeName.length; i++) {
138           result += sep;
139           result += new String(compoundTypeName[i]);
140           sep = ".";
141         }
142         return findType(result);
143       }
144 
145       /**
146        * Tries to find the class or source file defined by the given type
147        * name.  We construct a string from the compound name (e.g. "String",
148        * ["java", "lang"] becomes "java.lang.String") and search using that.
149        */
150       public NameEnvironmentAnswer findType(char[] typeName, 
151                                             char[][] packageName) {
152         String result = "";
153         String sep = "";
154         for (int i = 0; i < packageName.length; i++) {
155           result += sep;
156           result += new String(packageName[i]);
157           sep = ".";
158         }
159         result += sep;
160         result += new String(typeName);
161         return findType(result);
162       }
163       
164       /**
165        * Find the type referenced by the given name.
166        */
167       private NameEnvironmentAnswer findType(String className) {
168         if(DEBUG) {
169           System.err.println("NameEnvironment.findType(" + className +")");
170         }
171         try {
172           if (sources.containsKey(className)) {
173             if(DEBUG) {
174               System.err.println("Found " + className + " as one of the "
175                   "sources, returning it as a compilation unit");
176             }
177             // if it's one of the sources we were given to compile,
178             // return that as a CompilationUnit.
179             ICompilationUnit compilationUnit = new CompilationUnit(className);
180             return new NameEnvironmentAnswer(compilationUnit, null);
181           }
182 
183           // otherwise, try and load the class from the GATE classloader.
184           String resourceName = className.replace('.''/'".class";
185           InputStream is = classLoader.getResourceAsStream(resourceName);
186           if (is != null) {
187             if(DEBUG) {
188               System.err.println("Found " + className + " in GATE classloader, "
189                   "returning it as a class file reader");
190             }
191             byte[] classBytes;
192             byte[] buf = new byte[8192];
193             ByteArrayOutputStream baos = 
194               new ByteArrayOutputStream(buf.length);
195             int count;
196             while ((count = is.read(buf, 0, buf.length)) 0) {
197               baos.write(buf, 0, count);
198             }
199             baos.flush();
200             classBytes = baos.toByteArray();
201             char[] fileName = className.toCharArray();
202             ClassFileReader classFileReader = 
203               new ClassFileReader(classBytes, fileName, 
204                                   true);
205             return new NameEnvironmentAnswer(classFileReader, null);
206           }
207         }
208         catch (IOException exc) {
209           System.err.println("Compilation error");
210           exc.printStackTrace();
211         }
212         catch (gate.util.compilers.eclipse.jdt.internal.compiler
213                     .classfmt.ClassFormatException exc) {
214           System.err.println("Compilation error");
215           exc.printStackTrace();
216         }
217         // if no class found by that name, either as a source of in the
218         // GATE classloader, return null.  This will cause a compiler
219         // error.
220         if(DEBUG) {
221           System.err.println("Class " + className + " not found");
222         }
223         return null;
224       }
225 
226       /**
227        * Is the requested name a package?  We assume yes if it's not a class.
228        */
229       private boolean isPackage(String result) {
230         if (sources.containsKey(result)) {
231           return false;
232         }
233 //        String resourceName = result.replace('.', '/') + ".class";
234         Class theClass = null;
235         try{
236           theClass = classLoader.loadClass(result);
237         }catch(Throwable e){};
238         return theClass == null;
239       }
240 
241       /**
242        * Checks whether the given name refers to a package rather than a
243        * class.
244        */
245       public boolean isPackage(char[][] parentPackageName, 
246                                char[] packageName) {
247         String result = "";
248         String sep = "";
249         if (parentPackageName != null) {
250           for (int i = 0; i < parentPackageName.length; i++) {
251             result += sep;
252             String str = new String(parentPackageName[i]);
253             result += str;
254             sep = ".";
255           }
256         }
257         String str = new String(packageName);
258         if (Character.isUpperCase(str.charAt(0))) {
259           if (!isPackage(result)) {
260             return false;
261           }
262         }
263         result += sep;
264         result += str;
265         return isPackage(result);
266       }
267 
268       public void cleanup() {
269       }
270 
271     };
272 
273     // Error handling policy - try the best we can
274     final IErrorHandlingPolicy policy = 
275         DefaultErrorHandlingPolicies.proceedWithAllProblems();
276 
277     final Map settings = new HashMap();
278     settings.put(CompilerOptions.OPTION_LineNumberAttribute,
279                  CompilerOptions.GENERATE);
280     settings.put(CompilerOptions.OPTION_SourceFileAttribute,
281                  CompilerOptions.GENERATE);
282     settings.put(CompilerOptions.OPTION_ReportDeprecation,
283                  CompilerOptions.IGNORE);
284     // ignore unused imports, missing serial version UIDs and unused local
285     // variables - otherwise every JAPE action class would generate warnings...
286     settings.put(CompilerOptions.OPTION_ReportUnusedImport,
287                  CompilerOptions.IGNORE);
288     settings.put(CompilerOptions.OPTION_ReportMissingSerialVersion,
289                  CompilerOptions.IGNORE);
290     settings.put(CompilerOptions.OPTION_ReportUnusedLocal,
291                  CompilerOptions.IGNORE);
292     settings.put(CompilerOptions.OPTION_ReportUncheckedTypeOperation,
293                  CompilerOptions.IGNORE);
294     settings.put(CompilerOptions.OPTION_ReportRawTypeReference,
295                  CompilerOptions.IGNORE);
296 
297     // source and target - force 1.5 as GATE only works on 1.5 or later.
298     settings.put(CompilerOptions.OPTION_Source,
299                  CompilerOptions.VERSION_1_5);
300     settings.put(CompilerOptions.OPTION_TargetPlatform,
301                  CompilerOptions.VERSION_1_5);
302 
303     final IProblemFactory problemFactory = 
304       new DefaultProblemFactory(Locale.getDefault());
305 
306     // CompilerRequestor defines what to do with the result of a compilation.
307     final ICompilerRequestor requestor = new ICompilerRequestor() {
308       public void acceptResult(CompilationResult result) {
309         boolean errors = false;
310         if (result.hasProblems()) {
311           IProblem[] problems = result.getProblems();
312           for (int i = 0; i < problems.length; i++) {
313             // store all the errors and warnings from this result
314             IProblem problem = problems[i];
315             if (problem.isError()) {
316               errors = true;
317             }
318             addProblem(problem);
319           }
320         }
321         // if there were no errors (there may have been warnings), load the
322         // compiled classes into the GATE classloader
323         if (!errors) {
324           ClassFile[] classFiles = result.getClassFiles();
325           for (int i = 0; i < classFiles.length; i++) {
326             ClassFile classFile = classFiles[i];
327             char[][] compoundName = classFile.getCompoundName();
328             String className = "";
329             String sep = "";
330             for (int j = 0; j < compoundName.length; j++) {
331               className += sep;
332               className += new String(compoundName[j]);
333               sep = ".";
334             }
335             byte[] bytes = classFile.getBytes();
336             classLoader.defineGateClass(className, bytes,
337                                         0, bytes.length);
338           }
339         }
340       }
341 
342       private void addProblem(IProblem problem) {
343         String name = new String(problem.getOriginatingFileName());
344         List problemsForName = (List)problems.get(name);
345         if(problemsForName == null) {
346           problemsForName = new ArrayList();
347           problems.put(name, problemsForName);
348         }
349         problemsForName.add(problem);
350       }
351     };
352 
353     // Define the list of things to compile
354     ICompilationUnit[] compilationUnits = new ICompilationUnit[sources.size()];
355     int i = 0;
356     Iterator sourcesIt = sources.keySet().iterator();
357     while(sourcesIt.hasNext()) {
358       compilationUnits[i++=
359         new CompilationUnit((String)sourcesIt.next());
360     }
361 
362     // create the compiler
363     Compiler compiler = new Compiler(env,
364                                      policy,
365                                      new CompilerOptions(settings),
366                                      requestor,
367                                      problemFactory);
368 
369     // and compile the classes
370     compiler.compile(compilationUnits);
371 
372     if(!problems.isEmpty()) {
373       boolean errors = false;
374       Iterator problemsIt = problems.entrySet().iterator();
375       while(problemsIt.hasNext()) {
376         Map.Entry prob = (Map.Entry)problemsIt.next();
377         String name = (String)prob.getKey();
378         List probsForName = (List)prob.getValue();
379         Iterator probsForNameIt = probsForName.iterator();
380         while(probsForNameIt.hasNext()) {
381           IProblem problem = (IProblem)probsForNameIt.next();
382           if(problem.isError()) {
383             Err.pr("Error: ");
384             errors = true;
385           }
386           else if(problem.isWarning()) {
387             Err.pr("Warning: ");
388           }
389           Err.prln(problem.getMessage()
390                 " at line " 
391                 + problem.getSourceLineNumber() " in " + name);
392         }
393         // print the source for this class, to help the user debug.
394         Err.prln("\nThe offending input was:\n");
395         Err.prln(Strings.addLineNumbers((String)sources.get(name)));
396       }
397       if(errors) {
398         throw new GateException(
399           "There were errors; see error log for details!");
400       }
401     }
402   }
403 
404   private static GateClassLoader classLoader;
405 }