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