| /* | |
| * Copyright (c) 2015, 2023, 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 jdk.internal.jrtfs; | |
| import java.io.File; | |
| import java.io.IOError; | |
| 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.*; | |
| /** | |
| * Base class for Path implementation of jrt file systems. | |
| * | |
| * @implNote This class needs to maintain JDK 8 source compatibility. | |
| * | |
| * It is used internally in the JDK to implement jimage/jrtfs access, | |
| * 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 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.isEmpty()) | |
| 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; | |
| } | |
| @Override | |
| public final JrtPath getName(int index) { | |
| initOffsets(); | |
| if (index < 0 || index >= offsets.length) { | |
| throw new IllegalArgumentException("index: " + | |
| index + ", offsets length: " + offsets.length); | |
| } | |
| 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( | |
| "beginIndex: " + beginIndex + ", endIndex: " + endIndex + | |
| ", offsets length: " + offsets.length); | |
| } | |
| // 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() { | |
| String p = toAbsolutePath().path; | |
| if (!p.startsWith("/modules") || p.contains("..")) { | |
| throw new IOError(new RuntimeException(p + " cannot be represented as URI")); | |
| } | |
| p = p.substring("/modules".length()); | |
| if (p.isEmpty()) { | |
| p = "/"; | |
| } | |
| return toUri(p); | |
| } | |
| 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; | |
| } | |
| @Override | |
| public final JrtPath relativize(Path other) { | |
| final JrtPath o = checkPath(other); | |
| if (o.equals(this)) { | |
| return new JrtPath(jrtfs, "", true); | |
| } | |
| if (path.isEmpty()) { | |
| return o; | |
| } | |
| if (jrtfs != o.jrtfs || isAbsolute() != o.isAbsolute()) { | |
| throw new IllegalArgumentException( | |
| "Incorrect filesystem or path: " + other); | |
| } | |
| 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.isEmpty() && path.charAt(0) == '/'; | |
| } | |
| @Override | |
| public final JrtPath resolve(Path other) { | |
| final JrtPath o = checkPath(other); | |
| if (this.path.isEmpty() || o.isAbsolute()) { | |
| return o; | |
| } | |
| if (o.path.isEmpty()) { | |
| return this; | |
| } | |
| StringBuilder sb = new StringBuilder(path.length() + o.path.length() + 1); | |
| 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.isEmpty(); | |
| } | |
| // 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 | |
| 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("path class: " + | |
| path.getClass()); | |
| 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 slashes, 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() { | |
| int length = path.length(); | |
| if (length == 0 || (!path.contains("./") && path.charAt(length - 1) != '.')) { | |
| return path; | |
| } else { | |
| return resolvePath(); | |
| } | |
| } | |
| private String resolvePath() { | |
| 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 | |
| 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 { | |
| 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 { | |
| return jrtfs.getFileAttributes(this, options); | |
| } | |
| 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; | |
| } | |
| } | |
| } | |
| // adopted from sun.nio.fs.UnixUriUtils | |
| private static URI toUri(String str) { | |
| char[] path = str.toCharArray(); | |
| assert path[0] == '/'; | |
| StringBuilder sb = new StringBuilder(); | |
| sb.append(path[0]); | |
| for (int i = 1; i < path.length; i++) { | |
| char c = (char)(path[i] & 0xff); | |
| if (match(c, L_PATH, H_PATH)) { | |
| sb.append(c); | |
| } else { | |
| sb.append('%'); | |
| sb.append(hexDigits[(c >> 4) & 0x0f]); | |
| sb.append(hexDigits[(c) & 0x0f]); | |
| } | |
| } | |
| try { | |
| return new URI("jrt:" + sb.toString()); | |
| } catch (URISyntaxException x) { | |
| throw new AssertionError(x); // should not happen | |
| } | |
| } | |
| // The following is copied from java.net.URI | |
| // Compute the low-order mask for the characters in the given string | |
| private static long lowMask(String chars) { | |
| int n = chars.length(); | |
| long m = 0; | |
| for (int i = 0; i < n; i++) { | |
| char c = chars.charAt(i); | |
| if (c < 64) | |
| m |= (1L << c); | |
| } | |
| return m; | |
| } | |
| // Compute the high-order mask for the characters in the given string | |
| private static long highMask(String chars) { | |
| int n = chars.length(); | |
| long m = 0; | |
| for (int i = 0; i < n; i++) { | |
| char c = chars.charAt(i); | |
| if ((c >= 64) && (c < 128)) | |
| m |= (1L << (c - 64)); | |
| } | |
| return m; | |
| } | |
| // Compute a low-order mask for the characters | |
| // between first and last, inclusive | |
| private static long lowMask(char first, char last) { | |
| long m = 0; | |
| int f = Math.max(Math.min(first, 63), 0); | |
| int l = Math.max(Math.min(last, 63), 0); | |
| for (int i = f; i <= l; i++) | |
| m |= 1L << i; | |
| return m; | |
| } | |
| // Compute a high-order mask for the characters | |
| // between first and last, inclusive | |
| private static long highMask(char first, char last) { | |
| long m = 0; | |
| int f = Math.max(Math.min(first, 127), 64) - 64; | |
| int l = Math.max(Math.min(last, 127), 64) - 64; | |
| for (int i = f; i <= l; i++) | |
| m |= 1L << i; | |
| return m; | |
| } | |
| // Tell whether the given character is permitted by the given mask pair | |
| private static boolean match(char c, long lowMask, long highMask) { | |
| if (c < 64) | |
| return ((1L << c) & lowMask) != 0; | |
| if (c < 128) | |
| return ((1L << (c - 64)) & highMask) != 0; | |
| return false; | |
| } | |
| // digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | | |
| // "8" | "9" | |
| private static final long L_DIGIT = lowMask('0', '9'); | |
| private static final long H_DIGIT = 0L; | |
| // upalpha = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | | |
| // "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | | |
| // "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | |
| private static final long L_UPALPHA = 0L; | |
| private static final long H_UPALPHA = highMask('A', 'Z'); | |
| // lowalpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | | |
| // "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | | |
| // "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | |
| private static final long L_LOWALPHA = 0L; | |
| private static final long H_LOWALPHA = highMask('a', 'z'); | |
| // alpha = lowalpha | upalpha | |
| private static final long L_ALPHA = L_LOWALPHA | L_UPALPHA; | |
| private static final long H_ALPHA = H_LOWALPHA | H_UPALPHA; | |
| // alphanum = alpha | digit | |
| private static final long L_ALPHANUM = L_DIGIT | L_ALPHA; | |
| private static final long H_ALPHANUM = H_DIGIT | H_ALPHA; | |
| // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | | |
| // "(" | ")" | |
| private static final long L_MARK = lowMask("-_.!~*'()"); | |
| private static final long H_MARK = highMask("-_.!~*'()"); | |
| // unreserved = alphanum | mark | |
| private static final long L_UNRESERVED = L_ALPHANUM | L_MARK; | |
| private static final long H_UNRESERVED = H_ALPHANUM | H_MARK; | |
| // pchar = unreserved | escaped | | |
| // ":" | "@" | "&" | "=" | "+" | "$" | "," | |
| private static final long L_PCHAR | |
| = L_UNRESERVED | lowMask(":@&=+$,"); | |
| private static final long H_PCHAR | |
| = H_UNRESERVED | highMask(":@&=+$,"); | |
| // All valid path characters | |
| private static final long L_PATH = L_PCHAR | lowMask(";/"); | |
| private static final long H_PATH = H_PCHAR | highMask(";/"); | |
| private static final char[] hexDigits = { | |
| '0', '1', '2', '3', '4', '5', '6', '7', | |
| '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' | |
| }; | |
| } |