Locations.java revision 2674:e284f560acf6
1193323Sed/*
2193323Sed * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
3193323Sed * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4193323Sed *
5193323Sed * This code is free software; you can redistribute it and/or modify it
6193323Sed * under the terms of the GNU General Public License version 2 only, as
7193323Sed * published by the Free Software Foundation.  Oracle designates this
8193323Sed * particular file as subject to the "Classpath" exception as provided
9193323Sed * by Oracle in the LICENSE file that accompanied this code.
10193323Sed *
11193323Sed * This code is distributed in the hope that it will be useful, but WITHOUT
12193323Sed * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13193323Sed * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14193323Sed * version 2 for more details (a copy is included in the LICENSE file that
15193323Sed * accompanied this code).
16193323Sed *
17199481Srdivacky * You should have received a copy of the GNU General Public License version
18218893Sdim * 2 along with this work; if not, write to the Free Software Foundation,
19193323Sed * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20193323Sed *
21193323Sed * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22193323Sed * or visit www.oracle.com if you need additional information or have any
23198090Srdivacky * questions.
24245431Sdim */
25193323Sedpackage com.sun.tools.javac.file;
26193323Sed
27193323Sedimport java.io.File;
28198090Srdivackyimport java.io.FileNotFoundException;
29218893Sdimimport java.io.IOException;
30263509Sdimimport java.net.MalformedURLException;
31206274Srdivackyimport java.net.URL;
32245431Sdimimport java.nio.file.Files;
33245431Sdimimport java.nio.file.Path;
34193323Sedimport java.nio.file.Paths;
35193323Sedimport java.util.ArrayList;
36212904Sdimimport java.util.Arrays;
37193323Sedimport java.util.Collection;
38193323Sedimport java.util.Collections;
39193323Sedimport java.util.EnumMap;
40212904Sdimimport java.util.EnumSet;
41193323Sedimport java.util.HashMap;
42210299Sedimport java.util.HashSet;
43210299Sedimport java.util.Iterator;
44212904Sdimimport java.util.LinkedHashSet;
45193323Sedimport java.util.Map;
46193323Sedimport java.util.Set;
47193323Sedimport java.util.regex.Pattern;
48193323Sedimport java.util.stream.Stream;
49193323Sedimport java.util.zip.ZipFile;
50193323Sed
51193323Sedimport javax.tools.JavaFileManager;
52193323Sedimport javax.tools.JavaFileManager.Location;
53193323Sedimport javax.tools.StandardJavaFileManager;
54193323Sedimport javax.tools.StandardLocation;
55193323Sed
56193323Sedimport com.sun.tools.javac.code.Lint;
57193323Sedimport com.sun.tools.javac.main.Option;
58193323Sedimport com.sun.tools.javac.util.ListBuffer;
59193323Sedimport com.sun.tools.javac.util.Log;
60193323Sedimport com.sun.tools.javac.util.StringUtils;
61193323Sed
62193323Sedimport static javax.tools.StandardLocation.CLASS_PATH;
63193323Sedimport static javax.tools.StandardLocation.PLATFORM_CLASS_PATH;
64193323Sedimport static javax.tools.StandardLocation.SOURCE_PATH;
65193323Sed
66193323Sedimport static com.sun.tools.javac.main.Option.BOOTCLASSPATH;
67193323Sedimport static com.sun.tools.javac.main.Option.DJAVA_ENDORSED_DIRS;
68193323Sedimport static com.sun.tools.javac.main.Option.DJAVA_EXT_DIRS;
69193323Sedimport static com.sun.tools.javac.main.Option.ENDORSEDDIRS;
70193323Sedimport static com.sun.tools.javac.main.Option.EXTDIRS;
71193323Sedimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH;
72193323Sedimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_APPEND;
73193323Sedimport static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_PREPEND;
74193323Sed
75193323Sed/**
76193323Sed * This class converts command line arguments, environment variables and system properties (in
77193323Sed * File.pathSeparator-separated String form) into a boot class path, user class path, and source
78193323Sed * path (in {@code Collection<String>} form).
79193323Sed *
80193323Sed * <p>
81193323Sed * <b>This is NOT part of any supported API. If you write code that depends on this, you do so at
82193323Sed * your own risk. This code and its internal interfaces are subject to change or deletion without
83193323Sed * notice.</b>
84198396Srdivacky */
85198396Srdivackypublic class Locations {
86198396Srdivacky
87212904Sdim    /**
88193323Sed     * The log to use for warning output
89193323Sed     */
90193323Sed    private Log log;
91193323Sed
92193323Sed    /**
93193323Sed     * Access to (possibly cached) file info
94193323Sed     */
95193323Sed    private FSInfo fsInfo;
96193323Sed
97193323Sed    /**
98193323Sed     * Whether to warn about non-existent path elements
99193323Sed     */
100212904Sdim    private boolean warn;
101198396Srdivacky
102198396Srdivacky    public Locations() {
103198396Srdivacky        initHandlers();
104212904Sdim    }
105212904Sdim
106212904Sdim    // could replace Lint by "boolean warn"
107212904Sdim    public void update(Log log, Lint lint, FSInfo fsInfo) {
108212904Sdim        this.log = log;
109245431Sdim        warn = lint.isEnabled(Lint.LintCategory.PATH);
110245431Sdim        this.fsInfo = fsInfo;
111245431Sdim    }
112245431Sdim
113212904Sdim    public Collection<Path> bootClassPath() {
114212904Sdim        return getLocation(PLATFORM_CLASS_PATH);
115212904Sdim    }
116212904Sdim
117212904Sdim    public boolean isDefaultBootClassPath() {
118245431Sdim        BootClassPathLocationHandler h
119198396Srdivacky                = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH);
120245431Sdim        return h.isDefault();
121193323Sed    }
122193323Sed
123263509Sdim    boolean isDefaultBootClassPathRtJar(Path file) {
124263509Sdim        BootClassPathLocationHandler h
125193323Sed                = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH);
126193323Sed        return h.isDefaultRtJar(file);
127193323Sed    }
128193323Sed
129193323Sed    public Collection<Path> userClassPath() {
130193323Sed        return getLocation(CLASS_PATH);
131193323Sed    }
132193323Sed
133193323Sed    public Collection<Path> sourcePath() {
134193323Sed        Collection<Path> p = getLocation(SOURCE_PATH);
135193323Sed        // TODO: this should be handled by the LocationHandler
136193323Sed        return p == null || p.isEmpty() ? null : p;
137193323Sed    }
138193323Sed
139193323Sed    /**
140193323Sed     * Split a search path into its elements. Empty path elements will be ignored.
141193323Sed     *
142193323Sed     * @param searchPath The search path to be split
143193323Sed     * @return The elements of the path
144208599Srdivacky     */
145208599Srdivacky    private static Iterable<Path> getPathEntries(String searchPath) {
146208599Srdivacky        return getPathEntries(searchPath, null);
147208599Srdivacky    }
148193323Sed
149193323Sed    /**
150193323Sed     * Split a search path into its elements. If emptyPathDefault is not null, all empty elements in the
151193323Sed     * path, including empty elements at either end of the path, will be replaced with the value of
152193323Sed     * emptyPathDefault.
153193323Sed     *
154212904Sdim     * @param searchPath The search path to be split
155193323Sed     * @param emptyPathDefault The value to substitute for empty path elements, or null, to ignore
156198090Srdivacky     * empty path elements
157198090Srdivacky     * @return The elements of the path
158198090Srdivacky     */
159198090Srdivacky    private static Iterable<Path> getPathEntries(String searchPath, Path emptyPathDefault) {
160198090Srdivacky        ListBuffer<Path> entries = new ListBuffer<>();
161198090Srdivacky        for (String s: searchPath.split(Pattern.quote(File.pathSeparator), -1)) {
162198090Srdivacky            if (s.isEmpty()) {
163198090Srdivacky                if (emptyPathDefault != null) {
164193323Sed                    entries.add(emptyPathDefault);
165212904Sdim                }
166212904Sdim            } else {
167193323Sed                entries.add(Paths.get(s));
168212904Sdim            }
169193323Sed        }
170193323Sed        return entries;
171193323Sed    }
172193323Sed
173193323Sed    /**
174193323Sed     * Utility class to help evaluate a path option. Duplicate entries are ignored, jar class paths
175208599Srdivacky     * can be expanded.
176208599Srdivacky     */
177208599Srdivacky    private class SearchPath extends LinkedHashSet<Path> {
178208599Srdivacky
179208599Srdivacky        private static final long serialVersionUID = 0;
180208599Srdivacky
181193323Sed        private boolean expandJarClassPaths = false;
182193323Sed        private final Set<Path> canonicalValues = new HashSet<>();
183193323Sed
184193323Sed        public SearchPath expandJarClassPaths(boolean x) {
185193323Sed            expandJarClassPaths = x;
186226890Sdim            return this;
187226890Sdim        }
188226890Sdim
189226890Sdim        /**
190193323Sed         * What to use when path element is the empty string
191193323Sed         */
192193323Sed        private Path emptyPathDefault = null;
193193323Sed
194193323Sed        public SearchPath emptyPathDefault(Path x) {
195193323Sed            emptyPathDefault = x;
196193323Sed            return this;
197212904Sdim        }
198193323Sed
199193323Sed        public SearchPath addDirectories(String dirs, boolean warn) {
200193323Sed            boolean prev = expandJarClassPaths;
201193323Sed            expandJarClassPaths = true;
202193323Sed            try {
203198090Srdivacky                if (dirs != null) {
204198090Srdivacky                    for (Path dir : getPathEntries(dirs)) {
205198090Srdivacky                        addDirectory(dir, warn);
206198090Srdivacky                    }
207212904Sdim                }
208212904Sdim                return this;
209212904Sdim            } finally {
210212904Sdim                expandJarClassPaths = prev;
211212904Sdim            }
212212904Sdim        }
213212904Sdim
214212904Sdim        public SearchPath addDirectories(String dirs) {
215212904Sdim            return addDirectories(dirs, warn);
216212904Sdim        }
217212904Sdim
218212904Sdim        private void addDirectory(Path dir, boolean warn) {
219212904Sdim            if (!Files.isDirectory(dir)) {
220212904Sdim                if (warn) {
221212904Sdim                    log.warning(Lint.LintCategory.PATH,
222212904Sdim                            "dir.path.element.not.found", dir);
223252723Sdim                }
224252723Sdim                return;
225263509Sdim            }
226263765Sdim
227263765Sdim            try (Stream<Path> s = Files.list(dir)) {
228263765Sdim                s.filter(dirEntry -> isArchive(dirEntry))
229263765Sdim                        .forEach(dirEntry -> addFile(dirEntry, warn));
230263509Sdim            } catch (IOException ignore) {
231193323Sed            }
232263509Sdim        }
233263509Sdim
234193323Sed        public SearchPath addFiles(String files, boolean warn) {
235193323Sed            if (files != null) {
236193323Sed                addFiles(getPathEntries(files, emptyPathDefault), warn);
237208599Srdivacky            }
238208599Srdivacky            return this;
239193323Sed        }
240193323Sed
241226890Sdim        public SearchPath addFiles(String files) {
242193323Sed            return addFiles(files, warn);
243198090Srdivacky        }
244212904Sdim
245212904Sdim        public SearchPath addFiles(Iterable<? extends Path> files, boolean warn) {
246212904Sdim            if (files != null) {
247263765Sdim                for (Path file : files) {
248193323Sed                    addFile(file, warn);
249193323Sed                }
250193323Sed            }
251193323Sed            return this;
252193323Sed        }
253193323Sed
254193323Sed        public SearchPath addFiles(Iterable<? extends Path> files) {
255193323Sed            return addFiles(files, warn);
256193323Sed        }
257193323Sed
258193323Sed        public void addFile(Path file, boolean warn) {
259193323Sed            if (contains(file)) {
260193323Sed                // discard duplicates
261193323Sed                return;
262193323Sed            }
263193323Sed
264193323Sed            if (!fsInfo.exists(file)) {
265193323Sed                /* No such file or directory exists */
266193323Sed                if (warn) {
267226890Sdim                    log.warning(Lint.LintCategory.PATH,
268226890Sdim                            "path.element.not.found", file);
269226890Sdim                }
270226890Sdim                super.add(file);
271226890Sdim                return;
272193323Sed            }
273193323Sed
274193323Sed            Path canonFile = fsInfo.getCanonicalFile(file);
275193323Sed            if (canonicalValues.contains(canonFile)) {
276193323Sed                /* Discard duplicates and avoid infinite recursion */
277193323Sed                return;
278212904Sdim            }
279212904Sdim
280208599Srdivacky            if (fsInfo.isFile(file)) {
281208599Srdivacky                /* File is an ordinary file. */
282208599Srdivacky                if (!isArchive(file)) {
283208599Srdivacky                    /* Not a recognized extension; open it to see if
284193323Sed                     it looks like a valid zip file. */
285193323Sed                    try {
286193323Sed                        ZipFile z = new ZipFile(file.toFile());
287193323Sed                        z.close();
288193323Sed                        if (warn) {
289193323Sed                            log.warning(Lint.LintCategory.PATH,
290193323Sed                                    "unexpected.archive.file", file);
291193323Sed                        }
292212904Sdim                    } catch (IOException e) {
293193323Sed                        // FIXME: include e.getLocalizedMessage in warning
294193323Sed                        if (warn) {
295212904Sdim                            log.warning(Lint.LintCategory.PATH,
296193323Sed                                    "invalid.archive.file", file);
297193323Sed                        }
298193323Sed                        return;
299212904Sdim                    }
300212904Sdim                }
301212904Sdim            }
302212904Sdim
303212904Sdim            /* Now what we have left is either a directory or a file name
304212904Sdim             conforming to archive naming convention */
305212904Sdim            super.add(file);
306212904Sdim            canonicalValues.add(canonFile);
307212904Sdim
308212904Sdim            if (expandJarClassPaths && fsInfo.isFile(file)) {
309212904Sdim                addJarClassPath(file, warn);
310212904Sdim            }
311212904Sdim        }
312212904Sdim
313212904Sdim        // Adds referenced classpath elements from a jar's Class-Path
314212904Sdim        // Manifest entry.  In some future release, we may want to
315212904Sdim        // update this code to recognize URLs rather than simple
316212904Sdim        // filenames, but if we do, we should redo all path-related code.
317212904Sdim        private void addJarClassPath(Path jarFile, boolean warn) {
318212904Sdim            try {
319212904Sdim                for (Path f : fsInfo.getJarClassPath(jarFile)) {
320212904Sdim                    addFile(f, warn);
321212904Sdim                }
322212904Sdim            } catch (IOException e) {
323212904Sdim                log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e));
324212904Sdim            }
325212904Sdim        }
326212904Sdim    }
327212904Sdim
328212904Sdim    /**
329212904Sdim     * Base class for handling support for the representation of Locations. Implementations are
330212904Sdim     * responsible for handling the interactions between the command line options for a location,
331212904Sdim     * and API access via setLocation.
332212904Sdim     *
333212904Sdim     * @see #initHandlers
334212904Sdim     * @see #getHandler
335212904Sdim     */
336212904Sdim    protected abstract class LocationHandler {
337212904Sdim
338212904Sdim        final Location location;
339212904Sdim        final Set<Option> options;
340212904Sdim
341212904Sdim        /**
342212904Sdim         * Create a handler. The location and options provide a way to map from a location or an
343212904Sdim         * option to the corresponding handler.
344212904Sdim         *
345212904Sdim         * @param location the location for which this is the handler
346212904Sdim         * @param options the options affecting this location
347212904Sdim         * @see #initHandlers
348212904Sdim         */
349212904Sdim        protected LocationHandler(Location location, Option... options) {
350193323Sed            this.location = location;
351193323Sed            this.options = options.length == 0
352193323Sed                    ? EnumSet.noneOf(Option.class)
353193323Sed                    : EnumSet.copyOf(Arrays.asList(options));
354193323Sed        }
355193323Sed
356193323Sed        /**
357193323Sed         * @see JavaFileManager#handleOption
358193323Sed         */
359193323Sed        abstract boolean handleOption(Option option, String value);
360193323Sed
361193323Sed        /**
362193323Sed         * @see StandardJavaFileManager#getLocation
363193323Sed         */
364193323Sed        abstract Collection<Path> getLocation();
365193323Sed
366193323Sed        /**
367193323Sed         * @see StandardJavaFileManager#setLocation
368193323Sed         */
369193323Sed        abstract void setLocation(Iterable<? extends Path> files) throws IOException;
370193323Sed    }
371193323Sed
372193323Sed    /**
373193323Sed     * General purpose implementation for output locations, such as -d/CLASS_OUTPUT and
374193323Sed     * -s/SOURCE_OUTPUT. All options are treated as equivalent (i.e. aliases.) The value is a single
375193323Sed     * file, possibly null.
376193323Sed     */
377245431Sdim    private class OutputLocationHandler extends LocationHandler {
378193323Sed
379193323Sed        private Path outputDir;
380245431Sdim
381245431Sdim        OutputLocationHandler(Location location, Option... options) {
382245431Sdim            super(location, options);
383245431Sdim        }
384245431Sdim
385245431Sdim        @Override
386245431Sdim        boolean handleOption(Option option, String value) {
387245431Sdim            if (!options.contains(option)) {
388212904Sdim                return false;
389212904Sdim            }
390212904Sdim
391212904Sdim            // TODO: could/should validate outputDir exists and is a directory
392212904Sdim            // need to decide how best to report issue for benefit of
393212904Sdim            // direct API call on JavaFileManager.handleOption(specifies IAE)
394212904Sdim            // vs. command line decoding.
395212904Sdim            outputDir = (value == null) ? null : Paths.get(value);
396193323Sed            return true;
397193323Sed        }
398193323Sed
399193323Sed        @Override
400193323Sed        Collection<Path> getLocation() {
401193323Sed            return (outputDir == null) ? null : Collections.singleton(outputDir);
402193323Sed        }
403193323Sed
404193323Sed        @Override
405193323Sed        void setLocation(Iterable<? extends Path> files) throws IOException {
406193323Sed            if (files == null) {
407193323Sed                outputDir = null;
408193323Sed            } else {
409193323Sed                Iterator<? extends Path> pathIter = files.iterator();
410193323Sed                if (!pathIter.hasNext()) {
411193323Sed                    throw new IllegalArgumentException("empty path for directory");
412193323Sed                }
413193323Sed                Path dir = pathIter.next();
414193323Sed                if (pathIter.hasNext()) {
415193323Sed                    throw new IllegalArgumentException("path too long for directory");
416193323Sed                }
417193323Sed                if (!Files.exists(dir)) {
418193323Sed                    throw new FileNotFoundException(dir + ": does not exist");
419193323Sed                } else if (!Files.isDirectory(dir)) {
420193323Sed                    throw new IOException(dir + ": not a directory");
421193323Sed                }
422193323Sed                outputDir = dir;
423193323Sed            }
424193323Sed        }
425193323Sed    }
426193323Sed
427212904Sdim    /**
428252723Sdim     * General purpose implementation for search path locations, such as -sourcepath/SOURCE_PATH and
429252723Sdim     * -processorPath/ANNOTATION_PROCESS_PATH. All options are treated as equivalent (i.e. aliases.)
430252723Sdim     * The value is an ordered set of files and/or directories.
431193323Sed     */
432193323Sed    private class SimpleLocationHandler extends LocationHandler {
433193323Sed
434212904Sdim        protected Collection<Path> searchPath;
435193323Sed
436193323Sed        SimpleLocationHandler(Location location, Option... options) {
437193323Sed            super(location, options);
438193323Sed        }
439212904Sdim
440212904Sdim        @Override
441193323Sed        boolean handleOption(Option option, String value) {
442193323Sed            if (!options.contains(option)) {
443193323Sed                return false;
444212904Sdim            }
445245431Sdim            searchPath = value == null ? null
446245431Sdim                    : Collections.unmodifiableCollection(createPath().addFiles(value));
447252723Sdim            return true;
448200581Srdivacky        }
449208599Srdivacky
450208599Srdivacky        @Override
451208599Srdivacky        Collection<Path> getLocation() {
452208599Srdivacky            return searchPath;
453208599Srdivacky        }
454208599Srdivacky
455208599Srdivacky        @Override
456193323Sed        void setLocation(Iterable<? extends Path> files) {
457193323Sed            SearchPath p;
458193323Sed            if (files == null) {
459263765Sdim                p = computePath(null);
460263765Sdim            } else {
461263765Sdim                p = createPath().addFiles(files);
462263765Sdim            }
463193323Sed            searchPath = Collections.unmodifiableCollection(p);
464193323Sed        }
465193323Sed
466193323Sed        protected SearchPath computePath(String value) {
467193323Sed            return createPath().addFiles(value);
468193323Sed        }
469193323Sed
470193323Sed        protected SearchPath createPath() {
471193323Sed            return new SearchPath();
472193323Sed        }
473193323Sed    }
474193323Sed
475193323Sed    /**
476210299Sed     * Subtype of SimpleLocationHandler for -classpath/CLASS_PATH. If no value is given, a default
477212904Sdim     * is provided, based on system properties and other values.
478212904Sdim     */
479193323Sed    private class ClassPathLocationHandler extends SimpleLocationHandler {
480193323Sed
481193323Sed        ClassPathLocationHandler() {
482193323Sed            super(StandardLocation.CLASS_PATH,
483193323Sed                    Option.CLASSPATH, Option.CP);
484193323Sed        }
485193323Sed
486193323Sed        @Override
487193323Sed        Collection<Path> getLocation() {
488193323Sed            lazy();
489193323Sed            return searchPath;
490193323Sed        }
491193323Sed
492193323Sed        @Override
493198396Srdivacky        protected SearchPath computePath(String value) {
494198396Srdivacky            String cp = value;
495198396Srdivacky
496198396Srdivacky            // CLASSPATH environment variable when run from `javac'.
497198396Srdivacky            if (cp == null) {
498235633Sdim                cp = System.getProperty("env.class.path");
499198396Srdivacky            }
500198396Srdivacky
501193323Sed            // If invoked via a java VM (not the javac launcher), use the
502193323Sed            // platform class path
503193323Sed            if (cp == null && System.getProperty("application.home") == null) {
504193323Sed                cp = System.getProperty("java.class.path");
505193323Sed            }
506193323Sed
507193323Sed            // Default to current working directory.
508193323Sed            if (cp == null) {
509212904Sdim                cp = ".";
510212904Sdim            }
511193323Sed
512212904Sdim            return createPath().addFiles(cp);
513252723Sdim        }
514193323Sed
515212904Sdim        @Override
516212904Sdim        protected SearchPath createPath() {
517212904Sdim            return new SearchPath()
518199481Srdivacky                    .expandJarClassPaths(true) // Only search user jars for Class-Paths
519252723Sdim                    .emptyPathDefault(Paths.get("."));  // Empty path elt ==> current directory
520199481Srdivacky        }
521193323Sed
522193323Sed        private void lazy() {
523193323Sed            if (searchPath == null) {
524193323Sed                setLocation(null);
525193323Sed            }
526193323Sed        }
527193323Sed    }
528193323Sed
529193323Sed    /**
530193323Sed     * Custom subtype of LocationHandler for PLATFORM_CLASS_PATH. Various options are supported for
531193323Sed     * different components of the platform class path. Setting a value with setLocation overrides
532193323Sed     * all existing option values. Setting any option overrides any value set with setLocation, and
533263765Sdim     * reverts to using default values for options that have not been set. Setting -bootclasspath or
534199481Srdivacky     * -Xbootclasspath overrides any existing value for -Xbootclasspath/p: and -Xbootclasspath/a:.
535193323Sed     */
536193323Sed    private class BootClassPathLocationHandler extends LocationHandler {
537193323Sed
538193323Sed        private Collection<Path> searchPath;
539193323Sed        final Map<Option, String> optionValues = new EnumMap<>(Option.class);
540193323Sed
541193323Sed        /**
542193323Sed         * rt.jar as found on the default bootclasspath. If the user specified a bootclasspath, null
543212904Sdim         * is used.
544193323Sed         */
545193323Sed        private Path defaultBootClassPathRtJar = null;
546193323Sed
547198090Srdivacky        /**
548198090Srdivacky         * Is bootclasspath the default?
549198090Srdivacky         */
550198090Srdivacky        private boolean isDefaultBootClassPath;
551198090Srdivacky
552198090Srdivacky        BootClassPathLocationHandler() {
553198090Srdivacky            super(StandardLocation.PLATFORM_CLASS_PATH,
554198090Srdivacky                    Option.BOOTCLASSPATH, Option.XBOOTCLASSPATH,
555198090Srdivacky                    Option.XBOOTCLASSPATH_PREPEND,
556198090Srdivacky                    Option.XBOOTCLASSPATH_APPEND,
557198090Srdivacky                    Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS,
558198090Srdivacky                    Option.EXTDIRS, Option.DJAVA_EXT_DIRS);
559198090Srdivacky        }
560198090Srdivacky
561198090Srdivacky        boolean isDefault() {
562198090Srdivacky            lazy();
563193323Sed            return isDefaultBootClassPath;
564212904Sdim        }
565193323Sed
566198090Srdivacky        boolean isDefaultRtJar(Path file) {
567193323Sed            lazy();
568198090Srdivacky            return file.equals(defaultBootClassPathRtJar);
569193323Sed        }
570193323Sed
571193323Sed        @Override
572193323Sed        boolean handleOption(Option option, String value) {
573193323Sed            if (!options.contains(option)) {
574193323Sed                return false;
575            }
576
577            option = canonicalize(option);
578            optionValues.put(option, value);
579            if (option == BOOTCLASSPATH) {
580                optionValues.remove(XBOOTCLASSPATH_PREPEND);
581                optionValues.remove(XBOOTCLASSPATH_APPEND);
582            }
583            searchPath = null;  // reset to "uninitialized"
584            return true;
585        }
586        // where
587        // TODO: would be better if option aliasing was handled at a higher
588        // level
589        private Option canonicalize(Option option) {
590            switch (option) {
591                case XBOOTCLASSPATH:
592                    return Option.BOOTCLASSPATH;
593                case DJAVA_ENDORSED_DIRS:
594                    return Option.ENDORSEDDIRS;
595                case DJAVA_EXT_DIRS:
596                    return Option.EXTDIRS;
597                default:
598                    return option;
599            }
600        }
601
602        @Override
603        Collection<Path> getLocation() {
604            lazy();
605            return searchPath;
606        }
607
608        @Override
609        void setLocation(Iterable<? extends Path> files) {
610            if (files == null) {
611                searchPath = null;  // reset to "uninitialized"
612            } else {
613                defaultBootClassPathRtJar = null;
614                isDefaultBootClassPath = false;
615                SearchPath p = new SearchPath().addFiles(files, false);
616                searchPath = Collections.unmodifiableCollection(p);
617                optionValues.clear();
618            }
619        }
620
621        SearchPath computePath() {
622            defaultBootClassPathRtJar = null;
623            SearchPath path = new SearchPath();
624
625            String bootclasspathOpt = optionValues.get(BOOTCLASSPATH);
626            String endorseddirsOpt = optionValues.get(ENDORSEDDIRS);
627            String extdirsOpt = optionValues.get(EXTDIRS);
628            String xbootclasspathPrependOpt = optionValues.get(XBOOTCLASSPATH_PREPEND);
629            String xbootclasspathAppendOpt = optionValues.get(XBOOTCLASSPATH_APPEND);
630            path.addFiles(xbootclasspathPrependOpt);
631
632            if (endorseddirsOpt != null) {
633                path.addDirectories(endorseddirsOpt);
634            } else {
635                path.addDirectories(System.getProperty("java.endorsed.dirs"), false);
636            }
637
638            if (bootclasspathOpt != null) {
639                path.addFiles(bootclasspathOpt);
640            } else {
641                // Standard system classes for this compiler's release.
642                String files = System.getProperty("sun.boot.class.path");
643                path.addFiles(files, false);
644                Path rt_jar = Paths.get("rt.jar");
645                for (Path file : getPathEntries(files)) {
646                    if (file.getFileName().equals(rt_jar)) {
647                        defaultBootClassPathRtJar = file;
648                    }
649                }
650            }
651
652            path.addFiles(xbootclasspathAppendOpt);
653
654            // Strictly speaking, standard extensions are not bootstrap
655            // classes, but we treat them identically, so we'll pretend
656            // that they are.
657            if (extdirsOpt != null) {
658                path.addDirectories(extdirsOpt);
659            } else {
660                path.addDirectories(System.getProperty("java.ext.dirs"), false);
661            }
662
663            isDefaultBootClassPath
664                    = (xbootclasspathPrependOpt == null)
665                    && (bootclasspathOpt == null)
666                    && (xbootclasspathAppendOpt == null);
667
668            return path;
669        }
670
671        private void lazy() {
672            if (searchPath == null) {
673                searchPath = Collections.unmodifiableCollection(computePath());
674            }
675        }
676    }
677
678    Map<Location, LocationHandler> handlersForLocation;
679    Map<Option, LocationHandler> handlersForOption;
680
681    void initHandlers() {
682        handlersForLocation = new HashMap<>();
683        handlersForOption = new EnumMap<>(Option.class);
684
685        LocationHandler[] handlers = {
686            new BootClassPathLocationHandler(),
687            new ClassPathLocationHandler(),
688            new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCEPATH),
689            new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSORPATH),
690            new OutputLocationHandler((StandardLocation.CLASS_OUTPUT), Option.D),
691            new OutputLocationHandler((StandardLocation.SOURCE_OUTPUT), Option.S),
692            new OutputLocationHandler((StandardLocation.NATIVE_HEADER_OUTPUT), Option.H)
693        };
694
695        for (LocationHandler h : handlers) {
696            handlersForLocation.put(h.location, h);
697            for (Option o : h.options) {
698                handlersForOption.put(o, h);
699            }
700        }
701    }
702
703    public boolean handleOption(Option option, String value) {
704        LocationHandler h = handlersForOption.get(option);
705        return (h == null ? false : h.handleOption(option, value));
706    }
707
708    Collection<Path> getLocation(Location location) {
709        LocationHandler h = getHandler(location);
710        return (h == null ? null : h.getLocation());
711    }
712
713    Path getOutputLocation(Location location) {
714        if (!location.isOutputLocation()) {
715            throw new IllegalArgumentException();
716        }
717        LocationHandler h = getHandler(location);
718        return ((OutputLocationHandler) h).outputDir;
719    }
720
721    void setLocation(Location location, Iterable<? extends Path> files) throws IOException {
722        LocationHandler h = getHandler(location);
723        if (h == null) {
724            if (location.isOutputLocation()) {
725                h = new OutputLocationHandler(location);
726            } else {
727                h = new SimpleLocationHandler(location);
728            }
729            handlersForLocation.put(location, h);
730        }
731        h.setLocation(files);
732    }
733
734    protected LocationHandler getHandler(Location location) {
735        location.getClass(); // null check
736        return handlersForLocation.get(location);
737    }
738
739    /**
740     * Is this the name of an archive file?
741     */
742    private boolean isArchive(Path file) {
743        String n = StringUtils.toLowerCase(file.getFileName().toString());
744        return fsInfo.isFile(file)
745                && (n.endsWith(".jar") || n.endsWith(".zip"));
746    }
747
748    /**
749     * Utility method for converting a search path string to an array of directory and JAR file
750     * URLs.
751     *
752     * Note that this method is called by the DocletInvoker.
753     *
754     * @param path the search path string
755     * @return the resulting array of directory and JAR file URLs
756     */
757    public static URL[] pathToURLs(String path) {
758        java.util.List<URL> urls = new ArrayList<>();
759        for (String s: path.split(Pattern.quote(File.pathSeparator))) {
760            if (!s.isEmpty()) {
761                URL url = fileToURL(Paths.get(s));
762                if (url != null) {
763                    urls.add(url);
764                }
765            }
766        }
767        return urls.toArray(new URL[urls.size()]);
768    }
769
770    /**
771     * Returns the directory or JAR file URL corresponding to the specified local file name.
772     *
773     * @param file the Path object
774     * @return the resulting directory or JAR file URL, or null if unknown
775     */
776    private static URL fileToURL(Path file) {
777        Path p;
778        try {
779            p = file.toRealPath();
780        } catch (IOException e) {
781            p = file.toAbsolutePath();
782        }
783        try {
784            return p.normalize().toUri().toURL();
785        } catch (MalformedURLException e) {
786            return null;
787        }
788    }
789}
790