JavacFileManager.java revision 2774:70d213c84585
1280297Sjkim/* 2280297Sjkim * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. 3280297Sjkim * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4238384Sjkim * 5238384Sjkim * This code is free software; you can redistribute it and/or modify it 6348343Sjkim * under the terms of the GNU General Public License version 2 only, as 7238384Sjkim * published by the Free Software Foundation. Oracle designates this 8238384Sjkim * particular file as subject to the "Classpath" exception as provided 9238384Sjkim * by Oracle in the LICENSE file that accompanied this code. 10238384Sjkim * 11238384Sjkim * This code is distributed in the hope that it will be useful, but WITHOUT 12238384Sjkim * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13280297Sjkim * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14238384Sjkim * version 2 for more details (a copy is included in the LICENSE file that 15238384Sjkim * accompanied this code). 16238384Sjkim * 17238384Sjkim * You should have received a copy of the GNU General Public License version 18238384Sjkim * 2 along with this work; if not, write to the Free Software Foundation, 19238384Sjkim * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20238384Sjkim * 21238384Sjkim * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22238384Sjkim * or visit www.oracle.com if you need additional information or have any 23238384Sjkim * questions. 24238384Sjkim */ 25238384Sjkim 26238384Sjkimpackage com.sun.tools.javac.file; 27238384Sjkim 28238384Sjkimimport java.io.File; 29238384Sjkimimport java.io.FileNotFoundException; 30238384Sjkimimport java.io.IOException; 31238384Sjkimimport java.net.MalformedURLException; 32238384Sjkimimport java.net.URI; 33238384Sjkimimport java.net.URISyntaxException; 34238384Sjkimimport java.net.URL; 35238384Sjkimimport java.nio.CharBuffer; 36238384Sjkimimport java.nio.charset.Charset; 37238384Sjkimimport java.nio.file.Files; 38238384Sjkimimport java.nio.file.InvalidPathException; 39238384Sjkimimport java.nio.file.LinkOption; 40238384Sjkimimport java.nio.file.NoSuchFileException; 41238384Sjkimimport java.nio.file.Path; 42238384Sjkimimport java.nio.file.Paths; 43238384Sjkimimport java.util.ArrayList; 44238384Sjkimimport java.util.Arrays; 45238384Sjkimimport java.util.Collection; 46238384Sjkimimport java.util.Collections; 47238384Sjkimimport java.util.Comparator; 48238384Sjkimimport java.util.EnumSet; 49238384Sjkimimport java.util.HashMap; 50238384Sjkimimport java.util.Iterator; 51238384Sjkimimport java.util.Map; 52238384Sjkimimport java.util.Set; 53238384Sjkimimport java.util.stream.Collectors; 54238384Sjkimimport java.util.stream.Stream; 55238384Sjkimimport java.util.zip.ZipFile; 56238384Sjkim 57238384Sjkimimport javax.lang.model.SourceVersion; 58238384Sjkimimport javax.tools.FileObject; 59238384Sjkimimport javax.tools.JavaFileManager; 60238384Sjkimimport javax.tools.JavaFileObject; 61238384Sjkimimport javax.tools.StandardJavaFileManager; 62238384Sjkim 63238384Sjkimimport com.sun.tools.javac.file.RelativePath.RelativeDirectory; 64238384Sjkimimport com.sun.tools.javac.file.RelativePath.RelativeFile; 65238384Sjkimimport com.sun.tools.javac.nio.PathFileObject; 66290207Sjkimimport com.sun.tools.javac.util.BaseFileManager; 67290207Sjkimimport com.sun.tools.javac.util.Context; 68290207Sjkimimport com.sun.tools.javac.util.DefinedBy; 69290207Sjkimimport com.sun.tools.javac.util.DefinedBy.Api; 70238384Sjkimimport com.sun.tools.javac.util.List; 71238384Sjkimimport com.sun.tools.javac.util.ListBuffer; 72238384Sjkim 73238384Sjkimimport static javax.tools.StandardLocation.*; 74280297Sjkim 75280297Sjkimimport static com.sun.tools.javac.util.BaseFileManager.getKind; 76280297Sjkim 77280297Sjkim/** 78280297Sjkim * This class provides access to the source, class and other files 79290207Sjkim * used by the compiler and related tools. 80290207Sjkim * 81290207Sjkim * <p><b>This is NOT part of any supported API. 82290207Sjkim * If you write code that depends on this, you do so at your own risk. 83280297Sjkim * This code and its internal interfaces are subject to change or 84280297Sjkim * deletion without notice.</b> 85290207Sjkim */ 86290207Sjkimpublic class JavacFileManager extends BaseFileManager implements StandardJavaFileManager { 87290207Sjkim 88290207Sjkim @SuppressWarnings("cast") 89290207Sjkim public static char[] toArray(CharBuffer buffer) { 90290207Sjkim if (buffer.hasArray()) 91290207Sjkim return ((CharBuffer)buffer.compact().flip()).array(); 92290207Sjkim else 93290207Sjkim return buffer.toString().toCharArray(); 94290207Sjkim } 95290207Sjkim 96280297Sjkim private FSInfo fsInfo; 97238384Sjkim 98238384Sjkim private boolean contextUseOptimizedZip; 99280297Sjkim private ZipFileIndexCache zipFileIndexCache; 100280297Sjkim 101280297Sjkim private final Set<JavaFileObject.Kind> sourceOrClass = 102280297Sjkim EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS); 103280297Sjkim 104348343Sjkim protected boolean symbolFileEnabled; 105290207Sjkim 106280297Sjkim protected enum SortFiles implements Comparator<Path> { 107280297Sjkim FORWARD { 108290207Sjkim @Override 109290207Sjkim public int compare(Path f1, Path f2) { 110238384Sjkim return f1.getFileName().compareTo(f2.getFileName()); 111290207Sjkim } 112290207Sjkim }, 113290207Sjkim REVERSE { 114290207Sjkim @Override 115290207Sjkim public int compare(Path f1, Path f2) { 116290207Sjkim return -f1.getFileName().compareTo(f2.getFileName()); 117290207Sjkim } 118280297Sjkim } 119280297Sjkim } 120280297Sjkim 121238384Sjkim protected SortFiles sortFiles; 122280297Sjkim 123280297Sjkim /** 124280297Sjkim * Register a Context.Factory to create a JavacFileManager. 125238384Sjkim */ 126280297Sjkim public static void preRegister(Context context) { 127280297Sjkim context.put(JavaFileManager.class, new Context.Factory<JavaFileManager>() { 128280297Sjkim @Override 129280297Sjkim public JavaFileManager make(Context c) { 130280297Sjkim return new JavacFileManager(c, true, null); 131280297Sjkim } 132280297Sjkim }); 133290207Sjkim } 134280297Sjkim 135280297Sjkim /** 136290207Sjkim * Create a JavacFileManager using a given context, optionally registering 137290207Sjkim * it as the JavaFileManager for that context. 138290207Sjkim */ 139290207Sjkim public JavacFileManager(Context context, boolean register, Charset charset) { 140290207Sjkim super(charset); 141290207Sjkim if (register) 142290207Sjkim context.put(JavaFileManager.class, this); 143290207Sjkim setContext(context); 144290207Sjkim } 145290207Sjkim 146290207Sjkim /** 147290207Sjkim * Set the context for JavacFileManager. 148290207Sjkim */ 149280297Sjkim @Override 150280297Sjkim public void setContext(Context context) { 151238384Sjkim super.setContext(context); 152238384Sjkim 153280297Sjkim fsInfo = FSInfo.instance(context); 154280297Sjkim 155290207Sjkim contextUseOptimizedZip = options.getBoolean("useOptimizedZip", true); 156290207Sjkim if (contextUseOptimizedZip) 157290207Sjkim zipFileIndexCache = ZipFileIndexCache.getSharedInstance(); 158290207Sjkim 159290207Sjkim symbolFileEnabled = !options.isSet("ignore.symbol.file"); 160280297Sjkim 161290207Sjkim String sf = options.get("sortFiles"); 162280297Sjkim if (sf != null) { 163238384Sjkim sortFiles = (sf.equals("reverse") ? SortFiles.REVERSE : SortFiles.FORWARD); 164238384Sjkim } 165280297Sjkim } 166280297Sjkim 167280297Sjkim /** 168280297Sjkim * Set whether or not to use ct.sym as an alternate to rt.jar. 169280297Sjkim */ 170280297Sjkim public void setSymbolFileEnabled(boolean b) { 171280297Sjkim symbolFileEnabled = b; 172280297Sjkim } 173238384Sjkim 174290207Sjkim public boolean isSymbolFileEnabled() { 175290207Sjkim return symbolFileEnabled; 176290207Sjkim } 177290207Sjkim 178290207Sjkim // used by tests 179290207Sjkim public JavaFileObject getFileForInput(String name) { 180280297Sjkim return getRegularFile(Paths.get(name)); 181290207Sjkim } 182290207Sjkim 183280297Sjkim // used by tests 184280297Sjkim public JavaFileObject getRegularFile(Path file) { 185238384Sjkim return new RegularFileObject(this, file); 186290207Sjkim } 187290207Sjkim 188290207Sjkim public JavaFileObject getFileForOutput(String classname, 189290207Sjkim JavaFileObject.Kind kind, 190290207Sjkim JavaFileObject sibling) 191290207Sjkim throws IOException 192290207Sjkim { 193290207Sjkim return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling); 194290207Sjkim } 195290207Sjkim 196290207Sjkim @Override @DefinedBy(Api.COMPILER) 197290207Sjkim public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) { 198290207Sjkim ListBuffer<Path> paths = new ListBuffer<>(); 199290207Sjkim for (String name : names) 200290207Sjkim paths.append(Paths.get(nullCheck(name))); 201290207Sjkim return getJavaFileObjectsFromPaths(paths.toList()); 202290207Sjkim } 203280297Sjkim 204280297Sjkim @Override @DefinedBy(Api.COMPILER) 205280297Sjkim public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) { 206238384Sjkim return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names))); 207290207Sjkim } 208290207Sjkim 209290207Sjkim private static boolean isValidName(String name) { 210325337Sjkim // Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ), 211325337Sjkim // but the set of keywords depends on the source level, and we don't want 212325337Sjkim // impls of JavaFileManager to have to be dependent on the source level. 213290207Sjkim // Therefore we simply check that the argument is a sequence of identifiers 214325337Sjkim // separated by ".". 215290207Sjkim for (String s : name.split("\\.", -1)) { 216290207Sjkim if (!SourceVersion.isIdentifier(s)) 217290207Sjkim return false; 218290207Sjkim } 219290207Sjkim return true; 220290207Sjkim } 221290207Sjkim 222290207Sjkim private static void validateClassName(String className) { 223290207Sjkim if (!isValidName(className)) 224290207Sjkim throw new IllegalArgumentException("Invalid class name: " + className); 225290207Sjkim } 226290207Sjkim 227290207Sjkim private static void validatePackageName(String packageName) { 228290207Sjkim if (packageName.length() > 0 && !isValidName(packageName)) 229290207Sjkim throw new IllegalArgumentException("Invalid packageName name: " + packageName); 230290207Sjkim } 231290207Sjkim 232290207Sjkim public static void testName(String name, 233290207Sjkim boolean isValidPackageName, 234290207Sjkim boolean isValidClassName) 235290207Sjkim { 236290207Sjkim try { 237290207Sjkim validatePackageName(name); 238290207Sjkim if (!isValidPackageName) 239290207Sjkim throw new AssertionError("Invalid package name accepted: " + name); 240290207Sjkim printAscii("Valid package name: \"%s\"", name); 241290207Sjkim } catch (IllegalArgumentException e) { 242290207Sjkim if (isValidPackageName) 243290207Sjkim throw new AssertionError("Valid package name rejected: " + name); 244290207Sjkim printAscii("Invalid package name: \"%s\"", name); 245290207Sjkim } 246290207Sjkim try { 247290207Sjkim validateClassName(name); 248290207Sjkim if (!isValidClassName) 249290207Sjkim throw new AssertionError("Invalid class name accepted: " + name); 250290207Sjkim printAscii("Valid class name: \"%s\"", name); 251290207Sjkim } catch (IllegalArgumentException e) { 252290207Sjkim if (isValidClassName) 253290207Sjkim throw new AssertionError("Valid class name rejected: " + name); 254290207Sjkim printAscii("Invalid class name: \"%s\"", name); 255290207Sjkim } 256290207Sjkim } 257290207Sjkim 258290207Sjkim private static void printAscii(String format, Object... args) { 259290207Sjkim String message; 260290207Sjkim try { 261280297Sjkim final String ascii = "US-ASCII"; 262280297Sjkim message = new String(String.format(null, format, args).getBytes(ascii), ascii); 263238384Sjkim } catch (java.io.UnsupportedEncodingException ex) { 264280297Sjkim throw new AssertionError(ex); 265280297Sjkim } 266238384Sjkim System.out.println(message); 267238384Sjkim } 268280297Sjkim 269280297Sjkim /** 270280297Sjkim * Insert all files in a subdirectory of the platform image 271280297Sjkim * which match fileKinds into resultList. 272280297Sjkim */ 273280297Sjkim private void listJRTImage(RelativeDirectory subdirectory, 274280297Sjkim Set<JavaFileObject.Kind> fileKinds, 275290207Sjkim boolean recurse, 276290207Sjkim ListBuffer<JavaFileObject> resultList) throws IOException { 277290207Sjkim JRTIndex.Entry e = getJRTIndex().getEntry(subdirectory); 278290207Sjkim if (symbolFileEnabled && e.ctSym.hidden) 279290207Sjkim return; 280290207Sjkim for (Path file: e.files.values()) { 281290207Sjkim if (fileKinds.contains(getKind(file))) { 282290207Sjkim JavaFileObject fe 283290207Sjkim = PathFileObject.createJRTPathFileObject(JavacFileManager.this, file); 284280297Sjkim resultList.append(fe); 285280297Sjkim } 286280297Sjkim } 287280297Sjkim 288280297Sjkim if (recurse) { 289290207Sjkim for (RelativeDirectory rd: e.subdirs) { 290290207Sjkim listJRTImage(rd, fileKinds, recurse, resultList); 291290207Sjkim } 292290207Sjkim } 293290207Sjkim } 294290207Sjkim 295290207Sjkim private synchronized JRTIndex getJRTIndex() { 296290207Sjkim if (jrtIndex == null) 297290207Sjkim jrtIndex = JRTIndex.getSharedInstance(); 298290207Sjkim return jrtIndex; 299280297Sjkim } 300280297Sjkim 301238384Sjkim private JRTIndex jrtIndex; 302290207Sjkim 303290207Sjkim 304290207Sjkim /** 305290207Sjkim * Insert all files in subdirectory subdirectory of directory directory 306290207Sjkim * which match fileKinds into resultList 307290207Sjkim */ 308290207Sjkim private void listDirectory(Path directory, 309290207Sjkim RelativeDirectory subdirectory, 310290207Sjkim Set<JavaFileObject.Kind> fileKinds, 311290207Sjkim boolean recurse, 312290207Sjkim ListBuffer<JavaFileObject> resultList) { 313290207Sjkim Path d; 314290207Sjkim try { 315290207Sjkim d = subdirectory.getFile(directory); 316290207Sjkim } catch (InvalidPathException ignore) { 317290207Sjkim return; 318290207Sjkim } 319290207Sjkim 320290207Sjkim if (!Files.exists(d)) { 321290207Sjkim return; 322290207Sjkim } 323290207Sjkim 324290207Sjkim if (!caseMapCheck(d, subdirectory)) { 325290207Sjkim return; 326290207Sjkim } 327290207Sjkim 328290207Sjkim java.util.List<Path> files; 329290207Sjkim try (Stream<Path> s = Files.list(d)) { 330290207Sjkim files = (sortFiles == null ? s : s.sorted(sortFiles)).collect(Collectors.toList()); 331290207Sjkim } catch (IOException ignore) { 332290207Sjkim return; 333290207Sjkim } 334290207Sjkim 335290207Sjkim for (Path f: files) { 336290207Sjkim String fname = f.getFileName().toString(); 337290207Sjkim if (Files.isDirectory(f)) { 338290207Sjkim if (recurse && SourceVersion.isIdentifier(fname)) { 339290207Sjkim listDirectory(directory, 340290207Sjkim new RelativeDirectory(subdirectory, fname), 341290207Sjkim fileKinds, 342290207Sjkim recurse, 343290207Sjkim resultList); 344290207Sjkim } 345290207Sjkim } else { 346290207Sjkim if (isValidFile(fname, fileKinds)) { 347290207Sjkim JavaFileObject fe = 348290207Sjkim new RegularFileObject(this, fname, d.resolve(fname)); 349290207Sjkim resultList.append(fe); 350290207Sjkim } 351290207Sjkim } 352290207Sjkim } 353290207Sjkim } 354290207Sjkim 355290207Sjkim /** 356238384Sjkim * Insert all files in subdirectory subdirectory of archive archive 357280297Sjkim * which match fileKinds into resultList 358280297Sjkim */ 359280297Sjkim private void listArchive(Archive archive, 360280297Sjkim RelativeDirectory subdirectory, 361280297Sjkim Set<JavaFileObject.Kind> fileKinds, 362290207Sjkim boolean recurse, 363290207Sjkim ListBuffer<JavaFileObject> resultList) { 364290207Sjkim // Get the files directly in the subdir 365290207Sjkim List<String> files = archive.getFiles(subdirectory); 366290207Sjkim if (files != null) { 367290207Sjkim for (; !files.isEmpty(); files = files.tail) { 368290207Sjkim String file = files.head; 369290207Sjkim if (isValidFile(file, fileKinds)) { 370290207Sjkim resultList.append(archive.getFileObject(subdirectory, file)); 371290207Sjkim } 372290207Sjkim } 373290207Sjkim } 374290207Sjkim if (recurse) { 375290207Sjkim for (RelativeDirectory s: archive.getSubdirectories()) { 376290207Sjkim if (subdirectory.contains(s)) { 377290207Sjkim // Because the archive map is a flat list of directories, 378290207Sjkim // the enclosing loop will pick up all child subdirectories. 379290207Sjkim // Therefore, there is no need to recurse deeper. 380290207Sjkim listArchive(archive, s, fileKinds, false, resultList); 381290207Sjkim } 382290207Sjkim } 383280297Sjkim } 384280297Sjkim } 385280297Sjkim 386280297Sjkim /** 387280297Sjkim * container is a directory, a zip file, or a non-existant path. 388290207Sjkim * Insert all files in subdirectory subdirectory of container which 389290207Sjkim * match fileKinds into resultList 390290207Sjkim */ 391290207Sjkim private void listContainer(Path container, 392290207Sjkim RelativeDirectory subdirectory, 393290207Sjkim Set<JavaFileObject.Kind> fileKinds, 394290207Sjkim boolean recurse, 395290207Sjkim ListBuffer<JavaFileObject> resultList) { 396290207Sjkim Archive archive = archives.get(container); 397290207Sjkim if (archive == null) { 398290207Sjkim // Very temporary and obnoxious interim hack 399290207Sjkim if (container.endsWith("bootmodules.jimage")) { 400290207Sjkim System.err.println("Warning: reference to bootmodules.jimage replaced by jrt:"); 401290207Sjkim container = Locations.JRT_MARKER_FILE; 402280297Sjkim } else if (container.getFileName().toString().endsWith(".jimage")) { 403280297Sjkim System.err.println("Warning: reference to " + container + " ignored"); 404280297Sjkim return; 405280297Sjkim } 406280297Sjkim 407290207Sjkim // archives are not created for directories or jrt: images 408280297Sjkim if (container == Locations.JRT_MARKER_FILE) { 409280297Sjkim try { 410280297Sjkim listJRTImage(subdirectory, 411280297Sjkim fileKinds, 412280297Sjkim recurse, 413280297Sjkim resultList); 414238384Sjkim } catch (IOException ex) { 415238384Sjkim ex.printStackTrace(System.err); 416280297Sjkim log.error("error.reading.file", container, getMessage(ex)); 417280297Sjkim } 418280297Sjkim return; 419280297Sjkim } 420280297Sjkim 421280297Sjkim if (fsInfo.isDirectory(container)) { 422280297Sjkim listDirectory(container, 423280297Sjkim subdirectory, 424280297Sjkim fileKinds, 425290207Sjkim recurse, 426280297Sjkim resultList); 427280297Sjkim return; 428280297Sjkim } 429280297Sjkim 430280297Sjkim // Not a directory; either a file or non-existant, create the archive 431238384Sjkim try { 432280297Sjkim archive = openArchive(container); 433280297Sjkim } catch (IOException ex) { 434280297Sjkim log.error("error.reading.file", container, getMessage(ex)); 435280297Sjkim return; 436290207Sjkim } 437290207Sjkim } 438290207Sjkim listArchive(archive, 439280297Sjkim subdirectory, 440280297Sjkim fileKinds, 441280297Sjkim recurse, 442280297Sjkim resultList); 443290207Sjkim } 444290207Sjkim 445290207Sjkim private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) { 446290207Sjkim JavaFileObject.Kind kind = getKind(s); 447290207Sjkim return fileKinds.contains(kind); 448290207Sjkim } 449290207Sjkim 450290207Sjkim private static final boolean fileSystemIsCaseSensitive = 451290207Sjkim File.separatorChar == '/'; 452290207Sjkim 453290207Sjkim /** Hack to make Windows case sensitive. Test whether given path 454290207Sjkim * ends in a string of characters with the same case as given name. 455325337Sjkim * Ignore file separators in both path and name. 456325337Sjkim */ 457325337Sjkim private boolean caseMapCheck(Path f, RelativePath name) { 458290207Sjkim if (fileSystemIsCaseSensitive) return true; 459290207Sjkim // Note that toRealPath() returns the case-sensitive 460290207Sjkim // spelled file name. 461290207Sjkim String path; 462290207Sjkim char sep; 463290207Sjkim try { 464290207Sjkim path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString(); 465290207Sjkim sep = f.getFileSystem().getSeparator().charAt(0); 466290207Sjkim } catch (IOException ex) { 467290207Sjkim return false; 468290207Sjkim } 469290207Sjkim char[] pcs = path.toCharArray(); 470290207Sjkim char[] ncs = name.path.toCharArray(); 471290207Sjkim int i = pcs.length - 1; 472290207Sjkim int j = ncs.length - 1; 473290207Sjkim while (i >= 0 && j >= 0) { 474290207Sjkim while (i >= 0 && pcs[i] == sep) i--; 475290207Sjkim while (j >= 0 && ncs[j] == '/') j--; 476290207Sjkim if (i >= 0 && j >= 0) { 477290207Sjkim if (pcs[i] != ncs[j]) return false; 478290207Sjkim i--; 479290207Sjkim j--; 480290207Sjkim } 481290207Sjkim } 482290207Sjkim return j < 0; 483290207Sjkim } 484290207Sjkim 485290207Sjkim /** 486280297Sjkim * An archive provides a flat directory structure of a ZipFile by 487290207Sjkim * mapping directory names to lists of files (basenames). 488325337Sjkim */ 489337982Sjkim public interface Archive { 490280297Sjkim void close() throws IOException; 491238384Sjkim 492280297Sjkim boolean contains(RelativePath name); 493280297Sjkim 494290207Sjkim JavaFileObject getFileObject(RelativeDirectory subdirectory, String file); 495280297Sjkim 496280297Sjkim List<String> getFiles(RelativeDirectory subdirectory); 497280297Sjkim 498238384Sjkim Set<RelativeDirectory> getSubdirectories(); 499280297Sjkim } 500280297Sjkim 501238384Sjkim public class MissingArchive implements Archive { 502280297Sjkim final Path zipFileName; 503280297Sjkim public MissingArchive(Path name) { 504238384Sjkim zipFileName = name; 505280297Sjkim } 506280297Sjkim @Override 507238384Sjkim public boolean contains(RelativePath name) { 508280297Sjkim return false; 509280297Sjkim } 510238384Sjkim 511280297Sjkim @Override 512238384Sjkim public void close() { 513280297Sjkim } 514238384Sjkim 515280297Sjkim @Override 516238384Sjkim public JavaFileObject getFileObject(RelativeDirectory subdirectory, String file) { 517280297Sjkim return null; 518238384Sjkim } 519280297Sjkim 520280297Sjkim @Override 521238384Sjkim public List<String> getFiles(RelativeDirectory subdirectory) { 522280297Sjkim return List.nil(); 523280297Sjkim } 524280297Sjkim 525290207Sjkim @Override 526290207Sjkim public Set<RelativeDirectory> getSubdirectories() { 527290207Sjkim return Collections.emptySet(); 528290207Sjkim } 529290207Sjkim 530290207Sjkim @Override 531290207Sjkim public String toString() { 532290207Sjkim return "MissingArchive[" + zipFileName + "]"; 533290207Sjkim } 534290207Sjkim } 535290207Sjkim 536290207Sjkim /** A directory of zip files already opened. 537290207Sjkim */ 538290207Sjkim Map<Path, Archive> archives = new HashMap<>(); 539290207Sjkim 540290207Sjkim /* 541290207Sjkim * This method looks for a ZipFormatException and takes appropriate 542290207Sjkim * evasive action. If there is a failure in the fast mode then we 543290207Sjkim * fail over to the platform zip, and allow it to deal with a potentially 544290207Sjkim * non compliant zip file. 545290207Sjkim */ 546290207Sjkim protected Archive openArchive(Path zipFilename) throws IOException { 547290207Sjkim try { 548290207Sjkim return openArchive(zipFilename, contextUseOptimizedZip); 549290207Sjkim } catch (IOException ioe) { 550290207Sjkim if (ioe instanceof ZipFileIndex.ZipFormatException) { 551290207Sjkim return openArchive(zipFilename, false); 552290207Sjkim } else { 553290207Sjkim throw ioe; 554290207Sjkim } 555290207Sjkim } 556290207Sjkim } 557290207Sjkim 558290207Sjkim /** Open a new zip file directory, and cache it. 559 */ 560 private Archive openArchive(Path zipFileName, boolean useOptimizedZip) throws IOException { 561 Archive archive; 562 try { 563 564 ZipFile zdir = null; 565 566 boolean usePreindexedCache = false; 567 String preindexCacheLocation = null; 568 569 if (!useOptimizedZip) { 570 zdir = new ZipFile(zipFileName.toFile()); 571 } else { 572 usePreindexedCache = options.isSet("usezipindex"); 573 preindexCacheLocation = options.get("java.io.tmpdir"); 574 String optCacheLoc = options.get("cachezipindexdir"); 575 576 if (optCacheLoc != null && optCacheLoc.length() != 0) { 577 if (optCacheLoc.startsWith("\"")) { 578 if (optCacheLoc.endsWith("\"")) { 579 optCacheLoc = optCacheLoc.substring(1, optCacheLoc.length() - 1); 580 } 581 else { 582 optCacheLoc = optCacheLoc.substring(1); 583 } 584 } 585 586 File cacheDir = new File(optCacheLoc); 587 if (cacheDir.exists() && cacheDir.canWrite()) { 588 preindexCacheLocation = optCacheLoc; 589 if (!preindexCacheLocation.endsWith("/") && 590 !preindexCacheLocation.endsWith(File.separator)) { 591 preindexCacheLocation += File.separator; 592 } 593 } 594 } 595 } 596 597 if (!useOptimizedZip) { 598 archive = new ZipArchive(this, zdir); 599 } else { 600 archive = new ZipFileIndexArchive(this, 601 zipFileIndexCache.getZipFileIndex(zipFileName, 602 null, 603 usePreindexedCache, 604 preindexCacheLocation, 605 options.isSet("writezipindexfiles"))); 606 } 607 } catch (FileNotFoundException | NoSuchFileException ex) { 608 archive = new MissingArchive(zipFileName); 609 } catch (ZipFileIndex.ZipFormatException zfe) { 610 throw zfe; 611 } catch (IOException ex) { 612 if (Files.exists(zipFileName)) 613 log.error("error.reading.file", zipFileName, getMessage(ex)); 614 archive = new MissingArchive(zipFileName); 615 } 616 617 archives.put(zipFileName, archive); 618 return archive; 619 } 620 621 /** Flush any output resources. 622 */ 623 @Override @DefinedBy(Api.COMPILER) 624 public void flush() { 625 contentCache.clear(); 626 } 627 628 /** 629 * Close the JavaFileManager, releasing resources. 630 */ 631 @Override @DefinedBy(Api.COMPILER) 632 public void close() { 633 for (Iterator<Archive> i = archives.values().iterator(); i.hasNext(); ) { 634 Archive a = i.next(); 635 i.remove(); 636 try { 637 a.close(); 638 } catch (IOException ignore) { 639 } 640 } 641 } 642 643 @Override @DefinedBy(Api.COMPILER) 644 public ClassLoader getClassLoader(Location location) { 645 nullCheck(location); 646 Iterable<? extends File> path = getLocation(location); 647 if (path == null) 648 return null; 649 ListBuffer<URL> lb = new ListBuffer<>(); 650 for (File f: path) { 651 try { 652 lb.append(f.toURI().toURL()); 653 } catch (MalformedURLException e) { 654 throw new AssertionError(e); 655 } 656 } 657 658 return getClassLoader(lb.toArray(new URL[lb.size()])); 659 } 660 661 @Override @DefinedBy(Api.COMPILER) 662 public Iterable<JavaFileObject> list(Location location, 663 String packageName, 664 Set<JavaFileObject.Kind> kinds, 665 boolean recurse) 666 throws IOException 667 { 668 // validatePackageName(packageName); 669 nullCheck(packageName); 670 nullCheck(kinds); 671 672 Iterable<? extends Path> path = getLocationAsPaths(location); 673 if (path == null) 674 return List.nil(); 675 RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName); 676 ListBuffer<JavaFileObject> results = new ListBuffer<>(); 677 678 for (Path directory : path) 679 listContainer(directory, subdirectory, kinds, recurse, results); 680 return results.toList(); 681 } 682 683 @Override @DefinedBy(Api.COMPILER) 684 public String inferBinaryName(Location location, JavaFileObject file) { 685 file.getClass(); // null check 686 location.getClass(); // null check 687 // Need to match the path semantics of list(location, ...) 688 Iterable<? extends Path> path = getLocationAsPaths(location); 689 if (path == null) { 690 return null; 691 } 692 693 if (file instanceof BaseFileObject) { 694 return ((BaseFileObject) file).inferBinaryName(path); 695 } else if (file instanceof PathFileObject) { 696 return ((PathFileObject) file).inferBinaryName(null); 697 } else 698 throw new IllegalArgumentException(file.getClass().getName()); 699 } 700 701 @Override @DefinedBy(Api.COMPILER) 702 public boolean isSameFile(FileObject a, FileObject b) { 703 nullCheck(a); 704 nullCheck(b); 705 if (a instanceof PathFileObject && b instanceof PathFileObject) 706 return ((PathFileObject) a).isSameFile((PathFileObject) b); 707 // In time, we should phase out BaseFileObject in favor of PathFileObject 708 if (!(a instanceof BaseFileObject || a instanceof PathFileObject)) 709 throw new IllegalArgumentException("Not supported: " + a); 710 if (!(b instanceof BaseFileObject || b instanceof PathFileObject)) 711 throw new IllegalArgumentException("Not supported: " + b); 712 return a.equals(b); 713 } 714 715 @Override @DefinedBy(Api.COMPILER) 716 public boolean hasLocation(Location location) { 717 return getLocation(location) != null; 718 } 719 720 @Override @DefinedBy(Api.COMPILER) 721 public JavaFileObject getJavaFileForInput(Location location, 722 String className, 723 JavaFileObject.Kind kind) 724 throws IOException 725 { 726 nullCheck(location); 727 // validateClassName(className); 728 nullCheck(className); 729 nullCheck(kind); 730 if (!sourceOrClass.contains(kind)) 731 throw new IllegalArgumentException("Invalid kind: " + kind); 732 return getFileForInput(location, RelativeFile.forClass(className, kind)); 733 } 734 735 @Override @DefinedBy(Api.COMPILER) 736 public FileObject getFileForInput(Location location, 737 String packageName, 738 String relativeName) 739 throws IOException 740 { 741 nullCheck(location); 742 // validatePackageName(packageName); 743 nullCheck(packageName); 744 if (!isRelativeUri(relativeName)) 745 throw new IllegalArgumentException("Invalid relative name: " + relativeName); 746 RelativeFile name = packageName.length() == 0 747 ? new RelativeFile(relativeName) 748 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); 749 return getFileForInput(location, name); 750 } 751 752 private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException { 753 Iterable<? extends Path> path = getLocationAsPaths(location); 754 if (path == null) 755 return null; 756 757 for (Path file: path) { 758 Archive a = archives.get(file); 759 if (a == null) { 760 // archives are not created for directories or jrt: images 761 if (file == Locations.JRT_MARKER_FILE) { 762 JRTIndex.Entry e = getJRTIndex().getEntry(name.dirname()); 763 if (symbolFileEnabled && e.ctSym.hidden) 764 continue; 765 Path p = e.files.get(name.basename()); 766 if (p != null) 767 return PathFileObject.createJRTPathFileObject(this, p); 768 continue; 769 } else if (fsInfo.isDirectory(file)) { 770 try { 771 Path f = name.getFile(file); 772 if (Files.exists(f)) 773 return new RegularFileObject(this, f); 774 } catch (InvalidPathException ignore) { 775 } 776 continue; 777 } 778 // Not a directory, create the archive 779 a = openArchive(file); 780 } 781 // Process the archive 782 if (a.contains(name)) { 783 return a.getFileObject(name.dirname(), name.basename()); 784 } 785 } 786 return null; 787 } 788 789 @Override @DefinedBy(Api.COMPILER) 790 public JavaFileObject getJavaFileForOutput(Location location, 791 String className, 792 JavaFileObject.Kind kind, 793 FileObject sibling) 794 throws IOException 795 { 796 nullCheck(location); 797 // validateClassName(className); 798 nullCheck(className); 799 nullCheck(kind); 800 if (!sourceOrClass.contains(kind)) 801 throw new IllegalArgumentException("Invalid kind: " + kind); 802 return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling); 803 } 804 805 @Override @DefinedBy(Api.COMPILER) 806 public FileObject getFileForOutput(Location location, 807 String packageName, 808 String relativeName, 809 FileObject sibling) 810 throws IOException 811 { 812 nullCheck(location); 813 // validatePackageName(packageName); 814 nullCheck(packageName); 815 if (!isRelativeUri(relativeName)) 816 throw new IllegalArgumentException("Invalid relative name: " + relativeName); 817 RelativeFile name = packageName.length() == 0 818 ? new RelativeFile(relativeName) 819 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); 820 return getFileForOutput(location, name, sibling); 821 } 822 823 private JavaFileObject getFileForOutput(Location location, 824 RelativeFile fileName, 825 FileObject sibling) 826 throws IOException 827 { 828 Path dir; 829 if (location == CLASS_OUTPUT) { 830 if (getClassOutDir() != null) { 831 dir = getClassOutDir(); 832 } else { 833 Path siblingDir = null; 834 if (sibling != null && sibling instanceof RegularFileObject) { 835 siblingDir = ((RegularFileObject)sibling).file.getParent(); 836 } 837 if (siblingDir == null) 838 return new RegularFileObject(this, Paths.get(fileName.basename())); 839 else 840 return new RegularFileObject(this, siblingDir.resolve(fileName.basename())); 841 } 842 } else if (location == SOURCE_OUTPUT) { 843 dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir()); 844 } else { 845 Iterable<? extends Path> path = locations.getLocation(location); 846 dir = null; 847 for (Path f: path) { 848 dir = f; 849 break; 850 } 851 } 852 853 try { 854 Path file = fileName.getFile(dir); // null-safe 855 return new RegularFileObject(this, file); 856 } catch (InvalidPathException e) { 857 throw new IOException("bad filename " + fileName, e); 858 } 859 } 860 861 @Override @DefinedBy(Api.COMPILER) 862 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles( 863 Iterable<? extends File> files) 864 { 865 ArrayList<RegularFileObject> result; 866 if (files instanceof Collection<?>) 867 result = new ArrayList<>(((Collection<?>)files).size()); 868 else 869 result = new ArrayList<>(); 870 for (File f: files) 871 result.add(new RegularFileObject(this, nullCheck(f).toPath())); 872 return result; 873 } 874 875 @Override @DefinedBy(Api.COMPILER) 876 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths( 877 Iterable<? extends Path> paths) 878 { 879 ArrayList<RegularFileObject> result; 880 if (paths instanceof Collection<?>) 881 result = new ArrayList<>(((Collection<?>)paths).size()); 882 else 883 result = new ArrayList<>(); 884 for (Path p: paths) 885 result.add(new RegularFileObject(this, nullCheck(p))); 886 return result; 887 } 888 889 @Override @DefinedBy(Api.COMPILER) 890 public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) { 891 return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files))); 892 } 893 894 @Override @DefinedBy(Api.COMPILER) 895 public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) { 896 return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths))); 897 } 898 899 @Override @DefinedBy(Api.COMPILER) 900 public void setLocation(Location location, 901 Iterable<? extends File> searchpath) 902 throws IOException 903 { 904 nullCheck(location); 905 locations.setLocation(location, asPaths(searchpath)); 906 } 907 908 @Override @DefinedBy(Api.COMPILER) 909 public void setLocationFromPaths(Location location, 910 Iterable<? extends Path> searchpath) 911 throws IOException 912 { 913 nullCheck(location); 914 locations.setLocation(location, nullCheck(searchpath)); 915 } 916 917 @Override @DefinedBy(Api.COMPILER) 918 public Iterable<? extends File> getLocation(Location location) { 919 nullCheck(location); 920 return asFiles(locations.getLocation(location)); 921 } 922 923 @Override @DefinedBy(Api.COMPILER) 924 public Iterable<? extends Path> getLocationAsPaths(Location location) { 925 nullCheck(location); 926 return locations.getLocation(location); 927 } 928 929 private Path getClassOutDir() { 930 return locations.getOutputLocation(CLASS_OUTPUT); 931 } 932 933 private Path getSourceOutDir() { 934 return locations.getOutputLocation(SOURCE_OUTPUT); 935 } 936 937 @Override @DefinedBy(Api.COMPILER) 938 public Path asPath(FileObject file) { 939 if (file instanceof RegularFileObject) { 940 return ((RegularFileObject) file).file; 941 } else 942 throw new IllegalArgumentException(file.getName()); 943 } 944 945 /** 946 * Enforces the specification of a "relative" name as used in 947 * {@linkplain #getFileForInput(Location,String,String) 948 * getFileForInput}. This method must follow the rules defined in 949 * that method, do not make any changes without consulting the 950 * specification. 951 */ 952 protected static boolean isRelativeUri(URI uri) { 953 if (uri.isAbsolute()) 954 return false; 955 String path = uri.normalize().getPath(); 956 if (path.length() == 0 /* isEmpty() is mustang API */) 957 return false; 958 if (!path.equals(uri.getPath())) // implicitly checks for embedded . and .. 959 return false; 960 if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../")) 961 return false; 962 return true; 963 } 964 965 // Convenience method 966 protected static boolean isRelativeUri(String u) { 967 try { 968 return isRelativeUri(new URI(u)); 969 } catch (URISyntaxException e) { 970 return false; 971 } 972 } 973 974 /** 975 * Converts a relative file name to a relative URI. This is 976 * different from File.toURI as this method does not canonicalize 977 * the file before creating the URI. Furthermore, no schema is 978 * used. 979 * @param file a relative file name 980 * @return a relative URI 981 * @throws IllegalArgumentException if the file name is not 982 * relative according to the definition given in {@link 983 * javax.tools.JavaFileManager#getFileForInput} 984 */ 985 public static String getRelativeName(File file) { 986 if (!file.isAbsolute()) { 987 String result = file.getPath().replace(File.separatorChar, '/'); 988 if (isRelativeUri(result)) 989 return result; 990 } 991 throw new IllegalArgumentException("Invalid relative path: " + file); 992 } 993 994 /** 995 * Get a detail message from an IOException. 996 * Most, but not all, instances of IOException provide a non-null result 997 * for getLocalizedMessage(). But some instances return null: in these 998 * cases, fallover to getMessage(), and if even that is null, return the 999 * name of the exception itself. 1000 * @param e an IOException 1001 * @return a string to include in a compiler diagnostic 1002 */ 1003 public static String getMessage(IOException e) { 1004 String s = e.getLocalizedMessage(); 1005 if (s != null) 1006 return s; 1007 s = e.getMessage(); 1008 if (s != null) 1009 return s; 1010 return e.toString(); 1011 } 1012 1013 /* Converters between files and paths. 1014 * These are temporary until we can update the StandardJavaFileManager API. 1015 */ 1016 1017 private static Iterable<Path> asPaths(final Iterable<? extends File> files) { 1018 if (files == null) 1019 return null; 1020 1021 return () -> new Iterator<Path>() { 1022 Iterator<? extends File> iter = files.iterator(); 1023 1024 @Override 1025 public boolean hasNext() { 1026 return iter.hasNext(); 1027 } 1028 1029 @Override 1030 public Path next() { 1031 return iter.next().toPath(); 1032 } 1033 }; 1034 } 1035 1036 private static Iterable<File> asFiles(final Iterable<? extends Path> paths) { 1037 if (paths == null) 1038 return null; 1039 1040 return () -> new Iterator<File>() { 1041 Iterator<? extends Path> iter = paths.iterator(); 1042 1043 @Override 1044 public boolean hasNext() { 1045 return iter.hasNext(); 1046 } 1047 1048 @Override 1049 public File next() { 1050 try { 1051 return iter.next().toFile(); 1052 } catch (UnsupportedOperationException e) { 1053 throw new IllegalStateException(e); 1054 } 1055 } 1056 }; 1057 } 1058} 1059