Locations.java revision 2721:f7ce2cfa4cdb
1/*
2 * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package com.sun.tools.javac.file;
26
27import java.io.File;
28import java.io.FileNotFoundException;
29import java.io.IOException;
30import java.io.UncheckedIOException;
31import java.net.MalformedURLException;
32import java.net.URL;
33import java.nio.file.Files;
34import java.nio.file.Path;
35import java.nio.file.Paths;
36import java.util.Arrays;
37import java.util.Collection;
38import java.util.Collections;
39import java.util.EnumMap;
40import java.util.EnumSet;
41import java.util.HashMap;
42import java.util.HashSet;
43import java.util.Iterator;
44import java.util.LinkedHashSet;
45import java.util.Map;
46import java.util.Set;
47import java.util.StringTokenizer;
48import java.util.stream.Collectors;
49import java.util.stream.Stream;
50import java.util.zip.ZipFile;
51
52import javax.tools.JavaFileManager;
53import javax.tools.JavaFileManager.Location;
54import javax.tools.StandardJavaFileManager;
55import javax.tools.StandardLocation;
56
57import com.sun.tools.javac.code.Lint;
58import com.sun.tools.javac.main.Option;
59import com.sun.tools.javac.util.ListBuffer;
60import com.sun.tools.javac.util.Log;
61import com.sun.tools.javac.util.StringUtils;
62
63import static javax.tools.StandardLocation.CLASS_PATH;
64import static javax.tools.StandardLocation.PLATFORM_CLASS_PATH;
65import static javax.tools.StandardLocation.SOURCE_PATH;
66
67import static com.sun.tools.javac.main.Option.BOOTCLASSPATH;
68import static com.sun.tools.javac.main.Option.DJAVA_ENDORSED_DIRS;
69import static com.sun.tools.javac.main.Option.DJAVA_EXT_DIRS;
70import static com.sun.tools.javac.main.Option.ENDORSEDDIRS;
71import static com.sun.tools.javac.main.Option.EXTDIRS;
72import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH;
73import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_APPEND;
74import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_PREPEND;
75
76/**
77 * This class converts command line arguments, environment variables and system properties (in
78 * File.pathSeparator-separated String form) into a boot class path, user class path, and source
79 * path (in {@code Collection<String>} form).
80 *
81 * <p>
82 * <b>This is NOT part of any supported API. If you write code that depends on this, you do so at
83 * your own risk. This code and its internal interfaces are subject to change or deletion without
84 * notice.</b>
85 */
86public class Locations {
87
88    /**
89     * The log to use for warning output
90     */
91    private Log log;
92
93    /**
94     * Access to (possibly cached) file info
95     */
96    private FSInfo fsInfo;
97
98    /**
99     * Whether to warn about non-existent path elements
100     */
101    private boolean warn;
102
103    // Used by Locations(for now) to indicate that the PLATFORM_CLASS_PATH
104    // should use the jrt: file system.
105    // When Locations has been converted to use java.nio.file.Path,
106    // Locations can use Paths.get(URI.create("jrt:"))
107    static final File JRT_MARKER_FILE = new File("JRT_MARKER_FILE");
108
109    public Locations() {
110        initHandlers();
111    }
112
113    // could replace Lint by "boolean warn"
114    public void update(Log log, Lint lint, FSInfo fsInfo) {
115        this.log = log;
116        warn = lint.isEnabled(Lint.LintCategory.PATH);
117        this.fsInfo = fsInfo;
118    }
119
120    public Collection<File> bootClassPath() {
121        return getLocation(PLATFORM_CLASS_PATH);
122    }
123
124    public boolean isDefaultBootClassPath() {
125        BootClassPathLocationHandler h
126                = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH);
127        return h.isDefault();
128    }
129
130    public Collection<File> userClassPath() {
131        return getLocation(CLASS_PATH);
132    }
133
134    public Collection<File> sourcePath() {
135        Collection<File> p = getLocation(SOURCE_PATH);
136        // TODO: this should be handled by the LocationHandler
137        return p == null || p.isEmpty() ? null : p;
138    }
139
140    /**
141     * Split a path into its elements. Empty path elements will be ignored.
142     *
143     * @param path The path to be split
144     * @return The elements of the path
145     */
146    private static Iterable<File> getPathEntries(String path) {
147        return getPathEntries(path, null);
148    }
149
150    /**
151     * Split a path into its elements. If emptyPathDefault is not null, all empty elements in the
152     * path, including empty elements at either end of the path, will be replaced with the value of
153     * emptyPathDefault.
154     *
155     * @param path The path to be split
156     * @param emptyPathDefault The value to substitute for empty path elements, or null, to ignore
157     * empty path elements
158     * @return The elements of the path
159     */
160    private static Iterable<File> getPathEntries(String path, File emptyPathDefault) {
161        ListBuffer<File> entries = new ListBuffer<>();
162        int start = 0;
163        while (start <= path.length()) {
164            int sep = path.indexOf(File.pathSeparatorChar, start);
165            if (sep == -1) {
166                sep = path.length();
167            }
168            if (start < sep) {
169                entries.add(new File(path.substring(start, sep)));
170            } else if (emptyPathDefault != null) {
171                entries.add(emptyPathDefault);
172            }
173            start = sep + 1;
174        }
175        return entries;
176    }
177
178    /**
179     * Utility class to help evaluate a path option. Duplicate entries are ignored, jar class paths
180     * can be expanded.
181     */
182    private class SearchPath extends LinkedHashSet<File> {
183
184        private static final long serialVersionUID = 0;
185
186        private boolean expandJarClassPaths = false;
187        private final Set<File> canonicalValues = new HashSet<>();
188
189        public SearchPath expandJarClassPaths(boolean x) {
190            expandJarClassPaths = x;
191            return this;
192        }
193
194        /**
195         * What to use when path element is the empty string
196         */
197        private File emptyPathDefault = null;
198
199        public SearchPath emptyPathDefault(File x) {
200            emptyPathDefault = x;
201            return this;
202        }
203
204        public SearchPath addDirectories(String dirs, boolean warn) {
205            boolean prev = expandJarClassPaths;
206            expandJarClassPaths = true;
207            try {
208                if (dirs != null) {
209                    for (File dir : getPathEntries(dirs)) {
210                        addDirectory(dir, warn);
211                    }
212                }
213                return this;
214            } finally {
215                expandJarClassPaths = prev;
216            }
217        }
218
219        public SearchPath addDirectories(String dirs) {
220            return addDirectories(dirs, warn);
221        }
222
223        private void addDirectory(File dir, boolean warn) {
224            if (!dir.isDirectory()) {
225                if (warn) {
226                    log.warning(Lint.LintCategory.PATH,
227                            "dir.path.element.not.found", dir);
228                }
229                return;
230            }
231
232            File[] files = dir.listFiles();
233            if (files == null) {
234                return;
235            }
236
237            for (File direntry : files) {
238                if (isArchive(direntry)) {
239                    addFile(direntry, warn);
240                }
241            }
242        }
243
244        public SearchPath addFiles(String files, boolean warn) {
245            if (files != null) {
246                addFiles(getPathEntries(files, emptyPathDefault), warn);
247            }
248            return this;
249        }
250
251        public SearchPath addFiles(String files) {
252            return addFiles(files, warn);
253        }
254
255        public SearchPath addFiles(Iterable<? extends File> files, boolean warn) {
256            if (files != null) {
257                for (File file : files) {
258                    addFile(file, warn);
259                }
260            }
261            return this;
262        }
263
264        public SearchPath addFiles(Iterable<? extends File> files) {
265            return addFiles(files, warn);
266        }
267
268        public void addFile(File file, boolean warn) {
269            if (contains(file)) {
270                // discard duplicates
271                return;
272            }
273
274            if (!fsInfo.exists(file)) {
275                /* No such file or directory exists */
276                if (warn) {
277                    log.warning(Lint.LintCategory.PATH,
278                            "path.element.not.found", file);
279                }
280                super.add(file);
281                return;
282            }
283
284            File canonFile = fsInfo.getCanonicalFile(file);
285            if (canonicalValues.contains(canonFile)) {
286                /* Discard duplicates and avoid infinite recursion */
287                return;
288            }
289
290            if (fsInfo.isFile(file)) {
291                /* File is an ordinary file. */
292                if (!isArchive(file) && !file.getName().endsWith(".jimage")) {
293                    /* Not a recognized extension; open it to see if
294                     it looks like a valid zip file. */
295                    try {
296                        ZipFile z = new ZipFile(file);
297                        z.close();
298                        if (warn) {
299                            log.warning(Lint.LintCategory.PATH,
300                                    "unexpected.archive.file", file);
301                        }
302                    } catch (IOException e) {
303                        // FIXME: include e.getLocalizedMessage in warning
304                        if (warn) {
305                            log.warning(Lint.LintCategory.PATH,
306                                    "invalid.archive.file", file);
307                        }
308                        return;
309                    }
310                }
311            }
312
313            /* Now what we have left is either a directory or a file name
314             conforming to archive naming convention */
315            super.add(file);
316            canonicalValues.add(canonFile);
317
318            if (expandJarClassPaths && fsInfo.isFile(file) && !file.getName().endsWith(".jimage")) {
319                addJarClassPath(file, warn);
320            }
321        }
322
323        // Adds referenced classpath elements from a jar's Class-Path
324        // Manifest entry.  In some future release, we may want to
325        // update this code to recognize URLs rather than simple
326        // filenames, but if we do, we should redo all path-related code.
327        private void addJarClassPath(File jarFile, boolean warn) {
328            try {
329                for (File f : fsInfo.getJarClassPath(jarFile)) {
330                    addFile(f, warn);
331                }
332            } catch (IOException e) {
333                log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e));
334            }
335        }
336    }
337
338    /**
339     * Base class for handling support for the representation of Locations. Implementations are
340     * responsible for handling the interactions between the command line options for a location,
341     * and API access via setLocation.
342     *
343     * @see #initHandlers
344     * @see #getHandler
345     */
346    protected abstract class LocationHandler {
347
348        final Location location;
349        final Set<Option> options;
350
351        /**
352         * Create a handler. The location and options provide a way to map from a location or an
353         * option to the corresponding handler.
354         *
355         * @param location the location for which this is the handler
356         * @param options the options affecting this location
357         * @see #initHandlers
358         */
359        protected LocationHandler(Location location, Option... options) {
360            this.location = location;
361            this.options = options.length == 0
362                    ? EnumSet.noneOf(Option.class)
363                    : EnumSet.copyOf(Arrays.asList(options));
364        }
365
366        /**
367         * @see JavaFileManager#handleOption
368         */
369        abstract boolean handleOption(Option option, String value);
370
371        /**
372         * @see StandardJavaFileManager#getLocation
373         */
374        abstract Collection<File> getLocation();
375
376        /**
377         * @see StandardJavaFileManager#setLocation
378         */
379        abstract void setLocation(Iterable<? extends File> files) throws IOException;
380    }
381
382    /**
383     * General purpose implementation for output locations, such as -d/CLASS_OUTPUT and
384     * -s/SOURCE_OUTPUT. All options are treated as equivalent (i.e. aliases.) The value is a single
385     * file, possibly null.
386     */
387    private class OutputLocationHandler extends LocationHandler {
388
389        private File outputDir;
390
391        OutputLocationHandler(Location location, Option... options) {
392            super(location, options);
393        }
394
395        @Override
396        boolean handleOption(Option option, String value) {
397            if (!options.contains(option)) {
398                return false;
399            }
400
401            // TODO: could/should validate outputDir exists and is a directory
402            // need to decide how best to report issue for benefit of
403            // direct API call on JavaFileManager.handleOption(specifies IAE)
404            // vs. command line decoding.
405            outputDir = (value == null) ? null : new File(value);
406            return true;
407        }
408
409        @Override
410        Collection<File> getLocation() {
411            return (outputDir == null) ? null : Collections.singleton(outputDir);
412        }
413
414        @Override
415        void setLocation(Iterable<? extends File> files) throws IOException {
416            if (files == null) {
417                outputDir = null;
418            } else {
419                Iterator<? extends File> pathIter = files.iterator();
420                if (!pathIter.hasNext()) {
421                    throw new IllegalArgumentException("empty path for directory");
422                }
423                File dir = pathIter.next();
424                if (pathIter.hasNext()) {
425                    throw new IllegalArgumentException("path too long for directory");
426                }
427                if (!dir.exists()) {
428                    throw new FileNotFoundException(dir + ": does not exist");
429                } else if (!dir.isDirectory()) {
430                    throw new IOException(dir + ": not a directory");
431                }
432                outputDir = dir;
433            }
434        }
435    }
436
437    /**
438     * General purpose implementation for search path locations, such as -sourcepath/SOURCE_PATH and
439     * -processorPath/ANNOTATION_PROCESS_PATH. All options are treated as equivalent (i.e. aliases.)
440     * The value is an ordered set of files and/or directories.
441     */
442    private class SimpleLocationHandler extends LocationHandler {
443
444        protected Collection<File> searchPath;
445
446        SimpleLocationHandler(Location location, Option... options) {
447            super(location, options);
448        }
449
450        @Override
451        boolean handleOption(Option option, String value) {
452            if (!options.contains(option)) {
453                return false;
454            }
455            searchPath = value == null ? null
456                    : Collections.unmodifiableCollection(createPath().addFiles(value));
457            return true;
458        }
459
460        @Override
461        Collection<File> getLocation() {
462            return searchPath;
463        }
464
465        @Override
466        void setLocation(Iterable<? extends File> files) {
467            SearchPath p;
468            if (files == null) {
469                p = computePath(null);
470            } else {
471                p = createPath().addFiles(files);
472            }
473            searchPath = Collections.unmodifiableCollection(p);
474        }
475
476        protected SearchPath computePath(String value) {
477            return createPath().addFiles(value);
478        }
479
480        protected SearchPath createPath() {
481            return new SearchPath();
482        }
483    }
484
485    /**
486     * Subtype of SimpleLocationHandler for -classpath/CLASS_PATH. If no value is given, a default
487     * is provided, based on system properties and other values.
488     */
489    private class ClassPathLocationHandler extends SimpleLocationHandler {
490
491        ClassPathLocationHandler() {
492            super(StandardLocation.CLASS_PATH,
493                    Option.CLASSPATH, Option.CP);
494        }
495
496        @Override
497        Collection<File> getLocation() {
498            lazy();
499            return searchPath;
500        }
501
502        @Override
503        protected SearchPath computePath(String value) {
504            String cp = value;
505
506            // CLASSPATH environment variable when run from `javac'.
507            if (cp == null) {
508                cp = System.getProperty("env.class.path");
509            }
510
511            // If invoked via a java VM (not the javac launcher), use the
512            // platform class path
513            if (cp == null && System.getProperty("application.home") == null) {
514                cp = System.getProperty("java.class.path");
515            }
516
517            // Default to current working directory.
518            if (cp == null) {
519                cp = ".";
520            }
521
522            return createPath().addFiles(cp);
523        }
524
525        @Override
526        protected SearchPath createPath() {
527            return new SearchPath()
528                    .expandJarClassPaths(true) // Only search user jars for Class-Paths
529                    .emptyPathDefault(new File("."));  // Empty path elt ==> current directory
530        }
531
532        private void lazy() {
533            if (searchPath == null) {
534                setLocation(null);
535            }
536        }
537    }
538
539    /**
540     * Custom subtype of LocationHandler for PLATFORM_CLASS_PATH. Various options are supported for
541     * different components of the platform class path. Setting a value with setLocation overrides
542     * all existing option values. Setting any option overrides any value set with setLocation, and
543     * reverts to using default values for options that have not been set. Setting -bootclasspath or
544     * -Xbootclasspath overrides any existing value for -Xbootclasspath/p: and -Xbootclasspath/a:.
545     */
546    private class BootClassPathLocationHandler extends LocationHandler {
547
548        private Collection<File> searchPath;
549        final Map<Option, String> optionValues = new EnumMap<>(Option.class);
550
551        /**
552         * Is the bootclasspath the default?
553         */
554        private boolean isDefault;
555
556        BootClassPathLocationHandler() {
557            super(StandardLocation.PLATFORM_CLASS_PATH,
558                    Option.BOOTCLASSPATH, Option.XBOOTCLASSPATH,
559                    Option.XBOOTCLASSPATH_PREPEND,
560                    Option.XBOOTCLASSPATH_APPEND,
561                    Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS,
562                    Option.EXTDIRS, Option.DJAVA_EXT_DIRS);
563        }
564
565        boolean isDefault() {
566            lazy();
567            return isDefault;
568        }
569
570        @Override
571        boolean handleOption(Option option, String value) {
572            if (!options.contains(option)) {
573                return false;
574            }
575
576            option = canonicalize(option);
577            optionValues.put(option, value);
578            if (option == BOOTCLASSPATH) {
579                optionValues.remove(XBOOTCLASSPATH_PREPEND);
580                optionValues.remove(XBOOTCLASSPATH_APPEND);
581            }
582            searchPath = null;  // reset to "uninitialized"
583            return true;
584        }
585        // where
586        // TODO: would be better if option aliasing was handled at a higher
587        // level
588        private Option canonicalize(Option option) {
589            switch (option) {
590                case XBOOTCLASSPATH:
591                    return Option.BOOTCLASSPATH;
592                case DJAVA_ENDORSED_DIRS:
593                    return Option.ENDORSEDDIRS;
594                case DJAVA_EXT_DIRS:
595                    return Option.EXTDIRS;
596                default:
597                    return option;
598            }
599        }
600
601        @Override
602        Collection<File> getLocation() {
603            lazy();
604            return searchPath;
605        }
606
607        @Override
608        void setLocation(Iterable<? extends File> files) {
609            if (files == null) {
610                searchPath = null;  // reset to "uninitialized"
611            } else {
612                isDefault = false;
613                SearchPath p = new SearchPath().addFiles(files, false);
614                searchPath = Collections.unmodifiableCollection(p);
615                optionValues.clear();
616            }
617        }
618
619        SearchPath computePath() throws IOException {
620            String java_home = System.getProperty("java.home");
621
622            SearchPath path = new SearchPath();
623
624            String bootclasspathOpt = optionValues.get(BOOTCLASSPATH);
625            String endorseddirsOpt = optionValues.get(ENDORSEDDIRS);
626            String extdirsOpt = optionValues.get(EXTDIRS);
627            String xbootclasspathPrependOpt = optionValues.get(XBOOTCLASSPATH_PREPEND);
628            String xbootclasspathAppendOpt = optionValues.get(XBOOTCLASSPATH_APPEND);
629            path.addFiles(xbootclasspathPrependOpt);
630
631            if (endorseddirsOpt != null) {
632                path.addDirectories(endorseddirsOpt);
633            } else {
634                path.addDirectories(System.getProperty("java.endorsed.dirs"), false);
635            }
636
637            if (bootclasspathOpt != null) {
638                path.addFiles(bootclasspathOpt);
639            } else {
640                // Standard system classes for this compiler's release.
641                Collection<File> systemClasses = systemClasses(java_home);
642                if (systemClasses != null) {
643                    path.addFiles(systemClasses, false);
644                } else {
645                    // fallback to the value of sun.boot.class.path
646                    String files = System.getProperty("sun.boot.class.path");
647                    path.addFiles(files, false);
648                }
649            }
650
651            path.addFiles(xbootclasspathAppendOpt);
652
653            // Strictly speaking, standard extensions are not bootstrap
654            // classes, but we treat them identically, so we'll pretend
655            // that they are.
656            if (extdirsOpt != null) {
657                path.addDirectories(extdirsOpt);
658            } else {
659                // Add lib/jfxrt.jar to the search path
660                File jfxrt = new File(new File(java_home, "lib"), "jfxrt.jar");
661                if (jfxrt.exists()) {
662                    path.addFile(jfxrt, false);
663                }
664                path.addDirectories(System.getProperty("java.ext.dirs"), false);
665            }
666
667            isDefault =
668                       (xbootclasspathPrependOpt == null)
669                    && (bootclasspathOpt == null)
670                    && (xbootclasspathAppendOpt == null);
671
672            return path;
673        }
674
675        /**
676         * Return a collection of files containing system classes.
677         * Returns {@code null} if not running on a modular image.
678         *
679         * @throws UncheckedIOException if an I/O errors occurs
680         */
681        private Collection<File> systemClasses(String java_home) throws IOException {
682            // Return .jimage files if available
683            Path libModules = Paths.get(java_home, "lib", "modules");
684            if (Files.exists(libModules)) {
685                try (Stream<Path> files = Files.list(libModules)) {
686                    boolean haveJImageFiles =
687                            files.anyMatch(f -> f.getFileName().toString().endsWith(".jimage"));
688                    if (haveJImageFiles) {
689                        return Collections.singleton(JRT_MARKER_FILE);
690                    }
691                }
692            }
693
694            // Temporary: if no .jimage files, return individual modules
695            if (Files.exists(libModules.resolve("java.base"))) {
696                return Files.list(libModules)
697                            .map(d -> d.resolve("classes"))
698                            .map(Path::toFile)
699                            .collect(Collectors.toList());
700            }
701
702            // Exploded module image
703            Path modules = Paths.get(java_home, "modules");
704            if (Files.isDirectory(modules.resolve("java.base"))) {
705                return Files.list(modules)
706                            .map(Path::toFile)
707                            .collect(Collectors.toList());
708            }
709
710            // not a modular image that we know about
711            return null;
712        }
713
714        private void lazy() {
715            if (searchPath == null) {
716                try {
717                searchPath = Collections.unmodifiableCollection(computePath());
718                } catch (IOException e) {
719                    // TODO: need better handling here, e.g. javac Abort?
720                    throw new UncheckedIOException(e);
721                }
722            }
723        }
724    }
725
726    Map<Location, LocationHandler> handlersForLocation;
727    Map<Option, LocationHandler> handlersForOption;
728
729    void initHandlers() {
730        handlersForLocation = new HashMap<>();
731        handlersForOption = new EnumMap<>(Option.class);
732
733        LocationHandler[] handlers = {
734            new BootClassPathLocationHandler(),
735            new ClassPathLocationHandler(),
736            new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCEPATH),
737            new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSORPATH),
738            new OutputLocationHandler((StandardLocation.CLASS_OUTPUT), Option.D),
739            new OutputLocationHandler((StandardLocation.SOURCE_OUTPUT), Option.S),
740            new OutputLocationHandler((StandardLocation.NATIVE_HEADER_OUTPUT), Option.H)
741        };
742
743        for (LocationHandler h : handlers) {
744            handlersForLocation.put(h.location, h);
745            for (Option o : h.options) {
746                handlersForOption.put(o, h);
747            }
748        }
749    }
750
751    public boolean handleOption(Option option, String value) {
752        LocationHandler h = handlersForOption.get(option);
753        return (h == null ? false : h.handleOption(option, value));
754    }
755
756    Collection<File> getLocation(Location location) {
757        LocationHandler h = getHandler(location);
758        return (h == null ? null : h.getLocation());
759    }
760
761    File getOutputLocation(Location location) {
762        if (!location.isOutputLocation()) {
763            throw new IllegalArgumentException();
764        }
765        LocationHandler h = getHandler(location);
766        return ((OutputLocationHandler) h).outputDir;
767    }
768
769    void setLocation(Location location, Iterable<? extends File> files) throws IOException {
770        LocationHandler h = getHandler(location);
771        if (h == null) {
772            if (location.isOutputLocation()) {
773                h = new OutputLocationHandler(location);
774            } else {
775                h = new SimpleLocationHandler(location);
776            }
777            handlersForLocation.put(location, h);
778        }
779        h.setLocation(files);
780    }
781
782    protected LocationHandler getHandler(Location location) {
783        location.getClass(); // null check
784        return handlersForLocation.get(location);
785    }
786
787    /**
788     * Is this the name of an archive file?
789     */
790    private boolean isArchive(File file) {
791        String n = StringUtils.toLowerCase(file.getName());
792        return fsInfo.isFile(file)
793                && (n.endsWith(".jar") || n.endsWith(".zip"));
794    }
795
796    /**
797     * Utility method for converting a search path string to an array of directory and JAR file
798     * URLs.
799     *
800     * Note that this method is called by apt and the DocletInvoker.
801     *
802     * @param path the search path string
803     * @return the resulting array of directory and JAR file URLs
804     */
805    public static URL[] pathToURLs(String path) {
806        StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
807        URL[] urls = new URL[st.countTokens()];
808        int count = 0;
809        while (st.hasMoreTokens()) {
810            URL url = fileToURL(new File(st.nextToken()));
811            if (url != null) {
812                urls[count++] = url;
813            }
814        }
815        urls = Arrays.copyOf(urls, count);
816        return urls;
817    }
818
819    /**
820     * Returns the directory or JAR file URL corresponding to the specified local file name.
821     *
822     * @param file the File object
823     * @return the resulting directory or JAR file URL, or null if unknown
824     */
825    private static URL fileToURL(File file) {
826        String name;
827        try {
828            name = file.getCanonicalPath();
829        } catch (IOException e) {
830            name = file.getAbsolutePath();
831        }
832        name = name.replace(File.separatorChar, '/');
833        if (!name.startsWith("/")) {
834            name = "/" + name;
835        }
836        // If the file does not exist, then assume that it's a directory
837        if (!file.isFile()) {
838            name = name + "/";
839        }
840        try {
841            return new URL("file", "", name);
842        } catch (MalformedURLException e) {
843            throw new IllegalArgumentException(file.toString());
844        }
845    }
846}
847