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