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