/* * Copyright (c) 2003, 2019, 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.Closeable; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.DirectoryIteratorException; import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; import java.nio.file.FileSystemNotFoundException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.ProviderNotFoundException; import java.nio.file.spi.FileSystemProvider; 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.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.NoSuchElementException; import java.util.Set; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.jar.Attributes; import java.util.jar.Manifest; import javax.lang.model.SourceVersion; import javax.tools.JavaFileManager; import javax.tools.JavaFileManager.Location; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.StandardJavaFileManager.PathFactory; import javax.tools.StandardLocation; import jdk.internal.jmod.JmodFile; import com.sun.tools.javac.code.Lint; import com.sun.tools.javac.code.Lint.LintCategory; import com.sun.tools.javac.main.Option; import com.sun.tools.javac.resources.CompilerProperties.Errors; import com.sun.tools.javac.resources.CompilerProperties.Warnings; import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; import com.sun.tools.javac.util.JCDiagnostic.Warning; import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.jvm.ModuleNameReader; import com.sun.tools.javac.util.Iterators; import com.sun.tools.javac.util.Pair; import com.sun.tools.javac.util.StringUtils; import static javax.tools.StandardLocation.PLATFORM_CLASS_PATH; import static com.sun.tools.javac.main.Option.BOOT_CLASS_PATH; 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_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; private ModuleNameReader moduleNameReader; private PathFactory pathFactory = Paths::get; static final Path javaHome = FileSystems.getDefault().getPath(System.getProperty("java.home")); static final Path thisSystemModules = javaHome.resolve("lib").resolve("modules"); Map fileSystems = new LinkedHashMap<>(); List closeables = new ArrayList<>(); private Map fsEnv = Collections.emptyMap(); Locations() { initHandlers(); } Path getPath(String first, String... more) { try { return pathFactory.getPath(first, more); } catch (InvalidPathException ipe) { throw new IllegalArgumentException(ipe); } } public void close() throws IOException { ListBuffer list = new ListBuffer<>(); closeables.forEach(closeable -> { try { closeable.close(); } catch (IOException ex) { list.add(ex); } }); if (list.nonEmpty()) { IOException ex = new IOException(); for (IOException e: list) ex.addSuppressed(e); throw ex; } } void update(Log log, boolean warn, FSInfo fsInfo) { this.log = log; this.warn = warn; this.fsInfo = fsInfo; } void setPathFactory(PathFactory f) { pathFactory = f; } boolean isDefaultBootClassPath() { BootClassPathLocationHandler h = (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH); return h.isDefault(); } /** * 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 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 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 { try { entries.add(getPath(s)); } catch (IllegalArgumentException e) { if (warn) { log.warning(LintCategory.PATH, Warnings.InvalidPath(s)); } } } } return entries; } public void setMultiReleaseValue(String multiReleaseValue) { fsEnv = Collections.singletonMap("multi-release", multiReleaseValue); } private boolean contains(Collection searchPath, Path file) throws IOException { if (searchPath == null) { return false; } Path enclosingJar = null; if (file.getFileSystem().provider() == fsInfo.getJarFSProvider()) { URI uri = file.toUri(); if (uri.getScheme().equals("jar")) { String ssp = uri.getSchemeSpecificPart(); int sep = ssp.lastIndexOf("!"); if (ssp.startsWith("file:") && sep > 0) { enclosingJar = Paths.get(URI.create(ssp.substring(0, sep))); } } } Path nf = normalize(file); for (Path p : searchPath) { Path np = normalize(p); if (np.getFileSystem() == nf.getFileSystem() && Files.isDirectory(np) && nf.startsWith(np)) { return true; } if (enclosingJar != null && Files.isSameFile(enclosingJar, np)) { return true; } } return false; } /** * 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, Warnings.DirPathElementNotFound(dir)); } return; } try (Stream s = Files.list(dir)) { s.filter(Locations.this::isArchive) .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, Warnings.PathElementNotFound(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 ( !file.getFileName().toString().endsWith(".jmod") && !file.endsWith("modules")) { if (!isArchive(file)) { /* Not a recognized extension; open it to see if it looks like a valid zip file. */ try { FileSystems.newFileSystem(file, (ClassLoader)null).close(); if (warn) { log.warning(Lint.LintCategory.PATH, Warnings.UnexpectedArchiveFile(file)); } } catch (IOException | ProviderNotFoundException e) { // FIXME: include e.getLocalizedMessage in warning if (warn) { log.warning(Lint.LintCategory.PATH, Warnings.InvalidArchiveFile(file)); } return; } } else { if (fsInfo.getJarFSProvider() == null) { log.error(Errors.NoZipfsForArchive(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.endsWith("modules")) { 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(Errors.ErrorReadingFile(jarFile, JavacFileManager.getMessage(e))); } } } /** * Base class for handling support for the representation of Locations. * * Locations are (by design) opaque handles that can easily be implemented * by enums like StandardLocation. Within JavacFileManager, each Location * has an associated LocationHandler, which provides much of the appropriate * functionality for the corresponding Location. * * @see #initHandlers * @see #getHandler */ protected static abstract class LocationHandler { /** * @see JavaFileManager#handleOption */ abstract boolean handleOption(Option option, String value); /** * @see StandardJavaFileManager#hasLocation */ boolean isSet() { return (getPaths() != null); } abstract boolean isExplicit(); /** * @see StandardJavaFileManager#getLocation */ abstract Collection getPaths(); /** * @see StandardJavaFileManager#setLocation */ abstract void setPaths(Iterable paths) throws IOException; /** * @see StandardJavaFileManager#setLocationForModule */ abstract void setPathsForModule(String moduleName, Iterable paths) throws IOException; /** * @see JavaFileManager#getLocationForModule(Location, String) */ Location getLocationForModule(String moduleName) throws IOException { return null; } /** * @see JavaFileManager#getLocationForModule(Location, JavaFileObject, String) */ Location getLocationForModule(Path file) throws IOException { return null; } /** * @see JavaFileManager#inferModuleName */ String inferModuleName() { return null; } /** * @see JavaFileManager#listLocationsForModules */ Iterable> listLocationsForModules() throws IOException { return null; } /** * @see JavaFileManager#contains */ abstract boolean contains(Path file) throws IOException; } /** * A LocationHandler for a given Location, and associated set of options. */ private static abstract class BasicLocationHandler extends LocationHandler { final Location location; final Set