001    /*
002     * Copyright 2003-2008 Sun Microsystems, Inc.  All Rights Reserved.
003     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004     *
005     * This code is free software; you can redistribute it and/or modify it
006     * under the terms of the GNU General Public License version 2 only, as
007     * published by the Free Software Foundation.  Sun designates this
008     * particular file as subject to the "Classpath" exception as provided
009     * by Sun in the LICENSE file that accompanied this code.
010     *
011     * This code is distributed in the hope that it will be useful, but WITHOUT
012     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014     * version 2 for more details (a copy is included in the LICENSE file that
015     * accompanied this code).
016     *
017     * You should have received a copy of the GNU General Public License version
018     * 2 along with this work; if not, write to the Free Software Foundation,
019     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020     *
021     * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022     * CA 95054 USA or visit www.sun.com if you need additional information or
023     * have any questions.
024     */
025    
026    package com.sun.tools.javac.file;
027    
028    import java.io.File;
029    import java.io.IOException;
030    import java.util.HashMap;
031    import java.util.HashSet;
032    import java.util.Map;
033    import java.util.Set;
034    import java.util.Collection;
035    import java.util.Collections;
036    import java.util.LinkedHashSet;
037    import java.util.zip.ZipFile;
038    import javax.tools.JavaFileManager.Location;
039    
040    import com.sun.tools.javac.code.Lint;
041    import com.sun.tools.javac.util.Context;
042    import com.sun.tools.javac.util.ListBuffer;
043    import com.sun.tools.javac.util.Log;
044    import com.sun.tools.javac.util.Options;
045    
046    import static javax.tools.StandardLocation.*;
047    import static com.sun.tools.javac.main.OptionName.*;
048    
049    /** This class converts command line arguments, environment variables
050     *  and system properties (in File.pathSeparator-separated String form)
051     *  into a boot class path, user class path, and source path (in
052     *  Collection<String> form).
053     *
054     *  <p><b>This is NOT part of any API supported by Sun Microsystems.  If
055     *  you write code that depends on this, you do so at your own risk.
056     *  This code and its internal interfaces are subject to change or
057     *  deletion without notice.</b>
058     */
059    public class Paths {
060    
061        /** The context key for the todo list */
062        protected static final Context.Key<Paths> pathsKey =
063            new Context.Key<Paths>();
064    
065        /** Get the Paths instance for this context.
066         *  @param context the context
067         *  @return the Paths instance for this context
068         */
069        public static Paths instance(Context context) {
070            Paths instance = context.get(pathsKey);
071            if (instance == null)
072                instance = new Paths(context);
073            return instance;
074        }
075    
076        /** The log to use for warning output */
077        private Log log;
078    
079        /** Collection of command-line options */
080        private Options options;
081    
082        /** Handler for -Xlint options */
083        private Lint lint;
084    
085        /** Access to (possibly cached) file info */
086        private FSInfo fsInfo;
087    
088        protected Paths(Context context) {
089            context.put(pathsKey, this);
090            pathsForLocation = new HashMap<Location,Path>(16);
091            setContext(context);
092        }
093    
094        void setContext(Context context) {
095            log = Log.instance(context);
096            options = Options.instance(context);
097            lint = Lint.instance(context);
098            fsInfo = FSInfo.instance(context);
099        }
100    
101        /** Whether to warn about non-existent path elements */
102        private boolean warn;
103    
104        private Map<Location, Path> pathsForLocation;
105    
106        private boolean inited = false; // TODO? caching bad?
107    
108        /**
109         * rt.jar as found on the default bootclass path.  If the user specified a
110         * bootclasspath, null is used.
111         */
112        private File bootClassPathRtJar = null;
113    
114        Path getPathForLocation(Location location) {
115            Path path = pathsForLocation.get(location);
116            if (path == null)
117                setPathForLocation(location, null);
118            return pathsForLocation.get(location);
119        }
120    
121        void setPathForLocation(Location location, Iterable<? extends File> path) {
122            // TODO? if (inited) throw new IllegalStateException
123            // TODO: otherwise reset sourceSearchPath, classSearchPath as needed
124            Path p;
125            if (path == null) {
126                if (location == CLASS_PATH)
127                    p = computeUserClassPath();
128                else if (location == PLATFORM_CLASS_PATH)
129                    p = computeBootClassPath();
130                else if (location == ANNOTATION_PROCESSOR_PATH)
131                    p = computeAnnotationProcessorPath();
132                else if (location == SOURCE_PATH)
133                    p = computeSourcePath();
134                else
135                    // no defaults for other paths
136                    p = null;
137            } else {
138                p = new Path();
139                for (File f: path)
140                    p.addFile(f, warn); // TODO: is use of warn appropriate?
141            }
142            pathsForLocation.put(location, p);
143        }
144    
145        protected void lazy() {
146            if (!inited) {
147                warn = lint.isEnabled(Lint.LintCategory.PATH);
148    
149                pathsForLocation.put(PLATFORM_CLASS_PATH, computeBootClassPath());
150                pathsForLocation.put(CLASS_PATH, computeUserClassPath());
151                pathsForLocation.put(SOURCE_PATH, computeSourcePath());
152    
153                inited = true;
154            }
155        }
156    
157        public Collection<File> bootClassPath() {
158            lazy();
159            return Collections.unmodifiableCollection(getPathForLocation(PLATFORM_CLASS_PATH));
160        }
161        public Collection<File> userClassPath() {
162            lazy();
163            return Collections.unmodifiableCollection(getPathForLocation(CLASS_PATH));
164        }
165        public Collection<File> sourcePath() {
166            lazy();
167            Path p = getPathForLocation(SOURCE_PATH);
168            return p == null || p.size() == 0
169                ? null
170                : Collections.unmodifiableCollection(p);
171        }
172    
173        boolean isBootClassPathRtJar(File file) {
174            return file.equals(bootClassPathRtJar);
175        }
176    
177        /**
178         * Split a path into its elements. Empty path elements will be ignored.
179         * @param path The path to be split
180         * @return The elements of the path
181         */
182        private static Iterable<File> getPathEntries(String path) {
183            return getPathEntries(path, null);
184        }
185    
186        /**
187         * Split a path into its elements. If emptyPathDefault is not null, all
188         * empty elements in the path, including empty elements at either end of
189         * the path, will be replaced with the value of emptyPathDefault.
190         * @param path The path to be split
191         * @param emptyPathDefault The value to substitute for empty path elements,
192         *  or null, to ignore empty path elements
193         * @return The elements of the path
194         */
195        private static Iterable<File> getPathEntries(String path, File emptyPathDefault) {
196            ListBuffer<File> entries = new ListBuffer<File>();
197            int start = 0;
198            while (start <= path.length()) {
199                int sep = path.indexOf(File.pathSeparatorChar, start);
200                if (sep == -1)
201                    sep = path.length();
202                if (start < sep)
203                    entries.add(new File(path.substring(start, sep)));
204                else if (emptyPathDefault != null)
205                    entries.add(emptyPathDefault);
206                start = sep + 1;
207            }
208            return entries;
209        }
210    
211        private class Path extends LinkedHashSet<File> {
212            private static final long serialVersionUID = 0;
213    
214            private boolean expandJarClassPaths = false;
215            private Set<File> canonicalValues = new HashSet<File>();
216    
217            public Path expandJarClassPaths(boolean x) {
218                expandJarClassPaths = x;
219                return this;
220            }
221    
222            /** What to use when path element is the empty string */
223            private File emptyPathDefault = null;
224    
225            public Path emptyPathDefault(File x) {
226                emptyPathDefault = x;
227                return this;
228            }
229    
230            public Path() { super(); }
231    
232            public Path addDirectories(String dirs, boolean warn) {
233                if (dirs != null)
234                    for (File dir : getPathEntries(dirs))
235                        addDirectory(dir, warn);
236                return this;
237            }
238    
239            public Path addDirectories(String dirs) {
240                return addDirectories(dirs, warn);
241            }
242    
243            private void addDirectory(File dir, boolean warn) {
244                if (!dir.isDirectory()) {
245                    if (warn)
246                        log.warning("dir.path.element.not.found", dir);
247                    return;
248                }
249    
250                File[] files = dir.listFiles();
251                if (files == null)
252                    return;
253    
254                for (File direntry : files) {
255                    if (isArchive(direntry))
256                        addFile(direntry, warn);
257                }
258            }
259    
260            public Path addFiles(String files, boolean warn) {
261                if (files != null)
262                    for (File file : getPathEntries(files, emptyPathDefault))
263                        addFile(file, warn);
264                return this;
265            }
266    
267            public Path addFiles(String files) {
268                return addFiles(files, warn);
269            }
270    
271            public void addFile(File file, boolean warn) {
272                File canonFile = fsInfo.getCanonicalFile(file);
273                if (contains(file) || canonicalValues.contains(canonFile)) {
274                    /* Discard duplicates and avoid infinite recursion */
275                    return;
276                }
277    
278                if (! fsInfo.exists(file)) {
279                    /* No such file or directory exists */
280                    if (warn)
281                        log.warning("path.element.not.found", file);
282                } else if (fsInfo.isFile(file)) {
283                    /* File is an ordinary file. */
284                    if (!isArchive(file)) {
285                        /* Not a recognized extension; open it to see if
286                         it looks like a valid zip file. */
287                        try {
288                            ZipFile z = new ZipFile(file);
289                            z.close();
290                            if (warn)
291                                log.warning("unexpected.archive.file", file);
292                        } catch (IOException e) {
293                            // FIXME: include e.getLocalizedMessage in warning
294                            if (warn)
295                                log.warning("invalid.archive.file", file);
296                            return;
297                        }
298                    }
299                }
300    
301                /* Now what we have left is either a directory or a file name
302                   confirming to archive naming convention */
303                super.add(file);
304                canonicalValues.add(canonFile);
305    
306                if (expandJarClassPaths && fsInfo.exists(file) && fsInfo.isFile(file))
307                    addJarClassPath(file, warn);
308            }
309    
310            // Adds referenced classpath elements from a jar's Class-Path
311            // Manifest entry.  In some future release, we may want to
312            // update this code to recognize URLs rather than simple
313            // filenames, but if we do, we should redo all path-related code.
314            private void addJarClassPath(File jarFile, boolean warn) {
315                try {
316                    for (File f: fsInfo.getJarClassPath(jarFile)) {
317                        addFile(f, warn);
318                    }
319                } catch (IOException e) {
320                    log.error("error.reading.file", jarFile, e.getLocalizedMessage());
321                }
322            }
323        }
324    
325        private Path computeBootClassPath() {
326            bootClassPathRtJar = null;
327            String optionValue;
328            Path path = new Path();
329    
330            path.addFiles(options.get(XBOOTCLASSPATH_PREPEND));
331    
332            if ((optionValue = options.get(ENDORSEDDIRS)) != null)
333                path.addDirectories(optionValue);
334            else
335                path.addDirectories(System.getProperty("java.endorsed.dirs"), false);
336    
337            if ((optionValue = options.get(BOOTCLASSPATH)) != null) {
338                path.addFiles(optionValue);
339            } else {
340                // Standard system classes for this compiler's release.
341                String files = System.getProperty("sun.boot.class.path");
342                path.addFiles(files, false);
343                File rt_jar = new File("rt.jar");
344                for (File file : getPathEntries(files)) {
345                    if (new File(file.getName()).equals(rt_jar))
346                        bootClassPathRtJar = file;
347                }
348            }
349    
350            path.addFiles(options.get(XBOOTCLASSPATH_APPEND));
351    
352            // Strictly speaking, standard extensions are not bootstrap
353            // classes, but we treat them identically, so we'll pretend
354            // that they are.
355            if ((optionValue = options.get(EXTDIRS)) != null)
356                path.addDirectories(optionValue);
357            else
358                path.addDirectories(System.getProperty("java.ext.dirs"), false);
359    
360            return path;
361        }
362    
363        private Path computeUserClassPath() {
364            String cp = options.get(CLASSPATH);
365    
366            // CLASSPATH environment variable when run from `javac'.
367            if (cp == null) cp = System.getProperty("env.class.path");
368    
369            // If invoked via a java VM (not the javac launcher), use the
370            // platform class path
371            if (cp == null && System.getProperty("application.home") == null)
372                cp = System.getProperty("java.class.path");
373    
374            // Default to current working directory.
375            if (cp == null) cp = ".";
376    
377            return new Path()
378                .expandJarClassPaths(true)        // Only search user jars for Class-Paths
379                .emptyPathDefault(new File("."))  // Empty path elt ==> current directory
380                .addFiles(cp);
381        }
382    
383        private Path computeSourcePath() {
384            String sourcePathArg = options.get(SOURCEPATH);
385            if (sourcePathArg == null)
386                return null;
387    
388            return new Path().addFiles(sourcePathArg);
389        }
390    
391        private Path computeAnnotationProcessorPath() {
392            String processorPathArg = options.get(PROCESSORPATH);
393            if (processorPathArg == null)
394                return null;
395    
396            return new Path().addFiles(processorPathArg);
397        }
398    
399        /** The actual effective locations searched for sources */
400        private Path sourceSearchPath;
401    
402        public Collection<File> sourceSearchPath() {
403            if (sourceSearchPath == null) {
404                lazy();
405                Path sourcePath = getPathForLocation(SOURCE_PATH);
406                Path userClassPath = getPathForLocation(CLASS_PATH);
407                sourceSearchPath = sourcePath != null ? sourcePath : userClassPath;
408            }
409            return Collections.unmodifiableCollection(sourceSearchPath);
410        }
411    
412        /** The actual effective locations searched for classes */
413        private Path classSearchPath;
414    
415        public Collection<File> classSearchPath() {
416            if (classSearchPath == null) {
417                lazy();
418                Path bootClassPath = getPathForLocation(PLATFORM_CLASS_PATH);
419                Path userClassPath = getPathForLocation(CLASS_PATH);
420                classSearchPath = new Path();
421                classSearchPath.addAll(bootClassPath);
422                classSearchPath.addAll(userClassPath);
423            }
424            return Collections.unmodifiableCollection(classSearchPath);
425        }
426    
427        /** The actual effective locations for non-source, non-class files */
428        private Path otherSearchPath;
429    
430        Collection<File> otherSearchPath() {
431            if (otherSearchPath == null) {
432                lazy();
433                Path userClassPath = getPathForLocation(CLASS_PATH);
434                Path sourcePath = getPathForLocation(SOURCE_PATH);
435                if (sourcePath == null)
436                    otherSearchPath = userClassPath;
437                else {
438                    otherSearchPath = new Path();
439                    otherSearchPath.addAll(userClassPath);
440                    otherSearchPath.addAll(sourcePath);
441                }
442            }
443            return Collections.unmodifiableCollection(otherSearchPath);
444        }
445    
446        /** Is this the name of an archive file? */
447        private boolean isArchive(File file) {
448            String n = file.getName().toLowerCase();
449            return fsInfo.isFile(file)
450                && (n.endsWith(".jar") || n.endsWith(".zip"));
451        }
452    }