JavacTaskImpl.java revision 2693:c36403059804
114456Ssos/*
2230132Suqs * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
314456Ssos * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
414456Ssos *
514456Ssos * This code is free software; you can redistribute it and/or modify it
614456Ssos * under the terms of the GNU General Public License version 2 only, as
714456Ssos * published by the Free Software Foundation.  Oracle designates this
814456Ssos * particular file as subject to the "Classpath" exception as provided
914456Ssos * by Oracle in the LICENSE file that accompanied this code.
1014456Ssos *
1114456Ssos * This code is distributed in the hope that it will be useful, but WITHOUT
1214456Ssos * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1314456Ssos * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1414456Ssos * version 2 for more details (a copy is included in the LICENSE file that
1597748Sschweikh * accompanied this code).
1614456Ssos *
1714456Ssos * You should have received a copy of the GNU General Public License version
1814456Ssos * 2 along with this work; if not, write to the Free Software Foundation,
1914456Ssos * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
2014456Ssos *
2114456Ssos * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2214456Ssos * or visit www.oracle.com if you need additional information or have any
2314456Ssos * questions.
2414456Ssos */
2514456Ssos
2614456Ssospackage com.sun.tools.javac.api;
2714456Ssos
2850477Speterimport java.io.IOException;
2914456Ssosimport java.nio.CharBuffer;
3014456Ssosimport java.util.*;
3125984Sjdpimport java.util.concurrent.Callable;
32186668Sobrienimport java.util.concurrent.atomic.AtomicBoolean;
3314456Ssos
3425984Sjdpimport javax.annotation.processing.Processor;
3514456Ssosimport javax.lang.model.element.Element;
3655205Speterimport javax.lang.model.element.TypeElement;
3714456Ssosimport javax.tools.*;
38186668Sobrien
3914456Ssosimport com.sun.source.tree.*;
40100384Speterimport com.sun.tools.javac.code.*;
41100384Speterimport com.sun.tools.javac.code.Symbol.ClassSymbol;
4214456Ssosimport com.sun.tools.javac.comp.*;
4314456Ssosimport com.sun.tools.javac.main.*;
4414456Ssosimport com.sun.tools.javac.main.JavaCompiler;
4514456Ssosimport com.sun.tools.javac.parser.Parser;
4614456Ssosimport com.sun.tools.javac.parser.ParserFactory;
47153504Smarcelimport com.sun.tools.javac.processing.AnnotationProcessingError;
48153504Smarcelimport com.sun.tools.javac.tree.*;
49153504Smarcelimport com.sun.tools.javac.tree.JCTree.JCClassDecl;
50153504Smarcelimport com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
51153504Smarcelimport com.sun.tools.javac.util.*;
52153504Smarcelimport com.sun.tools.javac.util.DefinedBy.Api;
53153504Smarcelimport com.sun.tools.javac.util.List;
54153504Smarcelimport com.sun.tools.javac.util.Log.PrefixKind;
55108696Sjakeimport com.sun.tools.javac.util.Log.WriterKind;
5614456Ssos
5714456Ssos/**
58189771Sdchagin * Provides access to functionality specific to the JDK Java Compiler, javac.
59189771Sdchagin *
60189771Sdchagin * <p><b>This is NOT part of any supported API.
61196512Sbz * If you write code that depends on this, you do so at your own
62196512Sbz * risk.  This code and its internal interfaces are subject to change
63210446Skib * or deletion without notice.</b></p>
64210446Skib *
65189771Sdchagin * @author Peter von der Ah&eacute;
66189771Sdchagin * @author Jonathan Gibbons
67189771Sdchagin */
6859342Sobrienpublic class JavacTaskImpl extends BasicJavacTask {
69100384Speter    private final Arguments args;
7072999Sobrien    private JavaCompiler compiler;
7159342Sobrien    private JavaFileManager fileManager;
7259342Sobrien    private Locale locale;
73123742Speter    private Map<JavaFileObject, JCCompilationUnit> notYetEntered;
74123742Speter    private ListBuffer<Env<AttrContext>> genList;
75190708Sdchagin    private final AtomicBoolean used = new AtomicBoolean();
76189771Sdchagin    private Iterable<? extends Processor> processors;
77196653Sbz
78196653Sbz    JavacTaskImpl(Context context) {
79196653Sbz        super(context, true);
80108696Sjake        args = Arguments.instance(context);
8114456Ssos        fileManager = context.get(JavaFileManager.class);
82108696Sjake    }
83108696Sjake
8414456Ssos    @Override @DefinedBy(Api.COMPILER)
85186668Sobrien    public Boolean call() {
8636735Sdfr        return doCall().isOK();
87108696Sjake    }
88108696Sjake
89108696Sjake    /* Internal version of call exposing Main.Result. */
90108696Sjake    public Main.Result doCall() {
91204552Salfred        try {
9236735Sdfr            return handleExceptions(new Callable<Main.Result>() {
93133464Smarcel                @Override
94133464Smarcel                public Main.Result call() throws Exception {
95133464Smarcel                    prepareCompiler(false);
96186680Sbz                    compiler.compile(args.getFileObjects(), args.getClassNames(), processors);
97189771Sdchagin                    return (compiler.errorCount() > 0) ? Main.Result.ERROR : Main.Result.OK; // FIXME?
98196512Sbz                }
9955205Speter            }, Main.Result.SYSERR, Main.Result.ABNORMAL);
10025984Sjdp        } finally {
10125984Sjdp            try {
102                cleanup();
103            } catch (ClientCodeException e) {
104                throw new RuntimeException(e.getCause());
105            }
106        }
107    }
108
109    @Override @DefinedBy(Api.COMPILER)
110    public void setProcessors(Iterable<? extends Processor> processors) {
111        processors.getClass(); // null check
112        // not mt-safe
113        if (used.get())
114            throw new IllegalStateException();
115        this.processors = processors;
116    }
117
118    @Override @DefinedBy(Api.COMPILER)
119    public void setLocale(Locale locale) {
120        if (used.get())
121            throw new IllegalStateException();
122        this.locale = locale;
123    }
124
125    private <T> T handleExceptions(Callable<T> c, T sysErrorResult, T abnormalErrorResult) {
126        try {
127            return c.call();
128        } catch (FatalError ex) {
129            Log log = Log.instance(context);
130            Options options = Options.instance(context);
131            log.printRawLines(ex.getMessage());
132            if (ex.getCause() != null && options.isSet("dev")) {
133                ex.getCause().printStackTrace(log.getWriter(WriterKind.NOTICE));
134            }
135            return sysErrorResult;
136        } catch (AnnotationProcessingError | ClientCodeException e) {
137            // AnnotationProcessingError is thrown from JavacProcessingEnvironment,
138            // to forward errors thrown from an annotation processor
139            // ClientCodeException is thrown from ClientCodeWrapper,
140            // to forward errors thrown from user-supplied code for Compiler API
141            // as specified by javax.tools.JavaCompiler#getTask
142            // and javax.tools.JavaCompiler.CompilationTask#call
143            throw new RuntimeException(e.getCause());
144        } catch (PropagatedException e) {
145            throw e.getCause();
146        } catch (IllegalStateException e) {
147            throw e;
148        } catch (Exception | Error ex) {
149            // Nasty.  If we've already reported an error, compensate
150            // for buggy compiler error recovery by swallowing thrown
151            // exceptions.
152            if (compiler == null || compiler.errorCount() == 0
153                    || Options.instance(context).isSet("dev")) {
154                Log log = Log.instance(context);
155                log.printLines(PrefixKind.JAVAC, "msg.bug", JavaCompiler.version());
156                ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
157            }
158            return abnormalErrorResult;
159        }
160    }
161
162    private void prepareCompiler(boolean forParse) {
163        if (used.getAndSet(true)) {
164            if (compiler == null)
165                throw new PropagatedException(new IllegalStateException());
166        } else {
167            args.validate();
168
169            //initialize compiler's default locale
170            context.put(Locale.class, locale);
171
172            // hack
173            JavacMessages messages = context.get(JavacMessages.messagesKey);
174            if (messages != null && !messages.getCurrentLocale().equals(locale))
175                messages.setCurrentLocale(locale);
176
177            initPlugins(args.getPluginOpts());
178            initDocLint(args.getDocLintOpts());
179
180            // init JavaCompiler and queues
181            compiler = JavaCompiler.instance(context);
182            compiler.keepComments = true;
183            compiler.genEndPos = true;
184            notYetEntered = new HashMap<>();
185            if (forParse) {
186                compiler.initProcessAnnotations(processors);
187                for (JavaFileObject file: args.getFileObjects())
188                    notYetEntered.put(file, null);
189                genList = new ListBuffer<>();
190            }
191        }
192    }
193
194    <T> String toString(Iterable<T> items, String sep) {
195        String currSep = "";
196        StringBuilder sb = new StringBuilder();
197        for (T item: items) {
198            sb.append(currSep);
199            sb.append(item.toString());
200            currSep = sep;
201        }
202        return sb.toString();
203    }
204
205    void cleanup() {
206        if (compiler != null)
207            compiler.close();
208        if (fileManager instanceof BaseFileManager && ((BaseFileManager) fileManager).autoClose) {
209            try {
210                fileManager.close();
211            } catch (IOException ignore) {
212            }
213        }
214        compiler = null;
215        context = null;
216        notYetEntered = null;
217    }
218
219    @Override @DefinedBy(Api.COMPILER_TREE)
220    public Iterable<? extends CompilationUnitTree> parse() {
221        return handleExceptions(new Callable<Iterable<? extends CompilationUnitTree>>() {
222            @Override
223            public Iterable<? extends CompilationUnitTree> call() {
224                return parseInternal();
225            }
226        }, List.<CompilationUnitTree>nil(), List.<CompilationUnitTree>nil());
227    }
228
229    private Iterable<? extends CompilationUnitTree> parseInternal() {
230        try {
231            prepareCompiler(true);
232            List<JCCompilationUnit> units = compiler.parseFiles(args.getFileObjects());
233            for (JCCompilationUnit unit: units) {
234                JavaFileObject file = unit.getSourceFile();
235                if (notYetEntered.containsKey(file))
236                    notYetEntered.put(file, unit);
237            }
238            return units;
239        }
240        finally {
241            parsed = true;
242            if (compiler != null && compiler.log != null)
243                compiler.log.flush();
244        }
245    }
246
247    private boolean parsed = false;
248
249    /**
250     * Translate all the abstract syntax trees to elements.
251     *
252     * @return a list of elements corresponding to the top level
253     * classes in the abstract syntax trees
254     */
255    public Iterable<? extends TypeElement> enter() {
256        return enter(null);
257    }
258
259    /**
260     * Translate the given abstract syntax trees to elements.
261     *
262     * @param trees a list of abstract syntax trees.
263     * @return a list of elements corresponding to the top level
264     * classes in the abstract syntax trees
265     */
266    public Iterable<? extends TypeElement> enter(Iterable<? extends CompilationUnitTree> trees)
267    {
268        if (trees == null && notYetEntered != null && notYetEntered.isEmpty())
269            return List.nil();
270
271        prepareCompiler(true);
272
273        ListBuffer<JCCompilationUnit> roots = null;
274
275        if (trees == null) {
276            // If there are still files which were specified to be compiled
277            // (i.e. in fileObjects) but which have not yet been entered,
278            // then we make sure they have been parsed and add them to the
279            // list to be entered.
280            if (notYetEntered.size() > 0) {
281                if (!parsed)
282                    parseInternal(); // TODO would be nice to specify files needed to be parsed
283                for (JavaFileObject file: args.getFileObjects()) {
284                    JCCompilationUnit unit = notYetEntered.remove(file);
285                    if (unit != null) {
286                        if (roots == null)
287                            roots = new ListBuffer<>();
288                        roots.append(unit);
289                    }
290                }
291                notYetEntered.clear();
292            }
293        }
294        else {
295            for (CompilationUnitTree cu : trees) {
296                if (cu instanceof JCCompilationUnit) {
297                    if (roots == null)
298                        roots = new ListBuffer<>();
299                    roots.append((JCCompilationUnit)cu);
300                    notYetEntered.remove(cu.getSourceFile());
301                }
302                else
303                    throw new IllegalArgumentException(cu.toString());
304            }
305        }
306
307        if (roots == null)
308            return List.nil();
309
310        try {
311            List<JCCompilationUnit> units = compiler.enterTrees(roots.toList());
312
313            if (notYetEntered.isEmpty())
314                compiler.processAnnotations(units);
315
316            ListBuffer<TypeElement> elements = new ListBuffer<>();
317            for (JCCompilationUnit unit : units) {
318                for (JCTree node : unit.defs) {
319                    if (node.hasTag(JCTree.Tag.CLASSDEF)) {
320                        JCClassDecl cdef = (JCClassDecl) node;
321                        if (cdef.sym != null) // maybe null if errors in anno processing
322                            elements.append(cdef.sym);
323                    }
324                }
325            }
326            return elements.toList();
327        }
328        finally {
329            compiler.log.flush();
330        }
331    }
332
333    @Override @DefinedBy(Api.COMPILER_TREE)
334    public Iterable<? extends Element> analyze() {
335        return handleExceptions(new Callable<Iterable<? extends Element>>() {
336            @Override
337            public Iterable<? extends Element> call() {
338                return analyze(null);
339            }
340        }, List.<Element>nil(), List.<Element>nil());
341    }
342
343    /**
344     * Complete all analysis on the given classes.
345     * This can be used to ensure that all compile time errors are reported.
346     * The classes must have previously been returned from {@link #enter}.
347     * If null is specified, all outstanding classes will be analyzed.
348     *
349     * @param classes a list of class elements
350     * @return the elements that were analyzed
351     */
352    // This implementation requires that we open up privileges on JavaCompiler.
353    // An alternative implementation would be to move this code to JavaCompiler and
354    // wrap it here
355    public Iterable<? extends Element> analyze(Iterable<? extends TypeElement> classes) {
356        enter(null);  // ensure all classes have been entered
357
358        final ListBuffer<Element> results = new ListBuffer<>();
359        try {
360            if (classes == null) {
361                handleFlowResults(compiler.flow(compiler.attribute(compiler.todo)), results);
362            } else {
363                Filter f = new Filter() {
364                    @Override
365                    public void process(Env<AttrContext> env) {
366                        handleFlowResults(compiler.flow(compiler.attribute(env)), results);
367                    }
368                };
369                f.run(compiler.todo, classes);
370            }
371        } finally {
372            compiler.log.flush();
373        }
374        return results;
375    }
376    // where
377        private void handleFlowResults(Queue<Env<AttrContext>> queue, ListBuffer<Element> elems) {
378            for (Env<AttrContext> env: queue) {
379                switch (env.tree.getTag()) {
380                    case CLASSDEF:
381                        JCClassDecl cdef = (JCClassDecl) env.tree;
382                        if (cdef.sym != null)
383                            elems.append(cdef.sym);
384                        break;
385                    case TOPLEVEL:
386                        JCCompilationUnit unit = (JCCompilationUnit) env.tree;
387                        if (unit.packge != null)
388                            elems.append(unit.packge);
389                        break;
390                }
391            }
392            genList.addAll(queue);
393        }
394
395    @Override @DefinedBy(Api.COMPILER_TREE)
396    public Iterable<? extends JavaFileObject> generate() {
397        return handleExceptions(new Callable<Iterable<? extends JavaFileObject>>() {
398            @Override
399            public Iterable<? extends JavaFileObject> call() {
400                return generate(null);
401            }
402        }, List.<JavaFileObject>nil(), List.<JavaFileObject>nil());
403    }
404
405    /**
406     * Generate code corresponding to the given classes.
407     * The classes must have previously been returned from {@link #enter}.
408     * If there are classes outstanding to be analyzed, that will be done before
409     * any classes are generated.
410     * If null is specified, code will be generated for all outstanding classes.
411     *
412     * @param classes a list of class elements
413     * @return the files that were generated
414     */
415    public Iterable<? extends JavaFileObject> generate(Iterable<? extends TypeElement> classes) {
416        final ListBuffer<JavaFileObject> results = new ListBuffer<>();
417        try {
418            analyze(null);  // ensure all classes have been parsed, entered, and analyzed
419
420            if (classes == null) {
421                compiler.generate(compiler.desugar(genList), results);
422                genList.clear();
423            }
424            else {
425                Filter f = new Filter() {
426                        @Override
427                        public void process(Env<AttrContext> env) {
428                            compiler.generate(compiler.desugar(ListBuffer.of(env)), results);
429                        }
430                    };
431                f.run(genList, classes);
432            }
433            if (genList.isEmpty()) {
434                compiler.reportDeferredDiagnostics();
435                cleanup();
436            }
437        }
438        finally {
439            if (compiler != null)
440                compiler.log.flush();
441        }
442        return results;
443    }
444
445    public Iterable<? extends Tree> pathFor(CompilationUnitTree unit, Tree node) {
446        return TreeInfo.pathFor((JCTree) node, (JCTree.JCCompilationUnit) unit).reverse();
447    }
448
449    abstract class Filter {
450        void run(Queue<Env<AttrContext>> list, Iterable<? extends TypeElement> classes) {
451            Set<TypeElement> set = new HashSet<>();
452            for (TypeElement item: classes)
453                set.add(item);
454
455            ListBuffer<Env<AttrContext>> defer = new ListBuffer<>();
456            while (list.peek() != null) {
457                Env<AttrContext> env = list.remove();
458                ClassSymbol csym = env.enclClass.sym;
459                if (csym != null && set.contains(csym.outermostClass()))
460                    process(env);
461                else
462                    defer = defer.append(env);
463            }
464
465            list.addAll(defer);
466        }
467
468        abstract void process(Env<AttrContext> env);
469    }
470
471    /**
472     * For internal use only.  This method will be
473     * removed without warning.
474     * @param expr the type expression to be analyzed
475     * @param scope the scope in which to analyze the type expression
476     * @return the type
477     * @throws IllegalArgumentException if the type expression of null or empty
478     */
479    public Type parseType(String expr, TypeElement scope) {
480        if (expr == null || expr.equals(""))
481            throw new IllegalArgumentException();
482        compiler = JavaCompiler.instance(context);
483        JavaFileObject prev = compiler.log.useSource(null);
484        ParserFactory parserFactory = ParserFactory.instance(context);
485        Attr attr = Attr.instance(context);
486        try {
487            CharBuffer buf = CharBuffer.wrap((expr+"\u0000").toCharArray(), 0, expr.length());
488            Parser parser = parserFactory.newParser(buf, false, false, false);
489            JCTree tree = parser.parseType();
490            return attr.attribType(tree, (Symbol.TypeSymbol)scope);
491        } finally {
492            compiler.log.useSource(prev);
493        }
494    }
495
496}
497