1 /* 2 * Copyright (c) 2005, 2018, 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.processing; 27 28 import java.io.Closeable; 29 import java.io.FileNotFoundException; 30 import java.io.InputStream; 31 import java.io.OutputStream; 32 import java.io.FilterOutputStream; 33 import java.io.Reader; 34 import java.io.Writer; 35 import java.io.FilterWriter; 36 import java.io.PrintWriter; 37 import java.io.IOException; 38 import java.util.*; 39 40 import static java.util.Collections.*; 41 42 import javax.annotation.processing.*; 43 import javax.lang.model.SourceVersion; 44 import javax.lang.model.element.NestingKind; 45 import javax.lang.model.element.Modifier; 46 import javax.lang.model.element.Element; 47 import javax.tools.*; 48 import javax.tools.JavaFileManager.Location; 49 50 import static javax.tools.StandardLocation.SOURCE_OUTPUT; 51 import static javax.tools.StandardLocation.CLASS_OUTPUT; 52 53 import com.sun.tools.javac.code.Lint; 54 import com.sun.tools.javac.code.Symbol.ClassSymbol; 55 import com.sun.tools.javac.code.Symbol.ModuleSymbol; 56 import com.sun.tools.javac.code.Symbol.TypeSymbol; 57 import com.sun.tools.javac.code.Symtab; 58 import com.sun.tools.javac.comp.Modules; 59 import com.sun.tools.javac.model.JavacElements; 60 import com.sun.tools.javac.resources.CompilerProperties.Warnings; 61 import com.sun.tools.javac.util.*; 62 import com.sun.tools.javac.util.DefinedBy.Api; 63 64 import static com.sun.tools.javac.code.Lint.LintCategory.PROCESSING; 65 import com.sun.tools.javac.code.Symbol.PackageSymbol; 66 import com.sun.tools.javac.main.Option; 67 68 /** 69 * The FilerImplementation class must maintain a number of 70 * constraints. First, multiple attempts to open the same path within 71 * the same invocation of the tool results in an IOException being 72 * thrown. For example, trying to open the same source file twice: 73 * 74 * <pre> 75 * createSourceFile("foo.Bar") 76 * ... 77 * createSourceFile("foo.Bar") 78 * </pre> 79 * 80 * is disallowed as is opening a text file that happens to have 81 * the same name as a source file: 82 * 83 * <pre> 84 * createSourceFile("foo.Bar") 85 * ... 86 * createTextFile(SOURCE_TREE, "foo", new File("Bar"), null) 87 * </pre> 88 * 89 * <p>Additionally, creating a source file that corresponds to an 90 * already created class file (or vice versa) also results in an 91 * IOException since each type can only be created once. However, if 92 * the Filer is used to create a text file named *.java that happens 93 * to correspond to an existing class file, a warning is *not* 94 * generated. Similarly, a warning is not generated for a binary file 95 * named *.class and an existing source file. 96 * 97 * <p>The reason for this difference is that source files and class 98 * files are registered with the tool and can get passed on as 99 * declarations to the next round of processing. Files that are just 100 * named *.java and *.class are not processed in that manner; although 101 * having extra source files and class files on the source path and 102 * class path can alter the behavior of the tool and any final 103 * compile. 104 * 105 * <p><b>This is NOT part of any supported API. 106 * If you write code that depends on this, you do so at your own risk. 107 * This code and its internal interfaces are subject to change or 108 * deletion without notice.</b> 109 */ 110 public class JavacFiler implements Filer, Closeable { 111 // TODO: Implement different transaction model for updating the 112 // Filer's record keeping on file close. 113 114 private static final String ALREADY_OPENED = 115 "Output stream or writer has already been opened."; 116 private static final String NOT_FOR_READING = 117 "FileObject was not opened for reading."; 118 private static final String NOT_FOR_WRITING = 119 "FileObject was not opened for writing."; 120 121 /** 122 * Wrap a JavaFileObject to manage writing by the Filer. 123 */ 124 private class FilerOutputFileObject extends ForwardingFileObject<FileObject> { 125 private boolean opened = false; 126 private ModuleSymbol mod; 127 private String name; 128 129 FilerOutputFileObject(ModuleSymbol mod, String name, FileObject fileObject) { 130 super(fileObject); 131 this.mod = mod; 132 this.name = name; 133 } 134 135 @Override @DefinedBy(Api.COMPILER) 136 public synchronized OutputStream openOutputStream() throws IOException { 137 if (opened) 138 throw new IOException(ALREADY_OPENED); 139 opened = true; 140 return new FilerOutputStream(mod, name, fileObject); 141 } 142 143 @Override @DefinedBy(Api.COMPILER) 144 public synchronized Writer openWriter() throws IOException { 145 if (opened) 146 throw new IOException(ALREADY_OPENED); 147 opened = true; 148 return new FilerWriter(mod, name, fileObject); 149 } 150 151 // Three anti-literacy methods 152 @Override @DefinedBy(Api.COMPILER) 153 public InputStream openInputStream() throws IOException { 154 throw new IllegalStateException(NOT_FOR_READING); 155 } 156 157 @Override @DefinedBy(Api.COMPILER) 158 public Reader openReader(boolean ignoreEncodingErrors) throws IOException { 159 throw new IllegalStateException(NOT_FOR_READING); 160 } 161 162 @Override @DefinedBy(Api.COMPILER) 163 public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { 164 throw new IllegalStateException(NOT_FOR_READING); 165 } 166 167 @Override @DefinedBy(Api.COMPILER) 168 public boolean delete() { 169 return false; 170 } 171 } 172 173 private class FilerOutputJavaFileObject extends FilerOutputFileObject implements JavaFileObject { 174 private final JavaFileObject javaFileObject; 175 FilerOutputJavaFileObject(ModuleSymbol mod, String name, JavaFileObject javaFileObject) { 176 super(mod, name, javaFileObject); 177 this.javaFileObject = javaFileObject; 178 } 179 180 @DefinedBy(Api.COMPILER) 181 public JavaFileObject.Kind getKind() { 182 return javaFileObject.getKind(); 183 } 184 185 @DefinedBy(Api.COMPILER) 186 public boolean isNameCompatible(String simpleName, 187 JavaFileObject.Kind kind) { 188 return javaFileObject.isNameCompatible(simpleName, kind); 189 } 190 191 @DefinedBy(Api.COMPILER) 192 public NestingKind getNestingKind() { 193 return javaFileObject.getNestingKind(); 194 } 195 196 @DefinedBy(Api.COMPILER) 197 public Modifier getAccessLevel() { 198 return javaFileObject.getAccessLevel(); 199 } 200 } 201 202 /** 203 * Wrap a JavaFileObject to manage reading by the Filer. 204 */ 205 private class FilerInputFileObject extends ForwardingFileObject<FileObject> { 206 FilerInputFileObject(FileObject fileObject) { 207 super(fileObject); 208 } 209 210 @Override @DefinedBy(Api.COMPILER) 211 public OutputStream openOutputStream() throws IOException { 212 throw new IllegalStateException(NOT_FOR_WRITING); 213 } 214 215 @Override @DefinedBy(Api.COMPILER) 216 public Writer openWriter() throws IOException { 217 throw new IllegalStateException(NOT_FOR_WRITING); 218 } 219 220 @Override @DefinedBy(Api.COMPILER) 221 public boolean delete() { 222 return false; 223 } 224 } 225 226 private class FilerInputJavaFileObject extends FilerInputFileObject implements JavaFileObject { 227 private final JavaFileObject javaFileObject; 228 FilerInputJavaFileObject(JavaFileObject javaFileObject) { 229 super(javaFileObject); 230 this.javaFileObject = javaFileObject; 231 } 232 233 @DefinedBy(Api.COMPILER) 234 public JavaFileObject.Kind getKind() { 235 return javaFileObject.getKind(); 236 } 237 238 @DefinedBy(Api.COMPILER) 239 public boolean isNameCompatible(String simpleName, 240 JavaFileObject.Kind kind) { 241 return javaFileObject.isNameCompatible(simpleName, kind); 242 } 243 244 @DefinedBy(Api.COMPILER) 245 public NestingKind getNestingKind() { 246 return javaFileObject.getNestingKind(); 247 } 248 249 @DefinedBy(Api.COMPILER) 250 public Modifier getAccessLevel() { 251 return javaFileObject.getAccessLevel(); 252 } 253 } 254 255 256 /** 257 * Wrap a {@code OutputStream} returned from the {@code 258 * JavaFileManager} to properly register source or class files 259 * when they are closed. 260 */ 261 private class FilerOutputStream extends FilterOutputStream { 262 ModuleSymbol mod; 263 String typeName; 264 FileObject fileObject; 265 boolean closed = false; 266 267 /** 268 * @param typeName name of class or {@code null} if just a 269 * binary file 270 */ 271 FilerOutputStream(ModuleSymbol mod, String typeName, FileObject fileObject) throws IOException { 272 super(fileObject.openOutputStream()); 273 this.mod = mod; 274 this.typeName = typeName; 275 this.fileObject = fileObject; 276 } 277 278 public synchronized void close() throws IOException { 279 if (!closed) { 280 closed = true; 281 /* 282 * If an IOException occurs when closing the underlying 283 * stream, still try to process the file. 284 */ 285 286 closeFileObject(mod, typeName, fileObject); 287 out.close(); 288 } 289 } 290 } 291 292 /** 293 * Wrap a {@code Writer} returned from the {@code JavaFileManager} 294 * to properly register source or class files when they are 295 * closed. 296 */ 297 private class FilerWriter extends FilterWriter { 298 ModuleSymbol mod; 299 String typeName; 300 FileObject fileObject; 301 boolean closed = false; 302 303 /** 304 * @param fileObject the fileObject to be written to 305 * @param typeName name of source file or {@code null} if just a 306 * text file 307 */ 308 FilerWriter(ModuleSymbol mod, String typeName, FileObject fileObject) throws IOException { 309 super(fileObject.openWriter()); 310 this.mod = mod; 311 this.typeName = typeName; 312 this.fileObject = fileObject; 313 } 314 315 public synchronized void close() throws IOException { 316 if (!closed) { 317 closed = true; 318 /* 319 * If an IOException occurs when closing the underlying 320 * Writer, still try to process the file. 321 */ 322 323 closeFileObject(mod, typeName, fileObject); 324 out.close(); 325 } 326 } 327 } 328 329 JavaFileManager fileManager; 330 JavacElements elementUtils; 331 Log log; 332 Modules modules; 333 Names names; 334 Symtab syms; 335 Context context; 336 boolean lastRound; 337 338 private final boolean lint; 339 340 /** 341 * Initial inputs passed to the tool. This set must be 342 * synchronized. 343 */ 344 private final Set<FileObject> initialInputs; 345 346 /** 347 * Logical names of all created files. This set must be 348 * synchronized. 349 */ 350 private final Set<FileObject> fileObjectHistory; 351 352 /** 353 * Names of types that have had files created but not closed. 354 */ 355 private final Set<String> openTypeNames; 356 357 /** 358 * Names of source files closed in this round. This set must be 359 * synchronized. Its iterators should preserve insertion order. 360 */ 361 private Set<String> generatedSourceNames; 362 363 /** 364 * Names and class files of the class files closed in this round. 365 * This set must be synchronized. Its iterators should preserve 366 * insertion order. 367 */ 368 private final Map<ModuleSymbol, Map<String, JavaFileObject>> generatedClasses; 369 370 /** 371 * JavaFileObjects for source files closed in this round. This 372 * set must be synchronized. Its iterators should preserve 373 * insertion order. 374 */ 375 private Set<JavaFileObject> generatedSourceFileObjects; 376 377 /** 378 * Names of all created source files. Its iterators should 379 * preserve insertion order. 380 */ 381 private final Set<Pair<ModuleSymbol, String>> aggregateGeneratedSourceNames; 382 383 /** 384 * Names of all created class files. Its iterators should 385 * preserve insertion order. 386 */ 387 private final Set<Pair<ModuleSymbol, String>> aggregateGeneratedClassNames; 388 389 private final Set<String> initialClassNames; 390 391 private final String defaultTargetModule; 392 393 JavacFiler(Context context) { 394 this.context = context; 395 fileManager = context.get(JavaFileManager.class); 396 elementUtils = JavacElements.instance(context); 397 398 log = Log.instance(context); 399 modules = Modules.instance(context); 400 names = Names.instance(context); 401 syms = Symtab.instance(context); 402 403 initialInputs = synchronizedSet(new LinkedHashSet<>()); 404 fileObjectHistory = synchronizedSet(new LinkedHashSet<>()); 405 generatedSourceNames = synchronizedSet(new LinkedHashSet<>()); 406 generatedSourceFileObjects = synchronizedSet(new LinkedHashSet<>()); 407 408 generatedClasses = synchronizedMap(new LinkedHashMap<>()); 409 410 openTypeNames = synchronizedSet(new LinkedHashSet<>()); 411 412 aggregateGeneratedSourceNames = new LinkedHashSet<>(); 413 aggregateGeneratedClassNames = new LinkedHashSet<>(); 414 initialClassNames = new LinkedHashSet<>(); 415 416 lint = (Lint.instance(context)).isEnabled(PROCESSING); 417 418 Options options = Options.instance(context); 419 420 defaultTargetModule = options.get(Option.DEFAULT_MODULE_FOR_CREATED_FILES); 421 } 422 423 @Override @DefinedBy(Api.ANNOTATION_PROCESSING) 424 public JavaFileObject createSourceFile(CharSequence nameAndModule, 425 Element... originatingElements) throws IOException { 426 Pair<ModuleSymbol, String> moduleAndClass = checkOrInferModule(nameAndModule); 427 return createSourceOrClassFile(moduleAndClass.fst, true, moduleAndClass.snd); 428 } 429 430 @Override @DefinedBy(Api.ANNOTATION_PROCESSING) 431 public JavaFileObject createClassFile(CharSequence nameAndModule, 432 Element... originatingElements) throws IOException { 433 Pair<ModuleSymbol, String> moduleAndClass = checkOrInferModule(nameAndModule); 434 return createSourceOrClassFile(moduleAndClass.fst, false, moduleAndClass.snd); 435 } 436 437 private Pair<ModuleSymbol, String> checkOrInferModule(CharSequence moduleAndPkg) throws FilerException { 438 String moduleAndPkgString = moduleAndPkg.toString(); 439 int slash = moduleAndPkgString.indexOf('/'); 440 String module; 441 String pkg; 442 443 if (slash == (-1)) { 444 //module name not specified: 445 int lastDot = moduleAndPkgString.lastIndexOf('.'); 446 String pack = lastDot != (-1) ? moduleAndPkgString.substring(0, lastDot) : ""; 447 ModuleSymbol msym = inferModule(pack); 448 449 if (msym != null) { 450 return Pair.of(msym, moduleAndPkgString); 451 } 452 453 if (defaultTargetModule == null) { 454 throw new FilerException("Cannot determine target module."); 455 } 456 457 module = defaultTargetModule; 458 pkg = moduleAndPkgString; 459 } else { 460 //module name specified: 461 module = moduleAndPkgString.substring(0, slash); 462 pkg = moduleAndPkgString.substring(slash + 1); 463 } 464 465 ModuleSymbol explicitModule = syms.getModule(names.fromString(module)); 466 467 if (explicitModule == null) { 468 throw new FilerException("Module: " + module + " does not exist."); 469 } 470 471 if (!modules.isRootModule(explicitModule)) { 472 throw new FilerException("Cannot write to the given module."); 473 } 474 475 return Pair.of(explicitModule, pkg); 476 } 477 478 private JavaFileObject createSourceOrClassFile(ModuleSymbol mod, boolean isSourceFile, String name) throws IOException { 479 Assert.checkNonNull(mod); 480 481 if (lint) { 482 int periodIndex = name.lastIndexOf("."); 483 if (periodIndex != -1) { 484 String base = name.substring(periodIndex); 485 String extn = (isSourceFile ? ".java" : ".class"); 486 if (base.equals(extn)) 487 log.warning(Warnings.ProcSuspiciousClassName(name, extn)); 488 } 489 } 490 checkNameAndExistence(mod, name, isSourceFile); 491 Location loc = (isSourceFile ? SOURCE_OUTPUT : CLASS_OUTPUT); 492 493 if (modules.multiModuleMode) { 494 loc = this.fileManager.getLocationForModule(loc, mod.name.toString()); 495 } 496 JavaFileObject.Kind kind = (isSourceFile ? 497 JavaFileObject.Kind.SOURCE : 498 JavaFileObject.Kind.CLASS); 499 500 JavaFileObject fileObject = 501 fileManager.getJavaFileForOutput(loc, name, kind, null); 502 checkFileReopening(fileObject, true); 503 504 if (lastRound) 505 log.warning(Warnings.ProcFileCreateLastRound(name)); 506 507 if (isSourceFile) 508 aggregateGeneratedSourceNames.add(Pair.of(mod, name)); 509 else 510 aggregateGeneratedClassNames.add(Pair.of(mod, name)); 511 openTypeNames.add(name); 512 513 return new FilerOutputJavaFileObject(mod, name, fileObject); 514 } 515 516 @Override @DefinedBy(Api.ANNOTATION_PROCESSING) 517 public FileObject createResource(JavaFileManager.Location location, 518 CharSequence moduleAndPkg, 519 CharSequence relativeName, 520 Element... originatingElements) throws IOException { 521 Tuple3<Location, ModuleSymbol, String> locationModuleAndPackage = checkOrInferModule(location, moduleAndPkg, true); 522 location = locationModuleAndPackage.a; 523 ModuleSymbol msym = locationModuleAndPackage.b; 524 String pkg = locationModuleAndPackage.c; 525 526 locationCheck(location); 527 528 String strPkg = pkg.toString(); 529 if (strPkg.length() > 0) 530 checkName(strPkg); 531 532 FileObject fileObject = 533 fileManager.getFileForOutput(location, strPkg, 534 relativeName.toString(), null); 535 checkFileReopening(fileObject, true); 536 537 if (fileObject instanceof JavaFileObject) 538 return new FilerOutputJavaFileObject(msym, null, (JavaFileObject)fileObject); 539 else 540 return new FilerOutputFileObject(msym, null, fileObject); 541 } 542 543 private void locationCheck(JavaFileManager.Location location) { 544 if (location instanceof StandardLocation) { 545 StandardLocation stdLoc = (StandardLocation) location; 546 if (!stdLoc.isOutputLocation()) 547 throw new IllegalArgumentException("Resource creation not supported in location " + 548 stdLoc); 549 } 550 } 551 552 @Override @DefinedBy(Api.ANNOTATION_PROCESSING) 553 public FileObject getResource(JavaFileManager.Location location, 554 CharSequence moduleAndPkg, 555 CharSequence relativeName) throws IOException { 556 Tuple3<Location, ModuleSymbol, String> locationModuleAndPackage = checkOrInferModule(location, moduleAndPkg, false); 557 location = locationModuleAndPackage.a; 558 String pkg = locationModuleAndPackage.c; 559 560 if (pkg.length() > 0) 561 checkName(pkg); 562 563 // TODO: Only support reading resources in selected output 564 // locations? Only allow reading of non-source, non-class 565 // files from the supported input locations? 566 567 // In the following, getFileForInput is the "obvious" method 568 // to use, but it does not have the "obvious" semantics for 569 // SOURCE_OUTPUT and CLASS_OUTPUT. Conversely, getFileForOutput 570 // does not have the correct semantics for any "path" location 571 // with more than one component. So, for now, we use a hybrid 572 // invocation. 573 FileObject fileObject; 574 if (location.isOutputLocation()) { 575 fileObject = fileManager.getFileForOutput(location, 576 pkg, 577 relativeName.toString(), 578 null); 579 } else { 580 fileObject = fileManager.getFileForInput(location, 581 pkg, 582 relativeName.toString()); 583 } 584 if (fileObject == null) { 585 String name = (pkg.length() == 0) 586 ? relativeName.toString() : (pkg + "/" + relativeName); 587 throw new FileNotFoundException(name); 588 } 589 590 // If the path was already opened for writing, throw an exception. 591 checkFileReopening(fileObject, false); 592 return new FilerInputFileObject(fileObject); 593 } 594 595 private Tuple3<JavaFileManager.Location, ModuleSymbol, String> checkOrInferModule(JavaFileManager.Location location, 596 CharSequence moduleAndPkg, 597 boolean write) throws IOException { 598 String moduleAndPkgString = moduleAndPkg.toString(); 599 int slash = moduleAndPkgString.indexOf('/'); 600 boolean multiModuleLocation = location.isModuleOrientedLocation() || 601 (modules.multiModuleMode && location.isOutputLocation()); 602 String module; 603 String pkg; 604 605 if (slash == (-1)) { 606 //module name not specified: 607 if (!multiModuleLocation) { 608 //package oriented location: 609 return new Tuple3<>(location, modules.getDefaultModule(), moduleAndPkgString); 610 } 611 612 if (location.isOutputLocation()) { 613 ModuleSymbol msym = inferModule(moduleAndPkgString); 614 615 if (msym != null) { 616 Location moduleLoc = 617 fileManager.getLocationForModule(location, msym.name.toString()); 618 return new Tuple3<>(moduleLoc, msym, moduleAndPkgString); 619 } 620 } 621 622 if (defaultTargetModule == null) { 623 throw new FilerException("No module specified and the location is either " + 624 "a module-oriented location, or a multi-module " + 625 "output location."); 626 } 627 628 module = defaultTargetModule; 629 pkg = moduleAndPkgString; 630 } else { 631 //module name specified: 632 module = moduleAndPkgString.substring(0, slash); 633 pkg = moduleAndPkgString.substring(slash + 1); 634 } 635 636 if (multiModuleLocation) { 637 ModuleSymbol explicitModule = syms.getModule(names.fromString(module)); 638 639 if (explicitModule == null) { 640 throw new FilerException("Module: " + module + " does not exist."); 641 } 642 643 if (write && !modules.isRootModule(explicitModule)) { 644 throw new FilerException("Cannot write to the given module."); 645 } 646 647 Location moduleLoc = fileManager.getLocationForModule(location, module); 648 649 return new Tuple3<>(moduleLoc, explicitModule, pkg); 650 } else { 651 throw new FilerException("Module specified but the location is neither " + 652 "a module-oriented location, nor a multi-module " + 653 "output location."); 654 } 655 } 656 657 static final class Tuple3<A, B, C> { 658 final A a; 659 final B b; 660 final C c; 661 662 public Tuple3(A a, B b, C c) { 663 this.a = a; 664 this.b = b; 665 this.c = c; 666 } 667 } 668 669 private ModuleSymbol inferModule(String pkg) { 670 if (modules.getDefaultModule() == syms.noModule) 671 return modules.getDefaultModule(); 672 673 Set<ModuleSymbol> rootModules = modules.getRootModules(); 674 675 if (rootModules.size() == 1) { 676 return rootModules.iterator().next(); 677 } 678 679 PackageSymbol pack = elementUtils.getPackageElement(pkg); 680 681 if (pack != null && pack.modle != syms.unnamedModule) { 682 return pack.modle; 683 } 684 685 return null; 686 } 687 688 private void checkName(String name) throws FilerException { 689 checkName(name, false); 690 } 691 692 private void checkName(String name, boolean allowUnnamedPackageInfo) throws FilerException { 693 if (!SourceVersion.isName(name) && !isPackageInfo(name, allowUnnamedPackageInfo)) { 694 if (lint) 695 log.warning(Warnings.ProcIllegalFileName(name)); 696 throw new FilerException("Illegal name " + name); 697 } 698 } 699 700 private boolean isPackageInfo(String name, boolean allowUnnamedPackageInfo) { 701 // Is the name of the form "package-info" or 702 // "foo.bar.package-info"? 703 final String PKG_INFO = "package-info"; 704 int periodIndex = name.lastIndexOf("."); 705 if (periodIndex == -1) { 706 return allowUnnamedPackageInfo ? name.equals(PKG_INFO) : false; 707 } else { 708 // "foo.bar.package-info." illegal 709 String prefix = name.substring(0, periodIndex); 710 String simple = name.substring(periodIndex+1); 711 return SourceVersion.isName(prefix) && simple.equals(PKG_INFO); 712 } 713 } 714 715 private void checkNameAndExistence(ModuleSymbol mod, String typename, boolean allowUnnamedPackageInfo) throws FilerException { 716 checkName(typename, allowUnnamedPackageInfo); 717 ClassSymbol existing = elementUtils.getTypeElement(typename); 718 boolean alreadySeen = aggregateGeneratedSourceNames.contains(Pair.of(mod, typename)) || 719 aggregateGeneratedClassNames.contains(Pair.of(mod, typename)) || 720 initialClassNames.contains(typename) || 721 containedInInitialInputs(typename); 722 if (alreadySeen) { 723 if (lint) 724 log.warning(Warnings.ProcTypeRecreate(typename)); 725 throw new FilerException("Attempt to recreate a file for type " + typename); 726 } 727 if (lint && existing != null) { 728 log.warning(Warnings.ProcTypeAlreadyExists(typename)); 729 } 730 if (!mod.isUnnamed() && !typename.contains(".")) { 731 throw new FilerException("Attempt to create a type in unnamed package of a named module: " + typename); 732 } 733 } 734 735 private boolean containedInInitialInputs(String typename) { 736 // Name could be a type name or the name of a package-info file 737 JavaFileObject sourceFile = null; 738 739 ClassSymbol existingClass = elementUtils.getTypeElement(typename); 740 if (existingClass != null) { 741 sourceFile = existingClass.sourcefile; 742 } else if (typename.endsWith(".package-info")) { 743 String targetName = typename.substring(0, typename.length() - ".package-info".length()); 744 PackageSymbol existingPackage = elementUtils.getPackageElement(targetName); 745 if (existingPackage != null) 746 sourceFile = existingPackage.sourcefile; 747 } 748 return (sourceFile == null) ? false : initialInputs.contains(sourceFile); 749 } 750 751 /** 752 * Check to see if the file has already been opened; if so, throw 753 * an exception, otherwise add it to the set of files. 754 */ 755 private void checkFileReopening(FileObject fileObject, boolean forWriting) throws FilerException { 756 if (isInFileObjectHistory(fileObject, forWriting)) { 757 if (lint) 758 log.warning(Warnings.ProcFileReopening(fileObject.getName())); 759 throw new FilerException("Attempt to reopen a file for path " + fileObject.getName()); 760 } 761 if (forWriting) 762 fileObjectHistory.add(fileObject); 763 } 764 765 private boolean isInFileObjectHistory(FileObject fileObject, boolean forWriting) { 766 if (forWriting) { 767 for(FileObject veteran : initialInputs) { 768 try { 769 if (fileManager.isSameFile(veteran, fileObject)) { 770 return true; 771 } 772 } catch (IllegalArgumentException e) { 773 //ignore... 774 } 775 } 776 for (String className : initialClassNames) { 777 try { 778 ClassSymbol existing = elementUtils.getTypeElement(className); 779 if ( existing != null 780 && ( (existing.sourcefile != null && fileManager.isSameFile(existing.sourcefile, fileObject)) 781 || (existing.classfile != null && fileManager.isSameFile(existing.classfile, fileObject)))) { 782 return true; 783 } 784 } catch (IllegalArgumentException e) { 785 //ignore... 786 } 787 } 788 } 789 790 for(FileObject veteran : fileObjectHistory) { 791 if (fileManager.isSameFile(veteran, fileObject)) { 792 return true; 793 } 794 } 795 796 return false; 797 } 798 799 public boolean newFiles() { 800 return (!generatedSourceNames.isEmpty()) 801 || (!generatedClasses.isEmpty()); 802 } 803 804 public Set<String> getGeneratedSourceNames() { 805 return generatedSourceNames; 806 } 807 808 public Set<JavaFileObject> getGeneratedSourceFileObjects() { 809 return generatedSourceFileObjects; 810 } 811 812 public Map<ModuleSymbol, Map<String, JavaFileObject>> getGeneratedClasses() { 813 return generatedClasses; 814 } 815 816 public void warnIfUnclosedFiles() { 817 if (!openTypeNames.isEmpty()) 818 log.warning(Warnings.ProcUnclosedTypeFiles(openTypeNames)); 819 } 820 821 /** 822 * Update internal state for a new round. 823 */ 824 public void newRound() { 825 clearRoundState(); 826 } 827 828 void setLastRound(boolean lastRound) { 829 this.lastRound = lastRound; 830 } 831 832 public void setInitialState(Collection<? extends JavaFileObject> initialInputs, 833 Collection<String> initialClassNames) { 834 this.initialInputs.addAll(initialInputs); 835 this.initialClassNames.addAll(initialClassNames); 836 } 837 838 public void close() { 839 clearRoundState(); 840 // Cross-round state 841 initialClassNames.clear(); 842 initialInputs.clear(); 843 fileObjectHistory.clear(); 844 openTypeNames.clear(); 845 aggregateGeneratedSourceNames.clear(); 846 aggregateGeneratedClassNames.clear(); 847 } 848 849 private void clearRoundState() { 850 generatedSourceNames.clear(); 851 generatedSourceFileObjects.clear(); 852 generatedClasses.clear(); 853 } 854 855 /** 856 * Debugging function to display internal state. 857 */ 858 public void displayState() { 859 PrintWriter xout = context.get(Log.logKey).getWriter(Log.WriterKind.STDERR); 860 xout.println("File Object History : " + fileObjectHistory); 861 xout.println("Open Type Names : " + openTypeNames); 862 xout.println("Gen. Src Names : " + generatedSourceNames); 863 xout.println("Gen. Cls Names : " + generatedClasses.keySet()); 864 xout.println("Agg. Gen. Src Names : " + aggregateGeneratedSourceNames); 865 xout.println("Agg. Gen. Cls Names : " + aggregateGeneratedClassNames); 866 } 867 868 public String toString() { 869 return "javac Filer"; 870 } 871 872 /** 873 * Upon close, register files opened by create{Source, Class}File 874 * for annotation processing. 875 */ 876 private void closeFileObject(ModuleSymbol mod, String typeName, FileObject fileObject) { 877 /* 878 * If typeName is non-null, the file object was opened as a 879 * source or class file by the user. If a file was opened as 880 * a resource, typeName will be null and the file is *not* 881 * subject to annotation processing. 882 */ 883 if ((typeName != null)) { 884 if (!(fileObject instanceof JavaFileObject)) 885 throw new AssertionError("JavaFileOject not found for " + fileObject); 886 JavaFileObject javaFileObject = (JavaFileObject)fileObject; 887 switch(javaFileObject.getKind()) { 888 case SOURCE: 889 generatedSourceNames.add(typeName); 890 generatedSourceFileObjects.add(javaFileObject); 891 openTypeNames.remove(typeName); 892 break; 893 894 case CLASS: 895 generatedClasses.computeIfAbsent(mod, m -> Collections.synchronizedMap(new LinkedHashMap<>())).put(typeName, javaFileObject); 896 openTypeNames.remove(typeName); 897 break; 898 899 default: 900 break; 901 } 902 } 903 } 904 905 }