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