duke@1: /* jjg@1230: * Copyright (c) 2003, 2012, 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: jjg@1116: import java.io.FileNotFoundException; jjg@1116: import java.util.Iterator; duke@1: import java.io.File; duke@1: import java.io.IOException; darcy@497: import java.net.MalformedURLException; darcy@497: import java.net.URL; jjg@1116: import java.util.Arrays; jjg@1116: import java.util.Collection; jjg@1116: import java.util.Collections; jjg@1116: import java.util.EnumMap; jjg@1116: import java.util.EnumSet; duke@1: import java.util.HashMap; duke@1: import java.util.HashSet; jjg@1116: import java.util.LinkedHashSet; duke@1: import java.util.Map; duke@1: import java.util.Set; darcy@497: import java.util.StringTokenizer; duke@1: import java.util.zip.ZipFile; duke@1: import javax.tools.JavaFileManager.Location; jjg@1116: import javax.tools.StandardLocation; duke@1: jjg@50: import com.sun.tools.javac.code.Lint; jjg@1157: import com.sun.tools.javac.main.Option; 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@1116: import javax.tools.JavaFileManager; jjg@50: import static javax.tools.StandardLocation.*; jjg@1157: import static com.sun.tools.javac.main.Option.*; 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: * jjg@581: *

This is NOT part of any supported API. jjg@581: * If 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: */ jjg@1116: public class Locations { 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: jjg@1116: /** Whether to warn about non-existent path elements */ jjg@1116: private boolean warn; jjg@1116: jjg@1116: // TODO: remove need for this jjg@1116: private boolean inited = false; // TODO? caching bad? jjg@1116: jjg@1116: public Locations() { jjg@1116: initHandlers(); duke@1: } duke@1: jjg@1111: public void update(Log log, Options options, Lint lint, FSInfo fsInfo) { jjg@1111: this.log = log; jjg@1111: this.options = options; jjg@1111: this.lint = lint; jjg@1111: this.fsInfo = fsInfo; duke@1: } duke@1: jjg@1116: public Collection bootClassPath() { jjg@1116: return getLocation(PLATFORM_CLASS_PATH); duke@1: } duke@1: jjg@758: public boolean isDefaultBootClassPath() { jjg@1116: BootClassPathLocationHandler h = jjg@1116: (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH); jjg@1116: return h.isDefault(); duke@1: } duke@1: jjg@818: boolean isDefaultBootClassPathRtJar(File file) { jjg@1116: BootClassPathLocationHandler h = jjg@1116: (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH); jjg@1116: return h.isDefaultRtJar(file); jjg@1116: } jjg@1116: jjg@1116: public Collection userClassPath() { jjg@1116: return getLocation(CLASS_PATH); jjg@1116: } jjg@1116: jjg@1116: public Collection sourcePath() { jjg@1116: Collection p = getLocation(SOURCE_PATH); jjg@1116: // TODO: this should be handled by the LocationHandler jjg@1116: return p == null || p.isEmpty() ? null : p; 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: jjg@1116: /** jjg@1116: * Utility class to help evaluate a path option. jjg@1116: * Duplicate entries are ignored, jar class paths can be expanded. jjg@1116: */ 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) { jjg@874: boolean prev = expandJarClassPaths; jjg@874: expandJarClassPaths = true; jjg@874: try { jjg@874: if (dirs != null) jjg@874: for (File dir : getPathEntries(dirs)) jjg@874: addDirectory(dir, warn); jjg@874: return this; jjg@874: } finally { jjg@874: expandJarClassPaths = prev; jjg@874: } 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) jjg@612: log.warning(Lint.LintCategory.PATH, jjg@612: "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) { jjg@757: if (files != null) { jjg@1116: addFiles(getPathEntries(files, emptyPathDefault), warn); jjg@1116: } jjg@1116: return this; jjg@1116: } jjg@1116: jjg@1116: public Path addFiles(String files) { jjg@1116: return addFiles(files, warn); jjg@1116: } jjg@1116: jjg@1116: public Path addFiles(Iterable files, boolean warn) { jjg@1116: if (files != null) { jjg@1116: for (File file: files) duke@1: addFile(file, warn); jjg@757: } duke@1: return this; duke@1: } duke@1: jjg@1116: public Path addFiles(Iterable files) { duke@1: return addFiles(files, warn); duke@1: } duke@1: duke@1: public void addFile(File file, boolean warn) { jjh@801: if (contains(file)) { jjh@801: // discard duplicates duke@1: return; duke@1: } duke@1: jjg@106: if (! fsInfo.exists(file)) { duke@1: /* No such file or directory exists */ jjg@612: if (warn) { jjg@612: log.warning(Lint.LintCategory.PATH, jjg@612: "path.element.not.found", file); jjg@612: } jjh@801: super.add(file); jjh@801: return; jjh@801: } jjh@801: jjh@801: File canonFile = fsInfo.getCanonicalFile(file); jjh@801: if (canonicalValues.contains(canonFile)) { jjh@801: /* Discard duplicates and avoid infinite recursion */ jjh@801: return; jjh@801: } jjh@801: jjh@801: 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(); jjg@612: if (warn) { jjg@612: log.warning(Lint.LintCategory.PATH, jjg@612: "unexpected.archive.file", file); jjg@612: } duke@1: } catch (IOException e) { duke@1: // FIXME: include e.getLocalizedMessage in warning jjg@612: if (warn) { jjg@612: log.warning(Lint.LintCategory.PATH, jjg@612: "invalid.archive.file", file); jjg@612: } 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 jjh@801: conforming to archive naming convention */ duke@1: super.add(file); jjg@106: canonicalValues.add(canonFile); duke@1: jjh@801: if (expandJarClassPaths && 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: jjg@1116: /** jjg@1116: * Base class for handling support for the representation of Locations. jjg@1116: * Implementations are responsible for handling the interactions between jjg@1116: * the command line options for a location, and API access via setLocation. jjg@1116: * @see #initHandlers jjg@1116: * @see #getHandler jjg@1116: */ jjg@1116: protected abstract class LocationHandler { jjg@1116: final Location location; jjg@1157: final Set