001    /*
002     * Copyright 2005-2008 Sun Microsystems, Inc.  All Rights Reserved.
003     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004     *
005     * This code is free software; you can redistribute it and/or modify it
006     * under the terms of the GNU General Public License version 2 only, as
007     * published by the Free Software Foundation.  Sun designates this
008     * particular file as subject to the "Classpath" exception as provided
009     * by Sun in the LICENSE file that accompanied this code.
010     *
011     * This code is distributed in the hope that it will be useful, but WITHOUT
012     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014     * version 2 for more details (a copy is included in the LICENSE file that
015     * accompanied this code).
016     *
017     * You should have received a copy of the GNU General Public License version
018     * 2 along with this work; if not, write to the Free Software Foundation,
019     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020     *
021     * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022     * CA 95054 USA or visit www.sun.com if you need additional information or
023     * have any questions.
024     */
025    
026    package com.sun.tools.javac.file;
027    
028    import java.io.ByteArrayOutputStream;
029    import java.io.File;
030    import java.io.FileInputStream;
031    import java.io.FileNotFoundException;
032    import java.io.IOException;
033    import java.io.InputStream;
034    import java.io.OutputStreamWriter;
035    import java.lang.ref.SoftReference;
036    import java.net.MalformedURLException;
037    import java.net.URI;
038    import java.net.URL;
039    import java.net.URLClassLoader;
040    import java.nio.ByteBuffer;
041    import java.nio.CharBuffer;
042    import java.nio.channels.FileChannel;
043    import java.nio.charset.Charset;
044    import java.nio.charset.CharsetDecoder;
045    import java.nio.charset.CoderResult;
046    import java.nio.charset.CodingErrorAction;
047    import java.nio.charset.IllegalCharsetNameException;
048    import java.nio.charset.UnsupportedCharsetException;
049    import java.util.ArrayList;
050    import java.util.Arrays;
051    import java.util.Collection;
052    import java.util.Collections;
053    import java.util.EnumSet;
054    import java.util.HashMap;
055    import java.util.Iterator;
056    import java.util.Map;
057    import java.util.Set;
058    import java.util.zip.ZipFile;
059    
060    import javax.lang.model.SourceVersion;
061    import javax.tools.FileObject;
062    import javax.tools.JavaFileManager;
063    import javax.tools.JavaFileObject;
064    import javax.tools.StandardJavaFileManager;
065    
066    import com.sun.tools.javac.code.Source;
067    import com.sun.tools.javac.file.RelativePath.RelativeFile;
068    import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
069    import com.sun.tools.javac.main.JavacOption;
070    import com.sun.tools.javac.main.OptionName;
071    import com.sun.tools.javac.main.RecognizedOptions;
072    import com.sun.tools.javac.util.Context;
073    import com.sun.tools.javac.util.JCDiagnostic.SimpleDiagnosticPosition;
074    import com.sun.tools.javac.util.List;
075    import com.sun.tools.javac.util.ListBuffer;
076    import com.sun.tools.javac.util.Log;
077    import com.sun.tools.javac.util.Options;
078    
079    import static javax.tools.StandardLocation.*;
080    import static com.sun.tools.javac.main.OptionName.*;
081    
082    /**
083     * This class provides access to the source, class and other files
084     * used by the compiler and related tools.
085     */
086    public class JavacFileManager implements StandardJavaFileManager {
087    
088        boolean useZipFileIndex;
089    
090        public static char[] toArray(CharBuffer buffer) {
091            if (buffer.hasArray())
092                return ((CharBuffer)buffer.compact().flip()).array();
093            else
094                return buffer.toString().toCharArray();
095        }
096    
097        /**
098         * The log to be used for error reporting.
099         */
100        protected Log log;
101    
102        /** Encapsulates knowledge of paths
103         */
104        private Paths paths;
105    
106        private Options options;
107    
108        private FSInfo fsInfo;
109    
110        private final File uninited = new File("U N I N I T E D");
111    
112        private final Set<JavaFileObject.Kind> sourceOrClass =
113            EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS);
114    
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;
121    
122        /** The output directory, used when generating sources while processing annotations.
123         *  Initialized by the "-s" option.
124         */
125        private File sourceOutDir = uninited;
126    
127        protected boolean mmappedIO;
128        protected boolean ignoreSymbolFile;
129    
130        /**
131         * User provided charset (through javax.tools).
132         */
133        protected Charset charset;
134    
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        }
145    
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        }
157    
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            }
170    
171            options = Options.instance(context);
172            fsInfo = FSInfo.instance(context);
173    
174            useZipFileIndex = System.getProperty("useJavaUtilZip") == null;// TODO: options.get("useJavaUtilZip") == null;
175    
176            mmappedIO = options.get("mmappedIO") != null;
177            ignoreSymbolFile = options.get("ignore.symbol.file") != null;
178        }
179    
180        public JavaFileObject getFileForInput(String name) {
181            return getRegularFile(new File(name));
182        }
183    
184        public JavaFileObject getRegularFile(File file) {
185            return new RegularFileObject(this, file);
186        }
187    
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        }
195    
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        }
202    
203        public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
204            return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names)));
205        }
206    
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        }
217    
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        }
230    
231        private static void validateClassName(String className) {
232            if (!isValidName(className))
233                throw new IllegalArgumentException("Invalid class name: " + className);
234        }
235    
236        private static void validatePackageName(String packageName) {
237            if (packageName.length() > 0 && !isValidName(packageName))
238                throw new IllegalArgumentException("Invalid packageName name: " + packageName);
239        }
240    
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        }
266    
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        }
277    
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);
288    
289            boolean isFile = fsInfo.isFile(directory);
290    
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                }
301    
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;
325    
326                File[] files = d.listFiles();
327                if (files == null)
328                    return;
329    
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        }
350    
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        }
357    
358        private static final boolean fileSystemIsCaseSensitive =
359            File.separatorChar == '/';
360    
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        }
390    
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;
397    
398            boolean contains(RelativePath name);
399    
400            JavaFileObject getFileObject(RelativeDirectory subdirectory, String file);
401    
402            List<String> getFiles(RelativeDirectory subdirectory);
403    
404            Set<RelativeDirectory> getSubdirectories();
405        }
406    
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            }
415    
416            public void close() {
417            }
418    
419            public JavaFileObject getFileObject(RelativeDirectory subdirectory, String file) {
420                return null;
421            }
422    
423            public List<String> getFiles(RelativeDirectory subdirectory) {
424                return List.nil();
425            }
426    
427            public Set<RelativeDirectory> getSubdirectories() {
428                return Collections.emptySet();
429            }
430    
431            public String toString() {
432                return "MissingArchive[" + zipFileName + "]";
433            }
434        }
435    
436        /** A directory of zip files already opened.
437         */
438        Map<File, Archive> archives = new HashMap<File,Archive>();
439    
440        private static final String[] symbolFileLocation = { "lib", "ct.sym" };
441        private static final RelativeDirectory symbolFilePrefix
442                = new RelativeDirectory("META-INF/sym/rt.jar/");
443    
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                }
461    
462                try {
463    
464                    ZipFile zdir = null;
465    
466                    boolean usePreindexedCache = false;
467                    String preindexCacheLocation = null;
468    
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");
476    
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                            }
486    
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                    }
497    
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                }
530    
531                archives.put(origZipFileName, archive);
532            }
533            return archive;
534        }
535    
536        /** Flush any output resources.
537         */
538        public void flush() {
539            contentCache.clear();
540        }
541    
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        }
555    
556        CharBuffer getCachedContent(JavaFileObject file) {
557            SoftReference<CharBuffer> r = contentCache.get(file);
558            return (r == null ? null : r.get());
559        }
560    
561        void cache(JavaFileObject file, CharBuffer cb) {
562            contentCache.put(file, new SoftReference<CharBuffer>(cb));
563        }
564    
565        private final Map<JavaFileObject, SoftReference<CharBuffer>> contentCache
566                = new HashMap<JavaFileObject, SoftReference<CharBuffer>>();
567    
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        }
576    
577        protected String getEncodingName() {
578            String encName = options.get(OptionName.ENCODING);
579            if (encName == null)
580                return getDefaultEncodingName();
581            else
582                return encName;
583        }
584    
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        }
592    
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        }
621    
622        void recycleByteBuffer(ByteBuffer bb) {
623            byteBufferCache.put(bb);
624        }
625    
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        }
644    
645        private final ByteBufferCache byteBufferCache;
646    
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();
652    
653            CodingErrorAction action;
654            if (ignoreEncodingErrors)
655                action = CodingErrorAction.REPLACE;
656            else
657                action = CodingErrorAction.REPORT;
658    
659            return decoder
660                .onMalformedInput(action)
661                .onUnmappableCharacter(action);
662        }
663    
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            }
679    
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));
686    
687            while (true) {
688                CoderResult result = decoder.decode(inbuf, dest, true);
689                dest.flip();
690    
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
705    
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                    }
716    
717                    // skip past the coding error
718                    inbuf.position(inbuf.position() + result.length());
719    
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        }
731    
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        }
748    
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);
758    
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>();
764    
765            for (File directory : path)
766                listDirectory(directory, subdirectory, kinds, recurse, results);
767    
768            return results.toList();
769        }
770    
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            }
779    
780            if (file instanceof BaseFileObject) {
781                return ((BaseFileObject) file).inferBinaryName(path);
782            } else
783                throw new IllegalArgumentException(file.getClass().getName());
784        }
785    
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        }
795    
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            }
812    
813            return false;
814        }
815        // where
816            private static JavacOption[] javacFileManagerOptions =
817                RecognizedOptions.getJavacFileManagerOptions(
818                new RecognizedOptions.GrumpyHelper());
819    
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        }
827    
828        public boolean hasLocation(Location location) {
829            return getLocation(location) != null;
830        }
831    
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        }
845    
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        }
861    
862        private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
863            Iterable<? extends File> path = getLocation(location);
864            if (path == null)
865                return null;
866    
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                    }
877    
878                }
879            }
880    
881            return null;
882        }
883    
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        }
898    
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        }
915    
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            }
942    
943            File file = fileName.getFile(dir); // null-safe
944            return new RegularFileObject(this, file);
945    
946        }
947    
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        }
960    
961        public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
962            return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
963        }
964    
965        public void setLocation(Location location,
966                                Iterable<? extends File> path)
967            throws IOException
968        {
969            nullCheck(location);
970            paths.lazy();
971    
972            final File dir = location.isOutputLocation() ? getOutputDirectory(path) : null;
973    
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            }
997    
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        }
1006    
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        }
1017    
1018        private File getClassOutDir() {
1019            if (classOutDir == uninited)
1020                classOutDir = getOutputLocation(null, D);
1021            return classOutDir;
1022        }
1023    
1024        private File getSourceOutDir() {
1025            if (sourceOutDir == uninited)
1026                sourceOutDir = getOutputLocation(null, S);
1027            return sourceOutDir;
1028        }
1029    
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        }
1046    
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        }
1066    
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        }
1078    
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        }
1096    
1097        private static <T> T nullCheck(T o) {
1098            o.getClass(); // null check
1099            return o;
1100        }
1101    
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    }