001: /*
002: * Copyright 2003-2006 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.util;
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.jar.JarFile;
035: import java.util.jar.Manifest;
036: import java.util.jar.Attributes;
037: import java.util.Collection;
038: import java.util.Collections;
039: import java.util.LinkedHashSet;
040: import java.util.Iterator;
041: import java.util.StringTokenizer;
042: import java.util.zip.ZipException;
043: import java.util.zip.ZipFile;
044: import com.sun.tools.javac.code.Lint;
045: import com.sun.tools.javac.util.Context;
046: import com.sun.tools.javac.util.Log;
047: import com.sun.tools.javac.util.Options;
048: import com.sun.tools.javac.util.Position;
049: import java.util.ArrayList;
050: import java.util.concurrent.ConcurrentHashMap;
051: import java.util.concurrent.locks.Lock;
052: import java.util.concurrent.locks.ReentrantLock;
053: import javax.tools.JavaFileManager.Location;
054:
055: import static com.sun.tools.javac.main.OptionName.*;
056: import static javax.tools.StandardLocation.*;
057:
058: /** This class converts command line arguments, environment variables
059: * and system properties (in File.pathSeparator-separated String form)
060: * into a boot class path, user class path, and source path (in
061: * Collection<String> form).
062: *
063: * <p><b>This is NOT part of any API supported by Sun Microsystems. If
064: * you write code that depends on this, you do so at your own risk.
065: * This code and its internal interfaces are subject to change or
066: * deletion without notice.</b>
067: */
068: @Version("@(#)Paths.java 1.29 07/05/05")
069: public class Paths {
070:
071: /** The context key for the todo list */
072: protected static final Context.Key<Paths> pathsKey = new Context.Key<Paths>();
073:
074: /** Get the Paths instance for this context. */
075: public static Paths instance(Context context) {
076: Paths instance = context.get(pathsKey);
077: if (instance == null)
078: instance = new Paths(context);
079: return instance;
080: }
081:
082: /** The log to use for warning output */
083: private Log log;
084:
085: /** Collection of command-line options */
086: private Options options;
087:
088: /** Handler for -Xlint options */
089: private Lint lint;
090:
091: private static boolean NON_BATCH_MODE = System
092: .getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this.
093: private static Map<File, PathEntry> pathExistanceCache = new ConcurrentHashMap<File, PathEntry>();
094: private static Map<File, java.util.List<String>> manifestEntries = new ConcurrentHashMap<File, java.util.List<String>>();
095: private static Map<File, Boolean> isDirectory = new ConcurrentHashMap<File, Boolean>();
096: private static Lock lock = new ReentrantLock();
097:
098: public static void clearPathExistanceCache() {
099: pathExistanceCache.clear();
100: }
101:
102: static class PathEntry {
103: boolean exists = false;
104: boolean isFile = false;
105: File cannonicalPath = null;
106: }
107:
108: protected Paths(Context context) {
109: context.put(pathsKey, this );
110: pathsForLocation = new HashMap<Location, Path>(16);
111: setContext(context);
112: }
113:
114: void setContext(Context context) {
115: log = Log.instance(context);
116: options = Options.instance(context);
117: lint = Lint.instance(context);
118: }
119:
120: /** Whether to warn about non-existent path elements */
121: private boolean warn;
122:
123: private Map<Location, Path> pathsForLocation;
124:
125: private boolean inited = false; // TODO? caching bad?
126:
127: /**
128: * rt.jar as found on the default bootclass path. If the user specified a
129: * bootclasspath, null is used.
130: */
131: private File bootClassPathRtJar = null;
132:
133: Path getPathForLocation(Location location) {
134: Path path = pathsForLocation.get(location);
135: if (path == null)
136: setPathForLocation(location, null);
137: return pathsForLocation.get(location);
138: }
139:
140: void setPathForLocation(Location location,
141: Iterable<? extends File> path) {
142: // TODO? if (inited) throw new IllegalStateException
143: // TODO: otherwise reset sourceSearchPath, classSearchPath as needed
144: Path p;
145: if (path == null) {
146: if (location == CLASS_PATH)
147: p = computeUserClassPath();
148: else if (location == PLATFORM_CLASS_PATH)
149: p = computeBootClassPath();
150: else if (location == ANNOTATION_PROCESSOR_PATH)
151: p = computeAnnotationProcessorPath();
152: else if (location == SOURCE_PATH)
153: p = computeSourcePath();
154: else
155: // no defaults for other paths
156: p = null;
157: } else {
158: p = new Path();
159: for (File f : path)
160: p.addFile(f, warn); // TODO: is use of warn appropriate?
161: }
162: pathsForLocation.put(location, p);
163: }
164:
165: protected void lazy() {
166: if (!inited) {
167: warn = lint.isEnabled(Lint.LintCategory.PATH);
168:
169: pathsForLocation.put(PLATFORM_CLASS_PATH,
170: computeBootClassPath());
171: pathsForLocation.put(CLASS_PATH, computeUserClassPath());
172: pathsForLocation.put(SOURCE_PATH, computeSourcePath());
173:
174: inited = true;
175: }
176: }
177:
178: public Collection<File> bootClassPath() {
179: lazy();
180: return Collections
181: .unmodifiableCollection(getPathForLocation(PLATFORM_CLASS_PATH));
182: }
183:
184: public Collection<File> userClassPath() {
185: lazy();
186: return Collections
187: .unmodifiableCollection(getPathForLocation(CLASS_PATH));
188: }
189:
190: public Collection<File> sourcePath() {
191: lazy();
192: Path p = getPathForLocation(SOURCE_PATH);
193: return p == null || p.size() == 0 ? null : Collections
194: .unmodifiableCollection(p);
195: }
196:
197: boolean isBootClassPathRtJar(File file) {
198: return file.equals(bootClassPathRtJar);
199: }
200:
201: private static class PathIterator implements Iterable<String> {
202: private int pos = 0;
203: private final String path;
204: private final String emptyPathDefault;
205:
206: public PathIterator(String path, String emptyPathDefault) {
207: this .path = path;
208: this .emptyPathDefault = emptyPathDefault;
209: }
210:
211: public PathIterator(String path) {
212: this (path, null);
213: }
214:
215: public Iterator<String> iterator() {
216: return new Iterator<String>() {
217: public boolean hasNext() {
218: return pos <= path.length();
219: }
220:
221: public String next() {
222: int beg = pos;
223: int end = path.indexOf(File.pathSeparator, beg);
224: if (end == -1)
225: end = path.length();
226: pos = end + 1;
227:
228: if (beg == end && emptyPathDefault != null)
229: return emptyPathDefault;
230: else
231: return path.substring(beg, end);
232: }
233:
234: public void remove() {
235: throw new UnsupportedOperationException();
236: }
237: };
238: }
239: }
240:
241: private class Path extends LinkedHashSet<File> {
242: private static final long serialVersionUID = 0;
243:
244: private boolean expandJarClassPaths = false;
245: private Set<File> canonicalValues = new HashSet<File>();
246:
247: public Path expandJarClassPaths(boolean x) {
248: expandJarClassPaths = x;
249: return this ;
250: }
251:
252: /** What to use when path element is the empty string */
253: private String emptyPathDefault = null;
254:
255: public Path emptyPathDefault(String x) {
256: emptyPathDefault = x;
257: return this ;
258: }
259:
260: public Path() {
261: super ();
262: }
263:
264: public Path addDirectories(String dirs, boolean warn) {
265: if (dirs != null)
266: for (String dir : new PathIterator(dirs))
267: addDirectory(dir, warn);
268: return this ;
269: }
270:
271: public Path addDirectories(String dirs) {
272: return addDirectories(dirs, warn);
273: }
274:
275: private void addDirectory(String dir, boolean warn) {
276: if (!new File(dir).isDirectory()) {
277: if (warn)
278: log.warning("dir.path.element.not.found", dir);
279: return;
280: }
281:
282: File[] files = new File(dir).listFiles();
283: if (files == null)
284: return;
285:
286: for (File direntry : files) {
287: if (isArchive(direntry))
288: addFile(direntry, warn);
289: }
290: }
291:
292: public Path addFiles(String files, boolean warn) {
293: if (files != null)
294: for (String file : new PathIterator(files,
295: emptyPathDefault))
296: addFile(file, warn);
297: return this ;
298: }
299:
300: public Path addFiles(String files) {
301: return addFiles(files, warn);
302: }
303:
304: public Path addFile(String file, boolean warn) {
305: addFile(new File(file), warn);
306: return this ;
307: }
308:
309: public void addFile(File file, boolean warn) {
310: boolean foundInCache = false;
311: PathEntry pe = null;
312: if (!NON_BATCH_MODE) {
313: pe = pathExistanceCache.get(file);
314: if (pe != null) {
315: foundInCache = true;
316: } else {
317: pe = new PathEntry();
318: }
319: } else {
320: pe = new PathEntry();
321: }
322:
323: File canonFile;
324: try {
325: if (!foundInCache) {
326: pe.cannonicalPath = file.getCanonicalFile();
327: } else {
328: canonFile = pe.cannonicalPath;
329: }
330: } catch (IOException e) {
331: pe.cannonicalPath = canonFile = file;
332: }
333:
334: if (contains(file)
335: || canonicalValues.contains(pe.cannonicalPath)) {
336: /* Discard duplicates and avoid infinite recursion */
337: return;
338: }
339:
340: if (!foundInCache) {
341: pe.exists = file.exists();
342: pe.isFile = file.isFile();
343: if (!NON_BATCH_MODE) {
344: pathExistanceCache.put(file, pe);
345: }
346: }
347:
348: if (!pe.exists) {
349: /* No such file or directory exists */
350: if (warn)
351: log.warning("path.element.not.found", file);
352: } else if (pe.isFile) {
353: /* File is an ordinary file. */
354: if (!isArchive(file)) {
355: /* Not a recognized extension; open it to see if
356: it looks like a valid zip file. */
357: try {
358: ZipFile z = new ZipFile(file);
359: z.close();
360: if (warn)
361: log
362: .warning("unexpected.archive.file",
363: file);
364: } catch (IOException e) {
365: // FIXME: include e.getLocalizedMessage in warning
366: if (warn)
367: log.warning("invalid.archive.file", file);
368: return;
369: }
370: }
371: }
372:
373: /* Now what we have left is either a directory or a file name
374: confirming to archive naming convention */
375: super .add(file);
376: canonicalValues.add(pe.cannonicalPath);
377:
378: if (expandJarClassPaths && file.exists() && file.isFile())
379: addJarClassPath(file, warn);
380: }
381:
382: // Adds referenced classpath elements from a jar's Class-Path
383: // Manifest entry. In some future release, we may want to
384: // update this code to recognize URLs rather than simple
385: // filenames, but if we do, we should redo all path-related code.
386: private void addJarClassPath(File jarFile, boolean warn) {
387: try {
388: java.util.List<String> manifestsList = manifestEntries
389: .get(jarFile);
390: if (!NON_BATCH_MODE) {
391: lock.lock();
392: try {
393: if (manifestsList != null) {
394: for (String entr : manifestsList) {
395: addFile(new File(entr), warn);
396: }
397: return;
398: }
399: } finally {
400: lock.unlock();
401: }
402: }
403:
404: if (!NON_BATCH_MODE) {
405: manifestsList = new ArrayList<String>();
406: manifestEntries.put(jarFile, manifestsList);
407: }
408:
409: String jarParent = jarFile.getParent();
410: JarFile jar = new JarFile(jarFile);
411:
412: try {
413: Manifest man = jar.getManifest();
414: if (man == null)
415: return;
416:
417: Attributes attr = man.getMainAttributes();
418: if (attr == null)
419: return;
420:
421: String path = attr
422: .getValue(Attributes.Name.CLASS_PATH);
423: if (path == null)
424: return;
425:
426: for (StringTokenizer st = new StringTokenizer(path); st
427: .hasMoreTokens();) {
428: String elt = st.nextToken();
429: File f = (jarParent == null ? new File(elt)
430: : new File(jarParent, elt));
431: addFile(f, warn);
432:
433: if (!NON_BATCH_MODE) {
434: lock.lock();
435: try {
436: manifestsList.add(elt);
437: } finally {
438: lock.unlock();
439: }
440: }
441: }
442: } finally {
443: jar.close();
444: }
445: } catch (IOException e) {
446: log.error("error.reading.file", jarFile, e
447: .getLocalizedMessage());
448: }
449: }
450: }
451:
452: private Path computeBootClassPath() {
453: bootClassPathRtJar = null;
454: String optionValue;
455: Path path = new Path();
456:
457: path.addFiles(options.get(XBOOTCLASSPATH_PREPEND));
458:
459: if ((optionValue = options.get(ENDORSEDDIRS)) != null)
460: path.addDirectories(optionValue);
461: else
462: path.addDirectories(System
463: .getProperty("java.endorsed.dirs"), false);
464:
465: if ((optionValue = options.get(BOOTCLASSPATH)) != null) {
466: path.addFiles(optionValue);
467: } else {
468: // Standard system classes for this compiler's release.
469: String files = System.getProperty("sun.boot.class.path");
470: path.addFiles(files, false);
471: File rt_jar = new File("rt.jar");
472: for (String file : new PathIterator(files, null)) {
473: File f = new File(file);
474: if (new File(f.getName()).equals(rt_jar))
475: bootClassPathRtJar = f;
476: }
477: }
478:
479: path.addFiles(options.get(XBOOTCLASSPATH_APPEND));
480:
481: // Strictly speaking, standard extensions are not bootstrap
482: // classes, but we treat them identically, so we'll pretend
483: // that they are.
484: if ((optionValue = options.get(EXTDIRS)) != null)
485: path.addDirectories(optionValue);
486: else
487: path.addDirectories(System.getProperty("java.ext.dirs"),
488: false);
489:
490: return path;
491: }
492:
493: private Path computeUserClassPath() {
494: String cp = options.get(CLASSPATH);
495:
496: // CLASSPATH environment variable when run from `javac'.
497: if (cp == null)
498: cp = System.getProperty("env.class.path");
499:
500: // If invoked via a java VM (not the javac launcher), use the
501: // platform class path
502: if (cp == null
503: && System.getProperty("application.home") == null)
504: cp = System.getProperty("java.class.path");
505:
506: // Default to current working directory.
507: if (cp == null)
508: cp = ".";
509:
510: return new Path().expandJarClassPaths(true) // Only search user jars for Class-Paths
511: .emptyPathDefault(".") // Empty path elt ==> current directory
512: .addFiles(cp);
513: }
514:
515: private Path computeSourcePath() {
516: String sourcePathArg = options.get(SOURCEPATH);
517: if (sourcePathArg == null)
518: return null;
519:
520: return new Path().addFiles(sourcePathArg);
521: }
522:
523: private Path computeAnnotationProcessorPath() {
524: String processorPathArg = options.get(PROCESSORPATH);
525: if (processorPathArg == null)
526: return null;
527:
528: return new Path().addFiles(processorPathArg);
529: }
530:
531: /** The actual effective locations searched for sources */
532: private Path sourceSearchPath;
533:
534: public Collection<File> sourceSearchPath() {
535: if (sourceSearchPath == null) {
536: lazy();
537: Path sourcePath = getPathForLocation(SOURCE_PATH);
538: Path userClassPath = getPathForLocation(CLASS_PATH);
539: sourceSearchPath = sourcePath != null ? sourcePath
540: : userClassPath;
541: }
542: return Collections.unmodifiableCollection(sourceSearchPath);
543: }
544:
545: /** The actual effective locations searched for classes */
546: private Path classSearchPath;
547:
548: public Collection<File> classSearchPath() {
549: if (classSearchPath == null) {
550: lazy();
551: Path bootClassPath = getPathForLocation(PLATFORM_CLASS_PATH);
552: Path userClassPath = getPathForLocation(CLASS_PATH);
553: classSearchPath = new Path();
554: classSearchPath.addAll(bootClassPath);
555: classSearchPath.addAll(userClassPath);
556: }
557: return Collections.unmodifiableCollection(classSearchPath);
558: }
559:
560: /** The actual effective locations for non-source, non-class files */
561: private Path otherSearchPath;
562:
563: Collection<File> otherSearchPath() {
564: if (otherSearchPath == null) {
565: lazy();
566: Path userClassPath = getPathForLocation(CLASS_PATH);
567: Path sourcePath = getPathForLocation(SOURCE_PATH);
568: if (sourcePath == null)
569: otherSearchPath = userClassPath;
570: else {
571: otherSearchPath = new Path();
572: otherSearchPath.addAll(userClassPath);
573: otherSearchPath.addAll(sourcePath);
574: }
575: }
576: return Collections.unmodifiableCollection(otherSearchPath);
577: }
578:
579: /** Is this the name of an archive file? */
580: private static boolean isArchive(File file) {
581: String n = file.getName().toLowerCase();
582: boolean isFile = false;
583: if (!NON_BATCH_MODE) {
584: Boolean isf = isDirectory.get(file);
585: if (isf == null) {
586: isFile = file.isFile();
587: isDirectory.put(file, isFile);
588: } else {
589: isFile = isf;
590: }
591: } else {
592: isFile = file.isFile();
593: }
594:
595: return isFile && (n.endsWith(".jar") || n.endsWith(".zip"));
596: }
597: }
|