Sat, 01 Dec 2007 00:00:00 +0000
Initial load
1 /*
2 * Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
26 package com.sun.tools.javac.util;
27 import java.io.File;
28 import java.io.IOException;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.Map;
32 import java.util.Set;
33 import java.util.jar.JarFile;
34 import java.util.jar.Manifest;
35 import java.util.jar.Attributes;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.LinkedHashSet;
39 import java.util.Iterator;
40 import java.util.StringTokenizer;
41 import java.util.zip.ZipException;
42 import java.util.zip.ZipFile;
43 import com.sun.tools.javac.code.Lint;
44 import com.sun.tools.javac.util.Context;
45 import com.sun.tools.javac.util.Log;
46 import com.sun.tools.javac.util.Options;
47 import com.sun.tools.javac.util.Position;
48 import java.util.ArrayList;
49 import java.util.concurrent.ConcurrentHashMap;
50 import java.util.concurrent.locks.Lock;
51 import java.util.concurrent.locks.ReentrantLock;
52 import javax.tools.JavaFileManager.Location;
54 import static com.sun.tools.javac.main.OptionName.*;
55 import static javax.tools.StandardLocation.*;
57 /** This class converts command line arguments, environment variables
58 * and system properties (in File.pathSeparator-separated String form)
59 * into a boot class path, user class path, and source path (in
60 * Collection<String> form).
61 *
62 * <p><b>This is NOT part of any API supported by Sun Microsystems. If
63 * you write code that depends on this, you do so at your own risk.
64 * This code and its internal interfaces are subject to change or
65 * deletion without notice.</b>
66 */
67 public class Paths {
69 /** The context key for the todo list */
70 protected static final Context.Key<Paths> pathsKey =
71 new Context.Key<Paths>();
73 /** Get the Paths instance for this context. */
74 public static Paths instance(Context context) {
75 Paths instance = context.get(pathsKey);
76 if (instance == null)
77 instance = new Paths(context);
78 return instance;
79 }
81 /** The log to use for warning output */
82 private Log log;
84 /** Collection of command-line options */
85 private Options options;
87 /** Handler for -Xlint options */
88 private Lint lint;
90 private static boolean NON_BATCH_MODE = System.getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this.
91 private static Map<File, PathEntry> pathExistanceCache = new ConcurrentHashMap<File, PathEntry>();
92 private static Map<File, java.util.List<String>> manifestEntries = new ConcurrentHashMap<File, java.util.List<String>>();
93 private static Map<File, Boolean> isDirectory = new ConcurrentHashMap<File, Boolean>();
94 private static Lock lock = new ReentrantLock();
96 public static void clearPathExistanceCache() {
97 pathExistanceCache.clear();
98 }
100 static class PathEntry {
101 boolean exists = false;
102 boolean isFile = false;
103 File cannonicalPath = null;
104 }
106 protected Paths(Context context) {
107 context.put(pathsKey, this);
108 pathsForLocation = new HashMap<Location,Path>(16);
109 setContext(context);
110 }
112 void setContext(Context context) {
113 log = Log.instance(context);
114 options = Options.instance(context);
115 lint = Lint.instance(context);
116 }
118 /** Whether to warn about non-existent path elements */
119 private boolean warn;
121 private Map<Location, Path> pathsForLocation;
123 private boolean inited = false; // TODO? caching bad?
125 /**
126 * rt.jar as found on the default bootclass path. If the user specified a
127 * bootclasspath, null is used.
128 */
129 private File bootClassPathRtJar = null;
131 Path getPathForLocation(Location location) {
132 Path path = pathsForLocation.get(location);
133 if (path == null)
134 setPathForLocation(location, null);
135 return pathsForLocation.get(location);
136 }
138 void setPathForLocation(Location location, Iterable<? extends File> path) {
139 // TODO? if (inited) throw new IllegalStateException
140 // TODO: otherwise reset sourceSearchPath, classSearchPath as needed
141 Path p;
142 if (path == null) {
143 if (location == CLASS_PATH)
144 p = computeUserClassPath();
145 else if (location == PLATFORM_CLASS_PATH)
146 p = computeBootClassPath();
147 else if (location == ANNOTATION_PROCESSOR_PATH)
148 p = computeAnnotationProcessorPath();
149 else if (location == SOURCE_PATH)
150 p = computeSourcePath();
151 else
152 // no defaults for other paths
153 p = null;
154 } else {
155 p = new Path();
156 for (File f: path)
157 p.addFile(f, warn); // TODO: is use of warn appropriate?
158 }
159 pathsForLocation.put(location, p);
160 }
162 protected void lazy() {
163 if (!inited) {
164 warn = lint.isEnabled(Lint.LintCategory.PATH);
166 pathsForLocation.put(PLATFORM_CLASS_PATH, computeBootClassPath());
167 pathsForLocation.put(CLASS_PATH, computeUserClassPath());
168 pathsForLocation.put(SOURCE_PATH, computeSourcePath());
170 inited = true;
171 }
172 }
174 public Collection<File> bootClassPath() {
175 lazy();
176 return Collections.unmodifiableCollection(getPathForLocation(PLATFORM_CLASS_PATH));
177 }
178 public Collection<File> userClassPath() {
179 lazy();
180 return Collections.unmodifiableCollection(getPathForLocation(CLASS_PATH));
181 }
182 public Collection<File> sourcePath() {
183 lazy();
184 Path p = getPathForLocation(SOURCE_PATH);
185 return p == null || p.size() == 0
186 ? null
187 : Collections.unmodifiableCollection(p);
188 }
190 boolean isBootClassPathRtJar(File file) {
191 return file.equals(bootClassPathRtJar);
192 }
194 private static class PathIterator implements Iterable<String> {
195 private int pos = 0;
196 private final String path;
197 private final String emptyPathDefault;
199 public PathIterator(String path, String emptyPathDefault) {
200 this.path = path;
201 this.emptyPathDefault = emptyPathDefault;
202 }
203 public PathIterator(String path) { this(path, null); }
204 public Iterator<String> iterator() {
205 return new Iterator<String>() {
206 public boolean hasNext() {
207 return pos <= path.length();
208 }
209 public String next() {
210 int beg = pos;
211 int end = path.indexOf(File.pathSeparator, beg);
212 if (end == -1)
213 end = path.length();
214 pos = end + 1;
216 if (beg == end && emptyPathDefault != null)
217 return emptyPathDefault;
218 else
219 return path.substring(beg, end);
220 }
221 public void remove() {
222 throw new UnsupportedOperationException();
223 }
224 };
225 }
226 }
228 private class Path extends LinkedHashSet<File> {
229 private static final long serialVersionUID = 0;
231 private boolean expandJarClassPaths = false;
232 private Set<File> canonicalValues = new HashSet<File>();
234 public Path expandJarClassPaths(boolean x) {
235 expandJarClassPaths = x;
236 return this;
237 }
239 /** What to use when path element is the empty string */
240 private String emptyPathDefault = null;
242 public Path emptyPathDefault(String x) {
243 emptyPathDefault = x;
244 return this;
245 }
247 public Path() { super(); }
249 public Path addDirectories(String dirs, boolean warn) {
250 if (dirs != null)
251 for (String dir : new PathIterator(dirs))
252 addDirectory(dir, warn);
253 return this;
254 }
256 public Path addDirectories(String dirs) {
257 return addDirectories(dirs, warn);
258 }
260 private void addDirectory(String dir, boolean warn) {
261 if (! new File(dir).isDirectory()) {
262 if (warn)
263 log.warning("dir.path.element.not.found", dir);
264 return;
265 }
267 File[] files = new File(dir).listFiles();
268 if (files == null)
269 return;
271 for (File direntry : files) {
272 if (isArchive(direntry))
273 addFile(direntry, warn);
274 }
275 }
277 public Path addFiles(String files, boolean warn) {
278 if (files != null)
279 for (String file : new PathIterator(files, emptyPathDefault))
280 addFile(file, warn);
281 return this;
282 }
284 public Path addFiles(String files) {
285 return addFiles(files, warn);
286 }
288 public Path addFile(String file, boolean warn) {
289 addFile(new File(file), warn);
290 return this;
291 }
293 public void addFile(File file, boolean warn) {
294 boolean foundInCache = false;
295 PathEntry pe = null;
296 if (!NON_BATCH_MODE) {
297 pe = pathExistanceCache.get(file);
298 if (pe != null) {
299 foundInCache = true;
300 }
301 else {
302 pe = new PathEntry();
303 }
304 }
305 else {
306 pe = new PathEntry();
307 }
309 File canonFile;
310 try {
311 if (!foundInCache) {
312 pe.cannonicalPath = file.getCanonicalFile();
313 }
314 else {
315 canonFile = pe.cannonicalPath;
316 }
317 } catch (IOException e) {
318 pe.cannonicalPath = canonFile = file;
319 }
321 if (contains(file) || canonicalValues.contains(pe.cannonicalPath)) {
322 /* Discard duplicates and avoid infinite recursion */
323 return;
324 }
326 if (!foundInCache) {
327 pe.exists = file.exists();
328 pe.isFile = file.isFile();
329 if (!NON_BATCH_MODE) {
330 pathExistanceCache.put(file, pe);
331 }
332 }
334 if (! pe.exists) {
335 /* No such file or directory exists */
336 if (warn)
337 log.warning("path.element.not.found", file);
338 } else if (pe.isFile) {
339 /* File is an ordinary file. */
340 if (!isArchive(file)) {
341 /* Not a recognized extension; open it to see if
342 it looks like a valid zip file. */
343 try {
344 ZipFile z = new ZipFile(file);
345 z.close();
346 if (warn)
347 log.warning("unexpected.archive.file", file);
348 } catch (IOException e) {
349 // FIXME: include e.getLocalizedMessage in warning
350 if (warn)
351 log.warning("invalid.archive.file", file);
352 return;
353 }
354 }
355 }
357 /* Now what we have left is either a directory or a file name
358 confirming to archive naming convention */
359 super.add(file);
360 canonicalValues.add(pe.cannonicalPath);
362 if (expandJarClassPaths && file.exists() && file.isFile())
363 addJarClassPath(file, warn);
364 }
366 // Adds referenced classpath elements from a jar's Class-Path
367 // Manifest entry. In some future release, we may want to
368 // update this code to recognize URLs rather than simple
369 // filenames, but if we do, we should redo all path-related code.
370 private void addJarClassPath(File jarFile, boolean warn) {
371 try {
372 java.util.List<String> manifestsList = manifestEntries.get(jarFile);
373 if (!NON_BATCH_MODE) {
374 lock.lock();
375 try {
376 if (manifestsList != null) {
377 for (String entr : manifestsList) {
378 addFile(new File(entr), warn);
379 }
380 return;
381 }
382 }
383 finally {
384 lock.unlock();
385 }
386 }
388 if (!NON_BATCH_MODE) {
389 manifestsList = new ArrayList<String>();
390 manifestEntries.put(jarFile, manifestsList);
391 }
393 String jarParent = jarFile.getParent();
394 JarFile jar = new JarFile(jarFile);
396 try {
397 Manifest man = jar.getManifest();
398 if (man == null) return;
400 Attributes attr = man.getMainAttributes();
401 if (attr == null) return;
403 String path = attr.getValue(Attributes.Name.CLASS_PATH);
404 if (path == null) return;
406 for (StringTokenizer st = new StringTokenizer(path);
407 st.hasMoreTokens();) {
408 String elt = st.nextToken();
409 File f = (jarParent == null ? new File(elt) : new File(jarParent, elt));
410 addFile(f, warn);
412 if (!NON_BATCH_MODE) {
413 lock.lock();
414 try {
415 manifestsList.add(elt);
416 }
417 finally {
418 lock.unlock();
419 }
420 }
421 }
422 } finally {
423 jar.close();
424 }
425 } catch (IOException e) {
426 log.error("error.reading.file", jarFile, e.getLocalizedMessage());
427 }
428 }
429 }
431 private Path computeBootClassPath() {
432 bootClassPathRtJar = null;
433 String optionValue;
434 Path path = new Path();
436 path.addFiles(options.get(XBOOTCLASSPATH_PREPEND));
438 if ((optionValue = options.get(ENDORSEDDIRS)) != null)
439 path.addDirectories(optionValue);
440 else
441 path.addDirectories(System.getProperty("java.endorsed.dirs"), false);
443 if ((optionValue = options.get(BOOTCLASSPATH)) != null) {
444 path.addFiles(optionValue);
445 } else {
446 // Standard system classes for this compiler's release.
447 String files = System.getProperty("sun.boot.class.path");
448 path.addFiles(files, false);
449 File rt_jar = new File("rt.jar");
450 for (String file : new PathIterator(files, null)) {
451 File f = new File(file);
452 if (new File(f.getName()).equals(rt_jar))
453 bootClassPathRtJar = f;
454 }
455 }
457 path.addFiles(options.get(XBOOTCLASSPATH_APPEND));
459 // Strictly speaking, standard extensions are not bootstrap
460 // classes, but we treat them identically, so we'll pretend
461 // that they are.
462 if ((optionValue = options.get(EXTDIRS)) != null)
463 path.addDirectories(optionValue);
464 else
465 path.addDirectories(System.getProperty("java.ext.dirs"), false);
467 return path;
468 }
470 private Path computeUserClassPath() {
471 String cp = options.get(CLASSPATH);
473 // CLASSPATH environment variable when run from `javac'.
474 if (cp == null) cp = System.getProperty("env.class.path");
476 // If invoked via a java VM (not the javac launcher), use the
477 // platform class path
478 if (cp == null && System.getProperty("application.home") == null)
479 cp = System.getProperty("java.class.path");
481 // Default to current working directory.
482 if (cp == null) cp = ".";
484 return new Path()
485 .expandJarClassPaths(true) // Only search user jars for Class-Paths
486 .emptyPathDefault(".") // Empty path elt ==> current directory
487 .addFiles(cp);
488 }
490 private Path computeSourcePath() {
491 String sourcePathArg = options.get(SOURCEPATH);
492 if (sourcePathArg == null)
493 return null;
495 return new Path().addFiles(sourcePathArg);
496 }
498 private Path computeAnnotationProcessorPath() {
499 String processorPathArg = options.get(PROCESSORPATH);
500 if (processorPathArg == null)
501 return null;
503 return new Path().addFiles(processorPathArg);
504 }
506 /** The actual effective locations searched for sources */
507 private Path sourceSearchPath;
509 public Collection<File> sourceSearchPath() {
510 if (sourceSearchPath == null) {
511 lazy();
512 Path sourcePath = getPathForLocation(SOURCE_PATH);
513 Path userClassPath = getPathForLocation(CLASS_PATH);
514 sourceSearchPath = sourcePath != null ? sourcePath : userClassPath;
515 }
516 return Collections.unmodifiableCollection(sourceSearchPath);
517 }
519 /** The actual effective locations searched for classes */
520 private Path classSearchPath;
522 public Collection<File> classSearchPath() {
523 if (classSearchPath == null) {
524 lazy();
525 Path bootClassPath = getPathForLocation(PLATFORM_CLASS_PATH);
526 Path userClassPath = getPathForLocation(CLASS_PATH);
527 classSearchPath = new Path();
528 classSearchPath.addAll(bootClassPath);
529 classSearchPath.addAll(userClassPath);
530 }
531 return Collections.unmodifiableCollection(classSearchPath);
532 }
534 /** The actual effective locations for non-source, non-class files */
535 private Path otherSearchPath;
537 Collection<File> otherSearchPath() {
538 if (otherSearchPath == null) {
539 lazy();
540 Path userClassPath = getPathForLocation(CLASS_PATH);
541 Path sourcePath = getPathForLocation(SOURCE_PATH);
542 if (sourcePath == null)
543 otherSearchPath = userClassPath;
544 else {
545 otherSearchPath = new Path();
546 otherSearchPath.addAll(userClassPath);
547 otherSearchPath.addAll(sourcePath);
548 }
549 }
550 return Collections.unmodifiableCollection(otherSearchPath);
551 }
553 /** Is this the name of an archive file? */
554 private static boolean isArchive(File file) {
555 String n = file.getName().toLowerCase();
556 boolean isFile = false;
557 if (!NON_BATCH_MODE) {
558 Boolean isf = isDirectory.get(file);
559 if (isf == null) {
560 isFile = file.isFile();
561 isDirectory.put(file, isFile);
562 }
563 else {
564 isFile = isf;
565 }
566 }
567 else {
568 isFile = file.isFile();
569 }
571 return isFile
572 && (n.endsWith(".jar") || n.endsWith(".zip"));
573 }
574 }