JavacTaskImpl.java revision 3294:9adfb22ff08f
1/*
2 * Copyright (c) 2005, 2015, Oracle and/or its affiliates. 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.javac.api;
27
28import java.io.IOException;
29import java.nio.CharBuffer;
30import java.util.*;
31import java.util.concurrent.Callable;
32import java.util.concurrent.atomic.AtomicBoolean;
33
34import javax.annotation.processing.Processor;
35import javax.lang.model.element.Element;
36import javax.lang.model.element.ElementKind;
37import javax.lang.model.element.TypeElement;
38import javax.lang.model.util.ElementFilter;
39import javax.tools.*;
40import javax.tools.JavaFileObject.Kind;
41
42import com.sun.source.tree.*;
43import com.sun.tools.javac.code.*;
44import com.sun.tools.javac.code.Symbol.ClassSymbol;
45import com.sun.tools.javac.code.Symbol.ModuleSymbol;
46import com.sun.tools.javac.code.Symbol.PackageSymbol;
47import com.sun.tools.javac.comp.*;
48import com.sun.tools.javac.file.BaseFileManager;
49import com.sun.tools.javac.main.*;
50import com.sun.tools.javac.main.JavaCompiler;
51import com.sun.tools.javac.parser.Parser;
52import com.sun.tools.javac.parser.ParserFactory;
53import com.sun.tools.javac.processing.AnnotationProcessingError;
54import com.sun.tools.javac.tree.*;
55import com.sun.tools.javac.tree.JCTree.JCClassDecl;
56import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
57import com.sun.tools.javac.tree.JCTree.JCModuleDecl;
58import com.sun.tools.javac.tree.JCTree.Tag;
59import com.sun.tools.javac.util.*;
60import com.sun.tools.javac.util.DefinedBy.Api;
61import com.sun.tools.javac.util.List;
62import com.sun.tools.javac.util.Log.PrefixKind;
63import com.sun.tools.javac.util.Log.WriterKind;
64
65/**
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(new Callable<Main.Result>() {
101                @Override
102                public Main.Result call() throws Exception {
103                    prepareCompiler(false);
104                    compiler.compile(args.getFileObjects(), args.getClassNames(), processors);
105                    return (compiler.errorCount() > 0) ? Main.Result.ERROR : Main.Result.OK; // FIXME?
106                }
107            }, Main.Result.SYSERR, Main.Result.ABNORMAL);
108        } finally {
109            try {
110                cleanup();
111            } catch (ClientCodeException e) {
112                throw new RuntimeException(e.getCause());
113            }
114        }
115    }
116
117    @Override @DefinedBy(Api.COMPILER)
118    public void setProcessors(Iterable<? extends Processor> processors) {
119        Objects.requireNonNull(processors);
120        // not mt-safe
121        if (used.get())
122            throw new IllegalStateException();
123        this.processors = processors;
124    }
125
126    @Override @DefinedBy(Api.COMPILER)
127    public void setLocale(Locale locale) {
128        if (used.get())
129            throw new IllegalStateException();
130        this.locale = locale;
131    }
132
133    private <T> T handleExceptions(Callable<T> c, T sysErrorResult, T abnormalErrorResult) {
134        try {
135            return c.call();
136        } catch (FatalError ex) {
137            Log log = Log.instance(context);
138            Options options = Options.instance(context);
139            log.printRawLines(ex.getMessage());
140            if (ex.getCause() != null && options.isSet("dev")) {
141                ex.getCause().printStackTrace(log.getWriter(WriterKind.NOTICE));
142            }
143            return sysErrorResult;
144        } catch (AnnotationProcessingError | ClientCodeException e) {
145            // AnnotationProcessingError is thrown from JavacProcessingEnvironment,
146            // to forward errors thrown from an annotation processor
147            // ClientCodeException is thrown from ClientCodeWrapper,
148            // to forward errors thrown from user-supplied code for Compiler API
149            // as specified by javax.tools.JavaCompiler#getTask
150            // and javax.tools.JavaCompiler.CompilationTask#call
151            throw new RuntimeException(e.getCause());
152        } catch (PropagatedException e) {
153            throw e.getCause();
154        } catch (IllegalStateException e) {
155            throw e;
156        } catch (Exception | Error ex) {
157            // Nasty.  If we've already reported an error, compensate
158            // for buggy compiler error recovery by swallowing thrown
159            // exceptions.
160            if (compiler == null || compiler.errorCount() == 0
161                    || Options.instance(context).isSet("dev")) {
162                Log log = Log.instance(context);
163                log.printLines(PrefixKind.JAVAC, "msg.bug", JavaCompiler.version());
164                ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
165            }
166            return abnormalErrorResult;
167        }
168    }
169
170    private void prepareCompiler(boolean forParse) {
171        if (used.getAndSet(true)) {
172            if (compiler == null)
173                throw new PropagatedException(new IllegalStateException());
174        } else {
175            args.validate();
176
177            //initialize compiler's default locale
178            context.put(Locale.class, locale);
179
180            // hack
181            JavacMessages messages = context.get(JavacMessages.messagesKey);
182            if (messages != null && !messages.getCurrentLocale().equals(locale))
183                messages.setCurrentLocale(locale);
184
185            initPlugins(args.getPluginOpts());
186            initDocLint(args.getDocLintOpts());
187
188            // init JavaCompiler and queues
189            compiler = JavaCompiler.instance(context);
190            compiler.keepComments = true;
191            compiler.genEndPos = true;
192            notYetEntered = new HashMap<>();
193            if (forParse) {
194                compiler.initProcessAnnotations(processors);
195                for (JavaFileObject file: args.getFileObjects())
196                    notYetEntered.put(file, null);
197                genList = new ListBuffer<>();
198            }
199        }
200    }
201
202    <T> String toString(Iterable<T> items, String sep) {
203        String currSep = "";
204        StringBuilder sb = new StringBuilder();
205        for (T item: items) {
206            sb.append(currSep);
207            sb.append(item.toString());
208            currSep = sep;
209        }
210        return sb.toString();
211    }
212
213    void cleanup() {
214        if (compiler != null)
215            compiler.close();
216        if (fileManager instanceof BaseFileManager && ((BaseFileManager) fileManager).autoClose) {
217            try {
218                fileManager.close();
219            } catch (IOException ignore) {
220            }
221        }
222        compiler = null;
223        context = null;
224        notYetEntered = null;
225    }
226
227    @Override @DefinedBy(Api.COMPILER_TREE)
228    public Iterable<? extends CompilationUnitTree> parse() {
229        return handleExceptions(new Callable<Iterable<? extends CompilationUnitTree>>() {
230            @Override
231            public Iterable<? extends CompilationUnitTree> call() {
232                return parseInternal();
233            }
234        }, List.<CompilationUnitTree>nil(), List.<CompilationUnitTree>nil());
235    }
236
237    private Iterable<? extends CompilationUnitTree> parseInternal() {
238        try {
239            prepareCompiler(true);
240            List<JCCompilationUnit> units = compiler.parseFiles(args.getFileObjects());
241            for (JCCompilationUnit unit: units) {
242                JavaFileObject file = unit.getSourceFile();
243                if (notYetEntered.containsKey(file))
244                    notYetEntered.put(file, unit);
245            }
246            return units;
247        }
248        finally {
249            parsed = true;
250            if (compiler != null && compiler.log != null)
251                compiler.log.flush();
252        }
253    }
254
255    private boolean parsed = false;
256
257    /**
258     * Translate all the abstract syntax trees to elements.
259     *
260     * @return a list of elements corresponding to the top level
261     * classes in the abstract syntax trees
262     */
263    public Iterable<? extends Element> enter() {
264        return enter(null);
265    }
266
267    /**
268     * Translate the given abstract syntax trees to elements.
269     *
270     * @param trees a list of abstract syntax trees.
271     * @return a list of elements corresponding to the top level
272     * classes in the abstract syntax trees
273     */
274    public Iterable<? extends Element> enter(Iterable<? extends CompilationUnitTree> trees)
275    {
276        if (trees == null && notYetEntered != null && notYetEntered.isEmpty())
277            return List.nil();
278
279        boolean wasInitialized = compiler != null;
280
281        prepareCompiler(true);
282
283        ListBuffer<JCCompilationUnit> roots = null;
284
285        if (trees == null) {
286            // If there are still files which were specified to be compiled
287            // (i.e. in fileObjects) but which have not yet been entered,
288            // then we make sure they have been parsed and add them to the
289            // list to be entered.
290            if (notYetEntered.size() > 0) {
291                if (!parsed)
292                    parseInternal(); // TODO would be nice to specify files needed to be parsed
293                for (JavaFileObject file: args.getFileObjects()) {
294                    JCCompilationUnit unit = notYetEntered.remove(file);
295                    if (unit != null) {
296                        if (roots == null)
297                            roots = new ListBuffer<>();
298                        roots.append(unit);
299                    }
300                }
301                notYetEntered.clear();
302            }
303        }
304        else {
305            for (CompilationUnitTree cu : trees) {
306                if (cu instanceof JCCompilationUnit) {
307                    if (roots == null)
308                        roots = new ListBuffer<>();
309                    roots.append((JCCompilationUnit)cu);
310                    notYetEntered.remove(cu.getSourceFile());
311                }
312                else
313                    throw new IllegalArgumentException(cu.toString());
314            }
315        }
316
317        if (roots == null) {
318            if (trees == null && !wasInitialized) {
319                compiler.initModules(List.nil());
320            }
321            return List.nil();
322        }
323
324        List<JCCompilationUnit> units = compiler.initModules(roots.toList());
325
326        try {
327            units = compiler.enterTrees(units);
328
329            if (notYetEntered.isEmpty())
330                compiler.processAnnotations(units);
331
332            ListBuffer<Element> elements = new ListBuffer<>();
333            for (JCCompilationUnit unit : units) {
334                boolean isPkgInfo = unit.sourcefile.isNameCompatible("package-info",
335                                                                     JavaFileObject.Kind.SOURCE);
336                if (isPkgInfo) {
337                    elements.append(unit.packge);
338                } else {
339                    for (JCTree node : unit.defs) {
340                        if (node.hasTag(JCTree.Tag.CLASSDEF)) {
341                            JCClassDecl cdef = (JCClassDecl) node;
342                            if (cdef.sym != null) // maybe null if errors in anno processing
343                                elements.append(cdef.sym);
344                        } else if (node.hasTag(JCTree.Tag.MODULEDEF)) {
345                            JCModuleDecl mdef = (JCModuleDecl) node;
346                            if (mdef.sym != null)
347                                elements.append(mdef.sym);
348                        }
349                    }
350                }
351            }
352            return elements.toList();
353        }
354        finally {
355            compiler.log.flush();
356        }
357    }
358
359    @Override @DefinedBy(Api.COMPILER_TREE)
360    public Iterable<? extends Element> analyze() {
361        return handleExceptions(new Callable<Iterable<? extends Element>>() {
362            @Override
363            public Iterable<? extends Element> call() {
364                return analyze(null);
365            }
366        }, List.<Element>nil(), List.<Element>nil());
367    }
368
369    /**
370     * Complete all analysis on the given classes.
371     * This can be used to ensure that all compile time errors are reported.
372     * The classes must have previously been returned from {@link #enter}.
373     * If null is specified, all outstanding classes will be analyzed.
374     *
375     * @param classes a list of class elements
376     * @return the elements that were analyzed
377     */
378    // This implementation requires that we open up privileges on JavaCompiler.
379    // An alternative implementation would be to move this code to JavaCompiler and
380    // wrap it here
381    public Iterable<? extends Element> analyze(Iterable<? extends Element> classes) {
382        enter(null);  // ensure all classes have been entered
383
384        final ListBuffer<Element> results = new ListBuffer<>();
385        try {
386            if (classes == null) {
387                handleFlowResults(compiler.flow(compiler.attribute(compiler.todo)), results);
388            } else {
389                Filter f = new Filter() {
390                    @Override
391                    public void process(Env<AttrContext> env) {
392                        handleFlowResults(compiler.flow(compiler.attribute(env)), results);
393                    }
394                };
395                f.run(compiler.todo, classes);
396            }
397        } finally {
398            compiler.log.flush();
399        }
400        return results;
401    }
402    // where
403        private void handleFlowResults(Queue<Env<AttrContext>> queue, ListBuffer<Element> elems) {
404            for (Env<AttrContext> env: queue) {
405                switch (env.tree.getTag()) {
406                    case CLASSDEF:
407                        JCClassDecl cdef = (JCClassDecl) env.tree;
408                        if (cdef.sym != null)
409                            elems.append(cdef.sym);
410                        break;
411                    case MODULEDEF:
412                        JCModuleDecl mod = (JCModuleDecl) env.tree;
413                        if (mod.sym != null)
414                            elems.append(mod.sym);
415                        break;
416                    case PACKAGEDEF:
417                        JCCompilationUnit unit = env.toplevel;
418                        if (unit.packge != null)
419                            elems.append(unit.packge);
420                        break;
421                }
422            }
423            genList.addAll(queue);
424        }
425
426    @Override @DefinedBy(Api.COMPILER_TREE)
427    public Iterable<? extends JavaFileObject> generate() {
428        return handleExceptions(new Callable<Iterable<? extends JavaFileObject>>() {
429            @Override
430            public Iterable<? extends JavaFileObject> call() {
431                return generate(null);
432            }
433        }, List.<JavaFileObject>nil(), List.<JavaFileObject>nil());
434    }
435
436    /**
437     * Generate code corresponding to the given classes.
438     * The classes must have previously been returned from {@link #enter}.
439     * If there are classes outstanding to be analyzed, that will be done before
440     * any classes are generated.
441     * If null is specified, code will be generated for all outstanding classes.
442     *
443     * @param classes a list of class elements
444     * @return the files that were generated
445     */
446    public Iterable<? extends JavaFileObject> generate(Iterable<? extends Element> classes) {
447        final ListBuffer<JavaFileObject> results = new ListBuffer<>();
448        try {
449            analyze(null);  // ensure all classes have been parsed, entered, and analyzed
450
451            if (classes == null) {
452                compiler.generate(compiler.desugar(genList), results);
453                genList.clear();
454            }
455            else {
456                Filter f = new Filter() {
457                        @Override
458                        public void process(Env<AttrContext> env) {
459                            compiler.generate(compiler.desugar(ListBuffer.of(env)), results);
460                        }
461                    };
462                f.run(genList, classes);
463            }
464            if (genList.isEmpty()) {
465                compiler.reportDeferredDiagnostics();
466                cleanup();
467            }
468        }
469        finally {
470            if (compiler != null)
471                compiler.log.flush();
472        }
473        return results;
474    }
475
476    public Iterable<? extends Tree> pathFor(CompilationUnitTree unit, Tree node) {
477        return TreeInfo.pathFor((JCTree) node, (JCTree.JCCompilationUnit) unit).reverse();
478    }
479
480    public void ensureEntered() {
481        args.allowEmpty();
482        enter(null);
483    }
484
485    abstract class Filter {
486        void run(Queue<Env<AttrContext>> list, Iterable<? extends Element> elements) {
487            Set<Element> set = new HashSet<>();
488            for (Element item: elements) {
489                set.add(item);
490            }
491
492            ListBuffer<Env<AttrContext>> defer = new ListBuffer<>();
493            while (list.peek() != null) {
494                Env<AttrContext> env = list.remove();
495                Symbol test = null;
496
497                if (env.tree.hasTag(Tag.MODULEDEF)) {
498                    test = ((JCModuleDecl) env.tree).sym;
499                } else if (env.tree.hasTag(Tag.PACKAGEDEF)) {
500                    test = env.toplevel.packge;
501                } else {
502                    ClassSymbol csym = env.enclClass.sym;
503                    if (csym != null)
504                        test = csym.outermostClass();
505                }
506                if (test != null && set.contains(test))
507                    process(env);
508                else
509                    defer = defer.append(env);
510            }
511
512            list.addAll(defer);
513        }
514
515        abstract void process(Env<AttrContext> env);
516    }
517
518    /**
519     * For internal use only.  This method will be
520     * removed without warning.
521     * @param expr the type expression to be analyzed
522     * @param scope the scope in which to analyze the type expression
523     * @return the type
524     * @throws IllegalArgumentException if the type expression of null or empty
525     */
526    public Type parseType(String expr, TypeElement scope) {
527        if (expr == null || expr.equals(""))
528            throw new IllegalArgumentException();
529        compiler = JavaCompiler.instance(context);
530        JavaFileObject prev = compiler.log.useSource(null);
531        ParserFactory parserFactory = ParserFactory.instance(context);
532        Attr attr = Attr.instance(context);
533        try {
534            CharBuffer buf = CharBuffer.wrap((expr+"\u0000").toCharArray(), 0, expr.length());
535            Parser parser = parserFactory.newParser(buf, false, false, false);
536            JCTree tree = parser.parseType();
537            return attr.attribType(tree, (Symbol.TypeSymbol)scope);
538        } finally {
539            compiler.log.useSource(prev);
540        }
541    }
542
543}
544