1 | /* |
2 | * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved. |
3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 | * |
5 | * This code is free software; you can redistribute it and/or modify it |
6 | * under the terms of the GNU General Public License version 2 only, as |
7 | * published by the Free Software Foundation. Sun designates this |
8 | * particular file as subject to the "Classpath" exception as provided |
9 | * by Sun in the LICENSE file that accompanied this code. |
10 | * |
11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
14 | * version 2 for more details (a copy is included in the LICENSE file that |
15 | * accompanied this code). |
16 | * |
17 | * You should have received a copy of the GNU General Public License version |
18 | * 2 along with this work; if not, write to the Free Software Foundation, |
19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
20 | * |
21 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
23 | * have any questions. |
24 | */ |
25 | |
26 | package com.sun.tools.javac.api; |
27 | |
28 | import java.io.File; |
29 | import java.io.IOException; |
30 | import java.util.*; |
31 | import java.util.concurrent.atomic.AtomicBoolean; |
32 | |
33 | import javax.annotation.processing.Processor; |
34 | import javax.lang.model.element.Element; |
35 | import javax.lang.model.element.TypeElement; |
36 | import javax.lang.model.type.TypeMirror; |
37 | import javax.tools.*; |
38 | |
39 | import com.sun.source.tree.Tree; |
40 | import com.sun.source.tree.*; |
41 | import com.sun.source.util.*; |
42 | import com.sun.tools.javac.code.*; |
43 | import com.sun.tools.javac.code.Symbol.*; |
44 | import com.sun.tools.javac.comp.*; |
45 | import com.sun.tools.javac.main.*; |
46 | import com.sun.tools.javac.model.*; |
47 | import com.sun.tools.javac.parser.Parser; |
48 | import com.sun.tools.javac.parser.Scanner; |
49 | import com.sun.tools.javac.tree.*; |
50 | import com.sun.tools.javac.tree.JCTree.*; |
51 | import com.sun.tools.javac.util.*; |
52 | import com.sun.tools.javac.util.List; |
53 | import com.sun.tools.javac.main.JavaCompiler; |
54 | |
55 | /** |
56 | * Provides access to functionality specific to the Sun Java Compiler, javac. |
57 | * |
58 | * <p><b>This is NOT part of any API supported by Sun Microsystems. |
59 | * If you write code that depends on this, you do so at your own |
60 | * risk. This code and its internal interfaces are subject to change |
61 | * or deletion without notice.</b></p> |
62 | * |
63 | * @author Peter von der Ahé |
64 | * @author Jonathan Gibbons |
65 | */ |
66 | public class JavacTaskImpl extends JavacTask { |
67 | private JavacTool tool; |
68 | private Main compilerMain; |
69 | private JavaCompiler compiler; |
70 | private String[] args; |
71 | private Context context; |
72 | private List<JavaFileObject> fileObjects; |
73 | private Map<JavaFileObject, JCCompilationUnit> notYetEntered; |
74 | private ListBuffer<Env<AttrContext>> genList; |
75 | private TaskListener taskListener; |
76 | private AtomicBoolean used = new AtomicBoolean(); |
77 | private Iterable<? extends Processor> processors; |
78 | |
79 | private Integer result = null; |
80 | |
81 | JavacTaskImpl(JavacTool tool, |
82 | Main compilerMain, |
83 | String[] args, |
84 | Context context, |
85 | List<JavaFileObject> fileObjects) { |
86 | this.tool = tool; |
87 | this.compilerMain = compilerMain; |
88 | this.args = args; |
89 | this.context = context; |
90 | this.fileObjects = fileObjects; |
91 | // null checks |
92 | compilerMain.getClass(); |
93 | args.getClass(); |
94 | context.getClass(); |
95 | fileObjects.getClass(); |
96 | } |
97 | |
98 | JavacTaskImpl(JavacTool tool, |
99 | Main compilerMain, |
100 | Iterable<String> flags, |
101 | Context context, |
102 | Iterable<String> classes, |
103 | Iterable<? extends JavaFileObject> fileObjects) { |
104 | this(tool, compilerMain, toArray(flags, classes), context, toList(fileObjects)); |
105 | } |
106 | |
107 | static private String[] toArray(Iterable<String> flags, Iterable<String> classes) { |
108 | ListBuffer<String> result = new ListBuffer<String>(); |
109 | if (flags != null) |
110 | for (String flag : flags) |
111 | result.append(flag); |
112 | if (classes != null) |
113 | for (String cls : classes) |
114 | result.append(cls); |
115 | return result.toArray(new String[result.length()]); |
116 | } |
117 | |
118 | static private List<JavaFileObject> toList(Iterable<? extends JavaFileObject> fileObjects) { |
119 | if (fileObjects == null) |
120 | return List.nil(); |
121 | ListBuffer<JavaFileObject> result = new ListBuffer<JavaFileObject>(); |
122 | for (JavaFileObject fo : fileObjects) |
123 | result.append(fo); |
124 | return result.toList(); |
125 | } |
126 | |
127 | public Boolean call() { |
128 | if (!used.getAndSet(true)) { |
129 | beginContext(); |
130 | try { |
131 | compilerMain.setFatalErrors(true); |
132 | result = compilerMain.compile(args, context, fileObjects, processors); |
133 | } finally { |
134 | endContext(); |
135 | } |
136 | compilerMain = null; |
137 | args = null; |
138 | context = null; |
139 | fileObjects = null; |
140 | return result == 0; |
141 | } else { |
142 | throw new IllegalStateException("multiple calls to method 'call'"); |
143 | } |
144 | } |
145 | |
146 | public void setProcessors(Iterable<? extends Processor> processors) { |
147 | processors.getClass(); // null check |
148 | // not mt-safe |
149 | if (used.get()) |
150 | throw new IllegalStateException(); |
151 | this.processors = processors; |
152 | } |
153 | |
154 | public void setLocale(Locale locale) { |
155 | // locale argument is ignored, see RFE 6443132 |
156 | if (used.get()) |
157 | throw new IllegalStateException(); |
158 | } |
159 | |
160 | private void prepareCompiler() throws IOException { |
161 | if (!used.getAndSet(true)) { |
162 | beginContext(); |
163 | compilerMain.setOptions(Options.instance(context)); |
164 | compilerMain.filenames = new ListBuffer<File>(); |
165 | List<File> filenames = compilerMain.processArgs(CommandLine.parse(args)); |
166 | if (!filenames.isEmpty()) |
167 | throw new IllegalArgumentException("Malformed arguments " + filenames.toString(" ")); |
168 | compiler = JavaCompiler.instance(context); |
169 | // force the use of the scanner that captures Javadoc comments |
170 | com.sun.tools.javac.parser.DocCommentScanner.Factory.preRegister(context); |
171 | compiler.keepComments = true; |
172 | compiler.genEndPos = true; |
173 | // NOTE: this value will be updated after annotation processing |
174 | compiler.initProcessAnnotations(processors); |
175 | notYetEntered = new HashMap<JavaFileObject, JCCompilationUnit>(); |
176 | for (JavaFileObject file: fileObjects) |
177 | notYetEntered.put(file, null); |
178 | genList = new ListBuffer<Env<AttrContext>>(); |
179 | // endContext will be called when all classes have been generated |
180 | // TODO: should handle the case after each phase if errors have occurred |
181 | args = null; |
182 | } |
183 | } |
184 | |
185 | private void beginContext() { |
186 | context.put(JavacTaskImpl.class, this); |
187 | if (context.get(TaskListener.class) != null) |
188 | context.put(TaskListener.class, (TaskListener)null); |
189 | if (taskListener != null) |
190 | context.put(TaskListener.class, wrap(taskListener)); |
191 | tool.beginContext(context); |
192 | } |
193 | // where |
194 | private TaskListener wrap(final TaskListener tl) { |
195 | tl.getClass(); // null check |
196 | return new TaskListener() { |
197 | public void started(TaskEvent e) { |
198 | try { |
199 | tl.started(e); |
200 | } catch (Throwable t) { |
201 | throw new ClientCodeException(t); |
202 | } |
203 | } |
204 | |
205 | public void finished(TaskEvent e) { |
206 | try { |
207 | tl.finished(e); |
208 | } catch (Throwable t) { |
209 | throw new ClientCodeException(t); |
210 | } |
211 | } |
212 | |
213 | }; |
214 | } |
215 | |
216 | private void endContext() { |
217 | tool.endContext(); |
218 | } |
219 | |
220 | /** |
221 | * Construct a JavaFileObject from the given file. |
222 | * |
223 | * <p><b>TODO: this method is useless here</b></p> |
224 | * |
225 | * @param file a file |
226 | * @return a JavaFileObject from the standard file manager. |
227 | */ |
228 | public JavaFileObject asJavaFileObject(File file) { |
229 | JavacFileManager fm = (JavacFileManager)context.get(JavaFileManager.class); |
230 | return fm.getRegularFile(file); |
231 | } |
232 | |
233 | public void setTaskListener(TaskListener taskListener) { |
234 | this.taskListener = taskListener; |
235 | } |
236 | |
237 | /** |
238 | * Parse the specified files returning a list of abstract syntax trees. |
239 | * |
240 | * @throws java.io.IOException TODO |
241 | * @return a list of abstract syntax trees |
242 | */ |
243 | public Iterable<? extends CompilationUnitTree> parse() throws IOException { |
244 | try { |
245 | prepareCompiler(); |
246 | List<JCCompilationUnit> units = compiler.parseFiles(fileObjects); |
247 | for (JCCompilationUnit unit: units) { |
248 | JavaFileObject file = unit.getSourceFile(); |
249 | if (notYetEntered.containsKey(file)) |
250 | notYetEntered.put(file, unit); |
251 | } |
252 | return units; |
253 | } |
254 | finally { |
255 | parsed = true; |
256 | if (compiler != null && compiler.log != null) |
257 | compiler.log.flush(); |
258 | } |
259 | } |
260 | |
261 | private boolean parsed = false; |
262 | |
263 | /** |
264 | * Translate all the abstract syntax trees to elements. |
265 | * |
266 | * @throws IOException TODO |
267 | * @return a list of elements corresponding to the top level |
268 | * classes in the abstract syntax trees |
269 | */ |
270 | public Iterable<? extends TypeElement> enter() throws IOException { |
271 | return enter(null); |
272 | } |
273 | |
274 | /** |
275 | * Translate the given abstract syntax trees to elements. |
276 | * |
277 | * @param trees a list of abstract syntax trees. |
278 | * @throws java.io.IOException TODO |
279 | * @return a list of elements corresponding to the top level |
280 | * classes in the abstract syntax trees |
281 | */ |
282 | public Iterable<? extends TypeElement> enter(Iterable<? extends CompilationUnitTree> trees) |
283 | throws IOException |
284 | { |
285 | prepareCompiler(); |
286 | |
287 | ListBuffer<JCCompilationUnit> roots = null; |
288 | |
289 | if (trees == null) { |
290 | // If there are still files which were specified to be compiled |
291 | // (i.e. in fileObjects) but which have not yet been entered, |
292 | // then we make sure they have been parsed and add them to the |
293 | // list to be entered. |
294 | if (notYetEntered.size() > 0) { |
295 | if (!parsed) |
296 | parse(); // TODO would be nice to specify files needed to be parsed |
297 | for (JavaFileObject file: fileObjects) { |
298 | JCCompilationUnit unit = notYetEntered.remove(file); |
299 | if (unit != null) { |
300 | if (roots == null) |
301 | roots = new ListBuffer<JCCompilationUnit>(); |
302 | roots.append(unit); |
303 | } |
304 | } |
305 | notYetEntered.clear(); |
306 | } |
307 | } |
308 | else { |
309 | for (CompilationUnitTree cu : trees) { |
310 | if (cu instanceof JCCompilationUnit) { |
311 | if (roots == null) |
312 | roots = new ListBuffer<JCCompilationUnit>(); |
313 | roots.append((JCCompilationUnit)cu); |
314 | notYetEntered.remove(cu.getSourceFile()); |
315 | } |
316 | else |
317 | throw new IllegalArgumentException(cu.toString()); |
318 | } |
319 | } |
320 | |
321 | if (roots == null) |
322 | return List.nil(); |
323 | |
324 | try { |
325 | List<JCCompilationUnit> units = compiler.enterTrees(roots.toList()); |
326 | |
327 | if (notYetEntered.isEmpty()) |
328 | compiler = compiler.processAnnotations(units); |
329 | |
330 | ListBuffer<TypeElement> elements = new ListBuffer<TypeElement>(); |
331 | for (JCCompilationUnit unit : units) { |
332 | for (JCTree node : unit.defs) |
333 | if (node.getTag() == JCTree.CLASSDEF) |
334 | elements.append(((JCTree.JCClassDecl) node).sym); |
335 | } |
336 | return elements.toList(); |
337 | } |
338 | finally { |
339 | compiler.log.flush(); |
340 | } |
341 | } |
342 | |
343 | /** |
344 | * Complete all analysis. |
345 | * @throws IOException TODO |
346 | */ |
347 | @Override |
348 | public Iterable<? extends Element> analyze() throws IOException { |
349 | return analyze(null); |
350 | } |
351 | |
352 | /** |
353 | * Complete all analysis on the given classes. |
354 | * This can be used to ensure that all compile time errors are reported. |
355 | * The classes must have previously been returned from {@link #enter}. |
356 | * If null is specified, all outstanding classes will be analyzed. |
357 | * |
358 | * @param classes a list of class elements |
359 | */ |
360 | // This implementation requires that we open up privileges on JavaCompiler. |
361 | // An alternative implementation would be to move this code to JavaCompiler and |
362 | // wrap it here |
363 | public Iterable<? extends Element> analyze(Iterable<? extends TypeElement> classes) throws IOException { |
364 | enter(null); // ensure all classes have been entered |
365 | |
366 | final ListBuffer<Element> results = new ListBuffer<Element>(); |
367 | try { |
368 | if (classes == null) { |
369 | handleFlowResults(compiler.flow(compiler.attribute(compiler.todo)), results); |
370 | } else { |
371 | Filter f = new Filter() { |
372 | public void process(Env<AttrContext> env) { |
373 | handleFlowResults(compiler.flow(compiler.attribute(env)), results); |
374 | } |
375 | }; |
376 | f.run(compiler.todo, classes); |
377 | } |
378 | } finally { |
379 | compiler.log.flush(); |
380 | } |
381 | return results; |
382 | } |
383 | // where |
384 | private void handleFlowResults(List<Env<AttrContext>> list, ListBuffer<Element> elems) { |
385 | for (Env<AttrContext> env: list) { |
386 | switch (env.tree.getTag()) { |
387 | case JCTree.CLASSDEF: |
388 | JCClassDecl cdef = (JCClassDecl) env.tree; |
389 | if (cdef.sym != null) |
390 | elems.append(cdef.sym); |
391 | break; |
392 | case JCTree.TOPLEVEL: |
393 | JCCompilationUnit unit = (JCCompilationUnit) env.tree; |
394 | if (unit.packge != null) |
395 | elems.append(unit.packge); |
396 | break; |
397 | } |
398 | } |
399 | genList.appendList(list); |
400 | } |
401 | |
402 | |
403 | /** |
404 | * Generate code. |
405 | * @throws IOException TODO |
406 | */ |
407 | @Override |
408 | public Iterable<? extends JavaFileObject> generate() throws IOException { |
409 | return generate(null); |
410 | } |
411 | |
412 | /** |
413 | * Generate code corresponding to the given classes. |
414 | * The classes must have previously been returned from {@link #enter}. |
415 | * If there are classes outstanding to be analyzed, that will be done before |
416 | * any classes are generated. |
417 | * If null is specified, code will be generated for all outstanding classes. |
418 | * |
419 | * @param classes a list of class elements |
420 | */ |
421 | public Iterable<? extends JavaFileObject> generate(Iterable<? extends TypeElement> classes) throws IOException { |
422 | final ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>(); |
423 | try { |
424 | analyze(null); // ensure all classes have been parsed, entered, and analyzed |
425 | |
426 | if (classes == null) { |
427 | compiler.generate(compiler.desugar(genList.toList()), results); |
428 | genList.clear(); |
429 | } |
430 | else { |
431 | Filter f = new Filter() { |
432 | public void process(Env<AttrContext> env) { |
433 | compiler.generate(compiler.desugar(List.of(env)), results); |
434 | } |
435 | }; |
436 | f.run(genList, classes); |
437 | } |
438 | if (genList.isEmpty()) { |
439 | compiler.reportDeferredDiagnostics(); |
440 | compiler.log.flush(); |
441 | endContext(); |
442 | } |
443 | } |
444 | finally { |
445 | compiler.log.flush(); |
446 | } |
447 | return results; |
448 | } |
449 | |
450 | public TypeMirror getTypeMirror(Iterable<? extends Tree> path) { |
451 | // TODO: Should complete attribution if necessary |
452 | Tree last = null; |
453 | for (Tree node : path) |
454 | last = node; |
455 | return ((JCTree)last).type; |
456 | } |
457 | |
458 | public JavacElements getElements() { |
459 | if (context == null) |
460 | throw new IllegalStateException(); |
461 | return JavacElements.instance(context); |
462 | } |
463 | |
464 | public JavacTypes getTypes() { |
465 | if (context == null) |
466 | throw new IllegalStateException(); |
467 | return JavacTypes.instance(context); |
468 | } |
469 | |
470 | public Iterable<? extends Tree> pathFor(CompilationUnitTree unit, Tree node) { |
471 | return TreeInfo.pathFor((JCTree) node, (JCTree.JCCompilationUnit) unit).reverse(); |
472 | } |
473 | |
474 | abstract class Filter { |
475 | void run(ListBuffer<Env<AttrContext>> list, Iterable<? extends TypeElement> classes) { |
476 | Set<TypeElement> set = new HashSet<TypeElement>(); |
477 | for (TypeElement item: classes) |
478 | set.add(item); |
479 | |
480 | List<Env<AttrContext>> defer = List.<Env<AttrContext>>nil(); |
481 | while (list.nonEmpty()) { |
482 | Env<AttrContext> env = list.next(); |
483 | ClassSymbol csym = env.enclClass.sym; |
484 | if (csym != null && set.contains(csym.outermostClass())) |
485 | process(env); |
486 | else |
487 | defer = defer.prepend(env); |
488 | } |
489 | |
490 | for (List<Env<AttrContext>> l = defer; l.nonEmpty(); l = l.tail) |
491 | list.prepend(l.head); |
492 | } |
493 | |
494 | abstract void process(Env<AttrContext> env); |
495 | } |
496 | |
497 | /** |
498 | * For internal use by Sun Microsystems only. This method will be |
499 | * removed without warning. |
500 | */ |
501 | public Context getContext() { |
502 | return context; |
503 | } |
504 | |
505 | /** |
506 | * For internal use by Sun Microsystems only. This method will be |
507 | * removed without warning. |
508 | */ |
509 | public void updateContext(Context newContext) { |
510 | context = newContext; |
511 | } |
512 | |
513 | /** |
514 | * For internal use by Sun Microsystems only. This method will be |
515 | * removed without warning. |
516 | */ |
517 | public Type parseType(String expr, TypeElement scope) { |
518 | if (expr == null || expr.equals("")) |
519 | throw new IllegalArgumentException(); |
520 | compiler = JavaCompiler.instance(context); |
521 | JavaFileObject prev = compiler.log.useSource(null); |
522 | Scanner.Factory scannerFactory = Scanner.Factory.instance(context); |
523 | Parser.Factory parserFactory = Parser.Factory.instance(context); |
524 | Attr attr = Attr.instance(context); |
525 | try { |
526 | Scanner scanner = scannerFactory.newScanner((expr+"\u0000").toCharArray(), |
527 | expr.length()); |
528 | Parser parser = parserFactory.newParser(scanner, false, false); |
529 | JCTree tree = parser.type(); |
530 | return attr.attribType(tree, (Symbol.TypeSymbol)scope); |
531 | } finally { |
532 | compiler.log.useSource(prev); |
533 | } |
534 | } |
535 | |
536 | } |