1 /*
   2  * Copyright (c) 2005, 2011, 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 
  26 package com.sun.tools.javac.api;
  27 
  28 import java.io.File;
  29 import java.io.IOException;
  30 import java.nio.CharBuffer;
  31 import java.util.*;
  32 import java.util.concurrent.atomic.AtomicBoolean;
  33 
  34 import javax.annotation.processing.Processor;
  35 import javax.lang.model.element.Element;
  36 import javax.lang.model.element.TypeElement;
  37 import javax.lang.model.type.TypeMirror;
  38 import javax.tools.*;
  39 
  40 import com.sun.source.tree.*;
  41 import com.sun.source.util.*;
  42 import com.sun.tools.javac.code.*;
  43 import com.sun.tools.javac.code.Symbol.*;
  44 import com.sun.tools.javac.comp.*;
  45 import com.sun.tools.javac.file.JavacFileManager;
  46 import com.sun.tools.javac.main.*;
  47 import com.sun.tools.javac.model.*;
  48 import com.sun.tools.javac.parser.Parser;
  49 import com.sun.tools.javac.parser.ParserFactory;
  50 import com.sun.tools.javac.tree.*;
  51 import com.sun.tools.javac.tree.JCTree.*;
  52 import com.sun.tools.javac.util.*;
  53 import com.sun.tools.javac.util.List;
  54 import com.sun.tools.javac.main.JavaCompiler;
  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  */
  67 public class JavacTaskImpl extends JavacTask {
  68     private ClientCodeWrapper ccw;
  69     private Main compilerMain;
  70     private JavaCompiler compiler;
  71     private Locale locale;
  72     private String[] args;
  73     private Context context;
  74     private List<JavaFileObject> fileObjects;
  75     private Map<JavaFileObject, JCCompilationUnit> notYetEntered;
  76     private ListBuffer<Env<AttrContext>> genList;
  77     private TaskListener taskListener;
  78     private AtomicBoolean used = new AtomicBoolean();
  79     private Iterable<? extends Processor> processors;
  80 
  81     private Main.Result result = null;
  82 
  83     JavacTaskImpl(Main compilerMain,
  84                 String[] args,
  85                 Context context,
  86                 List<JavaFileObject> fileObjects) {
  87         this.ccw = ClientCodeWrapper.instance(context);
  88         this.compilerMain = compilerMain;
  89         this.args = args;
  90         this.context = context;
  91         this.fileObjects = fileObjects;
  92         setLocale(Locale.getDefault());
  93         // null checks
  94         compilerMain.getClass();
  95         args.getClass();
  96         fileObjects.getClass();
  97     }
  98 
  99     JavacTaskImpl(Main compilerMain,
 100                 Iterable<String> flags,
 101                 Context context,
 102                 Iterable<String> classes,
 103                 Iterable<? extends JavaFileObject> fileObjects) {
 104         this(compilerMain, toArray(flags, classes), context, toList(fileObjects));
 105     }
 106 
 107     static private String[] toArray(Iterable<String> flags, Iterable<String> classes) {
 108         ListBuffer<String> result = new ListBuffer<String>();
 109         if (flags != null)
 110             for (String flag : flags)
 111                 result.append(flag);
 112         if (classes != null)
 113             for (String cls : classes)
 114                 result.append(cls);
 115         return result.toArray(new String[result.length()]);
 116     }
 117 
 118     static private List<JavaFileObject> toList(Iterable<? extends JavaFileObject> fileObjects) {
 119         if (fileObjects == null)
 120             return List.nil();
 121         ListBuffer<JavaFileObject> result = new ListBuffer<JavaFileObject>();
 122         for (JavaFileObject fo : fileObjects)
 123             result.append(fo);
 124         return result.toList();
 125     }
 126 
 127     public Boolean call() {
 128         if (!used.getAndSet(true)) {
 129             initContext();
 130             notYetEntered = new HashMap<JavaFileObject, JCCompilationUnit>();
 131             compilerMain.setAPIMode(true);
 132             result = compilerMain.compile(args, context, fileObjects, processors);
 133             cleanup();
 134             return result.isOK();
 135         } else {
 136             throw new IllegalStateException("multiple calls to method 'call'");
 137         }
 138     }
 139 
 140     public void setProcessors(Iterable<? extends Processor> processors) {
 141         processors.getClass(); // null check
 142         // not mt-safe
 143         if (used.get())
 144             throw new IllegalStateException();
 145         this.processors = processors;
 146     }
 147 
 148     public void setLocale(Locale locale) {
 149         if (used.get())
 150             throw new IllegalStateException();
 151         this.locale = locale;
 152     }
 153 
 154     private void prepareCompiler() throws IOException {
 155         if (used.getAndSet(true)) {
 156             if (compiler == null)
 157                 throw new IllegalStateException();
 158         } else {
 159             initContext();
 160             compilerMain.setOptions(Options.instance(context));
 161             compilerMain.filenames = new LinkedHashSet<File>();
 162             Collection<File> filenames = compilerMain.processArgs(CommandLine.parse(args));
 163             if (!filenames.isEmpty())
 164                 throw new IllegalArgumentException("Malformed arguments " + toString(filenames, " "));
 165             compiler = JavaCompiler.instance(context);
 166             compiler.keepComments = true;
 167             compiler.genEndPos = true;
 168             // NOTE: this value will be updated after annotation processing
 169             compiler.initProcessAnnotations(processors);
 170             notYetEntered = new HashMap<JavaFileObject, JCCompilationUnit>();
 171             for (JavaFileObject file: fileObjects)
 172                 notYetEntered.put(file, null);
 173             genList = new ListBuffer<Env<AttrContext>>();
 174             // endContext will be called when all classes have been generated
 175             // TODO: should handle the case after each phase if errors have occurred
 176             args = null;
 177         }
 178     }
 179 
 180     <T> String toString(Iterable<T> items, String sep) {
 181         String currSep = "";
 182         StringBuilder sb = new StringBuilder();
 183         for (T item: items) {
 184             sb.append(currSep);
 185             sb.append(item.toString());
 186             currSep = sep;
 187         }
 188         return sb.toString();
 189     }
 190 
 191     private void initContext() {
 192         context.put(JavacTaskImpl.class, this);
 193         if (context.get(TaskListener.class) != null)
 194             context.put(TaskListener.class, (TaskListener)null);
 195         if (taskListener != null)
 196             context.put(TaskListener.class, ccw.wrap(taskListener));
 197         //initialize compiler's default locale
 198         context.put(Locale.class, locale);
 199     }
 200 
 201     void cleanup() {
 202         if (compiler != null)
 203             compiler.close();
 204         compiler = null;
 205         compilerMain = null;
 206         args = null;
 207         context = null;
 208         fileObjects = null;
 209         notYetEntered = null;
 210     }
 211 
 212     /**
 213      * Construct a JavaFileObject from the given file.
 214      *
 215      * <p><b>TODO: this method is useless here</b></p>
 216      *
 217      * @param file a file
 218      * @return a JavaFileObject from the standard file manager.
 219      */
 220     public JavaFileObject asJavaFileObject(File file) {
 221         JavacFileManager fm = (JavacFileManager)context.get(JavaFileManager.class);
 222         return fm.getRegularFile(file);
 223     }
 224 
 225     public void setTaskListener(TaskListener taskListener) {
 226         this.taskListener = taskListener;
 227     }
 228 
 229     /**
 230      * Parse the specified files returning a list of abstract syntax trees.
 231      *
 232      * @throws java.io.IOException TODO
 233      * @return a list of abstract syntax trees
 234      */
 235     public Iterable<? extends CompilationUnitTree> parse() throws IOException {
 236         try {
 237             prepareCompiler();
 238             List<JCCompilationUnit> units = compiler.parseFiles(fileObjects);
 239             for (JCCompilationUnit unit: units) {
 240                 JavaFileObject file = unit.getSourceFile();
 241                 if (notYetEntered.containsKey(file))
 242                     notYetEntered.put(file, unit);
 243             }
 244             return units;
 245         }
 246         finally {
 247             parsed = true;
 248             if (compiler != null && compiler.log != null)
 249                 compiler.log.flush();
 250         }
 251     }
 252 
 253     private boolean parsed = false;
 254 
 255     /**
 256      * Translate all the abstract syntax trees to elements.
 257      *
 258      * @throws IOException TODO
 259      * @return a list of elements corresponding to the top level
 260      * classes in the abstract syntax trees
 261      */
 262     public Iterable<? extends TypeElement> enter() throws IOException {
 263         return enter(null);
 264     }
 265 
 266     /**
 267      * Translate the given abstract syntax trees to elements.
 268      *
 269      * @param trees a list of abstract syntax trees.
 270      * @throws java.io.IOException TODO
 271      * @return a list of elements corresponding to the top level
 272      * classes in the abstract syntax trees
 273      */
 274     public Iterable<? extends TypeElement> enter(Iterable<? extends CompilationUnitTree> trees)
 275         throws IOException
 276     {
 277         if (trees == null && notYetEntered != null && notYetEntered.isEmpty())
 278             return List.nil();
 279 
 280         prepareCompiler();
 281 
 282         ListBuffer<JCCompilationUnit> roots = null;
 283 
 284         if (trees == null) {
 285             // If there are still files which were specified to be compiled
 286             // (i.e. in fileObjects) but which have not yet been entered,
 287             // then we make sure they have been parsed and add them to the
 288             // list to be entered.
 289             if (notYetEntered.size() > 0) {
 290                 if (!parsed)
 291                     parse(); // TODO would be nice to specify files needed to be parsed
 292                 for (JavaFileObject file: fileObjects) {
 293                     JCCompilationUnit unit = notYetEntered.remove(file);
 294                     if (unit != null) {
 295                         if (roots == null)
 296                             roots = new ListBuffer<JCCompilationUnit>();
 297                         roots.append(unit);
 298                     }
 299                 }
 300                 notYetEntered.clear();
 301             }
 302         }
 303         else {
 304             for (CompilationUnitTree cu : trees) {
 305                 if (cu instanceof JCCompilationUnit) {
 306                     if (roots == null)
 307                         roots = new ListBuffer<JCCompilationUnit>();
 308                     roots.append((JCCompilationUnit)cu);
 309                     notYetEntered.remove(cu.getSourceFile());
 310                 }
 311                 else
 312                     throw new IllegalArgumentException(cu.toString());
 313             }
 314         }
 315 
 316         if (roots == null)
 317             return List.nil();
 318 
 319         try {
 320             List<JCCompilationUnit> units = compiler.enterTrees(roots.toList());
 321 
 322             if (notYetEntered.isEmpty())
 323                 compiler = compiler.processAnnotations(units);
 324 
 325             ListBuffer<TypeElement> elements = new ListBuffer<TypeElement>();
 326             for (JCCompilationUnit unit : units) {
 327                 for (JCTree node : unit.defs) {
 328                     if (node.getTag() == JCTree.CLASSDEF) {
 329                         JCClassDecl cdef = (JCClassDecl) node;
 330                         if (cdef.sym != null) // maybe null if errors in anno processing
 331                             elements.append(cdef.sym);
 332                     }
 333                 }
 334             }
 335             return elements.toList();
 336         }
 337         finally {
 338             compiler.log.flush();
 339         }
 340     }
 341 
 342     /**
 343      * Complete all analysis.
 344      * @throws IOException TODO
 345      */
 346     @Override
 347     public Iterable<? extends Element> analyze() throws IOException {
 348         return analyze(null);
 349     }
 350 
 351     /**
 352      * Complete all analysis on the given classes.
 353      * This can be used to ensure that all compile time errors are reported.
 354      * The classes must have previously been returned from {@link #enter}.
 355      * If null is specified, all outstanding classes will be analyzed.
 356      *
 357      * @param classes a list of class elements
 358      */
 359     // This implementation requires that we open up privileges on JavaCompiler.
 360     // An alternative implementation would be to move this code to JavaCompiler and
 361     // wrap it here
 362     public Iterable<? extends Element> analyze(Iterable<? extends TypeElement> classes) throws IOException {
 363         enter(null);  // ensure all classes have been entered
 364 
 365         final ListBuffer<Element> results = new ListBuffer<Element>();
 366         try {
 367             if (classes == null) {
 368                 handleFlowResults(compiler.flow(compiler.attribute(compiler.todo)), results);
 369             } else {
 370                 Filter f = new Filter() {
 371                     public void process(Env<AttrContext> env) {
 372                         handleFlowResults(compiler.flow(compiler.attribute(env)), results);
 373                     }
 374                 };
 375                 f.run(compiler.todo, classes);
 376             }
 377         } finally {
 378             compiler.log.flush();
 379         }
 380         return results;
 381     }
 382     // where
 383         private void handleFlowResults(Queue<Env<AttrContext>> queue, ListBuffer<Element> elems) {
 384             for (Env<AttrContext> env: queue) {
 385                 switch (env.tree.getTag()) {
 386                     case JCTree.CLASSDEF:
 387                         JCClassDecl cdef = (JCClassDecl) env.tree;
 388                         if (cdef.sym != null)
 389                             elems.append(cdef.sym);
 390                         break;
 391                     case JCTree.TOPLEVEL:
 392                         JCCompilationUnit unit = (JCCompilationUnit) env.tree;
 393                         if (unit.packge != null)
 394                             elems.append(unit.packge);
 395                         break;
 396                 }
 397             }
 398             genList.addAll(queue);
 399         }
 400 
 401 
 402     /**
 403      * Generate code.
 404      * @throws IOException TODO
 405      */
 406     @Override
 407     public Iterable<? extends JavaFileObject> generate() throws IOException {
 408         return generate(null);
 409     }
 410 
 411     /**
 412      * Generate code corresponding to the given classes.
 413      * The classes must have previously been returned from {@link #enter}.
 414      * If there are classes outstanding to be analyzed, that will be done before
 415      * any classes are generated.
 416      * If null is specified, code will be generated for all outstanding classes.
 417      *
 418      * @param classes a list of class elements
 419      */
 420     public Iterable<? extends JavaFileObject> generate(Iterable<? extends TypeElement> classes) throws IOException {
 421         final ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>();
 422         try {
 423             analyze(null);  // ensure all classes have been parsed, entered, and analyzed
 424 
 425             if (classes == null) {
 426                 compiler.generate(compiler.desugar(genList), results);
 427                 genList.clear();
 428             }
 429             else {
 430                 Filter f = new Filter() {
 431                         public void process(Env<AttrContext> env) {
 432                             compiler.generate(compiler.desugar(ListBuffer.of(env)), results);
 433                         }
 434                     };
 435                 f.run(genList, classes);
 436             }
 437             if (genList.isEmpty()) {
 438                 compiler.reportDeferredDiagnostics();
 439                 cleanup();
 440             }
 441         }
 442         finally {
 443             if (compiler != null)
 444                 compiler.log.flush();
 445         }
 446         return results;
 447     }
 448 
 449     public TypeMirror getTypeMirror(Iterable<? extends Tree> path) {
 450         // TODO: Should complete attribution if necessary
 451         Tree last = null;
 452         for (Tree node : path)
 453             last = node;
 454         return ((JCTree)last).type;
 455     }
 456 
 457     public JavacElements getElements() {
 458         if (context == null)
 459             throw new IllegalStateException();
 460         return JavacElements.instance(context);
 461     }
 462 
 463     public JavacTypes getTypes() {
 464         if (context == null)
 465             throw new IllegalStateException();
 466         return JavacTypes.instance(context);
 467     }
 468 
 469     public Iterable<? extends Tree> pathFor(CompilationUnitTree unit, Tree node) {
 470         return TreeInfo.pathFor((JCTree) node, (JCTree.JCCompilationUnit) unit).reverse();
 471     }
 472 
 473     abstract class Filter {
 474         void run(Queue<Env<AttrContext>> list, Iterable<? extends TypeElement> classes) {
 475             Set<TypeElement> set = new HashSet<TypeElement>();
 476             for (TypeElement item: classes)
 477                 set.add(item);
 478 
 479             ListBuffer<Env<AttrContext>> defer = ListBuffer.<Env<AttrContext>>lb();
 480             while (list.peek() != null) {
 481                 Env<AttrContext> env = list.remove();
 482                 ClassSymbol csym = env.enclClass.sym;
 483                 if (csym != null && set.contains(csym.outermostClass()))
 484                     process(env);
 485                 else
 486                     defer = defer.append(env);
 487             }
 488 
 489             list.addAll(defer);
 490         }
 491 
 492         abstract void process(Env<AttrContext> env);
 493     }
 494 
 495     /**
 496      * For internal use only.  This method will be
 497      * removed without warning.
 498      */
 499     public Context getContext() {
 500         return context;
 501     }
 502 
 503     /**
 504      * For internal use only.  This method will be
 505      * removed without warning.
 506      */
 507     public void updateContext(Context newContext) {
 508         context = newContext;
 509     }
 510 
 511     /**
 512      * For internal use only.  This method will be
 513      * removed without warning.
 514      */
 515     public Type parseType(String expr, TypeElement scope) {
 516         if (expr == null || expr.equals(""))
 517             throw new IllegalArgumentException();
 518         compiler = JavaCompiler.instance(context);
 519         JavaFileObject prev = compiler.log.useSource(null);
 520         ParserFactory parserFactory = ParserFactory.instance(context);
 521         Attr attr = Attr.instance(context);
 522         try {
 523             CharBuffer buf = CharBuffer.wrap((expr+"\u0000").toCharArray(), 0, expr.length());
 524             Parser parser = parserFactory.newParser(buf, false, false, false);
 525             JCTree tree = parser.parseType();
 526             return attr.attribType(tree, (Symbol.TypeSymbol)scope);
 527         } finally {
 528             compiler.log.useSource(prev);
 529         }
 530     }
 531 
 532 }