Thu, 13 Jan 2011 11:48:10 -0800
6430241: Hard to disable symbol file feature through API
Reviewed-by: mcimadamore
1 /*
2 * Copyright (c) 2003, 2011, Oracle and/or its affiliates. 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 package com.sun.tools.javac.file;
28 import java.io.File;
29 import java.io.IOException;
30 import java.net.MalformedURLException;
31 import java.net.URL;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.LinkedHashSet;
39 import java.util.StringTokenizer;
40 import java.util.zip.ZipFile;
41 import javax.tools.JavaFileManager.Location;
43 import com.sun.tools.javac.code.Lint;
44 import com.sun.tools.javac.util.Context;
45 import com.sun.tools.javac.util.ListBuffer;
46 import com.sun.tools.javac.util.Log;
47 import com.sun.tools.javac.util.Options;
49 import static javax.tools.StandardLocation.*;
50 import static com.sun.tools.javac.main.OptionName.*;
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 supported API.
58 * If 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 /** Access to (possibly cached) file info */
89 private FSInfo fsInfo;
91 protected Paths(Context context) {
92 context.put(pathsKey, this);
93 pathsForLocation = new HashMap<Location,Path>(16);
94 setContext(context);
95 }
97 void setContext(Context context) {
98 log = Log.instance(context);
99 options = Options.instance(context);
100 lint = Lint.instance(context);
101 fsInfo = FSInfo.instance(context);
102 }
104 /** Whether to warn about non-existent path elements */
105 private boolean warn;
107 private Map<Location, Path> pathsForLocation;
109 private boolean inited = false; // TODO? caching bad?
111 /**
112 * rt.jar as found on the default bootclass path. If the user specified a
113 * bootclasspath, null is used.
114 */
115 private File defaultBootClassPathRtJar = null;
117 /**
118 * Is bootclasspath the default?
119 */
120 private boolean isDefaultBootClassPath;
122 Path getPathForLocation(Location location) {
123 Path path = pathsForLocation.get(location);
124 if (path == null)
125 setPathForLocation(location, null);
126 return pathsForLocation.get(location);
127 }
129 void setPathForLocation(Location location, Iterable<? extends File> path) {
130 // TODO? if (inited) throw new IllegalStateException
131 // TODO: otherwise reset sourceSearchPath, classSearchPath as needed
132 Path p;
133 if (path == null) {
134 if (location == CLASS_PATH)
135 p = computeUserClassPath();
136 else if (location == PLATFORM_CLASS_PATH)
137 p = computeBootClassPath(); // sets isDefaultBootClassPath
138 else if (location == ANNOTATION_PROCESSOR_PATH)
139 p = computeAnnotationProcessorPath();
140 else if (location == SOURCE_PATH)
141 p = computeSourcePath();
142 else
143 // no defaults for other paths
144 p = null;
145 } else {
146 if (location == PLATFORM_CLASS_PATH) {
147 defaultBootClassPathRtJar = null;
148 isDefaultBootClassPath = false;
149 }
150 p = new Path();
151 for (File f: path)
152 p.addFile(f, warn); // TODO: is use of warn appropriate?
153 }
154 pathsForLocation.put(location, p);
155 }
157 public boolean isDefaultBootClassPath() {
158 lazy();
159 return isDefaultBootClassPath;
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 isDefaultBootClassPathRtJar(File file) {
191 return file.equals(defaultBootClassPathRtJar);
192 }
194 /**
195 * Split a path into its elements. Empty path elements will be ignored.
196 * @param path The path to be split
197 * @return The elements of the path
198 */
199 private static Iterable<File> getPathEntries(String path) {
200 return getPathEntries(path, null);
201 }
203 /**
204 * Split a path into its elements. If emptyPathDefault is not null, all
205 * empty elements in the path, including empty elements at either end of
206 * the path, will be replaced with the value of emptyPathDefault.
207 * @param path The path to be split
208 * @param emptyPathDefault The value to substitute for empty path elements,
209 * or null, to ignore empty path elements
210 * @return The elements of the path
211 */
212 private static Iterable<File> getPathEntries(String path, File emptyPathDefault) {
213 ListBuffer<File> entries = new ListBuffer<File>();
214 int start = 0;
215 while (start <= path.length()) {
216 int sep = path.indexOf(File.pathSeparatorChar, start);
217 if (sep == -1)
218 sep = path.length();
219 if (start < sep)
220 entries.add(new File(path.substring(start, sep)));
221 else if (emptyPathDefault != null)
222 entries.add(emptyPathDefault);
223 start = sep + 1;
224 }
225 return entries;
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 File emptyPathDefault = null;
242 public Path emptyPathDefault(File 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 (File dir : getPathEntries(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(File dir, boolean warn) {
261 if (!dir.isDirectory()) {
262 if (warn)
263 log.warning(Lint.LintCategory.PATH,
264 "dir.path.element.not.found", dir);
265 return;
266 }
268 File[] files = dir.listFiles();
269 if (files == null)
270 return;
272 for (File direntry : files) {
273 if (isArchive(direntry))
274 addFile(direntry, warn);
275 }
276 }
278 public Path addFiles(String files, boolean warn) {
279 if (files != null) {
280 for (File file : getPathEntries(files, emptyPathDefault))
281 addFile(file, warn);
282 }
283 return this;
284 }
286 public Path addFiles(String files) {
287 return addFiles(files, warn);
288 }
290 public void addFile(File file, boolean warn) {
291 if (contains(file)) {
292 // discard duplicates
293 return;
294 }
296 if (! fsInfo.exists(file)) {
297 /* No such file or directory exists */
298 if (warn) {
299 log.warning(Lint.LintCategory.PATH,
300 "path.element.not.found", file);
301 }
302 super.add(file);
303 return;
304 }
306 File canonFile = fsInfo.getCanonicalFile(file);
307 if (canonicalValues.contains(canonFile)) {
308 /* Discard duplicates and avoid infinite recursion */
309 return;
310 }
312 if (fsInfo.isFile(file)) {
313 /* File is an ordinary file. */
314 if (!isArchive(file)) {
315 /* Not a recognized extension; open it to see if
316 it looks like a valid zip file. */
317 try {
318 ZipFile z = new ZipFile(file);
319 z.close();
320 if (warn) {
321 log.warning(Lint.LintCategory.PATH,
322 "unexpected.archive.file", file);
323 }
324 } catch (IOException e) {
325 // FIXME: include e.getLocalizedMessage in warning
326 if (warn) {
327 log.warning(Lint.LintCategory.PATH,
328 "invalid.archive.file", file);
329 }
330 return;
331 }
332 }
333 }
335 /* Now what we have left is either a directory or a file name
336 conforming to archive naming convention */
337 super.add(file);
338 canonicalValues.add(canonFile);
340 if (expandJarClassPaths && fsInfo.isFile(file))
341 addJarClassPath(file, warn);
342 }
344 // Adds referenced classpath elements from a jar's Class-Path
345 // Manifest entry. In some future release, we may want to
346 // update this code to recognize URLs rather than simple
347 // filenames, but if we do, we should redo all path-related code.
348 private void addJarClassPath(File jarFile, boolean warn) {
349 try {
350 for (File f: fsInfo.getJarClassPath(jarFile)) {
351 addFile(f, warn);
352 }
353 } catch (IOException e) {
354 log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e));
355 }
356 }
357 }
359 private Path computeBootClassPath() {
360 defaultBootClassPathRtJar = null;
361 Path path = new Path();
363 String bootclasspathOpt = options.get(BOOTCLASSPATH);
364 String endorseddirsOpt = options.get(ENDORSEDDIRS);
365 String extdirsOpt = options.get(EXTDIRS);
366 String xbootclasspathPrependOpt = options.get(XBOOTCLASSPATH_PREPEND);
367 String xbootclasspathAppendOpt = options.get(XBOOTCLASSPATH_APPEND);
369 path.addFiles(xbootclasspathPrependOpt);
371 if (endorseddirsOpt != null)
372 path.addDirectories(endorseddirsOpt);
373 else
374 path.addDirectories(System.getProperty("java.endorsed.dirs"), false);
376 if (bootclasspathOpt != null) {
377 path.addFiles(bootclasspathOpt);
378 } else {
379 // Standard system classes for this compiler's release.
380 String files = System.getProperty("sun.boot.class.path");
381 path.addFiles(files, false);
382 File rt_jar = new File("rt.jar");
383 for (File file : getPathEntries(files)) {
384 if (new File(file.getName()).equals(rt_jar))
385 defaultBootClassPathRtJar = file;
386 }
387 }
389 path.addFiles(xbootclasspathAppendOpt);
391 // Strictly speaking, standard extensions are not bootstrap
392 // classes, but we treat them identically, so we'll pretend
393 // that they are.
394 if (extdirsOpt != null)
395 path.addDirectories(extdirsOpt);
396 else
397 path.addDirectories(System.getProperty("java.ext.dirs"), false);
399 isDefaultBootClassPath =
400 (xbootclasspathPrependOpt == null) &&
401 (bootclasspathOpt == null) &&
402 (xbootclasspathAppendOpt == null);
404 return path;
405 }
407 private Path computeUserClassPath() {
408 String cp = options.get(CLASSPATH);
410 // CLASSPATH environment variable when run from `javac'.
411 if (cp == null) cp = System.getProperty("env.class.path");
413 // If invoked via a java VM (not the javac launcher), use the
414 // platform class path
415 if (cp == null && System.getProperty("application.home") == null)
416 cp = System.getProperty("java.class.path");
418 // Default to current working directory.
419 if (cp == null) cp = ".";
421 return new Path()
422 .expandJarClassPaths(true) // Only search user jars for Class-Paths
423 .emptyPathDefault(new File(".")) // Empty path elt ==> current directory
424 .addFiles(cp);
425 }
427 private Path computeSourcePath() {
428 String sourcePathArg = options.get(SOURCEPATH);
429 if (sourcePathArg == null)
430 return null;
432 return new Path().addFiles(sourcePathArg);
433 }
435 private Path computeAnnotationProcessorPath() {
436 String processorPathArg = options.get(PROCESSORPATH);
437 if (processorPathArg == null)
438 return null;
440 return new Path().addFiles(processorPathArg);
441 }
443 /** The actual effective locations searched for sources */
444 private Path sourceSearchPath;
446 public Collection<File> sourceSearchPath() {
447 if (sourceSearchPath == null) {
448 lazy();
449 Path sourcePath = getPathForLocation(SOURCE_PATH);
450 Path userClassPath = getPathForLocation(CLASS_PATH);
451 sourceSearchPath = sourcePath != null ? sourcePath : userClassPath;
452 }
453 return Collections.unmodifiableCollection(sourceSearchPath);
454 }
456 /** The actual effective locations searched for classes */
457 private Path classSearchPath;
459 public Collection<File> classSearchPath() {
460 if (classSearchPath == null) {
461 lazy();
462 Path bootClassPath = getPathForLocation(PLATFORM_CLASS_PATH);
463 Path userClassPath = getPathForLocation(CLASS_PATH);
464 classSearchPath = new Path();
465 classSearchPath.addAll(bootClassPath);
466 classSearchPath.addAll(userClassPath);
467 }
468 return Collections.unmodifiableCollection(classSearchPath);
469 }
471 /** The actual effective locations for non-source, non-class files */
472 private Path otherSearchPath;
474 Collection<File> otherSearchPath() {
475 if (otherSearchPath == null) {
476 lazy();
477 Path userClassPath = getPathForLocation(CLASS_PATH);
478 Path sourcePath = getPathForLocation(SOURCE_PATH);
479 if (sourcePath == null)
480 otherSearchPath = userClassPath;
481 else {
482 otherSearchPath = new Path();
483 otherSearchPath.addAll(userClassPath);
484 otherSearchPath.addAll(sourcePath);
485 }
486 }
487 return Collections.unmodifiableCollection(otherSearchPath);
488 }
490 /** Is this the name of an archive file? */
491 private boolean isArchive(File file) {
492 String n = file.getName().toLowerCase();
493 return fsInfo.isFile(file)
494 && (n.endsWith(".jar") || n.endsWith(".zip"));
495 }
497 /**
498 * Utility method for converting a search path string to an array
499 * of directory and JAR file URLs.
500 *
501 * Note that this method is called by apt and the DocletInvoker.
502 *
503 * @param path the search path string
504 * @return the resulting array of directory and JAR file URLs
505 */
506 public static URL[] pathToURLs(String path) {
507 StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
508 URL[] urls = new URL[st.countTokens()];
509 int count = 0;
510 while (st.hasMoreTokens()) {
511 URL url = fileToURL(new File(st.nextToken()));
512 if (url != null) {
513 urls[count++] = url;
514 }
515 }
516 if (urls.length != count) {
517 URL[] tmp = new URL[count];
518 System.arraycopy(urls, 0, tmp, 0, count);
519 urls = tmp;
520 }
521 return urls;
522 }
524 /**
525 * Returns the directory or JAR file URL corresponding to the specified
526 * local file name.
527 *
528 * @param file the File object
529 * @return the resulting directory or JAR file URL, or null if unknown
530 */
531 private static URL fileToURL(File file) {
532 String name;
533 try {
534 name = file.getCanonicalPath();
535 } catch (IOException e) {
536 name = file.getAbsolutePath();
537 }
538 name = name.replace(File.separatorChar, '/');
539 if (!name.startsWith("/")) {
540 name = "/" + name;
541 }
542 // If the file does not exist, then assume that it's a directory
543 if (!file.isFile()) {
544 name = name + "/";
545 }
546 try {
547 return new URL("file", "", name);
548 } catch (MalformedURLException e) {
549 throw new IllegalArgumentException(file.toString());
550 }
551 }
552 }