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