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 sources) throws GateException {
070 if(classLoader == null) classLoader = 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 }
|