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