001    /*
002     * Copyright 2005-2008 Sun Microsystems, Inc.  All Rights Reserved.
003     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004     *
005     * This code is free software; you can redistribute it and/or modify it
006     * under the terms of the GNU General Public License version 2 only, as
007     * published by the Free Software Foundation.  Sun designates this
008     * particular file as subject to the "Classpath" exception as provided
009     * by Sun in the LICENSE file that accompanied this code.
010     *
011     * This code is distributed in the hope that it will be useful, but WITHOUT
012     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014     * version 2 for more details (a copy is included in the LICENSE file that
015     * accompanied this code).
016     *
017     * You should have received a copy of the GNU General Public License version
018     * 2 along with this work; if not, write to the Free Software Foundation,
019     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020     *
021     * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022     * CA 95054 USA or visit www.sun.com if you need additional information or
023     * have any questions.
024     */
025    
026    package com.sun.tools.javac.api;
027    
028    import java.io.File;
029    import java.io.IOException;
030    import java.nio.CharBuffer;
031    import java.util.*;
032    import java.util.concurrent.atomic.AtomicBoolean;
033    
034    import javax.annotation.processing.Processor;
035    import javax.lang.model.element.Element;
036    import javax.lang.model.element.TypeElement;
037    import javax.lang.model.type.TypeMirror;
038    import javax.tools.*;
039    
040    import com.sun.source.tree.*;
041    import com.sun.source.util.*;
042    import com.sun.tools.javac.code.*;
043    import com.sun.tools.javac.code.Symbol.*;
044    import com.sun.tools.javac.comp.*;
045    import com.sun.tools.javac.file.JavacFileManager;
046    import com.sun.tools.javac.main.*;
047    import com.sun.tools.javac.model.*;
048    import com.sun.tools.javac.parser.Parser;
049    import com.sun.tools.javac.parser.ParserFactory;
050    import com.sun.tools.javac.tree.*;
051    import com.sun.tools.javac.tree.JCTree.*;
052    import com.sun.tools.javac.util.*;
053    import com.sun.tools.javac.util.List;
054    import com.sun.tools.javac.main.JavaCompiler;
055    
056    /**
057     * Provides access to functionality specific to the Sun Java Compiler, javac.
058     *
059     * <p><b>This is NOT part of any API supported by Sun Microsystems.
060     * If you write code that depends on this, you do so at your own
061     * risk.  This code and its internal interfaces are subject to change
062     * or deletion without notice.</b></p>
063     *
064     * @author Peter von der Ah&eacute;
065     * @author Jonathan Gibbons
066     */
067    public class JavacTaskImpl extends JavacTask {
068        private JavacTool tool;
069        private Main compilerMain;
070        private JavaCompiler compiler;
071        private Locale locale;
072        private String[] args;
073        private Context context;
074        private List<JavaFileObject> fileObjects;
075        private Map<JavaFileObject, JCCompilationUnit> notYetEntered;
076        private ListBuffer<Env<AttrContext>> genList;
077        private TaskListener taskListener;
078        private AtomicBoolean used = new AtomicBoolean();
079        private Iterable<? extends Processor> processors;
080    
081        private Integer result = null;
082    
083        JavacTaskImpl(JavacTool tool,
084                    Main compilerMain,
085                    String[] args,
086                    Context context,
087                    List<JavaFileObject> fileObjects) {
088            this.tool = tool;
089            this.compilerMain = compilerMain;
090            this.args = args;
091            this.context = context;
092            this.fileObjects = fileObjects;
093            setLocale(Locale.getDefault());
094            // null checks
095            compilerMain.getClass();
096            args.getClass();
097            context.getClass();
098            fileObjects.getClass();
099    
100            // force the use of the scanner that captures Javadoc comments
101            com.sun.tools.javac.parser.DocCommentScanner.Factory.preRegister(context);
102        }
103    
104        JavacTaskImpl(JavacTool tool,
105                    Main compilerMain,
106                    Iterable<String> flags,
107                    Context context,
108                    Iterable<String> classes,
109                    Iterable<? extends JavaFileObject> fileObjects) {
110            this(tool, compilerMain, toArray(flags, classes), context, toList(fileObjects));
111        }
112    
113        static private String[] toArray(Iterable<String> flags, Iterable<String> classes) {
114            ListBuffer<String> result = new ListBuffer<String>();
115            if (flags != null)
116                for (String flag : flags)
117                    result.append(flag);
118            if (classes != null)
119                for (String cls : classes)
120                    result.append(cls);
121            return result.toArray(new String[result.length()]);
122        }
123    
124        static private List<JavaFileObject> toList(Iterable<? extends JavaFileObject> fileObjects) {
125            if (fileObjects == null)
126                return List.nil();
127            ListBuffer<JavaFileObject> result = new ListBuffer<JavaFileObject>();
128            for (JavaFileObject fo : fileObjects)
129                result.append(fo);
130            return result.toList();
131        }
132    
133        public Boolean call() {
134            if (!used.getAndSet(true)) {
135                beginContext();
136                try {
137                    compilerMain.setFatalErrors(true);
138                    result = compilerMain.compile(args, context, fileObjects, processors);
139                } finally {
140                    endContext();
141                }
142                compilerMain = null;
143                args = null;
144                context = null;
145                fileObjects = null;
146                return result == 0;
147            } else {
148                throw new IllegalStateException("multiple calls to method 'call'");
149            }
150        }
151    
152        public void setProcessors(Iterable<? extends Processor> processors) {
153            processors.getClass(); // null check
154            // not mt-safe
155            if (used.get())
156                throw new IllegalStateException();
157            this.processors = processors;
158        }
159    
160        public void setLocale(Locale locale) {
161            if (used.get())
162                throw new IllegalStateException();
163            this.locale = locale;
164        }
165    
166        private void prepareCompiler() throws IOException {
167            if (!used.getAndSet(true)) {
168                beginContext();
169                compilerMain.setOptions(Options.instance(context));
170                compilerMain.filenames = new ListBuffer<File>();
171                List<File> filenames = compilerMain.processArgs(CommandLine.parse(args));
172                if (!filenames.isEmpty())
173                    throw new IllegalArgumentException("Malformed arguments " + filenames.toString(" "));
174                compiler = JavaCompiler.instance(context);
175                compiler.keepComments = true;
176                compiler.genEndPos = true;
177                // NOTE: this value will be updated after annotation processing
178                compiler.initProcessAnnotations(processors);
179                notYetEntered = new HashMap<JavaFileObject, JCCompilationUnit>();
180                for (JavaFileObject file: fileObjects)
181                    notYetEntered.put(file, null);
182                genList = new ListBuffer<Env<AttrContext>>();
183                // endContext will be called when all classes have been generated
184                // TODO: should handle the case after each phase if errors have occurred
185                args = null;
186            }
187        }
188    
189        private void beginContext() {
190            context.put(JavacTaskImpl.class, this);
191            if (context.get(TaskListener.class) != null)
192                context.put(TaskListener.class, (TaskListener)null);
193            if (taskListener != null)
194                context.put(TaskListener.class, wrap(taskListener));
195            tool.beginContext(context);
196            //initialize compiler's default locale
197            JavacMessages.instance(context).setCurrentLocale(locale);
198        }
199        // where
200        private TaskListener wrap(final TaskListener tl) {
201            tl.getClass(); // null check
202            return new TaskListener() {
203                public void started(TaskEvent e) {
204                    try {
205                        tl.started(e);
206                    } catch (Throwable t) {
207                        throw new ClientCodeException(t);
208                    }
209                }
210    
211                public void finished(TaskEvent e) {
212                    try {
213                        tl.finished(e);
214                    } catch (Throwable t) {
215                        throw new ClientCodeException(t);
216                    }
217                }
218    
219            };
220        }
221    
222        private void endContext() {
223            tool.endContext();
224        }
225    
226        /**
227         * Construct a JavaFileObject from the given file.
228         *
229         * <p><b>TODO: this method is useless here</b></p>
230         *
231         * @param file a file
232         * @return a JavaFileObject from the standard file manager.
233         */
234        public JavaFileObject asJavaFileObject(File file) {
235            JavacFileManager fm = (JavacFileManager)context.get(JavaFileManager.class);
236            return fm.getRegularFile(file);
237        }
238    
239        public void setTaskListener(TaskListener taskListener) {
240            this.taskListener = taskListener;
241        }
242    
243        /**
244         * Parse the specified files returning a list of abstract syntax trees.
245         *
246         * @throws java.io.IOException TODO
247         * @return a list of abstract syntax trees
248         */
249        public Iterable<? extends CompilationUnitTree> parse() throws IOException {
250            try {
251                prepareCompiler();
252                List<JCCompilationUnit> units = compiler.parseFiles(fileObjects);
253                for (JCCompilationUnit unit: units) {
254                    JavaFileObject file = unit.getSourceFile();
255                    if (notYetEntered.containsKey(file))
256                        notYetEntered.put(file, unit);
257                }
258                return units;
259            }
260            finally {
261                parsed = true;
262                if (compiler != null && compiler.log != null)
263                    compiler.log.flush();
264            }
265        }
266    
267        private boolean parsed = false;
268    
269        /**
270         * Translate all the abstract syntax trees to elements.
271         *
272         * @throws IOException TODO
273         * @return a list of elements corresponding to the top level
274         * classes in the abstract syntax trees
275         */
276        public Iterable<? extends TypeElement> enter() throws IOException {
277            return enter(null);
278        }
279    
280        /**
281         * Translate the given abstract syntax trees to elements.
282         *
283         * @param trees a list of abstract syntax trees.
284         * @throws java.io.IOException TODO
285         * @return a list of elements corresponding to the top level
286         * classes in the abstract syntax trees
287         */
288        public Iterable<? extends TypeElement> enter(Iterable<? extends CompilationUnitTree> trees)
289            throws IOException
290        {
291            prepareCompiler();
292    
293            ListBuffer<JCCompilationUnit> roots = null;
294    
295            if (trees == null) {
296                // If there are still files which were specified to be compiled
297                // (i.e. in fileObjects) but which have not yet been entered,
298                // then we make sure they have been parsed and add them to the
299                // list to be entered.
300                if (notYetEntered.size() > 0) {
301                    if (!parsed)
302                        parse(); // TODO would be nice to specify files needed to be parsed
303                    for (JavaFileObject file: fileObjects) {
304                        JCCompilationUnit unit = notYetEntered.remove(file);
305                        if (unit != null) {
306                            if (roots == null)
307                                roots = new ListBuffer<JCCompilationUnit>();
308                            roots.append(unit);
309                        }
310                    }
311                    notYetEntered.clear();
312                }
313            }
314            else {
315                for (CompilationUnitTree cu : trees) {
316                    if (cu instanceof JCCompilationUnit) {
317                        if (roots == null)
318                            roots = new ListBuffer<JCCompilationUnit>();
319                        roots.append((JCCompilationUnit)cu);
320                        notYetEntered.remove(cu.getSourceFile());
321                    }
322                    else
323                        throw new IllegalArgumentException(cu.toString());
324                }
325            }
326    
327            if (roots == null)
328                return List.nil();
329    
330            try {
331                List<JCCompilationUnit> units = compiler.enterTrees(roots.toList());
332    
333                if (notYetEntered.isEmpty())
334                    compiler = compiler.processAnnotations(units);
335    
336                ListBuffer<TypeElement> elements = new ListBuffer<TypeElement>();
337                for (JCCompilationUnit unit : units) {
338                    for (JCTree node : unit.defs)
339                        if (node.getTag() == JCTree.CLASSDEF)
340                            elements.append(((JCTree.JCClassDecl) node).sym);
341                }
342                return elements.toList();
343            }
344            finally {
345                compiler.log.flush();
346            }
347        }
348    
349        /**
350         * Complete all analysis.
351         * @throws IOException TODO
352         */
353        @Override
354        public Iterable<? extends Element> analyze() throws IOException {
355            return analyze(null);
356        }
357    
358        /**
359         * Complete all analysis on the given classes.
360         * This can be used to ensure that all compile time errors are reported.
361         * The classes must have previously been returned from {@link #enter}.
362         * If null is specified, all outstanding classes will be analyzed.
363         *
364         * @param classes a list of class elements
365         */
366        // This implementation requires that we open up privileges on JavaCompiler.
367        // An alternative implementation would be to move this code to JavaCompiler and
368        // wrap it here
369        public Iterable<? extends Element> analyze(Iterable<? extends TypeElement> classes) throws IOException {
370            enter(null);  // ensure all classes have been entered
371    
372            final ListBuffer<Element> results = new ListBuffer<Element>();
373            try {
374                if (classes == null) {
375                    handleFlowResults(compiler.flow(compiler.attribute(compiler.todo)), results);
376                } else {
377                    Filter f = new Filter() {
378                        public void process(Env<AttrContext> env) {
379                            handleFlowResults(compiler.flow(compiler.attribute(env)), results);
380                        }
381                    };
382                    f.run(compiler.todo, classes);
383                }
384            } finally {
385                compiler.log.flush();
386            }
387            return results;
388        }
389        // where
390            private void handleFlowResults(Queue<Env<AttrContext>> queue, ListBuffer<Element> elems) {
391                for (Env<AttrContext> env: queue) {
392                    switch (env.tree.getTag()) {
393                        case JCTree.CLASSDEF:
394                            JCClassDecl cdef = (JCClassDecl) env.tree;
395                            if (cdef.sym != null)
396                                elems.append(cdef.sym);
397                            break;
398                        case JCTree.TOPLEVEL:
399                            JCCompilationUnit unit = (JCCompilationUnit) env.tree;
400                            if (unit.packge != null)
401                                elems.append(unit.packge);
402                            break;
403                    }
404                }
405                genList.addAll(queue);
406            }
407    
408    
409        /**
410         * Generate code.
411         * @throws IOException TODO
412         */
413        @Override
414        public Iterable<? extends JavaFileObject> generate() throws IOException {
415            return generate(null);
416        }
417    
418        /**
419         * Generate code corresponding to the given classes.
420         * The classes must have previously been returned from {@link #enter}.
421         * If there are classes outstanding to be analyzed, that will be done before
422         * any classes are generated.
423         * If null is specified, code will be generated for all outstanding classes.
424         *
425         * @param classes a list of class elements
426         */
427        public Iterable<? extends JavaFileObject> generate(Iterable<? extends TypeElement> classes) throws IOException {
428            final ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>();
429            try {
430                analyze(null);  // ensure all classes have been parsed, entered, and analyzed
431    
432                if (classes == null) {
433                    compiler.generate(compiler.desugar(genList), results);
434                    genList.clear();
435                }
436                else {
437                    Filter f = new Filter() {
438                            public void process(Env<AttrContext> env) {
439                                compiler.generate(compiler.desugar(ListBuffer.of(env)), results);
440                            }
441                        };
442                    f.run(genList, classes);
443                }
444                if (genList.isEmpty()) {
445                    compiler.reportDeferredDiagnostics();
446                    compiler.log.flush();
447                    endContext();
448                }
449            }
450            finally {
451                compiler.log.flush();
452            }
453            return results;
454        }
455    
456        public TypeMirror getTypeMirror(Iterable<? extends Tree> path) {
457            // TODO: Should complete attribution if necessary
458            Tree last = null;
459            for (Tree node : path)
460                last = node;
461            return ((JCTree)last).type;
462        }
463    
464        public JavacElements getElements() {
465            if (context == null)
466                throw new IllegalStateException();
467            return JavacElements.instance(context);
468        }
469    
470        public JavacTypes getTypes() {
471            if (context == null)
472                throw new IllegalStateException();
473            return JavacTypes.instance(context);
474        }
475    
476        public Iterable<? extends Tree> pathFor(CompilationUnitTree unit, Tree node) {
477            return TreeInfo.pathFor((JCTree) node, (JCTree.JCCompilationUnit) unit).reverse();
478        }
479    
480        abstract class Filter {
481            void run(Queue<Env<AttrContext>> list, Iterable<? extends TypeElement> classes) {
482                Set<TypeElement> set = new HashSet<TypeElement>();
483                for (TypeElement item: classes)
484                    set.add(item);
485    
486                ListBuffer<Env<AttrContext>> defer = ListBuffer.<Env<AttrContext>>lb();
487                while (list.peek() != null) {
488                    Env<AttrContext> env = list.remove();
489                    ClassSymbol csym = env.enclClass.sym;
490                    if (csym != null && set.contains(csym.outermostClass()))
491                        process(env);
492                    else
493                        defer = defer.append(env);
494                }
495    
496                list.addAll(defer);
497            }
498    
499            abstract void process(Env<AttrContext> env);
500        }
501    
502        /**
503         * For internal use by Sun Microsystems only.  This method will be
504         * removed without warning.
505         */
506        public Context getContext() {
507            return context;
508        }
509    
510        /**
511         * For internal use by Sun Microsystems only.  This method will be
512         * removed without warning.
513         */
514        public void updateContext(Context newContext) {
515            context = newContext;
516        }
517    
518        /**
519         * For internal use by Sun Microsystems only.  This method will be
520         * removed without warning.
521         */
522        public Type parseType(String expr, TypeElement scope) {
523            if (expr == null || expr.equals(""))
524                throw new IllegalArgumentException();
525            compiler = JavaCompiler.instance(context);
526            JavaFileObject prev = compiler.log.useSource(null);
527            ParserFactory parserFactory = ParserFactory.instance(context);
528            Attr attr = Attr.instance(context);
529            try {
530                CharBuffer buf = CharBuffer.wrap((expr+"\u0000").toCharArray(), 0, expr.length());
531                Parser parser = parserFactory.newParser(buf, false, false, false);
532                JCTree tree = parser.parseType();
533                return attr.attribType(tree, (Symbol.TypeSymbol)scope);
534            } finally {
535                compiler.log.useSource(prev);
536            }
537        }
538    
539    }