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