Wed, 03 Sep 2008 10:46:25 -0700
6743107: clean up use of static caches in file manager
Reviewed-by: mcimadamore
1 /*
2 * Copyright 2005-2008 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
26 package com.sun.tools.javac.file;
28 import java.io.ByteArrayOutputStream;
29 import java.io.File;
30 import java.io.FileInputStream;
31 import java.io.FileNotFoundException;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.OutputStreamWriter;
35 import java.lang.ref.SoftReference;
36 import java.net.MalformedURLException;
37 import java.net.URI;
38 import java.net.URL;
39 import java.net.URLClassLoader;
40 import java.nio.ByteBuffer;
41 import java.nio.CharBuffer;
42 import java.nio.channels.FileChannel;
43 import java.nio.charset.Charset;
44 import java.nio.charset.CharsetDecoder;
45 import java.nio.charset.CoderResult;
46 import java.nio.charset.CodingErrorAction;
47 import java.nio.charset.IllegalCharsetNameException;
48 import java.nio.charset.UnsupportedCharsetException;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collection;
52 import java.util.Collections;
53 import java.util.EnumSet;
54 import java.util.HashMap;
55 import java.util.Iterator;
56 import java.util.Map;
57 import java.util.Set;
58 import java.util.zip.ZipFile;
60 import javax.lang.model.SourceVersion;
61 import javax.tools.FileObject;
62 import javax.tools.JavaFileManager;
63 import javax.tools.JavaFileObject;
64 import javax.tools.StandardJavaFileManager;
66 import com.sun.tools.javac.code.Source;
67 import com.sun.tools.javac.file.RelativePath.RelativeFile;
68 import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
69 import com.sun.tools.javac.main.JavacOption;
70 import com.sun.tools.javac.main.OptionName;
71 import com.sun.tools.javac.main.RecognizedOptions;
72 import com.sun.tools.javac.util.Context;
73 import com.sun.tools.javac.util.JCDiagnostic.SimpleDiagnosticPosition;
74 import com.sun.tools.javac.util.List;
75 import com.sun.tools.javac.util.ListBuffer;
76 import com.sun.tools.javac.util.Log;
77 import com.sun.tools.javac.util.Options;
79 import static javax.tools.StandardLocation.*;
80 import static com.sun.tools.javac.main.OptionName.*;
82 /**
83 * This class provides access to the source, class and other files
84 * used by the compiler and related tools.
85 */
86 public class JavacFileManager implements StandardJavaFileManager {
88 boolean useZipFileIndex;
90 public static char[] toArray(CharBuffer buffer) {
91 if (buffer.hasArray())
92 return ((CharBuffer)buffer.compact().flip()).array();
93 else
94 return buffer.toString().toCharArray();
95 }
97 /**
98 * The log to be used for error reporting.
99 */
100 protected Log log;
102 /** Encapsulates knowledge of paths
103 */
104 private Paths paths;
106 private Options options;
108 private FSInfo fsInfo;
110 private final File uninited = new File("U N I N I T E D");
112 private final Set<JavaFileObject.Kind> sourceOrClass =
113 EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS);
115 /** The standard output directory, primarily used for classes.
116 * Initialized by the "-d" option.
117 * If classOutDir = null, files are written into same directory as the sources
118 * they were generated from.
119 */
120 private File classOutDir = uninited;
122 /** The output directory, used when generating sources while processing annotations.
123 * Initialized by the "-s" option.
124 */
125 private File sourceOutDir = uninited;
127 protected boolean mmappedIO;
128 protected boolean ignoreSymbolFile;
130 /**
131 * User provided charset (through javax.tools).
132 */
133 protected Charset charset;
135 /**
136 * Register a Context.Factory to create a JavacFileManager.
137 */
138 public static void preRegister(final Context context) {
139 context.put(JavaFileManager.class, new Context.Factory<JavaFileManager>() {
140 public JavaFileManager make() {
141 return new JavacFileManager(context, true, null);
142 }
143 });
144 }
146 /**
147 * Create a JavacFileManager using a given context, optionally registering
148 * it as the JavaFileManager for that context.
149 */
150 public JavacFileManager(Context context, boolean register, Charset charset) {
151 if (register)
152 context.put(JavaFileManager.class, this);
153 byteBufferCache = new ByteBufferCache();
154 this.charset = charset;
155 setContext(context);
156 }
158 /**
159 * Set the context for JavacFileManager.
160 */
161 public void setContext(Context context) {
162 log = Log.instance(context);
163 if (paths == null) {
164 paths = Paths.instance(context);
165 } else {
166 // Reuse the Paths object as it stores the locations that
167 // have been set with setLocation, etc.
168 paths.setContext(context);
169 }
171 options = Options.instance(context);
172 fsInfo = FSInfo.instance(context);
174 useZipFileIndex = System.getProperty("useJavaUtilZip") == null;// TODO: options.get("useJavaUtilZip") == null;
176 mmappedIO = options.get("mmappedIO") != null;
177 ignoreSymbolFile = options.get("ignore.symbol.file") != null;
178 }
180 public JavaFileObject getFileForInput(String name) {
181 return getRegularFile(new File(name));
182 }
184 public JavaFileObject getRegularFile(File file) {
185 return new RegularFileObject(this, file);
186 }
188 public JavaFileObject getFileForOutput(String classname,
189 JavaFileObject.Kind kind,
190 JavaFileObject sibling)
191 throws IOException
192 {
193 return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling);
194 }
196 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
197 ListBuffer<File> files = new ListBuffer<File>();
198 for (String name : names)
199 files.append(new File(nullCheck(name)));
200 return getJavaFileObjectsFromFiles(files.toList());
201 }
203 public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
204 return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names)));
205 }
207 protected JavaFileObject.Kind getKind(String extension) {
208 if (extension.equals(JavaFileObject.Kind.CLASS.extension))
209 return JavaFileObject.Kind.CLASS;
210 else if (extension.equals(JavaFileObject.Kind.SOURCE.extension))
211 return JavaFileObject.Kind.SOURCE;
212 else if (extension.equals(JavaFileObject.Kind.HTML.extension))
213 return JavaFileObject.Kind.HTML;
214 else
215 return JavaFileObject.Kind.OTHER;
216 }
218 private static boolean isValidName(String name) {
219 // Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ),
220 // but the set of keywords depends on the source level, and we don't want
221 // impls of JavaFileManager to have to be dependent on the source level.
222 // Therefore we simply check that the argument is a sequence of identifiers
223 // separated by ".".
224 for (String s : name.split("\\.", -1)) {
225 if (!SourceVersion.isIdentifier(s))
226 return false;
227 }
228 return true;
229 }
231 private static void validateClassName(String className) {
232 if (!isValidName(className))
233 throw new IllegalArgumentException("Invalid class name: " + className);
234 }
236 private static void validatePackageName(String packageName) {
237 if (packageName.length() > 0 && !isValidName(packageName))
238 throw new IllegalArgumentException("Invalid packageName name: " + packageName);
239 }
241 public static void testName(String name,
242 boolean isValidPackageName,
243 boolean isValidClassName)
244 {
245 try {
246 validatePackageName(name);
247 if (!isValidPackageName)
248 throw new AssertionError("Invalid package name accepted: " + name);
249 printAscii("Valid package name: \"%s\"", name);
250 } catch (IllegalArgumentException e) {
251 if (isValidPackageName)
252 throw new AssertionError("Valid package name rejected: " + name);
253 printAscii("Invalid package name: \"%s\"", name);
254 }
255 try {
256 validateClassName(name);
257 if (!isValidClassName)
258 throw new AssertionError("Invalid class name accepted: " + name);
259 printAscii("Valid class name: \"%s\"", name);
260 } catch (IllegalArgumentException e) {
261 if (isValidClassName)
262 throw new AssertionError("Valid class name rejected: " + name);
263 printAscii("Invalid class name: \"%s\"", name);
264 }
265 }
267 private static void printAscii(String format, Object... args) {
268 String message;
269 try {
270 final String ascii = "US-ASCII";
271 message = new String(String.format(null, format, args).getBytes(ascii), ascii);
272 } catch (java.io.UnsupportedEncodingException ex) {
273 throw new AssertionError(ex);
274 }
275 System.out.println(message);
276 }
278 /**
279 * Insert all files in subdirectory `subdirectory' of `directory' which end
280 * in one of the extensions in `extensions' into packageSym.
281 */
282 private void listDirectory(File directory,
283 RelativeDirectory subdirectory,
284 Set<JavaFileObject.Kind> fileKinds,
285 boolean recurse,
286 ListBuffer<JavaFileObject> l) {
287 Archive archive = archives.get(directory);
289 boolean isFile = fsInfo.isFile(directory);
291 if (archive != null || isFile) {
292 if (archive == null) {
293 try {
294 archive = openArchive(directory);
295 } catch (IOException ex) {
296 log.error("error.reading.file",
297 directory, ex.getLocalizedMessage());
298 return;
299 }
300 }
302 List<String> files = archive.getFiles(subdirectory);
303 if (files != null) {
304 for (String file; !files.isEmpty(); files = files.tail) {
305 file = files.head;
306 if (isValidFile(file, fileKinds)) {
307 l.append(archive.getFileObject(subdirectory, file));
308 }
309 }
310 }
311 if (recurse) {
312 for (RelativeDirectory s: archive.getSubdirectories()) {
313 if (subdirectory.contains(s)) {
314 // Because the archive map is a flat list of directories,
315 // the enclosing loop will pick up all child subdirectories.
316 // Therefore, there is no need to recurse deeper.
317 listDirectory(directory, s, fileKinds, false, l);
318 }
319 }
320 }
321 } else {
322 File d = subdirectory.getFile(directory);
323 if (!caseMapCheck(d, subdirectory))
324 return;
326 File[] files = d.listFiles();
327 if (files == null)
328 return;
330 for (File f: files) {
331 String fname = f.getName();
332 if (f.isDirectory()) {
333 if (recurse && SourceVersion.isIdentifier(fname)) {
334 listDirectory(directory,
335 new RelativeDirectory(subdirectory, fname),
336 fileKinds,
337 recurse,
338 l);
339 }
340 } else {
341 if (isValidFile(fname, fileKinds)) {
342 JavaFileObject fe =
343 new RegularFileObject(this, fname, new File(d, fname));
344 l.append(fe);
345 }
346 }
347 }
348 }
349 }
351 private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
352 int lastDot = s.lastIndexOf(".");
353 String extn = (lastDot == -1 ? s : s.substring(lastDot));
354 JavaFileObject.Kind kind = getKind(extn);
355 return fileKinds.contains(kind);
356 }
358 private static final boolean fileSystemIsCaseSensitive =
359 File.separatorChar == '/';
361 /** Hack to make Windows case sensitive. Test whether given path
362 * ends in a string of characters with the same case as given name.
363 * Ignore file separators in both path and name.
364 */
365 private boolean caseMapCheck(File f, RelativePath name) {
366 if (fileSystemIsCaseSensitive) return true;
367 // Note that getCanonicalPath() returns the case-sensitive
368 // spelled file name.
369 String path;
370 try {
371 path = f.getCanonicalPath();
372 } catch (IOException ex) {
373 return false;
374 }
375 char[] pcs = path.toCharArray();
376 char[] ncs = name.path.toCharArray();
377 int i = pcs.length - 1;
378 int j = ncs.length - 1;
379 while (i >= 0 && j >= 0) {
380 while (i >= 0 && pcs[i] == File.separatorChar) i--;
381 while (j >= 0 && ncs[j] == '/') j--;
382 if (i >= 0 && j >= 0) {
383 if (pcs[i] != ncs[j]) return false;
384 i--;
385 j--;
386 }
387 }
388 return j < 0;
389 }
391 /**
392 * An archive provides a flat directory structure of a ZipFile by
393 * mapping directory names to lists of files (basenames).
394 */
395 public interface Archive {
396 void close() throws IOException;
398 boolean contains(RelativePath name);
400 JavaFileObject getFileObject(RelativeDirectory subdirectory, String file);
402 List<String> getFiles(RelativeDirectory subdirectory);
404 Set<RelativeDirectory> getSubdirectories();
405 }
407 public class MissingArchive implements Archive {
408 final File zipFileName;
409 public MissingArchive(File name) {
410 zipFileName = name;
411 }
412 public boolean contains(RelativePath name) {
413 return false;
414 }
416 public void close() {
417 }
419 public JavaFileObject getFileObject(RelativeDirectory subdirectory, String file) {
420 return null;
421 }
423 public List<String> getFiles(RelativeDirectory subdirectory) {
424 return List.nil();
425 }
427 public Set<RelativeDirectory> getSubdirectories() {
428 return Collections.emptySet();
429 }
431 public String toString() {
432 return "MissingArchive[" + zipFileName + "]";
433 }
434 }
436 /** A directory of zip files already opened.
437 */
438 Map<File, Archive> archives = new HashMap<File,Archive>();
440 private static final String[] symbolFileLocation = { "lib", "ct.sym" };
441 private static final RelativeDirectory symbolFilePrefix
442 = new RelativeDirectory("META-INF/sym/rt.jar/");
444 /** Open a new zip file directory.
445 */
446 protected Archive openArchive(File zipFileName) throws IOException {
447 Archive archive = archives.get(zipFileName);
448 if (archive == null) {
449 File origZipFileName = zipFileName;
450 if (!ignoreSymbolFile && paths.isBootClassPathRtJar(zipFileName)) {
451 File file = zipFileName.getParentFile().getParentFile(); // ${java.home}
452 if (new File(file.getName()).equals(new File("jre")))
453 file = file.getParentFile();
454 // file == ${jdk.home}
455 for (String name : symbolFileLocation)
456 file = new File(file, name);
457 // file == ${jdk.home}/lib/ct.sym
458 if (file.exists())
459 zipFileName = file;
460 }
462 try {
464 ZipFile zdir = null;
466 boolean usePreindexedCache = false;
467 String preindexCacheLocation = null;
469 if (!useZipFileIndex) {
470 zdir = new ZipFile(zipFileName);
471 }
472 else {
473 usePreindexedCache = options.get("usezipindex") != null;
474 preindexCacheLocation = options.get("java.io.tmpdir");
475 String optCacheLoc = options.get("cachezipindexdir");
477 if (optCacheLoc != null && optCacheLoc.length() != 0) {
478 if (optCacheLoc.startsWith("\"")) {
479 if (optCacheLoc.endsWith("\"")) {
480 optCacheLoc = optCacheLoc.substring(1, optCacheLoc.length() - 1);
481 }
482 else {
483 optCacheLoc = optCacheLoc.substring(1);
484 }
485 }
487 File cacheDir = new File(optCacheLoc);
488 if (cacheDir.exists() && cacheDir.canWrite()) {
489 preindexCacheLocation = optCacheLoc;
490 if (!preindexCacheLocation.endsWith("/") &&
491 !preindexCacheLocation.endsWith(File.separator)) {
492 preindexCacheLocation += File.separator;
493 }
494 }
495 }
496 }
498 if (origZipFileName == zipFileName) {
499 if (!useZipFileIndex) {
500 archive = new ZipArchive(this, zdir);
501 } else {
502 archive = new ZipFileIndexArchive(this,
503 ZipFileIndex.getZipFileIndex(zipFileName,
504 null,
505 usePreindexedCache,
506 preindexCacheLocation,
507 options.get("writezipindexfiles") != null));
508 }
509 }
510 else {
511 if (!useZipFileIndex) {
512 archive = new SymbolArchive(this, origZipFileName, zdir, symbolFilePrefix);
513 }
514 else {
515 archive = new ZipFileIndexArchive(this,
516 ZipFileIndex.getZipFileIndex(zipFileName,
517 symbolFilePrefix,
518 usePreindexedCache,
519 preindexCacheLocation,
520 options.get("writezipindexfiles") != null));
521 }
522 }
523 } catch (FileNotFoundException ex) {
524 archive = new MissingArchive(zipFileName);
525 } catch (IOException ex) {
526 if (zipFileName.exists())
527 log.error("error.reading.file", zipFileName, ex.getLocalizedMessage());
528 archive = new MissingArchive(zipFileName);
529 }
531 archives.put(origZipFileName, archive);
532 }
533 return archive;
534 }
536 /** Flush any output resources.
537 */
538 public void flush() {
539 contentCache.clear();
540 }
542 /**
543 * Close the JavaFileManager, releasing resources.
544 */
545 public void close() {
546 for (Iterator<Archive> i = archives.values().iterator(); i.hasNext(); ) {
547 Archive a = i.next();
548 i.remove();
549 try {
550 a.close();
551 } catch (IOException e) {
552 }
553 }
554 }
556 CharBuffer getCachedContent(JavaFileObject file) {
557 SoftReference<CharBuffer> r = contentCache.get(file);
558 return (r == null ? null : r.get());
559 }
561 void cache(JavaFileObject file, CharBuffer cb) {
562 contentCache.put(file, new SoftReference<CharBuffer>(cb));
563 }
565 private final Map<JavaFileObject, SoftReference<CharBuffer>> contentCache
566 = new HashMap<JavaFileObject, SoftReference<CharBuffer>>();
568 private String defaultEncodingName;
569 private String getDefaultEncodingName() {
570 if (defaultEncodingName == null) {
571 defaultEncodingName =
572 new OutputStreamWriter(new ByteArrayOutputStream()).getEncoding();
573 }
574 return defaultEncodingName;
575 }
577 protected String getEncodingName() {
578 String encName = options.get(OptionName.ENCODING);
579 if (encName == null)
580 return getDefaultEncodingName();
581 else
582 return encName;
583 }
585 protected Source getSource() {
586 String sourceName = options.get(OptionName.SOURCE);
587 Source source = null;
588 if (sourceName != null)
589 source = Source.lookup(sourceName);
590 return (source != null ? source : Source.DEFAULT);
591 }
593 /**
594 * Make a byte buffer from an input stream.
595 */
596 ByteBuffer makeByteBuffer(InputStream in)
597 throws IOException {
598 int limit = in.available();
599 if (mmappedIO && in instanceof FileInputStream) {
600 // Experimental memory mapped I/O
601 FileInputStream fin = (FileInputStream)in;
602 return fin.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, limit);
603 }
604 if (limit < 1024) limit = 1024;
605 ByteBuffer result = byteBufferCache.get(limit);
606 int position = 0;
607 while (in.available() != 0) {
608 if (position >= limit)
609 // expand buffer
610 result = ByteBuffer.
611 allocate(limit <<= 1).
612 put((ByteBuffer)result.flip());
613 int count = in.read(result.array(),
614 position,
615 limit - position);
616 if (count < 0) break;
617 result.position(position += count);
618 }
619 return (ByteBuffer)result.flip();
620 }
622 void recycleByteBuffer(ByteBuffer bb) {
623 byteBufferCache.put(bb);
624 }
626 /**
627 * A single-element cache of direct byte buffers.
628 */
629 private static class ByteBufferCache {
630 private ByteBuffer cached;
631 ByteBuffer get(int capacity) {
632 if (capacity < 20480) capacity = 20480;
633 ByteBuffer result =
634 (cached != null && cached.capacity() >= capacity)
635 ? (ByteBuffer)cached.clear()
636 : ByteBuffer.allocate(capacity + capacity>>1);
637 cached = null;
638 return result;
639 }
640 void put(ByteBuffer x) {
641 cached = x;
642 }
643 }
645 private final ByteBufferCache byteBufferCache;
647 CharsetDecoder getDecoder(String encodingName, boolean ignoreEncodingErrors) {
648 Charset charset = (this.charset == null)
649 ? Charset.forName(encodingName)
650 : this.charset;
651 CharsetDecoder decoder = charset.newDecoder();
653 CodingErrorAction action;
654 if (ignoreEncodingErrors)
655 action = CodingErrorAction.REPLACE;
656 else
657 action = CodingErrorAction.REPORT;
659 return decoder
660 .onMalformedInput(action)
661 .onUnmappableCharacter(action);
662 }
664 /**
665 * Decode a ByteBuffer into a CharBuffer.
666 */
667 CharBuffer decode(ByteBuffer inbuf, boolean ignoreEncodingErrors) {
668 String encodingName = getEncodingName();
669 CharsetDecoder decoder;
670 try {
671 decoder = getDecoder(encodingName, ignoreEncodingErrors);
672 } catch (IllegalCharsetNameException e) {
673 log.error("unsupported.encoding", encodingName);
674 return (CharBuffer)CharBuffer.allocate(1).flip();
675 } catch (UnsupportedCharsetException e) {
676 log.error("unsupported.encoding", encodingName);
677 return (CharBuffer)CharBuffer.allocate(1).flip();
678 }
680 // slightly overestimate the buffer size to avoid reallocation.
681 float factor =
682 decoder.averageCharsPerByte() * 0.8f +
683 decoder.maxCharsPerByte() * 0.2f;
684 CharBuffer dest = CharBuffer.
685 allocate(10 + (int)(inbuf.remaining()*factor));
687 while (true) {
688 CoderResult result = decoder.decode(inbuf, dest, true);
689 dest.flip();
691 if (result.isUnderflow()) { // done reading
692 // make sure there is at least one extra character
693 if (dest.limit() == dest.capacity()) {
694 dest = CharBuffer.allocate(dest.capacity()+1).put(dest);
695 dest.flip();
696 }
697 return dest;
698 } else if (result.isOverflow()) { // buffer too small; expand
699 int newCapacity =
700 10 + dest.capacity() +
701 (int)(inbuf.remaining()*decoder.maxCharsPerByte());
702 dest = CharBuffer.allocate(newCapacity).put(dest);
703 } else if (result.isMalformed() || result.isUnmappable()) {
704 // bad character in input
706 // report coding error (warn only pre 1.5)
707 if (!getSource().allowEncodingErrors()) {
708 log.error(new SimpleDiagnosticPosition(dest.limit()),
709 "illegal.char.for.encoding",
710 charset == null ? encodingName : charset.name());
711 } else {
712 log.warning(new SimpleDiagnosticPosition(dest.limit()),
713 "illegal.char.for.encoding",
714 charset == null ? encodingName : charset.name());
715 }
717 // skip past the coding error
718 inbuf.position(inbuf.position() + result.length());
720 // undo the flip() to prepare the output buffer
721 // for more translation
722 dest.position(dest.limit());
723 dest.limit(dest.capacity());
724 dest.put((char)0xfffd); // backward compatible
725 } else {
726 throw new AssertionError(result);
727 }
728 }
729 // unreached
730 }
732 public ClassLoader getClassLoader(Location location) {
733 nullCheck(location);
734 Iterable<? extends File> path = getLocation(location);
735 if (path == null)
736 return null;
737 ListBuffer<URL> lb = new ListBuffer<URL>();
738 for (File f: path) {
739 try {
740 lb.append(f.toURI().toURL());
741 } catch (MalformedURLException e) {
742 throw new AssertionError(e);
743 }
744 }
745 return new URLClassLoader(lb.toArray(new URL[lb.size()]),
746 getClass().getClassLoader());
747 }
749 public Iterable<JavaFileObject> list(Location location,
750 String packageName,
751 Set<JavaFileObject.Kind> kinds,
752 boolean recurse)
753 throws IOException
754 {
755 // validatePackageName(packageName);
756 nullCheck(packageName);
757 nullCheck(kinds);
759 Iterable<? extends File> path = getLocation(location);
760 if (path == null)
761 return List.nil();
762 RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
763 ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>();
765 for (File directory : path)
766 listDirectory(directory, subdirectory, kinds, recurse, results);
768 return results.toList();
769 }
771 public String inferBinaryName(Location location, JavaFileObject file) {
772 file.getClass(); // null check
773 location.getClass(); // null check
774 // Need to match the path semantics of list(location, ...)
775 Iterable<? extends File> path = getLocation(location);
776 if (path == null) {
777 return null;
778 }
780 if (file instanceof BaseFileObject) {
781 return ((BaseFileObject) file).inferBinaryName(path);
782 } else
783 throw new IllegalArgumentException(file.getClass().getName());
784 }
786 public boolean isSameFile(FileObject a, FileObject b) {
787 nullCheck(a);
788 nullCheck(b);
789 if (!(a instanceof BaseFileObject))
790 throw new IllegalArgumentException("Not supported: " + a);
791 if (!(b instanceof BaseFileObject))
792 throw new IllegalArgumentException("Not supported: " + b);
793 return a.equals(b);
794 }
796 public boolean handleOption(String current, Iterator<String> remaining) {
797 for (JavacOption o: javacFileManagerOptions) {
798 if (o.matches(current)) {
799 if (o.hasArg()) {
800 if (remaining.hasNext()) {
801 if (!o.process(options, current, remaining.next()))
802 return true;
803 }
804 } else {
805 if (!o.process(options, current))
806 return true;
807 }
808 // operand missing, or process returned false
809 throw new IllegalArgumentException(current);
810 }
811 }
813 return false;
814 }
815 // where
816 private static JavacOption[] javacFileManagerOptions =
817 RecognizedOptions.getJavacFileManagerOptions(
818 new RecognizedOptions.GrumpyHelper());
820 public int isSupportedOption(String option) {
821 for (JavacOption o : javacFileManagerOptions) {
822 if (o.matches(option))
823 return o.hasArg() ? 1 : 0;
824 }
825 return -1;
826 }
828 public boolean hasLocation(Location location) {
829 return getLocation(location) != null;
830 }
832 public JavaFileObject getJavaFileForInput(Location location,
833 String className,
834 JavaFileObject.Kind kind)
835 throws IOException
836 {
837 nullCheck(location);
838 // validateClassName(className);
839 nullCheck(className);
840 nullCheck(kind);
841 if (!sourceOrClass.contains(kind))
842 throw new IllegalArgumentException("Invalid kind " + kind);
843 return getFileForInput(location, RelativeFile.forClass(className, kind));
844 }
846 public FileObject getFileForInput(Location location,
847 String packageName,
848 String relativeName)
849 throws IOException
850 {
851 nullCheck(location);
852 // validatePackageName(packageName);
853 nullCheck(packageName);
854 if (!isRelativeUri(URI.create(relativeName))) // FIXME 6419701
855 throw new IllegalArgumentException("Invalid relative name: " + relativeName);
856 RelativeFile name = packageName.length() == 0
857 ? new RelativeFile(relativeName)
858 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
859 return getFileForInput(location, name);
860 }
862 private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
863 Iterable<? extends File> path = getLocation(location);
864 if (path == null)
865 return null;
867 for (File dir: path) {
868 if (dir.isDirectory()) {
869 File f = name.getFile(dir);
870 if (f.exists())
871 return new RegularFileObject(this, f);
872 } else {
873 Archive a = openArchive(dir);
874 if (a.contains(name)) {
875 return a.getFileObject(name.dirname(), name.basename());
876 }
878 }
879 }
881 return null;
882 }
884 public JavaFileObject getJavaFileForOutput(Location location,
885 String className,
886 JavaFileObject.Kind kind,
887 FileObject sibling)
888 throws IOException
889 {
890 nullCheck(location);
891 // validateClassName(className);
892 nullCheck(className);
893 nullCheck(kind);
894 if (!sourceOrClass.contains(kind))
895 throw new IllegalArgumentException("Invalid kind " + kind);
896 return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling);
897 }
899 public FileObject getFileForOutput(Location location,
900 String packageName,
901 String relativeName,
902 FileObject sibling)
903 throws IOException
904 {
905 nullCheck(location);
906 // validatePackageName(packageName);
907 nullCheck(packageName);
908 if (!isRelativeUri(URI.create(relativeName))) // FIXME 6419701
909 throw new IllegalArgumentException("relativeName is invalid");
910 RelativeFile name = packageName.length() == 0
911 ? new RelativeFile(relativeName)
912 : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
913 return getFileForOutput(location, name, sibling);
914 }
916 private JavaFileObject getFileForOutput(Location location,
917 RelativeFile fileName,
918 FileObject sibling)
919 throws IOException
920 {
921 File dir;
922 if (location == CLASS_OUTPUT) {
923 if (getClassOutDir() != null) {
924 dir = getClassOutDir();
925 } else {
926 File siblingDir = null;
927 if (sibling != null && sibling instanceof RegularFileObject) {
928 siblingDir = ((RegularFileObject)sibling).f.getParentFile();
929 }
930 return new RegularFileObject(this, new File(siblingDir, fileName.basename()));
931 }
932 } else if (location == SOURCE_OUTPUT) {
933 dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
934 } else {
935 Iterable<? extends File> path = paths.getPathForLocation(location);
936 dir = null;
937 for (File f: path) {
938 dir = f;
939 break;
940 }
941 }
943 File file = fileName.getFile(dir); // null-safe
944 return new RegularFileObject(this, file);
946 }
948 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
949 Iterable<? extends File> files)
950 {
951 ArrayList<RegularFileObject> result;
952 if (files instanceof Collection)
953 result = new ArrayList<RegularFileObject>(((Collection)files).size());
954 else
955 result = new ArrayList<RegularFileObject>();
956 for (File f: files)
957 result.add(new RegularFileObject(this, nullCheck(f)));
958 return result;
959 }
961 public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
962 return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
963 }
965 public void setLocation(Location location,
966 Iterable<? extends File> path)
967 throws IOException
968 {
969 nullCheck(location);
970 paths.lazy();
972 final File dir = location.isOutputLocation() ? getOutputDirectory(path) : null;
974 if (location == CLASS_OUTPUT)
975 classOutDir = getOutputLocation(dir, D);
976 else if (location == SOURCE_OUTPUT)
977 sourceOutDir = getOutputLocation(dir, S);
978 else
979 paths.setPathForLocation(location, path);
980 }
981 // where
982 private File getOutputDirectory(Iterable<? extends File> path) throws IOException {
983 if (path == null)
984 return null;
985 Iterator<? extends File> pathIter = path.iterator();
986 if (!pathIter.hasNext())
987 throw new IllegalArgumentException("empty path for directory");
988 File dir = pathIter.next();
989 if (pathIter.hasNext())
990 throw new IllegalArgumentException("path too long for directory");
991 if (!dir.exists())
992 throw new FileNotFoundException(dir + ": does not exist");
993 else if (!dir.isDirectory())
994 throw new IOException(dir + ": not a directory");
995 return dir;
996 }
998 private File getOutputLocation(File dir, OptionName defaultOptionName) {
999 if (dir != null)
1000 return dir;
1001 String arg = options.get(defaultOptionName);
1002 if (arg == null)
1003 return null;
1004 return new File(arg);
1005 }
1007 public Iterable<? extends File> getLocation(Location location) {
1008 nullCheck(location);
1009 paths.lazy();
1010 if (location == CLASS_OUTPUT) {
1011 return (getClassOutDir() == null ? null : List.of(getClassOutDir()));
1012 } else if (location == SOURCE_OUTPUT) {
1013 return (getSourceOutDir() == null ? null : List.of(getSourceOutDir()));
1014 } else
1015 return paths.getPathForLocation(location);
1016 }
1018 private File getClassOutDir() {
1019 if (classOutDir == uninited)
1020 classOutDir = getOutputLocation(null, D);
1021 return classOutDir;
1022 }
1024 private File getSourceOutDir() {
1025 if (sourceOutDir == uninited)
1026 sourceOutDir = getOutputLocation(null, S);
1027 return sourceOutDir;
1028 }
1030 /**
1031 * Enforces the specification of a "relative" URI as used in
1032 * {@linkplain #getFileForInput(Location,String,URI)
1033 * getFileForInput}. This method must follow the rules defined in
1034 * that method, do not make any changes without consulting the
1035 * specification.
1036 */
1037 protected static boolean isRelativeUri(URI uri) {
1038 if (uri.isAbsolute())
1039 return false;
1040 String path = uri.normalize().getPath();
1041 if (path.length() == 0 /* isEmpty() is mustang API */)
1042 return false;
1043 char first = path.charAt(0);
1044 return first != '.' && first != '/';
1045 }
1047 /**
1048 * Converts a relative file name to a relative URI. This is
1049 * different from File.toURI as this method does not canonicalize
1050 * the file before creating the URI. Furthermore, no schema is
1051 * used.
1052 * @param file a relative file name
1053 * @return a relative URI
1054 * @throws IllegalArgumentException if the file name is not
1055 * relative according to the definition given in {@link
1056 * javax.tools.JavaFileManager#getFileForInput}
1057 */
1058 public static String getRelativeName(File file) {
1059 if (!file.isAbsolute()) {
1060 String result = file.getPath().replace(File.separatorChar, '/');
1061 if (JavacFileManager.isRelativeUri(URI.create(result))) // FIXME 6419701
1062 return result;
1063 }
1064 throw new IllegalArgumentException("Invalid relative path: " + file);
1065 }
1067 @SuppressWarnings("deprecation") // bug 6410637
1068 public static String getJavacFileName(FileObject file) {
1069 if (file instanceof BaseFileObject)
1070 return ((BaseFileObject)file).getPath();
1071 URI uri = file.toUri();
1072 String scheme = uri.getScheme();
1073 if (scheme == null || scheme.equals("file") || scheme.equals("jar"))
1074 return uri.getPath();
1075 else
1076 return uri.toString();
1077 }
1079 @SuppressWarnings("deprecation") // bug 6410637
1080 public static String getJavacBaseFileName(FileObject file) {
1081 if (file instanceof BaseFileObject)
1082 return ((BaseFileObject)file).getName();
1083 URI uri = file.toUri();
1084 String scheme = uri.getScheme();
1085 if (scheme == null || scheme.equals("file") || scheme.equals("jar")) {
1086 String path = uri.getPath();
1087 if (path == null)
1088 return null;
1089 if (scheme != null && scheme.equals("jar"))
1090 path = path.substring(path.lastIndexOf('!') + 1);
1091 return path.substring(path.lastIndexOf('/') + 1);
1092 } else {
1093 return uri.toString();
1094 }
1095 }
1097 private static <T> T nullCheck(T o) {
1098 o.getClass(); // null check
1099 return o;
1100 }
1102 private static <T> Iterable<T> nullCheck(Iterable<T> it) {
1103 for (T t : it)
1104 t.getClass(); // null check
1105 return it;
1106 }
1107 }