JavacTaskImpl.java revision 3828:d30434bde0a8
150479Speter/*
22985Spst * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
3135549Sdes * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4135739Sru *
5135739Sru * This code is free software; you can redistribute it and/or modify it
6135549Sdes * under the terms of the GNU General Public License version 2 only, as
72985Spst * published by the Free Software Foundation.  Oracle designates this
8135549Sdes * particular file as subject to the "Classpath" exception as provided
917907Speter * by Oracle in the LICENSE file that accompanied this code.
10135549Sdes *
1171762Sasmodai * This code is distributed in the hope that it will be useful, but WITHOUT
12213005Sdougb * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13193149Sdougb * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14193280Sdougb * version 2 for more details (a copy is included in the LICENSE file that
15193280Sdougb * accompanied this code).
16193280Sdougb *
17193280Sdougb * You should have received a copy of the GNU General Public License version
18193280Sdougb * 2 along with this work; if not, write to the Free Software Foundation,
19193280Sdougb * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20193280Sdougb *
21193280Sdougb * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22193280Sdougb * or visit www.oracle.com if you need additional information or have any
23193280Sdougb * questions.
24193280Sdougb */
25193280Sdougb
26193280Sdougbpackage com.sun.tools.javac.api;
27193280Sdougb
28193280Sdougbimport java.io.IOException;
29193280Sdougbimport java.nio.CharBuffer;
30193280Sdougbimport java.util.*;
31193280Sdougbimport java.util.concurrent.Callable;
32135549Sdesimport java.util.concurrent.atomic.AtomicBoolean;
33135549Sdes
3471762Sasmodaiimport javax.annotation.processing.Processor;
35135549Sdesimport javax.lang.model.element.Element;
36170221Sdougbimport javax.lang.model.element.ElementKind;
37135549Sdesimport javax.lang.model.element.TypeElement;
38135549Sdesimport javax.lang.model.util.ElementFilter;
39224093Sdougbimport javax.tools.*;
40135549Sdesimport javax.tools.JavaFileObject.Kind;
41135549Sdes
42135549Sdesimport com.sun.source.tree.*;
43224093Sdougbimport com.sun.tools.javac.code.*;
4471762Sasmodaiimport com.sun.tools.javac.code.Symbol.ClassSymbol;
45193149Sdougbimport com.sun.tools.javac.code.Symbol.ModuleSymbol;
46193149Sdougbimport com.sun.tools.javac.code.Symbol.PackageSymbol;
47135549Sdesimport com.sun.tools.javac.comp.*;
48170221Sdougbimport com.sun.tools.javac.file.BaseFileManager;
4955737Sphantomimport com.sun.tools.javac.main.*;
50161258Scpercivaimport com.sun.tools.javac.main.JavaCompiler;
51161258Scpercivaimport com.sun.tools.javac.parser.Parser;
52161258Scpercivaimport com.sun.tools.javac.parser.ParserFactory;
53201390Sedimport com.sun.tools.javac.processing.AnnotationProcessingError;
54201390Sedimport com.sun.tools.javac.tree.*;
55170221Sdougbimport com.sun.tools.javac.tree.JCTree.JCClassDecl;
56170221Sdougbimport com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
5755737Sphantomimport com.sun.tools.javac.tree.JCTree.JCModuleDecl;
58135837Sdougbimport com.sun.tools.javac.tree.JCTree.Tag;
59135549Sdesimport com.sun.tools.javac.util.*;
60224122Sdougbimport com.sun.tools.javac.util.DefinedBy.Api;
61224122Sdougbimport com.sun.tools.javac.util.List;
62135806Sdougbimport com.sun.tools.javac.util.Log.PrefixKind;
63135549Sdesimport com.sun.tools.javac.util.Log.WriterKind;
64135549Sdes
652985Spst/**
66 * Provides access to functionality specific to the JDK Java Compiler, javac.
67 *
68 * <p><b>This is NOT part of any supported API.
69 * If you write code that depends on this, you do so at your own
70 * risk.  This code and its internal interfaces are subject to change
71 * or deletion without notice.</b></p>
72 *
73 * @author Peter von der Ah&eacute;
74 * @author Jonathan Gibbons
75 */
76public class JavacTaskImpl extends BasicJavacTask {
77    private final Arguments args;
78    private JavaCompiler compiler;
79    private JavaFileManager fileManager;
80    private Locale locale;
81    private Map<JavaFileObject, JCCompilationUnit> notYetEntered;
82    private ListBuffer<Env<AttrContext>> genList;
83    private final AtomicBoolean used = new AtomicBoolean();
84    private Iterable<? extends Processor> processors;
85
86    protected JavacTaskImpl(Context context) {
87        super(context, true);
88        args = Arguments.instance(context);
89        fileManager = context.get(JavaFileManager.class);
90    }
91
92    @Override @DefinedBy(Api.COMPILER)
93    public Boolean call() {
94        return doCall().isOK();
95    }
96
97    /* Internal version of call exposing Main.Result. */
98    public Main.Result doCall() {
99        try {
100            return handleExceptions(() -> {
101                prepareCompiler(false);
102                if (compiler.errorCount() > 0)
103                    return Main.Result.ERROR;
104                compiler.compile(args.getFileObjects(), args.getClassNames(), processors);
105                return (compiler.errorCount() > 0) ? Main.Result.ERROR : Main.Result.OK; // FIXME?
106            }, Main.Result.SYSERR, Main.Result.ABNORMAL);
107        } finally {
108            try {
109                cleanup();
110            } catch (ClientCodeException e) {
111                throw new RuntimeException(e.getCause());
112            }
113        }
114    }
115
116    @Override @DefinedBy(Api.COMPILER)
117    public void setProcessors(Iterable<? extends Processor> processors) {
118        Objects.requireNonNull(processors);
119        // not mt-safe
120        if (used.get())
121            throw new IllegalStateException();
122        this.processors = processors;
123    }
124
125    @Override @DefinedBy(Api.COMPILER)
126    public void setLocale(Locale locale) {
127        if (used.get())
128            throw new IllegalStateException();
129        this.locale = locale;
130    }
131
132    private <T> T handleExceptions(Callable<T> c, T sysErrorResult, T abnormalErrorResult) {
133        try {
134            return c.call();
135        } catch (FatalError ex) {
136            Log log = Log.instance(context);
137            Options options = Options.instance(context);
138            log.printRawLines(ex.getMessage());
139            if (ex.getCause() != null && options.isSet("dev")) {
140                ex.getCause().printStackTrace(log.getWriter(WriterKind.NOTICE));
141            }
142            return sysErrorResult;
143        } catch (AnnotationProcessingError | ClientCodeException e) {
144            // AnnotationProcessingError is thrown from JavacProcessingEnvironment,
145            // to forward errors thrown from an annotation processor
146            // ClientCodeException is thrown from ClientCodeWrapper,
147            // to forward errors thrown from user-supplied code for Compiler API
148            // as specified by javax.tools.JavaCompiler#getTask
149            // and javax.tools.JavaCompiler.CompilationTask#call
150            throw new RuntimeException(e.getCause());
151        } catch (PropagatedException e) {
152            throw e.getCause();
153        } catch (IllegalStateException e) {
154            throw e;
155        } catch (Exception | Error ex) {
156            // Nasty.  If we've already reported an error, compensate
157            // for buggy compiler error recovery by swallowing thrown
158            // exceptions.
159            if (compiler == null || compiler.errorCount() == 0
160                    || Options.instance(context).isSet("dev")) {
161                Log log = Log.instance(context);
162                log.printLines(PrefixKind.JAVAC, "msg.bug", JavaCompiler.version());
163                ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
164            }
165            return abnormalErrorResult;
166        }
167    }
168
169    private void prepareCompiler(boolean forParse) {
170        if (used.getAndSet(true)) {
171            if (compiler == null)
172                throw new PropagatedException(new IllegalStateException());
173        } else {
174            args.validate();
175
176            //initialize compiler's default locale
177            context.put(Locale.class, locale);
178
179            // hack
180            JavacMessages messages = context.get(JavacMessages.messagesKey);
181            if (messages != null && !messages.getCurrentLocale().equals(locale))
182                messages.setCurrentLocale(locale);
183
184            initPlugins(args.getPluginOpts());
185            initDocLint(args.getDocLintOpts());
186
187            // init JavaCompiler and queues
188            compiler = JavaCompiler.instance(context);
189            compiler.keepComments = true;
190            compiler.genEndPos = true;
191            notYetEntered = new HashMap<>();
192            if (forParse) {
193                compiler.initProcessAnnotations(processors);
194                for (JavaFileObject file: args.getFileObjects())
195                    notYetEntered.put(file, null);
196                genList = new ListBuffer<>();
197            }
198        }
199    }
200
201    <T> String toString(Iterable<T> items, String sep) {
202        String currSep = "";
203        StringBuilder sb = new StringBuilder();
204        for (T item: items) {
205            sb.append(currSep);
206            sb.append(item.toString());
207            currSep = sep;
208        }
209        return sb.toString();
210    }
211
212    void cleanup() {
213        if (compiler != null)
214            compiler.close();
215        if (fileManager instanceof BaseFileManager && ((BaseFileManager) fileManager).autoClose) {
216            try {
217                fileManager.close();
218            } catch (IOException ignore) {
219            }
220        }
221        compiler = null;
222        context = null;
223        notYetEntered = null;
224    }
225
226    @Override @DefinedBy(Api.COMPILER_TREE)
227    public Iterable<? extends CompilationUnitTree> parse() {
228        return handleExceptions(this::parseInternal, List.nil(), List.nil());
229    }
230
231    private Iterable<? extends CompilationUnitTree> parseInternal() {
232        try {
233            prepareCompiler(true);
234            List<JCCompilationUnit> units = compiler.parseFiles(args.getFileObjects());
235            for (JCCompilationUnit unit: units) {
236                JavaFileObject file = unit.getSourceFile();
237                if (notYetEntered.containsKey(file))
238                    notYetEntered.put(file, unit);
239            }
240            return units;
241        }
242        finally {
243            parsed = true;
244            if (compiler != null && compiler.log != null)
245                compiler.log.flush();
246        }
247    }
248
249    private boolean parsed = false;
250
251    /**
252     * Translate all the abstract syntax trees to elements.
253     *
254     * @return a list of elements corresponding to the top level
255     * classes in the abstract syntax trees
256     */
257    public Iterable<? extends Element> enter() {
258        return enter(null);
259    }
260
261    /**
262     * Translate the given abstract syntax trees to elements.
263     *
264     * @param trees a list of abstract syntax trees.
265     * @return a list of elements corresponding to the top level
266     * classes in the abstract syntax trees
267     */
268    public Iterable<? extends Element> enter(Iterable<? extends CompilationUnitTree> trees)
269    {
270        if (trees == null && notYetEntered != null && notYetEntered.isEmpty())
271            return List.nil();
272
273        boolean wasInitialized = compiler != null;
274
275        prepareCompiler(true);
276
277        ListBuffer<JCCompilationUnit> roots = null;
278
279        if (trees == null) {
280            // If there are still files which were specified to be compiled
281            // (i.e. in fileObjects) but which have not yet been entered,
282            // then we make sure they have been parsed and add them to the
283            // list to be entered.
284            if (notYetEntered.size() > 0) {
285                if (!parsed)
286                    parseInternal(); // TODO would be nice to specify files needed to be parsed
287                for (JavaFileObject file: args.getFileObjects()) {
288                    JCCompilationUnit unit = notYetEntered.remove(file);
289                    if (unit != null) {
290                        if (roots == null)
291                            roots = new ListBuffer<>();
292                        roots.append(unit);
293                    }
294                }
295                notYetEntered.clear();
296            }
297        }
298        else {
299            for (CompilationUnitTree cu : trees) {
300                if (cu instanceof JCCompilationUnit) {
301                    if (roots == null)
302                        roots = new ListBuffer<>();
303                    roots.append((JCCompilationUnit)cu);
304                    notYetEntered.remove(cu.getSourceFile());
305                }
306                else
307                    throw new IllegalArgumentException(cu.toString());
308            }
309        }
310
311        if (roots == null) {
312            if (trees == null && !wasInitialized) {
313                compiler.initModules(List.nil());
314            }
315            return List.nil();
316        }
317
318        List<JCCompilationUnit> units = compiler.initModules(roots.toList());
319
320        try {
321            units = compiler.enterTrees(units);
322
323            if (notYetEntered.isEmpty())
324                compiler.processAnnotations(units);
325
326            ListBuffer<Element> elements = new ListBuffer<>();
327            for (JCCompilationUnit unit : units) {
328                boolean isPkgInfo = unit.sourcefile.isNameCompatible("package-info",
329                                                                     JavaFileObject.Kind.SOURCE);
330                if (isPkgInfo) {
331                    elements.append(unit.packge);
332                } else {
333                    for (JCTree node : unit.defs) {
334                        if (node.hasTag(JCTree.Tag.CLASSDEF)) {
335                            JCClassDecl cdef = (JCClassDecl) node;
336                            if (cdef.sym != null) // maybe null if errors in anno processing
337                                elements.append(cdef.sym);
338                        } else if (node.hasTag(JCTree.Tag.MODULEDEF)) {
339                            JCModuleDecl mdef = (JCModuleDecl) node;
340                            if (mdef.sym != null)
341                                elements.append(mdef.sym);
342                        }
343                    }
344                }
345            }
346            return elements.toList();
347        }
348        finally {
349            compiler.log.flush();
350        }
351    }
352
353    @Override @DefinedBy(Api.COMPILER_TREE)
354    public Iterable<? extends Element> analyze() {
355        return handleExceptions(() -> analyze(null), List.nil(), List.nil());
356    }
357
358    /**
359     * Complete all analysis on the given classes.
360     * This can be used to ensure that all compile time errors are reported.
361     * The classes must have previously been returned from {@link #enter}.
362     * If null is specified, all outstanding classes will be analyzed.
363     *
364     * @param classes a list of class elements
365     * @return the elements that were analyzed
366     */
367    // This implementation requires that we open up privileges on JavaCompiler.
368    // An alternative implementation would be to move this code to JavaCompiler and
369    // wrap it here
370    public Iterable<? extends Element> analyze(Iterable<? extends Element> classes) {
371        enter(null);  // ensure all classes have been entered
372
373        final ListBuffer<Element> results = new ListBuffer<>();
374        try {
375            if (classes == null) {
376                handleFlowResults(compiler.flow(compiler.attribute(compiler.todo)), results);
377            } else {
378                Filter f = new Filter() {
379                    @Override
380                    public void process(Env<AttrContext> env) {
381                        handleFlowResults(compiler.flow(compiler.attribute(env)), results);
382                    }
383                };
384                f.run(compiler.todo, classes);
385            }
386        } finally {
387            compiler.log.flush();
388        }
389        return results;
390    }
391    // where
392        private void handleFlowResults(Queue<Env<AttrContext>> queue, ListBuffer<Element> elems) {
393            for (Env<AttrContext> env: queue) {
394                switch (env.tree.getTag()) {
395                    case CLASSDEF:
396                        JCClassDecl cdef = (JCClassDecl) env.tree;
397                        if (cdef.sym != null)
398                            elems.append(cdef.sym);
399                        break;
400                    case MODULEDEF:
401                        JCModuleDecl mod = (JCModuleDecl) env.tree;
402                        if (mod.sym != null)
403                            elems.append(mod.sym);
404                        break;
405                    case PACKAGEDEF:
406                        JCCompilationUnit unit = env.toplevel;
407                        if (unit.packge != null)
408                            elems.append(unit.packge);
409                        break;
410                }
411            }
412            genList.addAll(queue);
413        }
414
415    @Override @DefinedBy(Api.COMPILER_TREE)
416    public Iterable<? extends JavaFileObject> generate() {
417        return handleExceptions(() -> generate(null), List.nil(), List.nil());
418    }
419
420    /**
421     * Generate code corresponding to the given classes.
422     * The classes must have previously been returned from {@link #enter}.
423     * If there are classes outstanding to be analyzed, that will be done before
424     * any classes are generated.
425     * If null is specified, code will be generated for all outstanding classes.
426     *
427     * @param classes a list of class elements
428     * @return the files that were generated
429     */
430    public Iterable<? extends JavaFileObject> generate(Iterable<? extends Element> classes) {
431        final ListBuffer<JavaFileObject> results = new ListBuffer<>();
432        try {
433            analyze(null);  // ensure all classes have been parsed, entered, and analyzed
434
435            if (classes == null) {
436                compiler.generate(compiler.desugar(genList), results);
437                genList.clear();
438            }
439            else {
440                Filter f = new Filter() {
441                        @Override
442                        public void process(Env<AttrContext> env) {
443                            compiler.generate(compiler.desugar(ListBuffer.of(env)), results);
444                        }
445                    };
446                f.run(genList, classes);
447            }
448            if (genList.isEmpty()) {
449                compiler.reportDeferredDiagnostics();
450                cleanup();
451            }
452        }
453        finally {
454            if (compiler != null)
455                compiler.log.flush();
456        }
457        return results;
458    }
459
460    public Iterable<? extends Tree> pathFor(CompilationUnitTree unit, Tree node) {
461        return TreeInfo.pathFor((JCTree) node, (JCTree.JCCompilationUnit) unit).reverse();
462    }
463
464    public void ensureEntered() {
465        args.allowEmpty();
466        enter(null);
467    }
468
469    abstract class Filter {
470        void run(Queue<Env<AttrContext>> list, Iterable<? extends Element> elements) {
471            Set<Element> set = new HashSet<>();
472            for (Element item: elements) {
473                set.add(item);
474            }
475
476            ListBuffer<Env<AttrContext>> defer = new ListBuffer<>();
477            while (list.peek() != null) {
478                Env<AttrContext> env = list.remove();
479                Symbol test = null;
480
481                if (env.tree.hasTag(Tag.MODULEDEF)) {
482                    test = ((JCModuleDecl) env.tree).sym;
483                } else if (env.tree.hasTag(Tag.PACKAGEDEF)) {
484                    test = env.toplevel.packge;
485                } else {
486                    ClassSymbol csym = env.enclClass.sym;
487                    if (csym != null)
488                        test = csym.outermostClass();
489                }
490                if (test != null && set.contains(test))
491                    process(env);
492                else
493                    defer = defer.append(env);
494            }
495
496            list.addAll(defer);
497        }
498
499        abstract void process(Env<AttrContext> env);
500    }
501
502    /**
503     * For internal use only.  This method will be
504     * removed without warning.
505     * @param expr the type expression to be analyzed
506     * @param scope the scope in which to analyze the type expression
507     * @return the type
508     * @throws IllegalArgumentException if the type expression of null or empty
509     */
510    public Type parseType(String expr, TypeElement scope) {
511        if (expr == null || expr.equals(""))
512            throw new IllegalArgumentException();
513        compiler = JavaCompiler.instance(context);
514        JavaFileObject prev = compiler.log.useSource(null);
515        ParserFactory parserFactory = ParserFactory.instance(context);
516        Attr attr = Attr.instance(context);
517        try {
518            CharBuffer buf = CharBuffer.wrap((expr+"\u0000").toCharArray(), 0, expr.length());
519            Parser parser = parserFactory.newParser(buf, false, false, false);
520            JCTree tree = parser.parseType();
521            return attr.attribType(tree, (Symbol.TypeSymbol)scope);
522        } finally {
523            compiler.log.useSource(prev);
524        }
525    }
526
527}
528