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