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