--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/PathFileObject.java Mon Dec 07 09:18:07 2015 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/PathFileObject.java Mon Dec 07 14:02:55 2015 -0800
@@ -33,18 +33,24 @@
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
+import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetDecoder;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
+import java.text.Normalizer;
import java.util.Objects;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind;
+import javax.tools.FileObject;
import javax.tools.JavaFileObject;
+import com.sun.tools.javac.file.RelativePath.RelativeFile;
import com.sun.tools.javac.util.DefinedBy;
import com.sun.tools.javac.util.DefinedBy.Api;
@@ -53,10 +59,16 @@
* Implementation of JavaFileObject using java.nio.file API.
*
* <p>PathFileObjects are, for the most part, straightforward wrappers around
- * Path objects. The primary complexity is the support for "inferBinaryName".
- * This is left as an abstract method, implemented by each of a number of
- * different factory methods, which compute the binary name based on
- * information available at the time the file object is created.
+ * immutable absolute Path objects. Different subtypes are used to provide
+ * specialized implementations of "inferBinaryName" and "getName" that capture
+ * additional information available at the time the object is created.
+ *
+ * <p>In general, {@link JavaFileManager#isSameFile} should be used to
+ * determine whether two file objects refer to the same file on disk.
+ * PathFileObject also supports the standard {@code equals} and {@code hashCode}
+ * methods, primarily for convenience when working with collections.
+ * All of these operations delegate to the equivalent operations on the
+ * underlying Path object.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
@@ -64,108 +76,297 @@
* deletion without notice.</b>
*/
public abstract class PathFileObject implements JavaFileObject {
- private final BaseFileManager fileManager;
- private final Path path;
+ private static final FileSystem defaultFileSystem = FileSystems.getDefault();
+ private static final boolean isMacOS = System.getProperty("os.name", "").contains("OS X");
+
+ protected final BaseFileManager fileManager;
+ protected final Path path;
+ private boolean hasParents;
/**
- * Create a PathFileObject within a directory, such that the binary name
- * can be inferred from the relationship to the parent directory.
+ * Create a PathFileObject for a file within a directory, such that the
+ * binary name can be inferred from the relationship to an enclosing directory.
+ *
+ * The binary name is derived from {@code relativePath}.
+ * The name is derived from the composition of {@code userPackageRootDir}
+ * and {@code relativePath}.
+ *
+ * @param fileManager the file manager creating this file object
+ * @param path the absolute path referred to by this file object
+ * @param userPackageRootDir the path of the directory containing the
+ * root of the package hierarchy
+ * @param relativePath the path of this file relative to {@code userPackageRootDir}
*/
- static PathFileObject createDirectoryPathFileObject(BaseFileManager fileManager,
- final Path path, final Path dir) {
- return new PathFileObject(fileManager, path) {
- @Override
- public String inferBinaryName(Iterable<? extends Path> paths) {
- return toBinaryName(dir.relativize(path));
- }
- };
+ static PathFileObject forDirectoryPath(BaseFileManager fileManager, Path path,
+ Path userPackageRootDir, RelativePath relativePath) {
+ return new DirectoryFileObject(fileManager, path, userPackageRootDir, relativePath);
+ }
+
+ private static class DirectoryFileObject extends PathFileObject {
+ private final Path userPackageRootDir;
+ private final RelativePath relativePath;
+
+ private DirectoryFileObject(BaseFileManager fileManager, Path path,
+ Path userPackageRootDir, RelativePath relativePath) {
+ super(fileManager, path);
+ this.userPackageRootDir = userPackageRootDir;
+ this.relativePath = relativePath;
+ }
+
+ @Override @DefinedBy(Api.COMPILER)
+ public String getName() {
+ return relativePath.resolveAgainst(userPackageRootDir).toString();
+ }
+
+ @Override
+ public String inferBinaryName(Iterable<? extends Path> paths) {
+ return toBinaryName(relativePath);
+ }
+
+ @Override
+ public String toString() {
+ return "DirectoryFileObject[" + userPackageRootDir + ":" + relativePath.path + "]";
+ }
+
+ @Override
+ PathFileObject getSibling(String baseName) {
+ return new DirectoryFileObject(fileManager,
+ path.resolveSibling(baseName),
+ userPackageRootDir,
+ new RelativeFile(relativePath.dirname(), baseName)
+ );
+ }
}
/**
- * Create a PathFileObject in a file system such as a jar file, such that
- * the binary name can be inferred from its position within the filesystem.
+ * Create a PathFileObject for a file in a file system such as a jar file,
+ * such that the binary name can be inferred from its position within the
+ * file system.
+ *
+ * The binary name is derived from {@code path}.
+ * The name is derived from the composition of {@code userJarPath}
+ * and {@code path}.
+ *
+ * @param fileManager the file manager creating this file object
+ * @param path the path referred to by this file object
+ * @param userJarPath the path of the jar file containing the file system.
*/
- public static PathFileObject createJarPathFileObject(BaseFileManager fileManager,
- final Path path) {
- return new PathFileObject(fileManager, path) {
- @Override
- public String inferBinaryName(Iterable<? extends Path> paths) {
- return toBinaryName(path);
- }
- };
+ public static PathFileObject forJarPath(BaseFileManager fileManager,
+ Path path, Path userJarPath) {
+ return new JarFileObject(fileManager, path, userJarPath);
}
- /**
- * Create a PathFileObject in a modular file system, such as jrt:, such that
- * the binary name can be inferred from its position within the filesystem.
- */
- public static PathFileObject createJRTPathFileObject(BaseFileManager fileManager,
- final Path path) {
- return new PathFileObject(fileManager, path) {
- @Override
- public String inferBinaryName(Iterable<? extends Path> paths) {
- // use subpath to ignore the leading /modules/MODULE-NAME
- return toBinaryName(path.subpath(2, path.getNameCount()));
+ private static class JarFileObject extends PathFileObject {
+ private final Path userJarPath;
+
+ private JarFileObject(BaseFileManager fileManager, Path path, Path userJarPath) {
+ super(fileManager, path);
+ this.userJarPath = userJarPath;
+ }
+
+ @Override @DefinedBy(Api.COMPILER)
+ public String getName() {
+ // The use of ( ) to delimit the entry name is not ideal
+ // but it does match earlier behavior
+ return userJarPath + "(" + path + ")";
+ }
+
+ @Override
+ public String inferBinaryName(Iterable<? extends Path> paths) {
+ Path root = path.getFileSystem().getRootDirectories().iterator().next();
+ return toBinaryName(root.relativize(path));
+ }
+
+ @Override @DefinedBy(Api.COMPILER)
+ public URI toUri() {
+ // Work around bug JDK-8134451:
+ // path.toUri() returns double-encoded URIs, that cannot be opened by URLConnection
+ return createJarUri(userJarPath, path.toString());
+ }
+
+ @Override
+ public String toString() {
+ return "JarFileObject[" + userJarPath + ":" + path + "]";
+ }
+
+ @Override
+ PathFileObject getSibling(String baseName) {
+ return new JarFileObject(fileManager,
+ path.resolveSibling(baseName),
+ userJarPath
+ );
+ }
+
+ private static URI createJarUri(Path jarFile, String entryName) {
+ URI jarURI = jarFile.toUri().normalize();
+ String separator = entryName.startsWith("/") ? "!" : "!/";
+ try {
+ // The jar URI convention appears to be not to re-encode the jarURI
+ return new URI("jar:" + jarURI + separator + entryName);
+ } catch (URISyntaxException e) {
+ throw new CannotCreateUriError(jarURI + separator + entryName, e);
}
- };
+ }
}
/**
- * Create a PathFileObject whose binary name can be inferred from the
- * relative path to a sibling.
+ * Create a PathFileObject for a file in a modular file system, such as jrt:,
+ * such that the binary name can be inferred from its position within the
+ * filesystem.
+ *
+ * The binary name is derived from {@code path}, ignoring the first two
+ * elements of the name (which are "modules" and a module name).
+ * The name is derived from {@code path}.
+ *
+ * @param fileManager the file manager creating this file object
+ * @param path the path referred to by this file object
*/
- static PathFileObject createSiblingPathFileObject(BaseFileManager fileManager,
- final Path path, final String relativePath) {
- return new PathFileObject(fileManager, path) {
- @Override
- public String inferBinaryName(Iterable<? extends Path> paths) {
- return toBinaryName(relativePath, "/");
- }
- };
+ public static PathFileObject forJRTPath(BaseFileManager fileManager,
+ final Path path) {
+ return new JRTFileObject(fileManager, path);
+ }
+
+ private static class JRTFileObject extends PathFileObject {
+ // private final Path javaHome;
+ private JRTFileObject(BaseFileManager fileManager, Path path) {
+ super(fileManager, path);
+ }
+
+ @Override @DefinedBy(Api.COMPILER)
+ public String getName() {
+ return path.toString();
+ }
+
+ @Override
+ public String inferBinaryName(Iterable<? extends Path> paths) {
+ // use subpath to ignore the leading /modules/MODULE-NAME
+ return toBinaryName(path.subpath(2, path.getNameCount()));
+ }
+
+ @Override
+ public String toString() {
+ return "JRTFileObject[" + path + "]";
+ }
+
+ @Override
+ PathFileObject getSibling(String baseName) {
+ return new JRTFileObject(fileManager,
+ path.resolveSibling(baseName)
+ );
+ }
}
/**
- * Create a PathFileObject whose binary name might be inferred from its
- * position on a search path.
+ * Create a PathFileObject for a file whose binary name must be inferred
+ * from its position on a search path.
+ *
+ * The binary name is inferred by finding an enclosing directory in
+ * the sequence of paths associated with the location given to
+ * {@link JavaFileManager#inferBinaryName).
+ * The name is derived from {@code userPath}.
+ *
+ * @param fileManager the file manager creating this file object
+ * @param path the path referred to by this file object
+ * @param userPath the "user-friendly" name for this path.
*/
- static PathFileObject createSimplePathFileObject(BaseFileManager fileManager,
- final Path path) {
- return new PathFileObject(fileManager, path) {
- @Override
- public String inferBinaryName(Iterable<? extends Path> paths) {
- Path absPath = path.toAbsolutePath();
- for (Path p: paths) {
- Path ap = p.toAbsolutePath();
- if (absPath.startsWith(ap)) {
- try {
- Path rp = ap.relativize(absPath);
- if (rp != null) // maybe null if absPath same as ap
- return toBinaryName(rp);
- } catch (IllegalArgumentException e) {
- // ignore this p if cannot relativize path to p
- }
+ static PathFileObject forSimplePath(BaseFileManager fileManager,
+ Path path, Path userPath) {
+ return new SimpleFileObject(fileManager, path, userPath);
+ }
+
+ private static class SimpleFileObject extends PathFileObject {
+ private final Path userPath;
+ private SimpleFileObject(BaseFileManager fileManager, Path path, Path userPath) {
+ super(fileManager, path);
+ this.userPath = userPath;
+ }
+
+ @Override @DefinedBy(Api.COMPILER)
+ public String getName() {
+ return userPath.toString();
+ }
+
+ @Override
+ public String inferBinaryName(Iterable<? extends Path> paths) {
+ Path absPath = path.toAbsolutePath();
+ for (Path p: paths) {
+ Path ap = p.toAbsolutePath();
+ if (absPath.startsWith(ap)) {
+ try {
+ Path rp = ap.relativize(absPath);
+ if (rp != null) // maybe null if absPath same as ap
+ return toBinaryName(rp);
+ } catch (IllegalArgumentException e) {
+ // ignore this p if cannot relativize path to p
}
}
- return null;
}
- };
+ return null;
+ }
+
+ @Override
+ PathFileObject getSibling(String baseName) {
+ return new SimpleFileObject(fileManager,
+ path.resolveSibling(baseName),
+ userPath.resolveSibling(baseName)
+ );
+ }
}
+ /**
+ * Create a PathFileObject, for a specified path, in the context of
+ * a given file manager.
+ *
+ * In general, this path should be an
+ * {@link Path#toAbsolutePath absolute path}, if not a
+ * {@link Path#toRealPath} real path.
+ * It will be used as the basis of {@code equals}, {@code hashCode}
+ * and {@code isSameFile} methods on this file object.
+ *
+ * A PathFileObject should also have a "friendly name" per the
+ * specification for {@link FileObject#getName}. The friendly name
+ * is provided by the various subtypes of {@code PathFileObject}.
+ *
+ * @param fileManager the file manager creating this file object
+ * @param path the path contained in this file object.
+ */
protected PathFileObject(BaseFileManager fileManager, Path path) {
this.fileManager = Objects.requireNonNull(fileManager);
- this.path = Objects.requireNonNull(path);
+ if (Files.isDirectory(path)) {
+ throw new IllegalArgumentException("directories not supported");
+ }
+ this.path = path;
}
- public abstract String inferBinaryName(Iterable<? extends Path> paths);
+ /**
+ * See {@link JavacFileManager#inferBinaryName}.
+ */
+ abstract String inferBinaryName(Iterable<? extends Path> paths);
+
+ /**
+ * Return the file object for a sibling file with a given file name.
+ * See {@link JavacFileManager#getFileForOutput} and
+ * {@link JavacFileManager#getJavaFileForOutput}.
+ */
+ abstract PathFileObject getSibling(String basename);
/**
* Return the Path for this object.
* @return the Path for this object.
+ * @see StandardJavaFileManager#asPath
*/
public Path getPath() {
return path;
}
+ /**
+ * The short name is used when generating raw diagnostics.
+ * @return the last component of the path
+ */
+ public String getShortName() {
+ return path.getFileName().toString();
+ }
+
@Override @DefinedBy(Api.COMPILER)
public Kind getKind() {
return BaseFileManager.getKind(path.getFileName().toString());
@@ -174,22 +375,43 @@
@Override @DefinedBy(Api.COMPILER)
public boolean isNameCompatible(String simpleName, Kind kind) {
Objects.requireNonNull(simpleName);
- // null check
+ Objects.requireNonNull(kind);
+
if (kind == Kind.OTHER && getKind() != kind) {
return false;
}
+
String sn = simpleName + kind.extension;
String pn = path.getFileName().toString();
if (pn.equals(sn)) {
return true;
}
- if (pn.equalsIgnoreCase(sn)) {
- try {
- // allow for Windows
- return path.toRealPath(LinkOption.NOFOLLOW_LINKS).getFileName().toString().equals(sn);
- } catch (IOException e) {
+
+ if (path.getFileSystem() == defaultFileSystem) {
+ if (isMacOS) {
+ String name = path.getFileName().toString();
+ if (Normalizer.isNormalized(name, Normalizer.Form.NFD)
+ && Normalizer.isNormalized(sn, Normalizer.Form.NFC)) {
+ // On Mac OS X it is quite possible to have the file name and the
+ // given simple name normalized in different ways.
+ // In that case we have to normalize file name to the
+ // Normal Form Composed (NFC).
+ String normName = Normalizer.normalize(name, Normalizer.Form.NFC);
+ if (normName.equals(sn)) {
+ return true;
+ }
+ }
+ }
+
+ if (pn.equalsIgnoreCase(sn)) {
+ try {
+ // allow for Windows
+ return path.toRealPath(LinkOption.NOFOLLOW_LINKS).getFileName().toString().equals(sn);
+ } catch (IOException e) {
+ }
}
}
+
return false;
}
@@ -209,11 +431,6 @@
}
@Override @DefinedBy(Api.COMPILER)
- public String getName() {
- return path.toString();
- }
-
- @Override @DefinedBy(Api.COMPILER)
public InputStream openInputStream() throws IOException {
return Files.newInputStream(path);
}
@@ -264,7 +481,7 @@
try {
return Files.getLastModifiedTime(path).toMillis();
} catch (IOException e) {
- return -1;
+ return 0;
}
}
@@ -278,7 +495,7 @@
}
}
- public boolean isSameFile(PathFileObject other) {
+ boolean isSameFile(PathFileObject other) {
try {
return Files.isSameFile(path, other.path);
} catch (IOException e) {
@@ -302,17 +519,21 @@
}
private void ensureParentDirectoriesExist() throws IOException {
- Path parent = path.getParent();
- if (parent != null)
- Files.createDirectories(parent);
+ if (!hasParents) {
+ Path parent = path.getParent();
+ if (parent != null && !Files.isDirectory(parent)) {
+ try {
+ Files.createDirectories(parent);
+ } catch (IOException e) {
+ throw new IOException("could not create parent directories", e);
+ }
+ }
+ hasParents = true;
+ }
}
- private long size() {
- try {
- return Files.size(path);
- } catch (IOException e) {
- return -1;
- }
+ protected static String toBinaryName(RelativePath relativePath) {
+ return toBinaryName(relativePath.path, "/");
}
protected static String toBinaryName(Path relativePath) {
@@ -320,12 +541,32 @@
relativePath.getFileSystem().getSeparator());
}
- protected static String toBinaryName(String relativePath, String sep) {
+ private static String toBinaryName(String relativePath, String sep) {
return removeExtension(relativePath).replace(sep, ".");
}
- protected static String removeExtension(String fileName) {
+ private static String removeExtension(String fileName) {
int lastDot = fileName.lastIndexOf(".");
return (lastDot == -1 ? fileName : fileName.substring(0, lastDot));
}
+
+ /** Return the last component of a presumed hierarchical URI.
+ * From the scheme specific part of the URI, it returns the substring
+ * after the last "/" if any, or everything if no "/" is found.
+ */
+ public static String getSimpleName(FileObject fo) {
+ URI uri = fo.toUri();
+ String s = uri.getSchemeSpecificPart();
+ return s.substring(s.lastIndexOf("/") + 1); // safe when / not found
+
+ }
+
+ /** Used when URLSyntaxException is thrown unexpectedly during
+ * implementations of FileObject.toURI(). */
+ public static class CannotCreateUriError extends Error {
+ private static final long serialVersionUID = 9101708840997613546L;
+ public CannotCreateUriError(String value, Throwable cause) {
+ super(value, cause);
+ }
+ }
}