/* * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.javac.file; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.UncheckedIOException; import java.net.MalformedURLException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.zip.ZipFile; import javax.tools.JavaFileManager; import javax.tools.JavaFileManager.Location; import javax.tools.StandardJavaFileManager; import javax.tools.StandardLocation; import com.sun.tools.javac.code.Lint; import com.sun.tools.javac.main.Option; import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.StringUtils; import static javax.tools.StandardLocation.CLASS_PATH; import static javax.tools.StandardLocation.PLATFORM_CLASS_PATH; import static javax.tools.StandardLocation.SOURCE_PATH; import static com.sun.tools.javac.main.Option.BOOTCLASSPATH; import static com.sun.tools.javac.main.Option.DJAVA_ENDORSED_DIRS; import static com.sun.tools.javac.main.Option.DJAVA_EXT_DIRS; import static com.sun.tools.javac.main.Option.ENDORSEDDIRS; import static com.sun.tools.javac.main.Option.EXTDIRS; import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH; import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_APPEND; import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_PREPEND; /** * This class converts command line arguments, environment variables and system properties (in * File.pathSeparator-separated String form) into a boot class path, user class path, and source * path (in {@code Collection} form). * *

* This is NOT part of any supported API. If you write code that depends on this, you do so at * your own risk. This code and its internal interfaces are subject to change or deletion without * notice. */ public class Locations { /** * The log to use for warning output */ private Log log; /** * Access to (possibly cached) file info */ private FSInfo fsInfo; /** * Whether to warn about non-existent path elements */ private boolean warn; // Used by Locations(for now) to indicate that the PLATFORM_CLASS_PATH // should use the jrt: file system. // When Locations has been converted to use java.nio.file.Path, // Locations can use Paths.get(URI.create("jrt:")) static final Path JRT_MARKER_FILE = Paths.get("JRT_MARKER_FILE"); public Locations() { initHandlers(); } // could replace Lint by "boolean warn" public void update(Log log, Lint lint, FSInfo fsInfo) { this.log = log; warn = lint.isEnabled(Lint.LintCategory.PATH); this.fsInfo = fsInfo; } public Collection bootClassPath() { return getLocation(PLATFORM_CLASS_PATH); } public boolean isDefaultBootClassPath() { BootClassPathLocationHandler h = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH); return h.isDefault(); } public Collection userClassPath() { return getLocation(CLASS_PATH); } public Collection sourcePath() { Collection p = getLocation(SOURCE_PATH); // TODO: this should be handled by the LocationHandler return p == null || p.isEmpty() ? null : p; } /** * Split a search path into its elements. Empty path elements will be ignored. * * @param searchPath The search path to be split * @return The elements of the path */ private static Iterable getPathEntries(String searchPath) { return getPathEntries(searchPath, null); } /** * Split a search path into its elements. If emptyPathDefault is not null, all empty elements in the * path, including empty elements at either end of the path, will be replaced with the value of * emptyPathDefault. * * @param searchPath The search path to be split * @param emptyPathDefault The value to substitute for empty path elements, or null, to ignore * empty path elements * @return The elements of the path */ private static Iterable getPathEntries(String searchPath, Path emptyPathDefault) { ListBuffer entries = new ListBuffer<>(); for (String s: searchPath.split(Pattern.quote(File.pathSeparator), -1)) { if (s.isEmpty()) { if (emptyPathDefault != null) { entries.add(emptyPathDefault); } } else { entries.add(Paths.get(s)); } } return entries; } /** * Utility class to help evaluate a path option. Duplicate entries are ignored, jar class paths * can be expanded. */ private class SearchPath extends LinkedHashSet { private static final long serialVersionUID = 0; private boolean expandJarClassPaths = false; private final Set canonicalValues = new HashSet<>(); public SearchPath expandJarClassPaths(boolean x) { expandJarClassPaths = x; return this; } /** * What to use when path element is the empty string */ private Path emptyPathDefault = null; public SearchPath emptyPathDefault(Path x) { emptyPathDefault = x; return this; } public SearchPath addDirectories(String dirs, boolean warn) { boolean prev = expandJarClassPaths; expandJarClassPaths = true; try { if (dirs != null) { for (Path dir : getPathEntries(dirs)) { addDirectory(dir, warn); } } return this; } finally { expandJarClassPaths = prev; } } public SearchPath addDirectories(String dirs) { return addDirectories(dirs, warn); } private void addDirectory(Path dir, boolean warn) { if (!Files.isDirectory(dir)) { if (warn) { log.warning(Lint.LintCategory.PATH, "dir.path.element.not.found", dir); } return; } try (Stream s = Files.list(dir)) { s.filter(dirEntry -> isArchive(dirEntry)) .forEach(dirEntry -> addFile(dirEntry, warn)); } catch (IOException ignore) { } } public SearchPath addFiles(String files, boolean warn) { if (files != null) { addFiles(getPathEntries(files, emptyPathDefault), warn); } return this; } public SearchPath addFiles(String files) { return addFiles(files, warn); } public SearchPath addFiles(Iterable files, boolean warn) { if (files != null) { for (Path file : files) { addFile(file, warn); } } return this; } public SearchPath addFiles(Iterable files) { return addFiles(files, warn); } public void addFile(Path file, boolean warn) { if (contains(file)) { // discard duplicates return; } if (!fsInfo.exists(file)) { /* No such file or directory exists */ if (warn) { log.warning(Lint.LintCategory.PATH, "path.element.not.found", file); } super.add(file); return; } Path canonFile = fsInfo.getCanonicalFile(file); if (canonicalValues.contains(canonFile)) { /* Discard duplicates and avoid infinite recursion */ return; } if (fsInfo.isFile(file)) { /* File is an ordinary file. */ if (!isArchive(file) && !file.getFileName().toString().endsWith(".jimage")) { /* Not a recognized extension; open it to see if it looks like a valid zip file. */ try { ZipFile z = new ZipFile(file.toFile()); z.close(); if (warn) { log.warning(Lint.LintCategory.PATH, "unexpected.archive.file", file); } } catch (IOException e) { // FIXME: include e.getLocalizedMessage in warning if (warn) { log.warning(Lint.LintCategory.PATH, "invalid.archive.file", file); } return; } } } /* Now what we have left is either a directory or a file name conforming to archive naming convention */ super.add(file); canonicalValues.add(canonFile); if (expandJarClassPaths && fsInfo.isFile(file) && !file.getFileName().toString().endsWith(".jimage")) { addJarClassPath(file, warn); } } // Adds referenced classpath elements from a jar's Class-Path // Manifest entry. In some future release, we may want to // update this code to recognize URLs rather than simple // filenames, but if we do, we should redo all path-related code. private void addJarClassPath(Path jarFile, boolean warn) { try { for (Path f : fsInfo.getJarClassPath(jarFile)) { addFile(f, warn); } } catch (IOException e) { log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e)); } } } /** * Base class for handling support for the representation of Locations. Implementations are * responsible for handling the interactions between the command line options for a location, * and API access via setLocation. * * @see #initHandlers * @see #getHandler */ protected abstract class LocationHandler { final Location location; final Set