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