Fri, 04 Jul 2008 15:06:27 -0700
Merge
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.concurrent.ConcurrentHashMap;
59 import java.util.zip.ZipFile;
61 import javax.lang.model.SourceVersion;
62 import javax.tools.FileObject;
63 import javax.tools.JavaFileManager;
64 import javax.tools.JavaFileObject;
65 import javax.tools.StandardJavaFileManager;
67 import com.sun.tools.javac.code.Source;
68 import com.sun.tools.javac.main.JavacOption;
69 import com.sun.tools.javac.main.OptionName;
70 import com.sun.tools.javac.main.RecognizedOptions;
71 import com.sun.tools.javac.util.Context;
72 import com.sun.tools.javac.util.JCDiagnostic.SimpleDiagnosticPosition;
73 import com.sun.tools.javac.util.List;
74 import com.sun.tools.javac.util.ListBuffer;
75 import com.sun.tools.javac.util.Log;
76 import com.sun.tools.javac.util.Options;
78 import static com.sun.tools.javac.main.OptionName.*;
79 import static javax.tools.StandardLocation.*;
81 /**
82 * This class provides access to the source, class and other files
83 * used by the compiler and related tools.
84 */
85 public class JavacFileManager implements StandardJavaFileManager {
87 private static final String[] symbolFileLocation = { "lib", "ct.sym" };
88 private static final String symbolFilePrefix = "META-INF/sym/rt.jar/";
90 boolean useZipFileIndex;
92 private static boolean CHECK_ZIP_TIMESTAMP = false;
93 private static Map<File, Boolean> isDirectory = new ConcurrentHashMap<File, Boolean>();
96 public static char[] toArray(CharBuffer buffer) {
97 if (buffer.hasArray())
98 return ((CharBuffer)buffer.compact().flip()).array();
99 else
100 return buffer.toString().toCharArray();
101 }
103 /**
104 * The log to be used for error reporting.
105 */
106 protected Log log;
108 /** Encapsulates knowledge of paths
109 */
110 private Paths paths;
112 private Options options;
114 private final File uninited = new File("U N I N I T E D");
116 private final Set<JavaFileObject.Kind> sourceOrClass =
117 EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS);
119 /** The standard output directory, primarily used for classes.
120 * Initialized by the "-d" option.
121 * If classOutDir = null, files are written into same directory as the sources
122 * they were generated from.
123 */
124 private File classOutDir = uninited;
126 /** The output directory, used when generating sources while processing annotations.
127 * Initialized by the "-s" option.
128 */
129 private File sourceOutDir = uninited;
131 protected boolean mmappedIO;
132 protected boolean ignoreSymbolFile;
134 /**
135 * User provided charset (through javax.tools).
136 */
137 protected Charset charset;
139 /**
140 * Register a Context.Factory to create a JavacFileManager.
141 */
142 public static void preRegister(final Context context) {
143 context.put(JavaFileManager.class, new Context.Factory<JavaFileManager>() {
144 public JavaFileManager make() {
145 return new JavacFileManager(context, true, null);
146 }
147 });
148 }
150 /**
151 * Create a JavacFileManager using a given context, optionally registering
152 * it as the JavaFileManager for that context.
153 */
154 public JavacFileManager(Context context, boolean register, Charset charset) {
155 if (register)
156 context.put(JavaFileManager.class, this);
157 byteBufferCache = new ByteBufferCache();
158 this.charset = charset;
159 setContext(context);
160 }
162 /**
163 * Set the context for JavacFileManager.
164 */
165 public void setContext(Context context) {
166 log = Log.instance(context);
167 if (paths == null) {
168 paths = Paths.instance(context);
169 } else {
170 // Reuse the Paths object as it stores the locations that
171 // have been set with setLocation, etc.
172 paths.setContext(context);
173 }
175 options = Options.instance(context);
177 useZipFileIndex = System.getProperty("useJavaUtilZip") == null;// TODO: options.get("useJavaUtilZip") == null;
178 CHECK_ZIP_TIMESTAMP = System.getProperty("checkZipIndexTimestamp") != null;// TODO: options.get("checkZipIndexTimestamp") != null;
180 mmappedIO = options.get("mmappedIO") != null;
181 ignoreSymbolFile = options.get("ignore.symbol.file") != null;
182 }
184 public JavaFileObject getFileForInput(String name) {
185 return getRegularFile(new File(name));
186 }
188 public JavaFileObject getRegularFile(File file) {
189 return new RegularFileObject(this, file);
190 }
192 public JavaFileObject getFileForOutput(String classname,
193 JavaFileObject.Kind kind,
194 JavaFileObject sibling)
195 throws IOException
196 {
197 return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling);
198 }
200 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
201 ListBuffer<File> files = new ListBuffer<File>();
202 for (String name : names)
203 files.append(new File(nullCheck(name)));
204 return getJavaFileObjectsFromFiles(files.toList());
205 }
207 public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
208 return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names)));
209 }
211 protected JavaFileObject.Kind getKind(String extension) {
212 if (extension.equals(JavaFileObject.Kind.CLASS.extension))
213 return JavaFileObject.Kind.CLASS;
214 else if (extension.equals(JavaFileObject.Kind.SOURCE.extension))
215 return JavaFileObject.Kind.SOURCE;
216 else if (extension.equals(JavaFileObject.Kind.HTML.extension))
217 return JavaFileObject.Kind.HTML;
218 else
219 return JavaFileObject.Kind.OTHER;
220 }
222 private static boolean isValidName(String name) {
223 // Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ),
224 // but the set of keywords depends on the source level, and we don't want
225 // impls of JavaFileManager to have to be dependent on the source level.
226 // Therefore we simply check that the argument is a sequence of identifiers
227 // separated by ".".
228 for (String s : name.split("\\.", -1)) {
229 if (!SourceVersion.isIdentifier(s))
230 return false;
231 }
232 return true;
233 }
235 private static void validateClassName(String className) {
236 if (!isValidName(className))
237 throw new IllegalArgumentException("Invalid class name: " + className);
238 }
240 private static void validatePackageName(String packageName) {
241 if (packageName.length() > 0 && !isValidName(packageName))
242 throw new IllegalArgumentException("Invalid packageName name: " + packageName);
243 }
245 public static void testName(String name,
246 boolean isValidPackageName,
247 boolean isValidClassName)
248 {
249 try {
250 validatePackageName(name);
251 if (!isValidPackageName)
252 throw new AssertionError("Invalid package name accepted: " + name);
253 printAscii("Valid package name: \"%s\"", name);
254 } catch (IllegalArgumentException e) {
255 if (isValidPackageName)
256 throw new AssertionError("Valid package name rejected: " + name);
257 printAscii("Invalid package name: \"%s\"", name);
258 }
259 try {
260 validateClassName(name);
261 if (!isValidClassName)
262 throw new AssertionError("Invalid class name accepted: " + name);
263 printAscii("Valid class name: \"%s\"", name);
264 } catch (IllegalArgumentException e) {
265 if (isValidClassName)
266 throw new AssertionError("Valid class name rejected: " + name);
267 printAscii("Invalid class name: \"%s\"", name);
268 }
269 }
270 private static void printAscii(String format, Object... args) {
271 String message;
272 try {
273 final String ascii = "US-ASCII";
274 message = new String(String.format(null, format, args).getBytes(ascii), ascii);
275 } catch (java.io.UnsupportedEncodingException ex) {
276 throw new AssertionError(ex);
277 }
278 System.out.println(message);
279 }
281 /** Return external representation of name,
282 * converting '.' to File.separatorChar.
283 */
284 private static String externalizeFileName(CharSequence name) {
285 return name.toString().replace('.', File.separatorChar);
286 }
288 private static String externalizeFileName(CharSequence n, JavaFileObject.Kind kind) {
289 return externalizeFileName(n) + kind.extension;
290 }
292 private static String baseName(String fileName) {
293 return fileName.substring(fileName.lastIndexOf(File.separatorChar) + 1);
294 }
296 /**
297 * Insert all files in subdirectory `subdirectory' of `directory' which end
298 * in one of the extensions in `extensions' into packageSym.
299 */
300 private void listDirectory(File directory,
301 String subdirectory,
302 Set<JavaFileObject.Kind> fileKinds,
303 boolean recurse,
304 ListBuffer<JavaFileObject> l) {
305 Archive archive = archives.get(directory);
307 boolean isFile = false;
308 if (CHECK_ZIP_TIMESTAMP) {
309 Boolean isf = isDirectory.get(directory);
310 if (isf == null) {
311 isFile = directory.isFile();
312 isDirectory.put(directory, isFile);
313 }
314 else {
315 isFile = directory.isFile();
316 }
317 }
318 else {
319 isFile = directory.isFile();
320 }
322 if (archive != null || isFile) {
323 if (archive == null) {
324 try {
325 archive = openArchive(directory);
326 } catch (IOException ex) {
327 log.error("error.reading.file",
328 directory, ex.getLocalizedMessage());
329 return;
330 }
331 }
332 if (subdirectory.length() != 0) {
333 if (!useZipFileIndex) {
334 subdirectory = subdirectory.replace('\\', '/');
335 if (!subdirectory.endsWith("/")) subdirectory = subdirectory + "/";
336 }
337 else {
338 if (File.separatorChar == '/') {
339 subdirectory = subdirectory.replace('\\', '/');
340 }
341 else {
342 subdirectory = subdirectory.replace('/', '\\');
343 }
345 if (!subdirectory.endsWith(File.separator)) subdirectory = subdirectory + File.separator;
346 }
347 }
349 List<String> files = archive.getFiles(subdirectory);
350 if (files != null) {
351 for (String file; !files.isEmpty(); files = files.tail) {
352 file = files.head;
353 if (isValidFile(file, fileKinds)) {
354 l.append(archive.getFileObject(subdirectory, file));
355 }
356 }
357 }
358 if (recurse) {
359 for (String s: archive.getSubdirectories()) {
360 if (s.startsWith(subdirectory) && !s.equals(subdirectory)) {
361 // Because the archive map is a flat list of directories,
362 // the enclosing loop will pick up all child subdirectories.
363 // Therefore, there is no need to recurse deeper.
364 listDirectory(directory, s, fileKinds, false, l);
365 }
366 }
367 }
368 } else {
369 File d = subdirectory.length() != 0
370 ? new File(directory, subdirectory)
371 : directory;
372 if (!caseMapCheck(d, subdirectory))
373 return;
375 File[] files = d.listFiles();
376 if (files == null)
377 return;
379 for (File f: files) {
380 String fname = f.getName();
381 if (f.isDirectory()) {
382 if (recurse && SourceVersion.isIdentifier(fname)) {
383 listDirectory(directory,
384 subdirectory + File.separator + fname,
385 fileKinds,
386 recurse,
387 l);
388 }
389 } else {
390 if (isValidFile(fname, fileKinds)) {
391 JavaFileObject fe =
392 new RegularFileObject(this, fname, new File(d, fname));
393 l.append(fe);
394 }
395 }
396 }
397 }
398 }
400 private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
401 int lastDot = s.lastIndexOf(".");
402 String extn = (lastDot == -1 ? s : s.substring(lastDot));
403 JavaFileObject.Kind kind = getKind(extn);
404 return fileKinds.contains(kind);
405 }
407 private static final boolean fileSystemIsCaseSensitive =
408 File.separatorChar == '/';
410 /** Hack to make Windows case sensitive. Test whether given path
411 * ends in a string of characters with the same case as given name.
412 * Ignore file separators in both path and name.
413 */
414 private boolean caseMapCheck(File f, String name) {
415 if (fileSystemIsCaseSensitive) return true;
416 // Note that getCanonicalPath() returns the case-sensitive
417 // spelled file name.
418 String path;
419 try {
420 path = f.getCanonicalPath();
421 } catch (IOException ex) {
422 return false;
423 }
424 char[] pcs = path.toCharArray();
425 char[] ncs = name.toCharArray();
426 int i = pcs.length - 1;
427 int j = ncs.length - 1;
428 while (i >= 0 && j >= 0) {
429 while (i >= 0 && pcs[i] == File.separatorChar) i--;
430 while (j >= 0 && ncs[j] == File.separatorChar) j--;
431 if (i >= 0 && j >= 0) {
432 if (pcs[i] != ncs[j]) return false;
433 i--;
434 j--;
435 }
436 }
437 return j < 0;
438 }
440 /**
441 * An archive provides a flat directory structure of a ZipFile by
442 * mapping directory names to lists of files (basenames).
443 */
444 public interface Archive {
445 void close() throws IOException;
447 boolean contains(String name);
449 JavaFileObject getFileObject(String subdirectory, String file);
451 List<String> getFiles(String subdirectory);
453 Set<String> getSubdirectories();
454 }
456 public class MissingArchive implements Archive {
457 final File zipFileName;
458 public MissingArchive(File name) {
459 zipFileName = name;
460 }
461 public boolean contains(String name) {
462 return false;
463 }
465 public void close() {
466 }
468 public JavaFileObject getFileObject(String subdirectory, String file) {
469 return null;
470 }
472 public List<String> getFiles(String subdirectory) {
473 return List.nil();
474 }
476 public Set<String> getSubdirectories() {
477 return Collections.emptySet();
478 }
479 }
481 /** A directory of zip files already opened.
482 */
483 Map<File, Archive> archives = new HashMap<File,Archive>();
485 /** Open a new zip file directory.
486 */
487 protected Archive openArchive(File zipFileName) throws IOException {
488 Archive archive = archives.get(zipFileName);
489 if (archive == null) {
490 File origZipFileName = zipFileName;
491 if (!ignoreSymbolFile && paths.isBootClassPathRtJar(zipFileName)) {
492 File file = zipFileName.getParentFile().getParentFile(); // ${java.home}
493 if (new File(file.getName()).equals(new File("jre")))
494 file = file.getParentFile();
495 // file == ${jdk.home}
496 for (String name : symbolFileLocation)
497 file = new File(file, name);
498 // file == ${jdk.home}/lib/ct.sym
499 if (file.exists())
500 zipFileName = file;
501 }
503 try {
505 ZipFile zdir = null;
507 boolean usePreindexedCache = false;
508 String preindexCacheLocation = null;
510 if (!useZipFileIndex) {
511 zdir = new ZipFile(zipFileName);
512 }
513 else {
514 usePreindexedCache = options.get("usezipindex") != null;
515 preindexCacheLocation = options.get("java.io.tmpdir");
516 String optCacheLoc = options.get("cachezipindexdir");
518 if (optCacheLoc != null && optCacheLoc.length() != 0) {
519 if (optCacheLoc.startsWith("\"")) {
520 if (optCacheLoc.endsWith("\"")) {
521 optCacheLoc = optCacheLoc.substring(1, optCacheLoc.length() - 1);
522 }
523 else {
524 optCacheLoc = optCacheLoc.substring(1);
525 }
526 }
528 File cacheDir = new File(optCacheLoc);
529 if (cacheDir.exists() && cacheDir.canWrite()) {
530 preindexCacheLocation = optCacheLoc;
531 if (!preindexCacheLocation.endsWith("/") &&
532 !preindexCacheLocation.endsWith(File.separator)) {
533 preindexCacheLocation += File.separator;
534 }
535 }
536 }
537 }
539 if (origZipFileName == zipFileName) {
540 if (!useZipFileIndex) {
541 archive = new ZipArchive(this, zdir);
542 } else {
543 archive = new ZipFileIndexArchive(this, ZipFileIndex.getZipFileIndex(zipFileName, null,
544 usePreindexedCache, preindexCacheLocation, options.get("writezipindexfiles") != null));
545 }
546 }
547 else {
548 if (!useZipFileIndex) {
549 archive = new SymbolArchive(this, origZipFileName, zdir, symbolFilePrefix);
550 }
551 else {
552 archive = new ZipFileIndexArchive(this,
553 ZipFileIndex.getZipFileIndex(zipFileName,
554 symbolFilePrefix,
555 usePreindexedCache,
556 preindexCacheLocation,
557 options.get("writezipindexfiles") != null));
558 }
559 }
560 } catch (FileNotFoundException ex) {
561 archive = new MissingArchive(zipFileName);
562 } catch (IOException ex) {
563 if (zipFileName.exists())
564 log.error("error.reading.file", zipFileName, ex.getLocalizedMessage());
565 archive = new MissingArchive(zipFileName);
566 }
568 archives.put(origZipFileName, archive);
569 }
570 return archive;
571 }
573 /** Flush any output resources.
574 */
575 public void flush() {
576 contentCache.clear();
577 }
579 /**
580 * Close the JavaFileManager, releasing resources.
581 */
582 public void close() {
583 for (Iterator<Archive> i = archives.values().iterator(); i.hasNext(); ) {
584 Archive a = i.next();
585 i.remove();
586 try {
587 a.close();
588 } catch (IOException e) {
589 }
590 }
591 }
593 CharBuffer getCachedContent(JavaFileObject file) {
594 SoftReference<CharBuffer> r = contentCache.get(file);
595 return (r == null ? null : r.get());
596 }
598 void cache(JavaFileObject file, CharBuffer cb) {
599 contentCache.put(file, new SoftReference<CharBuffer>(cb));
600 }
602 private final Map<JavaFileObject, SoftReference<CharBuffer>> contentCache
603 = new HashMap<JavaFileObject, SoftReference<CharBuffer>>();
605 private String defaultEncodingName;
606 private String getDefaultEncodingName() {
607 if (defaultEncodingName == null) {
608 defaultEncodingName =
609 new OutputStreamWriter(new ByteArrayOutputStream()).getEncoding();
610 }
611 return defaultEncodingName;
612 }
614 protected String getEncodingName() {
615 String encName = options.get(OptionName.ENCODING);
616 if (encName == null)
617 return getDefaultEncodingName();
618 else
619 return encName;
620 }
622 protected Source getSource() {
623 String sourceName = options.get(OptionName.SOURCE);
624 Source source = null;
625 if (sourceName != null)
626 source = Source.lookup(sourceName);
627 return (source != null ? source : Source.DEFAULT);
628 }
630 /**
631 * Make a byte buffer from an input stream.
632 */
633 ByteBuffer makeByteBuffer(InputStream in)
634 throws IOException {
635 int limit = in.available();
636 if (mmappedIO && in instanceof FileInputStream) {
637 // Experimental memory mapped I/O
638 FileInputStream fin = (FileInputStream)in;
639 return fin.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, limit);
640 }
641 if (limit < 1024) limit = 1024;
642 ByteBuffer result = byteBufferCache.get(limit);
643 int position = 0;
644 while (in.available() != 0) {
645 if (position >= limit)
646 // expand buffer
647 result = ByteBuffer.
648 allocate(limit <<= 1).
649 put((ByteBuffer)result.flip());
650 int count = in.read(result.array(),
651 position,
652 limit - position);
653 if (count < 0) break;
654 result.position(position += count);
655 }
656 return (ByteBuffer)result.flip();
657 }
659 void recycleByteBuffer(ByteBuffer bb) {
660 byteBufferCache.put(bb);
661 }
663 /**
664 * A single-element cache of direct byte buffers.
665 */
666 private static class ByteBufferCache {
667 private ByteBuffer cached;
668 ByteBuffer get(int capacity) {
669 if (capacity < 20480) capacity = 20480;
670 ByteBuffer result =
671 (cached != null && cached.capacity() >= capacity)
672 ? (ByteBuffer)cached.clear()
673 : ByteBuffer.allocate(capacity + capacity>>1);
674 cached = null;
675 return result;
676 }
677 void put(ByteBuffer x) {
678 cached = x;
679 }
680 }
682 private final ByteBufferCache byteBufferCache;
684 CharsetDecoder getDecoder(String encodingName, boolean ignoreEncodingErrors) {
685 Charset charset = (this.charset == null)
686 ? Charset.forName(encodingName)
687 : this.charset;
688 CharsetDecoder decoder = charset.newDecoder();
690 CodingErrorAction action;
691 if (ignoreEncodingErrors)
692 action = CodingErrorAction.REPLACE;
693 else
694 action = CodingErrorAction.REPORT;
696 return decoder
697 .onMalformedInput(action)
698 .onUnmappableCharacter(action);
699 }
701 /**
702 * Decode a ByteBuffer into a CharBuffer.
703 */
704 CharBuffer decode(ByteBuffer inbuf, boolean ignoreEncodingErrors) {
705 String encodingName = getEncodingName();
706 CharsetDecoder decoder;
707 try {
708 decoder = getDecoder(encodingName, ignoreEncodingErrors);
709 } catch (IllegalCharsetNameException e) {
710 log.error("unsupported.encoding", encodingName);
711 return (CharBuffer)CharBuffer.allocate(1).flip();
712 } catch (UnsupportedCharsetException e) {
713 log.error("unsupported.encoding", encodingName);
714 return (CharBuffer)CharBuffer.allocate(1).flip();
715 }
717 // slightly overestimate the buffer size to avoid reallocation.
718 float factor =
719 decoder.averageCharsPerByte() * 0.8f +
720 decoder.maxCharsPerByte() * 0.2f;
721 CharBuffer dest = CharBuffer.
722 allocate(10 + (int)(inbuf.remaining()*factor));
724 while (true) {
725 CoderResult result = decoder.decode(inbuf, dest, true);
726 dest.flip();
728 if (result.isUnderflow()) { // done reading
729 // make sure there is at least one extra character
730 if (dest.limit() == dest.capacity()) {
731 dest = CharBuffer.allocate(dest.capacity()+1).put(dest);
732 dest.flip();
733 }
734 return dest;
735 } else if (result.isOverflow()) { // buffer too small; expand
736 int newCapacity =
737 10 + dest.capacity() +
738 (int)(inbuf.remaining()*decoder.maxCharsPerByte());
739 dest = CharBuffer.allocate(newCapacity).put(dest);
740 } else if (result.isMalformed() || result.isUnmappable()) {
741 // bad character in input
743 // report coding error (warn only pre 1.5)
744 if (!getSource().allowEncodingErrors()) {
745 log.error(new SimpleDiagnosticPosition(dest.limit()),
746 "illegal.char.for.encoding",
747 charset == null ? encodingName : charset.name());
748 } else {
749 log.warning(new SimpleDiagnosticPosition(dest.limit()),
750 "illegal.char.for.encoding",
751 charset == null ? encodingName : charset.name());
752 }
754 // skip past the coding error
755 inbuf.position(inbuf.position() + result.length());
757 // undo the flip() to prepare the output buffer
758 // for more translation
759 dest.position(dest.limit());
760 dest.limit(dest.capacity());
761 dest.put((char)0xfffd); // backward compatible
762 } else {
763 throw new AssertionError(result);
764 }
765 }
766 // unreached
767 }
769 public ClassLoader getClassLoader(Location location) {
770 nullCheck(location);
771 Iterable<? extends File> path = getLocation(location);
772 if (path == null)
773 return null;
774 ListBuffer<URL> lb = new ListBuffer<URL>();
775 for (File f: path) {
776 try {
777 lb.append(f.toURI().toURL());
778 } catch (MalformedURLException e) {
779 throw new AssertionError(e);
780 }
781 }
782 return new URLClassLoader(lb.toArray(new URL[lb.size()]),
783 getClass().getClassLoader());
784 }
786 public Iterable<JavaFileObject> list(Location location,
787 String packageName,
788 Set<JavaFileObject.Kind> kinds,
789 boolean recurse)
790 throws IOException
791 {
792 // validatePackageName(packageName);
793 nullCheck(packageName);
794 nullCheck(kinds);
796 Iterable<? extends File> path = getLocation(location);
797 if (path == null)
798 return List.nil();
799 String subdirectory = externalizeFileName(packageName);
800 ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>();
802 for (File directory : path)
803 listDirectory(directory, subdirectory, kinds, recurse, results);
805 return results.toList();
806 }
808 public String inferBinaryName(Location location, JavaFileObject file) {
809 file.getClass(); // null check
810 location.getClass(); // null check
811 // Need to match the path semantics of list(location, ...)
812 Iterable<? extends File> path = getLocation(location);
813 if (path == null) {
814 return null;
815 }
817 if (file instanceof BaseFileObject) {
818 return ((BaseFileObject) file).inferBinaryName(path);
819 } else
820 throw new IllegalArgumentException(file.getClass().getName());
821 }
823 public boolean isSameFile(FileObject a, FileObject b) {
824 nullCheck(a);
825 nullCheck(b);
826 if (!(a instanceof BaseFileObject))
827 throw new IllegalArgumentException("Not supported: " + a);
828 if (!(b instanceof BaseFileObject))
829 throw new IllegalArgumentException("Not supported: " + b);
830 return a.equals(b);
831 }
833 public boolean handleOption(String current, Iterator<String> remaining) {
834 for (JavacOption o: javacFileManagerOptions) {
835 if (o.matches(current)) {
836 if (o.hasArg()) {
837 if (remaining.hasNext()) {
838 if (!o.process(options, current, remaining.next()))
839 return true;
840 }
841 } else {
842 if (!o.process(options, current))
843 return true;
844 }
845 // operand missing, or process returned false
846 throw new IllegalArgumentException(current);
847 }
848 }
850 return false;
851 }
852 // where
853 private static JavacOption[] javacFileManagerOptions =
854 RecognizedOptions.getJavacFileManagerOptions(
855 new RecognizedOptions.GrumpyHelper());
857 public int isSupportedOption(String option) {
858 for (JavacOption o : javacFileManagerOptions) {
859 if (o.matches(option))
860 return o.hasArg() ? 1 : 0;
861 }
862 return -1;
863 }
865 public boolean hasLocation(Location location) {
866 return getLocation(location) != null;
867 }
869 public JavaFileObject getJavaFileForInput(Location location,
870 String className,
871 JavaFileObject.Kind kind)
872 throws IOException
873 {
874 nullCheck(location);
875 // validateClassName(className);
876 nullCheck(className);
877 nullCheck(kind);
878 if (!sourceOrClass.contains(kind))
879 throw new IllegalArgumentException("Invalid kind " + kind);
880 return getFileForInput(location, externalizeFileName(className, kind));
881 }
883 public FileObject getFileForInput(Location location,
884 String packageName,
885 String relativeName)
886 throws IOException
887 {
888 nullCheck(location);
889 // validatePackageName(packageName);
890 nullCheck(packageName);
891 if (!isRelativeUri(URI.create(relativeName))) // FIXME 6419701
892 throw new IllegalArgumentException("Invalid relative name: " + relativeName);
893 String name = packageName.length() == 0
894 ? relativeName
895 : new File(externalizeFileName(packageName), relativeName).getPath();
896 return getFileForInput(location, name);
897 }
899 private JavaFileObject getFileForInput(Location location, String name) throws IOException {
900 Iterable<? extends File> path = getLocation(location);
901 if (path == null)
902 return null;
904 for (File dir: path) {
905 if (dir.isDirectory()) {
906 File f = new File(dir, name.replace('/', File.separatorChar));
907 if (f.exists())
908 return new RegularFileObject(this, f);
909 } else {
910 Archive a = openArchive(dir);
911 if (a.contains(name)) {
912 int i = name.lastIndexOf('/');
913 String dirname = name.substring(0, i+1);
914 String basename = name.substring(i+1);
915 return a.getFileObject(dirname, basename);
916 }
918 }
919 }
920 return null;
922 }
924 public JavaFileObject getJavaFileForOutput(Location location,
925 String className,
926 JavaFileObject.Kind kind,
927 FileObject sibling)
928 throws IOException
929 {
930 nullCheck(location);
931 // validateClassName(className);
932 nullCheck(className);
933 nullCheck(kind);
934 if (!sourceOrClass.contains(kind))
935 throw new IllegalArgumentException("Invalid kind " + kind);
936 return getFileForOutput(location, externalizeFileName(className, kind), sibling);
937 }
939 public FileObject getFileForOutput(Location location,
940 String packageName,
941 String relativeName,
942 FileObject sibling)
943 throws IOException
944 {
945 nullCheck(location);
946 // validatePackageName(packageName);
947 nullCheck(packageName);
948 if (!isRelativeUri(URI.create(relativeName))) // FIXME 6419701
949 throw new IllegalArgumentException("relativeName is invalid");
950 String name = packageName.length() == 0
951 ? relativeName
952 : new File(externalizeFileName(packageName), relativeName).getPath();
953 return getFileForOutput(location, name, sibling);
954 }
956 private JavaFileObject getFileForOutput(Location location,
957 String fileName,
958 FileObject sibling)
959 throws IOException
960 {
961 File dir;
962 if (location == CLASS_OUTPUT) {
963 if (getClassOutDir() != null) {
964 dir = getClassOutDir();
965 } else {
966 File siblingDir = null;
967 if (sibling != null && sibling instanceof RegularFileObject) {
968 siblingDir = ((RegularFileObject)sibling).f.getParentFile();
969 }
970 return new RegularFileObject(this, new File(siblingDir, baseName(fileName)));
971 }
972 } else if (location == SOURCE_OUTPUT) {
973 dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
974 } else {
975 Iterable<? extends File> path = paths.getPathForLocation(location);
976 dir = null;
977 for (File f: path) {
978 dir = f;
979 break;
980 }
981 }
983 File file = (dir == null ? new File(fileName) : new File(dir, fileName));
984 return new RegularFileObject(this, file);
986 }
988 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
989 Iterable<? extends File> files)
990 {
991 ArrayList<RegularFileObject> result;
992 if (files instanceof Collection)
993 result = new ArrayList<RegularFileObject>(((Collection)files).size());
994 else
995 result = new ArrayList<RegularFileObject>();
996 for (File f: files)
997 result.add(new RegularFileObject(this, nullCheck(f)));
998 return result;
999 }
1001 public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
1002 return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
1003 }
1005 public void setLocation(Location location,
1006 Iterable<? extends File> path)
1007 throws IOException
1008 {
1009 nullCheck(location);
1010 paths.lazy();
1012 final File dir = location.isOutputLocation() ? getOutputDirectory(path) : null;
1014 if (location == CLASS_OUTPUT)
1015 classOutDir = getOutputLocation(dir, D);
1016 else if (location == SOURCE_OUTPUT)
1017 sourceOutDir = getOutputLocation(dir, S);
1018 else
1019 paths.setPathForLocation(location, path);
1020 }
1021 // where
1022 private File getOutputDirectory(Iterable<? extends File> path) throws IOException {
1023 if (path == null)
1024 return null;
1025 Iterator<? extends File> pathIter = path.iterator();
1026 if (!pathIter.hasNext())
1027 throw new IllegalArgumentException("empty path for directory");
1028 File dir = pathIter.next();
1029 if (pathIter.hasNext())
1030 throw new IllegalArgumentException("path too long for directory");
1031 if (!dir.exists())
1032 throw new FileNotFoundException(dir + ": does not exist");
1033 else if (!dir.isDirectory())
1034 throw new IOException(dir + ": not a directory");
1035 return dir;
1036 }
1038 private File getOutputLocation(File dir, OptionName defaultOptionName) {
1039 if (dir != null)
1040 return dir;
1041 String arg = options.get(defaultOptionName);
1042 if (arg == null)
1043 return null;
1044 return new File(arg);
1045 }
1047 public Iterable<? extends File> getLocation(Location location) {
1048 nullCheck(location);
1049 paths.lazy();
1050 if (location == CLASS_OUTPUT) {
1051 return (getClassOutDir() == null ? null : List.of(getClassOutDir()));
1052 } else if (location == SOURCE_OUTPUT) {
1053 return (getSourceOutDir() == null ? null : List.of(getSourceOutDir()));
1054 } else
1055 return paths.getPathForLocation(location);
1056 }
1058 private File getClassOutDir() {
1059 if (classOutDir == uninited)
1060 classOutDir = getOutputLocation(null, D);
1061 return classOutDir;
1062 }
1064 private File getSourceOutDir() {
1065 if (sourceOutDir == uninited)
1066 sourceOutDir = getOutputLocation(null, S);
1067 return sourceOutDir;
1068 }
1070 /**
1071 * Enforces the specification of a "relative" URI as used in
1072 * {@linkplain #getFileForInput(Location,String,URI)
1073 * getFileForInput}. This method must follow the rules defined in
1074 * that method, do not make any changes without consulting the
1075 * specification.
1076 */
1077 protected static boolean isRelativeUri(URI uri) {
1078 if (uri.isAbsolute())
1079 return false;
1080 String path = uri.normalize().getPath();
1081 if (path.length() == 0 /* isEmpty() is mustang API */)
1082 return false;
1083 char first = path.charAt(0);
1084 return first != '.' && first != '/';
1085 }
1087 /**
1088 * Converts a relative file name to a relative URI. This is
1089 * different from File.toURI as this method does not canonicalize
1090 * the file before creating the URI. Furthermore, no schema is
1091 * used.
1092 * @param file a relative file name
1093 * @return a relative URI
1094 * @throws IllegalArgumentException if the file name is not
1095 * relative according to the definition given in {@link
1096 * javax.tools.JavaFileManager#getFileForInput}
1097 */
1098 public static String getRelativeName(File file) {
1099 if (!file.isAbsolute()) {
1100 String result = file.getPath().replace(File.separatorChar, '/');
1101 if (JavacFileManager.isRelativeUri(URI.create(result))) // FIXME 6419701
1102 return result;
1103 }
1104 throw new IllegalArgumentException("Invalid relative path: " + file);
1105 }
1107 @SuppressWarnings("deprecation") // bug 6410637
1108 public static String getJavacFileName(FileObject file) {
1109 if (file instanceof BaseFileObject)
1110 return ((BaseFileObject)file).getPath();
1111 URI uri = file.toUri();
1112 String scheme = uri.getScheme();
1113 if (scheme == null || scheme.equals("file") || scheme.equals("jar"))
1114 return uri.getPath();
1115 else
1116 return uri.toString();
1117 }
1119 @SuppressWarnings("deprecation") // bug 6410637
1120 public static String getJavacBaseFileName(FileObject file) {
1121 if (file instanceof BaseFileObject)
1122 return ((BaseFileObject)file).getName();
1123 URI uri = file.toUri();
1124 String scheme = uri.getScheme();
1125 if (scheme == null || scheme.equals("file") || scheme.equals("jar")) {
1126 String path = uri.getPath();
1127 if (path == null)
1128 return null;
1129 if (scheme != null && scheme.equals("jar"))
1130 path = path.substring(path.lastIndexOf('!') + 1);
1131 return path.substring(path.lastIndexOf('/') + 1);
1132 } else {
1133 return uri.toString();
1134 }
1135 }
1137 private static <T> T nullCheck(T o) {
1138 o.getClass(); // null check
1139 return o;
1140 }
1142 private static <T> Iterable<T> nullCheck(Iterable<T> it) {
1143 for (T t : it)
1144 t.getClass(); // null check
1145 return it;
1146 }
1147 }