Fri, 14 Mar 2008 16:09:30 -0700
6638501: Regression with Javac in JDK6 U4 b03?
Summary: replace some String paths with File paths in Paths.java
Reviewed-by: ksrini
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.ZipFile;
42 import com.sun.tools.javac.code.Lint;
43 import java.util.ArrayList;
44 import java.util.concurrent.ConcurrentHashMap;
45 import java.util.concurrent.locks.Lock;
46 import java.util.concurrent.locks.ReentrantLock;
47 import javax.tools.JavaFileManager.Location;
49 import static com.sun.tools.javac.main.OptionName.*;
50 import static javax.tools.StandardLocation.*;
52 /** This class converts command line arguments, environment variables
53 * and system properties (in File.pathSeparator-separated String form)
54 * into a boot class path, user class path, and source path (in
55 * Collection<String> form).
56 *
57 * <p><b>This is NOT part of any API supported by Sun Microsystems. If
58 * you write code that depends on this, you do so at your own risk.
59 * This code and its internal interfaces are subject to change or
60 * deletion without notice.</b>
61 */
62 public class Paths {
64 /** The context key for the todo list */
65 protected static final Context.Key<Paths> pathsKey =
66 new Context.Key<Paths>();
68 /** Get the Paths instance for this context.
69 * @param context the context
70 * @return the Paths instance for this context
71 */
72 public static Paths instance(Context context) {
73 Paths instance = context.get(pathsKey);
74 if (instance == null)
75 instance = new Paths(context);
76 return instance;
77 }
79 /** The log to use for warning output */
80 private Log log;
82 /** Collection of command-line options */
83 private Options options;
85 /** Handler for -Xlint options */
86 private Lint lint;
88 private static boolean NON_BATCH_MODE = System.getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this.
89 private static Map<File, PathEntry> pathExistanceCache = new ConcurrentHashMap<File, PathEntry>();
90 private static Map<File, java.util.List<File>> manifestEntries = new ConcurrentHashMap<File, java.util.List<File>>();
91 private static Map<File, Boolean> isDirectory = new ConcurrentHashMap<File, Boolean>();
92 private static Lock lock = new ReentrantLock();
94 public static void clearPathExistanceCache() {
95 pathExistanceCache.clear();
96 }
98 static class PathEntry {
99 boolean exists = false;
100 boolean isFile = false;
101 File cannonicalPath = null;
102 }
104 protected Paths(Context context) {
105 context.put(pathsKey, this);
106 pathsForLocation = new HashMap<Location,Path>(16);
107 setContext(context);
108 }
110 void setContext(Context context) {
111 log = Log.instance(context);
112 options = Options.instance(context);
113 lint = Lint.instance(context);
114 }
116 /** Whether to warn about non-existent path elements */
117 private boolean warn;
119 private Map<Location, Path> pathsForLocation;
121 private boolean inited = false; // TODO? caching bad?
123 /**
124 * rt.jar as found on the default bootclass path. If the user specified a
125 * bootclasspath, null is used.
126 */
127 private File bootClassPathRtJar = null;
129 Path getPathForLocation(Location location) {
130 Path path = pathsForLocation.get(location);
131 if (path == null)
132 setPathForLocation(location, null);
133 return pathsForLocation.get(location);
134 }
136 void setPathForLocation(Location location, Iterable<? extends File> path) {
137 // TODO? if (inited) throw new IllegalStateException
138 // TODO: otherwise reset sourceSearchPath, classSearchPath as needed
139 Path p;
140 if (path == null) {
141 if (location == CLASS_PATH)
142 p = computeUserClassPath();
143 else if (location == PLATFORM_CLASS_PATH)
144 p = computeBootClassPath();
145 else if (location == ANNOTATION_PROCESSOR_PATH)
146 p = computeAnnotationProcessorPath();
147 else if (location == SOURCE_PATH)
148 p = computeSourcePath();
149 else
150 // no defaults for other paths
151 p = null;
152 } else {
153 p = new Path();
154 for (File f: path)
155 p.addFile(f, warn); // TODO: is use of warn appropriate?
156 }
157 pathsForLocation.put(location, p);
158 }
160 protected void lazy() {
161 if (!inited) {
162 warn = lint.isEnabled(Lint.LintCategory.PATH);
164 pathsForLocation.put(PLATFORM_CLASS_PATH, computeBootClassPath());
165 pathsForLocation.put(CLASS_PATH, computeUserClassPath());
166 pathsForLocation.put(SOURCE_PATH, computeSourcePath());
168 inited = true;
169 }
170 }
172 public Collection<File> bootClassPath() {
173 lazy();
174 return Collections.unmodifiableCollection(getPathForLocation(PLATFORM_CLASS_PATH));
175 }
176 public Collection<File> userClassPath() {
177 lazy();
178 return Collections.unmodifiableCollection(getPathForLocation(CLASS_PATH));
179 }
180 public Collection<File> sourcePath() {
181 lazy();
182 Path p = getPathForLocation(SOURCE_PATH);
183 return p == null || p.size() == 0
184 ? null
185 : Collections.unmodifiableCollection(p);
186 }
188 boolean isBootClassPathRtJar(File file) {
189 return file.equals(bootClassPathRtJar);
190 }
192 private static class PathIterator implements Iterable<String> {
193 private int pos = 0;
194 private final String path;
195 private final String emptyPathDefault;
197 public PathIterator(String path, String emptyPathDefault) {
198 this.path = path;
199 this.emptyPathDefault = emptyPathDefault;
200 }
201 public PathIterator(String path) { this(path, null); }
202 public Iterator<String> iterator() {
203 return new Iterator<String>() {
204 public boolean hasNext() {
205 return pos <= path.length();
206 }
207 public String next() {
208 int beg = pos;
209 int end = path.indexOf(File.pathSeparator, beg);
210 if (end == -1)
211 end = path.length();
212 pos = end + 1;
214 if (beg == end && emptyPathDefault != null)
215 return emptyPathDefault;
216 else
217 return path.substring(beg, end);
218 }
219 public void remove() {
220 throw new UnsupportedOperationException();
221 }
222 };
223 }
224 }
226 private class Path extends LinkedHashSet<File> {
227 private static final long serialVersionUID = 0;
229 private boolean expandJarClassPaths = false;
230 private Set<File> canonicalValues = new HashSet<File>();
232 public Path expandJarClassPaths(boolean x) {
233 expandJarClassPaths = x;
234 return this;
235 }
237 /** What to use when path element is the empty string */
238 private String emptyPathDefault = null;
240 public Path emptyPathDefault(String x) {
241 emptyPathDefault = x;
242 return this;
243 }
245 public Path() { super(); }
247 public Path addDirectories(String dirs, boolean warn) {
248 if (dirs != null)
249 for (String dir : new PathIterator(dirs))
250 addDirectory(dir, warn);
251 return this;
252 }
254 public Path addDirectories(String dirs) {
255 return addDirectories(dirs, warn);
256 }
258 private void addDirectory(String dir, boolean warn) {
259 if (! new File(dir).isDirectory()) {
260 if (warn)
261 log.warning("dir.path.element.not.found", dir);
262 return;
263 }
265 File[] files = new File(dir).listFiles();
266 if (files == null)
267 return;
269 for (File direntry : files) {
270 if (isArchive(direntry))
271 addFile(direntry, warn);
272 }
273 }
275 public Path addFiles(String files, boolean warn) {
276 if (files != null)
277 for (String file : new PathIterator(files, emptyPathDefault))
278 addFile(file, warn);
279 return this;
280 }
282 public Path addFiles(String files) {
283 return addFiles(files, warn);
284 }
286 public Path addFile(String file, boolean warn) {
287 addFile(new File(file), warn);
288 return this;
289 }
291 public void addFile(File file, boolean warn) {
292 boolean foundInCache = false;
293 PathEntry pe = null;
294 if (!NON_BATCH_MODE) {
295 pe = pathExistanceCache.get(file);
296 if (pe != null) {
297 foundInCache = true;
298 }
299 else {
300 pe = new PathEntry();
301 }
302 }
303 else {
304 pe = new PathEntry();
305 }
307 File canonFile;
308 try {
309 if (!foundInCache) {
310 pe.cannonicalPath = file.getCanonicalFile();
311 }
312 else {
313 canonFile = pe.cannonicalPath;
314 }
315 } catch (IOException e) {
316 pe.cannonicalPath = canonFile = file;
317 }
319 if (contains(file) || canonicalValues.contains(pe.cannonicalPath)) {
320 /* Discard duplicates and avoid infinite recursion */
321 return;
322 }
324 if (!foundInCache) {
325 pe.exists = file.exists();
326 pe.isFile = file.isFile();
327 if (!NON_BATCH_MODE) {
328 pathExistanceCache.put(file, pe);
329 }
330 }
332 if (! pe.exists) {
333 /* No such file or directory exists */
334 if (warn)
335 log.warning("path.element.not.found", file);
336 } else if (pe.isFile) {
337 /* File is an ordinary file. */
338 if (!isArchive(file)) {
339 /* Not a recognized extension; open it to see if
340 it looks like a valid zip file. */
341 try {
342 ZipFile z = new ZipFile(file);
343 z.close();
344 if (warn)
345 log.warning("unexpected.archive.file", file);
346 } catch (IOException e) {
347 // FIXME: include e.getLocalizedMessage in warning
348 if (warn)
349 log.warning("invalid.archive.file", file);
350 return;
351 }
352 }
353 }
355 /* Now what we have left is either a directory or a file name
356 confirming to archive naming convention */
357 super.add(file);
358 canonicalValues.add(pe.cannonicalPath);
360 if (expandJarClassPaths && file.exists() && file.isFile())
361 addJarClassPath(file, warn);
362 }
364 // Adds referenced classpath elements from a jar's Class-Path
365 // Manifest entry. In some future release, we may want to
366 // update this code to recognize URLs rather than simple
367 // filenames, but if we do, we should redo all path-related code.
368 private void addJarClassPath(File jarFile, boolean warn) {
369 try {
370 java.util.List<File> manifestsList = manifestEntries.get(jarFile);
371 if (!NON_BATCH_MODE) {
372 lock.lock();
373 try {
374 if (manifestsList != null) {
375 for (File entr : manifestsList) {
376 addFile(entr, warn);
377 }
378 return;
379 }
380 }
381 finally {
382 lock.unlock();
383 }
384 }
386 if (!NON_BATCH_MODE) {
387 manifestsList = new ArrayList<File>();
388 manifestEntries.put(jarFile, manifestsList);
389 }
391 String jarParent = jarFile.getParent();
392 JarFile jar = new JarFile(jarFile);
394 try {
395 Manifest man = jar.getManifest();
396 if (man == null) return;
398 Attributes attr = man.getMainAttributes();
399 if (attr == null) return;
401 String path = attr.getValue(Attributes.Name.CLASS_PATH);
402 if (path == null) return;
404 for (StringTokenizer st = new StringTokenizer(path);
405 st.hasMoreTokens();) {
406 String elt = st.nextToken();
407 File f = (jarParent == null ? new File(elt) : new File(jarParent, elt));
408 addFile(f, warn);
410 if (!NON_BATCH_MODE) {
411 lock.lock();
412 try {
413 manifestsList.add(f);
414 }
415 finally {
416 lock.unlock();
417 }
418 }
419 }
420 } finally {
421 jar.close();
422 }
423 } catch (IOException e) {
424 log.error("error.reading.file", jarFile, e.getLocalizedMessage());
425 }
426 }
427 }
429 private Path computeBootClassPath() {
430 bootClassPathRtJar = null;
431 String optionValue;
432 Path path = new Path();
434 path.addFiles(options.get(XBOOTCLASSPATH_PREPEND));
436 if ((optionValue = options.get(ENDORSEDDIRS)) != null)
437 path.addDirectories(optionValue);
438 else
439 path.addDirectories(System.getProperty("java.endorsed.dirs"), false);
441 if ((optionValue = options.get(BOOTCLASSPATH)) != null) {
442 path.addFiles(optionValue);
443 } else {
444 // Standard system classes for this compiler's release.
445 String files = System.getProperty("sun.boot.class.path");
446 path.addFiles(files, false);
447 File rt_jar = new File("rt.jar");
448 for (String file : new PathIterator(files, null)) {
449 File f = new File(file);
450 if (new File(f.getName()).equals(rt_jar))
451 bootClassPathRtJar = f;
452 }
453 }
455 path.addFiles(options.get(XBOOTCLASSPATH_APPEND));
457 // Strictly speaking, standard extensions are not bootstrap
458 // classes, but we treat them identically, so we'll pretend
459 // that they are.
460 if ((optionValue = options.get(EXTDIRS)) != null)
461 path.addDirectories(optionValue);
462 else
463 path.addDirectories(System.getProperty("java.ext.dirs"), false);
465 return path;
466 }
468 private Path computeUserClassPath() {
469 String cp = options.get(CLASSPATH);
471 // CLASSPATH environment variable when run from `javac'.
472 if (cp == null) cp = System.getProperty("env.class.path");
474 // If invoked via a java VM (not the javac launcher), use the
475 // platform class path
476 if (cp == null && System.getProperty("application.home") == null)
477 cp = System.getProperty("java.class.path");
479 // Default to current working directory.
480 if (cp == null) cp = ".";
482 return new Path()
483 .expandJarClassPaths(true) // Only search user jars for Class-Paths
484 .emptyPathDefault(".") // Empty path elt ==> current directory
485 .addFiles(cp);
486 }
488 private Path computeSourcePath() {
489 String sourcePathArg = options.get(SOURCEPATH);
490 if (sourcePathArg == null)
491 return null;
493 return new Path().addFiles(sourcePathArg);
494 }
496 private Path computeAnnotationProcessorPath() {
497 String processorPathArg = options.get(PROCESSORPATH);
498 if (processorPathArg == null)
499 return null;
501 return new Path().addFiles(processorPathArg);
502 }
504 /** The actual effective locations searched for sources */
505 private Path sourceSearchPath;
507 public Collection<File> sourceSearchPath() {
508 if (sourceSearchPath == null) {
509 lazy();
510 Path sourcePath = getPathForLocation(SOURCE_PATH);
511 Path userClassPath = getPathForLocation(CLASS_PATH);
512 sourceSearchPath = sourcePath != null ? sourcePath : userClassPath;
513 }
514 return Collections.unmodifiableCollection(sourceSearchPath);
515 }
517 /** The actual effective locations searched for classes */
518 private Path classSearchPath;
520 public Collection<File> classSearchPath() {
521 if (classSearchPath == null) {
522 lazy();
523 Path bootClassPath = getPathForLocation(PLATFORM_CLASS_PATH);
524 Path userClassPath = getPathForLocation(CLASS_PATH);
525 classSearchPath = new Path();
526 classSearchPath.addAll(bootClassPath);
527 classSearchPath.addAll(userClassPath);
528 }
529 return Collections.unmodifiableCollection(classSearchPath);
530 }
532 /** The actual effective locations for non-source, non-class files */
533 private Path otherSearchPath;
535 Collection<File> otherSearchPath() {
536 if (otherSearchPath == null) {
537 lazy();
538 Path userClassPath = getPathForLocation(CLASS_PATH);
539 Path sourcePath = getPathForLocation(SOURCE_PATH);
540 if (sourcePath == null)
541 otherSearchPath = userClassPath;
542 else {
543 otherSearchPath = new Path();
544 otherSearchPath.addAll(userClassPath);
545 otherSearchPath.addAll(sourcePath);
546 }
547 }
548 return Collections.unmodifiableCollection(otherSearchPath);
549 }
551 /** Is this the name of an archive file? */
552 private static boolean isArchive(File file) {
553 String n = file.getName().toLowerCase();
554 boolean isFile = false;
555 if (!NON_BATCH_MODE) {
556 Boolean isf = isDirectory.get(file);
557 if (isf == null) {
558 isFile = file.isFile();
559 isDirectory.put(file, isFile);
560 }
561 else {
562 isFile = isf;
563 }
564 }
565 else {
566 isFile = file.isFile();
567 }
569 return isFile
570 && (n.endsWith(".jar") || n.endsWith(".zip"));
571 }
572 }