--- a/jdk/src/java.base/share/classes/jdk/internal/jrtfs/JrtPath.java Fri Apr 15 10:14:57 2016 -0700
+++ b/jdk/src/java.base/share/classes/jdk/internal/jrtfs/JrtPath.java Fri Apr 15 13:05:52 2016 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 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
@@ -24,8 +24,30 @@
*/
package jdk.internal.jrtfs;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.*;
+import java.nio.file.DirectoryStream.Filter;;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileTime;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Set;
+import static java.nio.file.StandardOpenOption.*;
+import static java.nio.file.StandardCopyOption.*;
+
/**
- * jrt Path implementation for jrt on .jimage files.
+ * Base class for Path implementation of jrt file systems.
*
* @implNote This class needs to maintain JDK 8 source compatibility.
*
@@ -33,23 +55,756 @@
* but also compiled and delivered as part of the jrtfs.jar to support access
* to the jimage file provided by the shipped JDK by tools running on JDK 8.
*/
-final class JrtPath extends AbstractJrtPath {
+final class JrtPath implements Path {
+
+ final JrtFileSystem jrtfs;
+ private final String path;
+ private volatile int[] offsets;
+
+ JrtPath(JrtFileSystem jrtfs, String path) {
+ this.jrtfs = jrtfs;
+ this.path = normalize(path);
+ this.resolved = null;
+ }
+
+ JrtPath(JrtFileSystem jrtfs, String path, boolean normalized) {
+ this.jrtfs = jrtfs;
+ this.path = normalized ? path : normalize(path);
+ this.resolved = null;
+ }
+
+ final String getName() {
+ return path;
+ }
+
+ @Override
+ public final JrtPath getRoot() {
+ if (this.isAbsolute()) {
+ return jrtfs.getRootPath();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public final JrtPath getFileName() {
+ if (path.length() == 0)
+ return this;
+ if (path.length() == 1 && path.charAt(0) == '/')
+ return null;
+ int off = path.lastIndexOf('/');
+ if (off == -1)
+ return this;
+ return new JrtPath(jrtfs, path.substring(off + 1), true);
+ }
+
+ @Override
+ public final JrtPath getParent() {
+ initOffsets();
+ int count = offsets.length;
+ if (count == 0) { // no elements so no parent
+ return null;
+ }
+ int off = offsets[count - 1] - 1;
+ if (off <= 0) { // parent is root only (may be null)
+ return getRoot();
+ }
+ return new JrtPath(jrtfs, path.substring(0, off));
+ }
+
+ @Override
+ public final int getNameCount() {
+ initOffsets();
+ return offsets.length;
+ }
- JrtPath(AbstractJrtFileSystem jrtfs, byte[] path) {
- this(jrtfs, path, false);
+ @Override
+ public final JrtPath getName(int index) {
+ initOffsets();
+ if (index < 0 || index >= offsets.length) {
+ throw new IllegalArgumentException();
+ }
+ int begin = offsets[index];
+ int end;
+ if (index == (offsets.length - 1)) {
+ end = path.length();
+ } else {
+ end = offsets[index + 1];
+ }
+ return new JrtPath(jrtfs, path.substring(begin, end));
+ }
+
+ @Override
+ public final JrtPath subpath(int beginIndex, int endIndex) {
+ initOffsets();
+ if (beginIndex < 0 || endIndex > offsets.length ||
+ beginIndex >= endIndex) {
+ throw new IllegalArgumentException();
+ }
+ // starting/ending offsets
+ int begin = offsets[beginIndex];
+ int end;
+ if (endIndex == offsets.length) {
+ end = path.length();
+ } else {
+ end = offsets[endIndex];
+ }
+ return new JrtPath(jrtfs, path.substring(begin, end));
+ }
+
+ @Override
+ public final JrtPath toRealPath(LinkOption... options) throws IOException {
+ return jrtfs.toRealPath(this, options);
+ }
+
+ @Override
+ public final JrtPath toAbsolutePath() {
+ if (isAbsolute())
+ return this;
+ return new JrtPath(jrtfs, "/" + path, true);
+ }
+
+ @Override
+ public final URI toUri() {
+ try {
+ return new URI("jrt", toAbsolutePath().path, null);
+ } catch (URISyntaxException ex) {
+ throw new AssertionError(ex);
+ }
+ }
+
+ private boolean equalsNameAt(JrtPath other, int index) {
+ int mbegin = offsets[index];
+ int mlen;
+ if (index == (offsets.length - 1)) {
+ mlen = path.length() - mbegin;
+ } else {
+ mlen = offsets[index + 1] - mbegin - 1;
+ }
+ int obegin = other.offsets[index];
+ int olen;
+ if (index == (other.offsets.length - 1)) {
+ olen = other.path.length() - obegin;
+ } else {
+ olen = other.offsets[index + 1] - obegin - 1;
+ }
+ if (mlen != olen) {
+ return false;
+ }
+ int n = 0;
+ while (n < mlen) {
+ if (path.charAt(mbegin + n) != other.path.charAt(obegin + n)) {
+ return false;
+ }
+ n++;
+ }
+ return true;
}
- JrtPath(AbstractJrtFileSystem jrtfs, byte[] path, boolean normalized) {
- super(jrtfs, path, normalized);
+ @Override
+ public final JrtPath relativize(Path other) {
+ final JrtPath o = checkPath(other);
+ if (o.equals(this)) {
+ return new JrtPath(jrtfs, "", true);
+ }
+ if (path.length() == 0) {
+ return o;
+ }
+ if (jrtfs != o.jrtfs || isAbsolute() != o.isAbsolute()) {
+ throw new IllegalArgumentException();
+ }
+ final String tp = this.path;
+ final String op = o.path;
+ if (op.startsWith(tp)) { // fast path
+ int off = tp.length();
+ if (op.charAt(off - 1) == '/')
+ return new JrtPath(jrtfs, op.substring(off), true);
+ if (op.charAt(off) == '/')
+ return new JrtPath(jrtfs, op.substring(off + 1), true);
+ }
+ int mc = this.getNameCount();
+ int oc = o.getNameCount();
+ int n = Math.min(mc, oc);
+ int i = 0;
+ while (i < n) {
+ if (!equalsNameAt(o, i)) {
+ break;
+ }
+ i++;
+ }
+ int dotdots = mc - i;
+ int len = dotdots * 3 - 1;
+ if (i < oc) {
+ len += (o.path.length() - o.offsets[i] + 1);
+ }
+ StringBuilder sb = new StringBuilder(len);
+ while (dotdots > 0) {
+ sb.append("..");
+ if (sb.length() < len) { // no tailing slash at the end
+ sb.append('/');
+ }
+ dotdots--;
+ }
+ if (i < oc) {
+ sb.append(o.path, o.offsets[i], o.path.length());
+ }
+ return new JrtPath(jrtfs, sb.toString(), true);
+ }
+
+ @Override
+ public JrtFileSystem getFileSystem() {
+ return jrtfs;
+ }
+
+ @Override
+ public final boolean isAbsolute() {
+ return path.length() > 0 && path.charAt(0) == '/';
+ }
+
+ @Override
+ public final JrtPath resolve(Path other) {
+ final JrtPath o = checkPath(other);
+ if (this.path.length() == 0 || o.isAbsolute()) {
+ return o;
+ }
+ if (o.path.length() == 0) {
+ return this;
+ }
+ StringBuilder sb = new StringBuilder(path.length() + o.path.length());
+ sb.append(path);
+ if (path.charAt(path.length() - 1) != '/')
+ sb.append('/');
+ sb.append(o.path);
+ return new JrtPath(jrtfs, sb.toString(), true);
+ }
+
+ @Override
+ public final Path resolveSibling(Path other) {
+ Objects.requireNonNull(other, "other");
+ Path parent = getParent();
+ return (parent == null) ? other : parent.resolve(other);
+ }
+
+ @Override
+ public final boolean startsWith(Path other) {
+ if (!(Objects.requireNonNull(other) instanceof JrtPath))
+ return false;
+ final JrtPath o = (JrtPath)other;
+ final String tp = this.path;
+ final String op = o.path;
+ if (isAbsolute() != o.isAbsolute() || !tp.startsWith(op)) {
+ return false;
+ }
+ int off = op.length();
+ if (off == 0) {
+ return tp.length() == 0;
+ }
+ // check match is on name boundary
+ return tp.length() == off || tp.charAt(off) == '/' ||
+ off == 0 || op.charAt(off - 1) == '/';
+ }
+
+ @Override
+ public final boolean endsWith(Path other) {
+ if (!(Objects.requireNonNull(other) instanceof JrtPath))
+ return false;
+ final JrtPath o = (JrtPath)other;
+ final JrtPath t = this;
+ int olast = o.path.length() - 1;
+ if (olast > 0 && o.path.charAt(olast) == '/') {
+ olast--;
+ }
+ int last = t.path.length() - 1;
+ if (last > 0 && t.path.charAt(last) == '/') {
+ last--;
+ }
+ if (olast == -1) { // o.path.length == 0
+ return last == -1;
+ }
+ if ((o.isAbsolute() && (!t.isAbsolute() || olast != last))
+ || last < olast) {
+ return false;
+ }
+ for (; olast >= 0; olast--, last--) {
+ if (o.path.charAt(olast) != t.path.charAt(last)) {
+ return false;
+ }
+ }
+ return o.path.charAt(olast + 1) == '/' ||
+ last == -1 || t.path.charAt(last) == '/';
+ }
+
+ @Override
+ public final JrtPath resolve(String other) {
+ return resolve(getFileSystem().getPath(other));
+ }
+
+ @Override
+ public final Path resolveSibling(String other) {
+ return resolveSibling(getFileSystem().getPath(other));
+ }
+
+ @Override
+ public final boolean startsWith(String other) {
+ return startsWith(getFileSystem().getPath(other));
+ }
+
+ @Override
+ public final boolean endsWith(String other) {
+ return endsWith(getFileSystem().getPath(other));
}
@Override
- protected JrtPath newJrtPath(byte[] path) {
- return new JrtPath(jrtfs, path);
+ public final JrtPath normalize() {
+ String res = getResolved();
+ if (res == path) { // no change
+ return this;
+ }
+ return new JrtPath(jrtfs, res, true);
+ }
+
+ private JrtPath checkPath(Path path) {
+ Objects.requireNonNull(path);
+ if (!(path instanceof JrtPath))
+ throw new ProviderMismatchException();
+ return (JrtPath) path;
+ }
+
+ // create offset list if not already created
+ private void initOffsets() {
+ if (this.offsets == null) {
+ int len = path.length();
+ // count names
+ int count = 0;
+ int off = 0;
+ while (off < len) {
+ char c = path.charAt(off++);
+ if (c != '/') {
+ count++;
+ off = path.indexOf('/', off);
+ if (off == -1)
+ break;
+ }
+ }
+ // populate offsets
+ int[] offsets = new int[count];
+ count = 0;
+ off = 0;
+ while (off < len) {
+ char c = path.charAt(off);
+ if (c == '/') {
+ off++;
+ } else {
+ offsets[count++] = off++;
+ off = path.indexOf('/', off);
+ if (off == -1)
+ break;
+ }
+ }
+ this.offsets = offsets;
+ }
+ }
+
+ private volatile String resolved;
+
+ final String getResolvedPath() {
+ String r = resolved;
+ if (r == null) {
+ if (isAbsolute()) {
+ r = getResolved();
+ } else {
+ r = toAbsolutePath().getResolvedPath();
+ }
+ resolved = r;
+ }
+ return r;
+ }
+
+ // removes redundant slashs, replace "\" to separator "/"
+ // and check for invalid characters
+ private static String normalize(String path) {
+ int len = path.length();
+ if (len == 0) {
+ return path;
+ }
+ char prevC = 0;
+ for (int i = 0; i < len; i++) {
+ char c = path.charAt(i);
+ if (c == '\\' || c == '\u0000') {
+ return normalize(path, i);
+ }
+ if (c == '/' && prevC == '/') {
+ return normalize(path, i - 1);
+ }
+ prevC = c;
+ }
+ if (prevC == '/' && len > 1) {
+ return path.substring(0, len - 1);
+ }
+ return path;
+ }
+
+ private static String normalize(String path, int off) {
+ int len = path.length();
+ StringBuilder to = new StringBuilder(len);
+ to.append(path, 0, off);
+ char prevC = 0;
+ while (off < len) {
+ char c = path.charAt(off++);
+ if (c == '\\') {
+ c = '/';
+ }
+ if (c == '/' && prevC == '/') {
+ continue;
+ }
+ if (c == '\u0000') {
+ throw new InvalidPathException(path,
+ "Path: nul character not allowed");
+ }
+ to.append(c);
+ prevC = c;
+ }
+ len = to.length();
+ if (len > 1 && to.charAt(len - 1) == '/') {
+ to.deleteCharAt(len - 1);
+ }
+ return to.toString();
+ }
+
+ // Remove DotSlash(./) and resolve DotDot (..) components
+ private String getResolved() {
+ if (path.length() == 0) {
+ return path;
+ }
+ if (path.indexOf('.') == -1) {
+ return path;
+ }
+ int length = path.length();
+ char[] to = new char[length];
+ int nc = getNameCount();
+ int[] lastM = new int[nc];
+ int lastMOff = -1;
+ int m = 0;
+ for (int i = 0; i < nc; i++) {
+ int n = offsets[i];
+ int len = (i == offsets.length - 1) ? length - n
+ : offsets[i + 1] - n - 1;
+ if (len == 1 && path.charAt(n) == '.') {
+ if (m == 0 && path.charAt(0) == '/') // absolute path
+ to[m++] = '/';
+ continue;
+ }
+ if (len == 2 && path.charAt(n) == '.' && path.charAt(n + 1) == '.') {
+ if (lastMOff >= 0) {
+ m = lastM[lastMOff--]; // retreat
+ continue;
+ }
+ if (path.charAt(0) == '/') { // "/../xyz" skip
+ if (m == 0)
+ to[m++] = '/';
+ } else { // "../xyz" -> "../xyz"
+ if (m != 0 && to[m-1] != '/')
+ to[m++] = '/';
+ while (len-- > 0)
+ to[m++] = path.charAt(n++);
+ }
+ continue;
+ }
+ if (m == 0 && path.charAt(0) == '/' || // absolute path
+ m != 0 && to[m-1] != '/') { // not the first name
+ to[m++] = '/';
+ }
+ lastM[++lastMOff] = m;
+ while (len-- > 0)
+ to[m++] = path.charAt(n++);
+ }
+ if (m > 1 && to[m - 1] == '/')
+ m--;
+ return (m == to.length) ? new String(to) : new String(to, 0, m);
+ }
+
+ @Override
+ public final String toString() {
+ return path;
+ }
+
+ @Override
+ public final int hashCode() {
+ return path.hashCode();
+ }
+
+ @Override
+ public final boolean equals(Object obj) {
+ return obj instanceof JrtPath &&
+ this.path.equals(((JrtPath) obj).path);
+ }
+
+ @Override
+ public final int compareTo(Path other) {
+ final JrtPath o = checkPath(other);
+ return path.compareTo(o.path);
+ }
+
+ @Override
+ public final WatchKey register(
+ WatchService watcher,
+ WatchEvent.Kind<?>[] events,
+ WatchEvent.Modifier... modifiers) {
+ Objects.requireNonNull(watcher, "watcher");
+ Objects.requireNonNull(events, "events");
+ Objects.requireNonNull(modifiers, "modifiers");
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public final WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) {
+ return register(watcher, events, new WatchEvent.Modifier[0]);
+ }
+
+ @Override
+ public final File toFile() {
+ throw new UnsupportedOperationException();
}
@Override
- protected JrtPath newJrtPath(byte[] path, boolean normalized) {
- return new JrtPath(jrtfs, path, normalized);
+ public final Iterator<Path> iterator() {
+ return new Iterator<Path>() {
+ private int i = 0;
+
+ @Override
+ public boolean hasNext() {
+ return (i < getNameCount());
+ }
+
+ @Override
+ public Path next() {
+ if (i < getNameCount()) {
+ Path result = getName(i);
+ i++;
+ return result;
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new ReadOnlyFileSystemException();
+ }
+ };
+ }
+
+ // Helpers for JrtFileSystemProvider and JrtFileSystem
+
+ final JrtPath readSymbolicLink() throws IOException {
+ if (!jrtfs.isLink(this)) {
+ throw new IOException("not a symbolic link");
+ }
+ return jrtfs.resolveLink(this);
+ }
+
+ final boolean isHidden() {
+ return false;
+ }
+
+ final void createDirectory(FileAttribute<?>... attrs)
+ throws IOException {
+ jrtfs.createDirectory(this, attrs);
+ }
+
+ final InputStream newInputStream(OpenOption... options) throws IOException {
+ if (options.length > 0) {
+ for (OpenOption opt : options) {
+ if (opt != READ) {
+ throw new UnsupportedOperationException("'" + opt + "' not allowed");
+ }
+ }
+ }
+ return jrtfs.newInputStream(this);
+ }
+
+ final DirectoryStream<Path> newDirectoryStream(Filter<? super Path> filter)
+ throws IOException {
+ return new JrtDirectoryStream(this, filter);
+ }
+
+ final void delete() throws IOException {
+ jrtfs.deleteFile(this, true);
+ }
+
+ final void deleteIfExists() throws IOException {
+ jrtfs.deleteFile(this, false);
+ }
+
+ final JrtFileAttributes getAttributes(LinkOption... options) throws IOException {
+ JrtFileAttributes zfas = jrtfs.getFileAttributes(this, options);
+ if (zfas == null) {
+ throw new NoSuchFileException(toString());
+ }
+ return zfas;
+ }
+
+ final void setAttribute(String attribute, Object value, LinkOption... options)
+ throws IOException {
+ JrtFileAttributeView.setAttribute(this, attribute, value);
+ }
+
+ final Map<String, Object> readAttributes(String attributes, LinkOption... options)
+ throws IOException {
+ return JrtFileAttributeView.readAttributes(this, attributes, options);
+ }
+
+ final void setTimes(FileTime mtime, FileTime atime, FileTime ctime)
+ throws IOException {
+ jrtfs.setTimes(this, mtime, atime, ctime);
+ }
+
+ final FileStore getFileStore() throws IOException {
+ // each JrtFileSystem only has one root (as requested for now)
+ if (exists()) {
+ return jrtfs.getFileStore(this);
+ }
+ throw new NoSuchFileException(path);
+ }
+
+ final boolean isSameFile(Path other) throws IOException {
+ if (this == other || this.equals(other)) {
+ return true;
+ }
+ if (other == null || this.getFileSystem() != other.getFileSystem()) {
+ return false;
+ }
+ this.checkAccess();
+ JrtPath o = (JrtPath) other;
+ o.checkAccess();
+ return this.getResolvedPath().equals(o.getResolvedPath()) ||
+ jrtfs.isSameFile(this, o);
+ }
+
+ final SeekableByteChannel newByteChannel(Set<? extends OpenOption> options,
+ FileAttribute<?>... attrs)
+ throws IOException
+ {
+ return jrtfs.newByteChannel(this, options, attrs);
+ }
+
+ final FileChannel newFileChannel(Set<? extends OpenOption> options,
+ FileAttribute<?>... attrs)
+ throws IOException {
+ return jrtfs.newFileChannel(this, options, attrs);
+ }
+
+ final void checkAccess(AccessMode... modes) throws IOException {
+ if (modes.length == 0) { // check if the path exists
+ jrtfs.checkNode(this); // no need to follow link. the "link" node
+ // is built from real node under "/module"
+ } else {
+ boolean w = false;
+ for (AccessMode mode : modes) {
+ switch (mode) {
+ case READ:
+ break;
+ case WRITE:
+ w = true;
+ break;
+ case EXECUTE:
+ throw new AccessDeniedException(toString());
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+ jrtfs.checkNode(this);
+ if (w && jrtfs.isReadOnly()) {
+ throw new AccessDeniedException(toString());
+ }
+ }
+ }
+
+ final boolean exists() {
+ try {
+ return jrtfs.exists(this);
+ } catch (IOException x) {}
+ return false;
+ }
+
+ final OutputStream newOutputStream(OpenOption... options) throws IOException {
+ if (options.length == 0) {
+ return jrtfs.newOutputStream(this, CREATE_NEW, WRITE);
+ }
+ return jrtfs.newOutputStream(this, options);
+ }
+
+ final void move(JrtPath target, CopyOption... options) throws IOException {
+ if (this.jrtfs == target.jrtfs) {
+ jrtfs.copyFile(true, this, target, options);
+ } else {
+ copyToTarget(target, options);
+ delete();
+ }
+ }
+
+ final void copy(JrtPath target, CopyOption... options) throws IOException {
+ if (this.jrtfs == target.jrtfs) {
+ jrtfs.copyFile(false, this, target, options);
+ } else {
+ copyToTarget(target, options);
+ }
+ }
+
+ private void copyToTarget(JrtPath target, CopyOption... options)
+ throws IOException {
+ boolean replaceExisting = false;
+ boolean copyAttrs = false;
+ for (CopyOption opt : options) {
+ if (opt == REPLACE_EXISTING) {
+ replaceExisting = true;
+ } else if (opt == COPY_ATTRIBUTES) {
+ copyAttrs = true;
+ }
+ }
+ // attributes of source file
+ BasicFileAttributes jrtfas = getAttributes();
+ // check if target exists
+ boolean exists;
+ if (replaceExisting) {
+ try {
+ target.deleteIfExists();
+ exists = false;
+ } catch (DirectoryNotEmptyException x) {
+ exists = true;
+ }
+ } else {
+ exists = target.exists();
+ }
+ if (exists) {
+ throw new FileAlreadyExistsException(target.toString());
+ }
+ if (jrtfas.isDirectory()) {
+ // create directory or file
+ target.createDirectory();
+ } else {
+ try (InputStream is = jrtfs.newInputStream(this);
+ OutputStream os = target.newOutputStream()) {
+ byte[] buf = new byte[8192];
+ int n;
+ while ((n = is.read(buf)) != -1) {
+ os.write(buf, 0, n);
+ }
+ }
+ }
+ if (copyAttrs) {
+ BasicFileAttributeView view =
+ Files.getFileAttributeView(target, BasicFileAttributeView.class);
+ try {
+ view.setTimes(jrtfas.lastModifiedTime(),
+ jrtfas.lastAccessTime(),
+ jrtfas.creationTime());
+ } catch (IOException x) {
+ try {
+ target.delete(); // rollback?
+ } catch (IOException ignore) {}
+ throw x;
+ }
+ }
}
}