duke@1: /* ohair@554: * Copyright (c) 2003, 2008, Oracle and/or its affiliates. All rights reserved. duke@1: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. duke@1: * duke@1: * This code is free software; you can redistribute it and/or modify it duke@1: * under the terms of the GNU General Public License version 2 only, as ohair@554: * published by the Free Software Foundation. Oracle designates this duke@1: * particular file as subject to the "Classpath" exception as provided ohair@554: * by Oracle in the LICENSE file that accompanied this code. duke@1: * duke@1: * This code is distributed in the hope that it will be useful, but WITHOUT duke@1: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or duke@1: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License duke@1: * version 2 for more details (a copy is included in the LICENSE file that duke@1: * accompanied this code). duke@1: * duke@1: * You should have received a copy of the GNU General Public License version duke@1: * 2 along with this work; if not, write to the Free Software Foundation, duke@1: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. duke@1: * ohair@554: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA ohair@554: * or visit www.oracle.com if you need additional information or have any ohair@554: * questions. duke@1: */ duke@1: jjg@50: package com.sun.tools.javac.file; jjg@50: duke@1: import java.io.File; duke@1: import java.io.IOException; darcy@497: import java.net.MalformedURLException; darcy@497: import java.net.URL; duke@1: import java.util.HashMap; duke@1: import java.util.HashSet; duke@1: import java.util.Map; duke@1: import java.util.Set; duke@1: import java.util.Collection; duke@1: import java.util.Collections; duke@1: import java.util.LinkedHashSet; darcy@497: import java.util.StringTokenizer; duke@1: import java.util.zip.ZipFile; duke@1: import javax.tools.JavaFileManager.Location; duke@1: jjg@50: import com.sun.tools.javac.code.Lint; jjg@50: import com.sun.tools.javac.util.Context; jjg@151: import com.sun.tools.javac.util.ListBuffer; jjg@50: import com.sun.tools.javac.util.Log; jjg@50: import com.sun.tools.javac.util.Options; jjg@50: jjg@50: import static javax.tools.StandardLocation.*; duke@1: import static com.sun.tools.javac.main.OptionName.*; duke@1: duke@1: /** This class converts command line arguments, environment variables duke@1: * and system properties (in File.pathSeparator-separated String form) duke@1: * into a boot class path, user class path, and source path (in duke@1: * Collection form). duke@1: * duke@1: *

This is NOT part of any API supported by Sun Microsystems. If duke@1: * you write code that depends on this, you do so at your own risk. duke@1: * This code and its internal interfaces are subject to change or duke@1: * deletion without notice. duke@1: */ duke@1: public class Paths { duke@1: duke@1: /** The context key for the todo list */ duke@1: protected static final Context.Key pathsKey = duke@1: new Context.Key(); duke@1: jjg@14: /** Get the Paths instance for this context. jjg@14: * @param context the context jjg@14: * @return the Paths instance for this context jjg@14: */ jjg@450: public static Paths instance(Context context) { duke@1: Paths instance = context.get(pathsKey); duke@1: if (instance == null) duke@1: instance = new Paths(context); duke@1: return instance; duke@1: } duke@1: duke@1: /** The log to use for warning output */ duke@1: private Log log; duke@1: duke@1: /** Collection of command-line options */ duke@1: private Options options; duke@1: duke@1: /** Handler for -Xlint options */ duke@1: private Lint lint; duke@1: jjg@106: /** Access to (possibly cached) file info */ jjg@106: private FSInfo fsInfo; duke@1: duke@1: protected Paths(Context context) { duke@1: context.put(pathsKey, this); duke@1: pathsForLocation = new HashMap(16); duke@1: setContext(context); duke@1: } duke@1: duke@1: void setContext(Context context) { duke@1: log = Log.instance(context); duke@1: options = Options.instance(context); duke@1: lint = Lint.instance(context); jjg@106: fsInfo = FSInfo.instance(context); duke@1: } duke@1: duke@1: /** Whether to warn about non-existent path elements */ duke@1: private boolean warn; duke@1: duke@1: private Map pathsForLocation; duke@1: duke@1: private boolean inited = false; // TODO? caching bad? duke@1: duke@1: /** duke@1: * rt.jar as found on the default bootclass path. If the user specified a duke@1: * bootclasspath, null is used. duke@1: */ duke@1: private File bootClassPathRtJar = null; duke@1: duke@1: Path getPathForLocation(Location location) { duke@1: Path path = pathsForLocation.get(location); duke@1: if (path == null) duke@1: setPathForLocation(location, null); duke@1: return pathsForLocation.get(location); duke@1: } duke@1: duke@1: void setPathForLocation(Location location, Iterable path) { duke@1: // TODO? if (inited) throw new IllegalStateException duke@1: // TODO: otherwise reset sourceSearchPath, classSearchPath as needed duke@1: Path p; duke@1: if (path == null) { duke@1: if (location == CLASS_PATH) duke@1: p = computeUserClassPath(); duke@1: else if (location == PLATFORM_CLASS_PATH) duke@1: p = computeBootClassPath(); duke@1: else if (location == ANNOTATION_PROCESSOR_PATH) duke@1: p = computeAnnotationProcessorPath(); duke@1: else if (location == SOURCE_PATH) duke@1: p = computeSourcePath(); duke@1: else duke@1: // no defaults for other paths duke@1: p = null; duke@1: } else { duke@1: p = new Path(); duke@1: for (File f: path) duke@1: p.addFile(f, warn); // TODO: is use of warn appropriate? duke@1: } duke@1: pathsForLocation.put(location, p); duke@1: } duke@1: duke@1: protected void lazy() { duke@1: if (!inited) { duke@1: warn = lint.isEnabled(Lint.LintCategory.PATH); duke@1: duke@1: pathsForLocation.put(PLATFORM_CLASS_PATH, computeBootClassPath()); duke@1: pathsForLocation.put(CLASS_PATH, computeUserClassPath()); duke@1: pathsForLocation.put(SOURCE_PATH, computeSourcePath()); duke@1: duke@1: inited = true; duke@1: } duke@1: } duke@1: duke@1: public Collection bootClassPath() { duke@1: lazy(); duke@1: return Collections.unmodifiableCollection(getPathForLocation(PLATFORM_CLASS_PATH)); duke@1: } duke@1: public Collection userClassPath() { duke@1: lazy(); duke@1: return Collections.unmodifiableCollection(getPathForLocation(CLASS_PATH)); duke@1: } duke@1: public Collection sourcePath() { duke@1: lazy(); duke@1: Path p = getPathForLocation(SOURCE_PATH); duke@1: return p == null || p.size() == 0 duke@1: ? null duke@1: : Collections.unmodifiableCollection(p); duke@1: } duke@1: duke@1: boolean isBootClassPathRtJar(File file) { duke@1: return file.equals(bootClassPathRtJar); duke@1: } duke@1: jjg@151: /** jjg@151: * Split a path into its elements. Empty path elements will be ignored. jjg@151: * @param path The path to be split jjg@151: * @return The elements of the path jjg@151: */ jjg@151: private static Iterable getPathEntries(String path) { jjg@151: return getPathEntries(path, null); jjg@151: } duke@1: jjg@151: /** jjg@151: * Split a path into its elements. If emptyPathDefault is not null, all jjg@151: * empty elements in the path, including empty elements at either end of jjg@151: * the path, will be replaced with the value of emptyPathDefault. jjg@151: * @param path The path to be split jjg@151: * @param emptyPathDefault The value to substitute for empty path elements, jjg@151: * or null, to ignore empty path elements jjg@151: * @return The elements of the path jjg@151: */ jjg@151: private static Iterable getPathEntries(String path, File emptyPathDefault) { jjg@151: ListBuffer entries = new ListBuffer(); jjg@151: int start = 0; jjg@151: while (start <= path.length()) { jjg@151: int sep = path.indexOf(File.pathSeparatorChar, start); jjg@151: if (sep == -1) jjg@151: sep = path.length(); jjg@151: if (start < sep) jjg@151: entries.add(new File(path.substring(start, sep))); jjg@151: else if (emptyPathDefault != null) jjg@151: entries.add(emptyPathDefault); jjg@151: start = sep + 1; duke@1: } jjg@151: return entries; duke@1: } duke@1: duke@1: private class Path extends LinkedHashSet { duke@1: private static final long serialVersionUID = 0; duke@1: duke@1: private boolean expandJarClassPaths = false; duke@1: private Set canonicalValues = new HashSet(); duke@1: duke@1: public Path expandJarClassPaths(boolean x) { duke@1: expandJarClassPaths = x; duke@1: return this; duke@1: } duke@1: duke@1: /** What to use when path element is the empty string */ jjg@151: private File emptyPathDefault = null; duke@1: jjg@151: public Path emptyPathDefault(File x) { duke@1: emptyPathDefault = x; duke@1: return this; duke@1: } duke@1: duke@1: public Path() { super(); } duke@1: duke@1: public Path addDirectories(String dirs, boolean warn) { duke@1: if (dirs != null) jjg@151: for (File dir : getPathEntries(dirs)) duke@1: addDirectory(dir, warn); duke@1: return this; duke@1: } duke@1: duke@1: public Path addDirectories(String dirs) { duke@1: return addDirectories(dirs, warn); duke@1: } duke@1: jjg@151: private void addDirectory(File dir, boolean warn) { jjg@151: if (!dir.isDirectory()) { duke@1: if (warn) duke@1: log.warning("dir.path.element.not.found", dir); duke@1: return; duke@1: } duke@1: jjg@151: File[] files = dir.listFiles(); duke@1: if (files == null) duke@1: return; duke@1: duke@1: for (File direntry : files) { duke@1: if (isArchive(direntry)) duke@1: addFile(direntry, warn); duke@1: } duke@1: } duke@1: duke@1: public Path addFiles(String files, boolean warn) { duke@1: if (files != null) jjg@151: for (File file : getPathEntries(files, emptyPathDefault)) duke@1: addFile(file, warn); duke@1: return this; duke@1: } duke@1: duke@1: public Path addFiles(String files) { duke@1: return addFiles(files, warn); duke@1: } duke@1: duke@1: public void addFile(File file, boolean warn) { jjg@106: File canonFile = fsInfo.getCanonicalFile(file); jjg@106: if (contains(file) || canonicalValues.contains(canonFile)) { duke@1: /* Discard duplicates and avoid infinite recursion */ duke@1: return; duke@1: } duke@1: jjg@106: if (! fsInfo.exists(file)) { duke@1: /* No such file or directory exists */ duke@1: if (warn) duke@1: log.warning("path.element.not.found", file); jjg@106: } else if (fsInfo.isFile(file)) { duke@1: /* File is an ordinary file. */ duke@1: if (!isArchive(file)) { duke@1: /* Not a recognized extension; open it to see if duke@1: it looks like a valid zip file. */ duke@1: try { duke@1: ZipFile z = new ZipFile(file); duke@1: z.close(); duke@1: if (warn) duke@1: log.warning("unexpected.archive.file", file); duke@1: } catch (IOException e) { duke@1: // FIXME: include e.getLocalizedMessage in warning duke@1: if (warn) duke@1: log.warning("invalid.archive.file", file); duke@1: return; duke@1: } duke@1: } duke@1: } duke@1: duke@1: /* Now what we have left is either a directory or a file name duke@1: confirming to archive naming convention */ duke@1: super.add(file); jjg@106: canonicalValues.add(canonFile); duke@1: jjg@106: if (expandJarClassPaths && fsInfo.exists(file) && fsInfo.isFile(file)) duke@1: addJarClassPath(file, warn); duke@1: } duke@1: duke@1: // Adds referenced classpath elements from a jar's Class-Path duke@1: // Manifest entry. In some future release, we may want to duke@1: // update this code to recognize URLs rather than simple duke@1: // filenames, but if we do, we should redo all path-related code. duke@1: private void addJarClassPath(File jarFile, boolean warn) { duke@1: try { jjg@106: for (File f: fsInfo.getJarClassPath(jarFile)) { jjg@106: addFile(f, warn); duke@1: } duke@1: } catch (IOException e) { jjg@510: log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e)); duke@1: } duke@1: } duke@1: } duke@1: duke@1: private Path computeBootClassPath() { duke@1: bootClassPathRtJar = null; duke@1: String optionValue; duke@1: Path path = new Path(); duke@1: duke@1: path.addFiles(options.get(XBOOTCLASSPATH_PREPEND)); duke@1: duke@1: if ((optionValue = options.get(ENDORSEDDIRS)) != null) duke@1: path.addDirectories(optionValue); duke@1: else duke@1: path.addDirectories(System.getProperty("java.endorsed.dirs"), false); duke@1: duke@1: if ((optionValue = options.get(BOOTCLASSPATH)) != null) { duke@1: path.addFiles(optionValue); duke@1: } else { duke@1: // Standard system classes for this compiler's release. duke@1: String files = System.getProperty("sun.boot.class.path"); duke@1: path.addFiles(files, false); duke@1: File rt_jar = new File("rt.jar"); jjg@151: for (File file : getPathEntries(files)) { jjg@151: if (new File(file.getName()).equals(rt_jar)) jjg@151: bootClassPathRtJar = file; duke@1: } duke@1: } duke@1: duke@1: path.addFiles(options.get(XBOOTCLASSPATH_APPEND)); duke@1: duke@1: // Strictly speaking, standard extensions are not bootstrap duke@1: // classes, but we treat them identically, so we'll pretend duke@1: // that they are. duke@1: if ((optionValue = options.get(EXTDIRS)) != null) duke@1: path.addDirectories(optionValue); duke@1: else duke@1: path.addDirectories(System.getProperty("java.ext.dirs"), false); duke@1: duke@1: return path; duke@1: } duke@1: duke@1: private Path computeUserClassPath() { duke@1: String cp = options.get(CLASSPATH); duke@1: duke@1: // CLASSPATH environment variable when run from `javac'. duke@1: if (cp == null) cp = System.getProperty("env.class.path"); duke@1: duke@1: // If invoked via a java VM (not the javac launcher), use the duke@1: // platform class path duke@1: if (cp == null && System.getProperty("application.home") == null) duke@1: cp = System.getProperty("java.class.path"); duke@1: duke@1: // Default to current working directory. duke@1: if (cp == null) cp = "."; duke@1: duke@1: return new Path() jjg@151: .expandJarClassPaths(true) // Only search user jars for Class-Paths jjg@151: .emptyPathDefault(new File(".")) // Empty path elt ==> current directory duke@1: .addFiles(cp); duke@1: } duke@1: duke@1: private Path computeSourcePath() { duke@1: String sourcePathArg = options.get(SOURCEPATH); duke@1: if (sourcePathArg == null) duke@1: return null; duke@1: duke@1: return new Path().addFiles(sourcePathArg); duke@1: } duke@1: duke@1: private Path computeAnnotationProcessorPath() { duke@1: String processorPathArg = options.get(PROCESSORPATH); duke@1: if (processorPathArg == null) duke@1: return null; duke@1: duke@1: return new Path().addFiles(processorPathArg); duke@1: } duke@1: duke@1: /** The actual effective locations searched for sources */ duke@1: private Path sourceSearchPath; duke@1: duke@1: public Collection sourceSearchPath() { duke@1: if (sourceSearchPath == null) { duke@1: lazy(); duke@1: Path sourcePath = getPathForLocation(SOURCE_PATH); duke@1: Path userClassPath = getPathForLocation(CLASS_PATH); duke@1: sourceSearchPath = sourcePath != null ? sourcePath : userClassPath; duke@1: } duke@1: return Collections.unmodifiableCollection(sourceSearchPath); duke@1: } duke@1: duke@1: /** The actual effective locations searched for classes */ duke@1: private Path classSearchPath; duke@1: duke@1: public Collection classSearchPath() { duke@1: if (classSearchPath == null) { duke@1: lazy(); duke@1: Path bootClassPath = getPathForLocation(PLATFORM_CLASS_PATH); duke@1: Path userClassPath = getPathForLocation(CLASS_PATH); duke@1: classSearchPath = new Path(); duke@1: classSearchPath.addAll(bootClassPath); duke@1: classSearchPath.addAll(userClassPath); duke@1: } duke@1: return Collections.unmodifiableCollection(classSearchPath); duke@1: } duke@1: duke@1: /** The actual effective locations for non-source, non-class files */ duke@1: private Path otherSearchPath; duke@1: duke@1: Collection otherSearchPath() { duke@1: if (otherSearchPath == null) { duke@1: lazy(); duke@1: Path userClassPath = getPathForLocation(CLASS_PATH); duke@1: Path sourcePath = getPathForLocation(SOURCE_PATH); duke@1: if (sourcePath == null) duke@1: otherSearchPath = userClassPath; duke@1: else { duke@1: otherSearchPath = new Path(); duke@1: otherSearchPath.addAll(userClassPath); duke@1: otherSearchPath.addAll(sourcePath); duke@1: } duke@1: } duke@1: return Collections.unmodifiableCollection(otherSearchPath); duke@1: } duke@1: duke@1: /** Is this the name of an archive file? */ jjg@106: private boolean isArchive(File file) { duke@1: String n = file.getName().toLowerCase(); jjg@106: return fsInfo.isFile(file) duke@1: && (n.endsWith(".jar") || n.endsWith(".zip")); duke@1: } darcy@497: darcy@497: /** darcy@497: * Utility method for converting a search path string to an array darcy@497: * of directory and JAR file URLs. darcy@497: * darcy@497: * Note that this method is called by apt and the DocletInvoker. darcy@497: * darcy@497: * @param path the search path string darcy@497: * @return the resulting array of directory and JAR file URLs darcy@497: */ darcy@497: public static URL[] pathToURLs(String path) { darcy@497: StringTokenizer st = new StringTokenizer(path, File.pathSeparator); darcy@497: URL[] urls = new URL[st.countTokens()]; darcy@497: int count = 0; darcy@497: while (st.hasMoreTokens()) { darcy@497: URL url = fileToURL(new File(st.nextToken())); darcy@497: if (url != null) { darcy@497: urls[count++] = url; darcy@497: } darcy@497: } darcy@497: if (urls.length != count) { darcy@497: URL[] tmp = new URL[count]; darcy@497: System.arraycopy(urls, 0, tmp, 0, count); darcy@497: urls = tmp; darcy@497: } darcy@497: return urls; darcy@497: } darcy@497: darcy@497: /** darcy@497: * Returns the directory or JAR file URL corresponding to the specified darcy@497: * local file name. darcy@497: * darcy@497: * @param file the File object darcy@497: * @return the resulting directory or JAR file URL, or null if unknown darcy@497: */ darcy@497: private static URL fileToURL(File file) { darcy@497: String name; darcy@497: try { darcy@497: name = file.getCanonicalPath(); darcy@497: } catch (IOException e) { darcy@497: name = file.getAbsolutePath(); darcy@497: } darcy@497: name = name.replace(File.separatorChar, '/'); darcy@497: if (!name.startsWith("/")) { darcy@497: name = "/" + name; darcy@497: } darcy@497: // If the file does not exist, then assume that it's a directory darcy@497: if (!file.isFile()) { darcy@497: name = name + "/"; darcy@497: } darcy@497: try { darcy@497: return new URL("file", "", name); darcy@497: } catch (MalformedURLException e) { darcy@497: throw new IllegalArgumentException(file.toString()); darcy@497: } darcy@497: } duke@1: }