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