src/java.base/share/classes/jdk/internal/jrtfs/JrtPath.java
changeset 47216 71c04702a3d5
parent 41121 91734a3ed04b
child 48039 a9160acae595
equal deleted inserted replaced
47215:4ebc2e2fb97c 47216:71c04702a3d5
       
     1 /*
       
     2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 package jdk.internal.jrtfs;
       
    26 
       
    27 import java.io.File;
       
    28 import java.io.IOException;
       
    29 import java.io.InputStream;
       
    30 import java.io.OutputStream;
       
    31 import java.net.URI;
       
    32 import java.net.URISyntaxException;
       
    33 import java.nio.channels.FileChannel;
       
    34 import java.nio.channels.SeekableByteChannel;
       
    35 import java.nio.file.*;
       
    36 import java.nio.file.DirectoryStream.Filter;;
       
    37 import java.nio.file.attribute.BasicFileAttributes;
       
    38 import java.nio.file.attribute.BasicFileAttributeView;
       
    39 import java.nio.file.attribute.FileAttribute;
       
    40 import java.nio.file.attribute.FileTime;
       
    41 import java.util.Iterator;
       
    42 import java.util.Map;
       
    43 import java.util.NoSuchElementException;
       
    44 import java.util.Objects;
       
    45 import java.util.Set;
       
    46 import static java.nio.file.StandardOpenOption.*;
       
    47 import static java.nio.file.StandardCopyOption.*;
       
    48 
       
    49 /**
       
    50  * Base class for Path implementation of jrt file systems.
       
    51  *
       
    52  * @implNote This class needs to maintain JDK 8 source compatibility.
       
    53  *
       
    54  * It is used internally in the JDK to implement jimage/jrtfs access,
       
    55  * but also compiled and delivered as part of the jrtfs.jar to support access
       
    56  * to the jimage file provided by the shipped JDK by tools running on JDK 8.
       
    57  */
       
    58 final class JrtPath implements Path {
       
    59 
       
    60     final JrtFileSystem jrtfs;
       
    61     private final String path;
       
    62     private volatile int[] offsets;
       
    63 
       
    64     JrtPath(JrtFileSystem jrtfs, String path) {
       
    65         this.jrtfs = jrtfs;
       
    66         this.path = normalize(path);
       
    67         this.resolved = null;
       
    68     }
       
    69 
       
    70     JrtPath(JrtFileSystem jrtfs, String path, boolean normalized) {
       
    71         this.jrtfs = jrtfs;
       
    72         this.path = normalized ? path : normalize(path);
       
    73         this.resolved = null;
       
    74     }
       
    75 
       
    76     final String getName() {
       
    77         return path;
       
    78     }
       
    79 
       
    80     @Override
       
    81     public final JrtPath getRoot() {
       
    82         if (this.isAbsolute()) {
       
    83             return jrtfs.getRootPath();
       
    84         } else {
       
    85             return null;
       
    86         }
       
    87     }
       
    88 
       
    89     @Override
       
    90     public final JrtPath getFileName() {
       
    91         if (path.length() == 0)
       
    92             return this;
       
    93         if (path.length() == 1 && path.charAt(0) == '/')
       
    94             return null;
       
    95         int off = path.lastIndexOf('/');
       
    96         if (off == -1)
       
    97             return this;
       
    98         return new JrtPath(jrtfs, path.substring(off + 1), true);
       
    99     }
       
   100 
       
   101     @Override
       
   102     public final JrtPath getParent() {
       
   103         initOffsets();
       
   104         int count = offsets.length;
       
   105         if (count == 0) {     // no elements so no parent
       
   106             return null;
       
   107         }
       
   108         int off = offsets[count - 1] - 1;
       
   109         if (off <= 0) {       // parent is root only (may be null)
       
   110             return getRoot();
       
   111         }
       
   112         return new JrtPath(jrtfs, path.substring(0, off));
       
   113     }
       
   114 
       
   115     @Override
       
   116     public final int getNameCount() {
       
   117         initOffsets();
       
   118         return offsets.length;
       
   119     }
       
   120 
       
   121     @Override
       
   122     public final JrtPath getName(int index) {
       
   123         initOffsets();
       
   124         if (index < 0 || index >= offsets.length) {
       
   125             throw new IllegalArgumentException("index: " +
       
   126                 index + ", offsets length: " + offsets.length);
       
   127         }
       
   128         int begin = offsets[index];
       
   129         int end;
       
   130         if (index == (offsets.length - 1)) {
       
   131             end = path.length();
       
   132         } else {
       
   133             end = offsets[index + 1];
       
   134         }
       
   135         return new JrtPath(jrtfs, path.substring(begin, end));
       
   136     }
       
   137 
       
   138     @Override
       
   139     public final JrtPath subpath(int beginIndex, int endIndex) {
       
   140         initOffsets();
       
   141         if (beginIndex < 0 || endIndex > offsets.length ||
       
   142             beginIndex >= endIndex) {
       
   143             throw new IllegalArgumentException(
       
   144                 "beginIndex: " + beginIndex + ", endIndex: " + endIndex +
       
   145                 ", offsets length: " + offsets.length);
       
   146         }
       
   147         // starting/ending offsets
       
   148         int begin = offsets[beginIndex];
       
   149         int end;
       
   150         if (endIndex == offsets.length) {
       
   151             end = path.length();
       
   152         } else {
       
   153             end = offsets[endIndex];
       
   154         }
       
   155         return new JrtPath(jrtfs, path.substring(begin, end));
       
   156     }
       
   157 
       
   158     @Override
       
   159     public final JrtPath toRealPath(LinkOption... options) throws IOException {
       
   160         return jrtfs.toRealPath(this, options);
       
   161     }
       
   162 
       
   163     @Override
       
   164     public final JrtPath toAbsolutePath() {
       
   165         if (isAbsolute())
       
   166             return this;
       
   167         return new JrtPath(jrtfs, "/" + path, true);
       
   168     }
       
   169 
       
   170     @Override
       
   171     public final URI toUri() {
       
   172         try {
       
   173             return new URI("jrt", toAbsolutePath().path, null);
       
   174         } catch (URISyntaxException ex) {
       
   175             throw new AssertionError(ex);
       
   176         }
       
   177     }
       
   178 
       
   179     private boolean equalsNameAt(JrtPath other, int index) {
       
   180         int mbegin = offsets[index];
       
   181         int mlen;
       
   182         if (index == (offsets.length - 1)) {
       
   183             mlen = path.length() - mbegin;
       
   184         } else {
       
   185             mlen = offsets[index + 1] - mbegin - 1;
       
   186         }
       
   187         int obegin = other.offsets[index];
       
   188         int olen;
       
   189         if (index == (other.offsets.length - 1)) {
       
   190             olen = other.path.length() - obegin;
       
   191         } else {
       
   192             olen = other.offsets[index + 1] - obegin - 1;
       
   193         }
       
   194         if (mlen != olen) {
       
   195             return false;
       
   196         }
       
   197         int n = 0;
       
   198         while (n < mlen) {
       
   199             if (path.charAt(mbegin + n) != other.path.charAt(obegin + n)) {
       
   200                 return false;
       
   201             }
       
   202             n++;
       
   203         }
       
   204         return true;
       
   205     }
       
   206 
       
   207     @Override
       
   208     public final JrtPath relativize(Path other) {
       
   209         final JrtPath o = checkPath(other);
       
   210         if (o.equals(this)) {
       
   211             return new JrtPath(jrtfs, "", true);
       
   212         }
       
   213         if (path.length() == 0) {
       
   214             return o;
       
   215         }
       
   216         if (jrtfs != o.jrtfs || isAbsolute() != o.isAbsolute()) {
       
   217             throw new IllegalArgumentException(
       
   218                 "Incorrect filesystem or path: " + other);
       
   219         }
       
   220         final String tp = this.path;
       
   221         final String op = o.path;
       
   222         if (op.startsWith(tp)) {    // fast path
       
   223             int off = tp.length();
       
   224             if (op.charAt(off - 1) == '/')
       
   225                 return new JrtPath(jrtfs, op.substring(off), true);
       
   226             if (op.charAt(off) == '/')
       
   227                 return new JrtPath(jrtfs, op.substring(off + 1), true);
       
   228         }
       
   229         int mc = this.getNameCount();
       
   230         int oc = o.getNameCount();
       
   231         int n = Math.min(mc, oc);
       
   232         int i = 0;
       
   233         while (i < n) {
       
   234             if (!equalsNameAt(o, i)) {
       
   235                 break;
       
   236             }
       
   237             i++;
       
   238         }
       
   239         int dotdots = mc - i;
       
   240         int len = dotdots * 3 - 1;
       
   241         if (i < oc) {
       
   242             len += (o.path.length() - o.offsets[i] + 1);
       
   243         }
       
   244         StringBuilder sb  = new StringBuilder(len);
       
   245         while (dotdots > 0) {
       
   246             sb.append("..");
       
   247             if (sb.length() < len) {  // no tailing slash at the end
       
   248                 sb.append('/');
       
   249             }
       
   250             dotdots--;
       
   251         }
       
   252         if (i < oc) {
       
   253             sb.append(o.path, o.offsets[i], o.path.length());
       
   254         }
       
   255         return new JrtPath(jrtfs, sb.toString(), true);
       
   256     }
       
   257 
       
   258     @Override
       
   259     public JrtFileSystem getFileSystem() {
       
   260         return jrtfs;
       
   261     }
       
   262 
       
   263     @Override
       
   264     public final boolean isAbsolute() {
       
   265         return path.length() > 0 && path.charAt(0) == '/';
       
   266     }
       
   267 
       
   268     @Override
       
   269     public final JrtPath resolve(Path other) {
       
   270         final JrtPath o = checkPath(other);
       
   271         if (this.path.length() == 0 || o.isAbsolute()) {
       
   272             return o;
       
   273         }
       
   274         if (o.path.length() == 0) {
       
   275             return this;
       
   276         }
       
   277         StringBuilder sb = new StringBuilder(path.length() + o.path.length());
       
   278         sb.append(path);
       
   279         if (path.charAt(path.length() - 1) != '/')
       
   280             sb.append('/');
       
   281         sb.append(o.path);
       
   282         return new JrtPath(jrtfs, sb.toString(), true);
       
   283     }
       
   284 
       
   285     @Override
       
   286     public final Path resolveSibling(Path other) {
       
   287         Objects.requireNonNull(other, "other");
       
   288         Path parent = getParent();
       
   289         return (parent == null) ? other : parent.resolve(other);
       
   290     }
       
   291 
       
   292     @Override
       
   293     public final boolean startsWith(Path other) {
       
   294         if (!(Objects.requireNonNull(other) instanceof JrtPath))
       
   295             return false;
       
   296         final JrtPath o = (JrtPath)other;
       
   297         final String tp = this.path;
       
   298         final String op = o.path;
       
   299         if (isAbsolute() != o.isAbsolute() || !tp.startsWith(op)) {
       
   300             return false;
       
   301         }
       
   302         int off = op.length();
       
   303         if (off == 0) {
       
   304             return tp.length() == 0;
       
   305         }
       
   306         // check match is on name boundary
       
   307         return tp.length() == off || tp.charAt(off) == '/' ||
       
   308                off == 0 || op.charAt(off - 1) == '/';
       
   309     }
       
   310 
       
   311     @Override
       
   312     public final boolean endsWith(Path other) {
       
   313         if (!(Objects.requireNonNull(other) instanceof JrtPath))
       
   314             return false;
       
   315         final JrtPath o = (JrtPath)other;
       
   316         final JrtPath t = this;
       
   317         int olast = o.path.length() - 1;
       
   318         if (olast > 0 && o.path.charAt(olast) == '/') {
       
   319             olast--;
       
   320         }
       
   321         int last = t.path.length() - 1;
       
   322         if (last > 0 && t.path.charAt(last) == '/') {
       
   323             last--;
       
   324         }
       
   325         if (olast == -1) {  // o.path.length == 0
       
   326             return last == -1;
       
   327         }
       
   328         if ((o.isAbsolute() && (!t.isAbsolute() || olast != last))
       
   329             || last < olast) {
       
   330             return false;
       
   331         }
       
   332         for (; olast >= 0; olast--, last--) {
       
   333             if (o.path.charAt(olast) != t.path.charAt(last)) {
       
   334                 return false;
       
   335             }
       
   336         }
       
   337         return o.path.charAt(olast + 1) == '/' ||
       
   338                last == -1 || t.path.charAt(last) == '/';
       
   339     }
       
   340 
       
   341     @Override
       
   342     public final JrtPath resolve(String other) {
       
   343         return resolve(getFileSystem().getPath(other));
       
   344     }
       
   345 
       
   346     @Override
       
   347     public final Path resolveSibling(String other) {
       
   348         return resolveSibling(getFileSystem().getPath(other));
       
   349     }
       
   350 
       
   351     @Override
       
   352     public final boolean startsWith(String other) {
       
   353         return startsWith(getFileSystem().getPath(other));
       
   354     }
       
   355 
       
   356     @Override
       
   357     public final boolean endsWith(String other) {
       
   358         return endsWith(getFileSystem().getPath(other));
       
   359     }
       
   360 
       
   361     @Override
       
   362     public final JrtPath normalize() {
       
   363         String res = getResolved();
       
   364         if (res == path) {  // no change
       
   365             return this;
       
   366         }
       
   367         return new JrtPath(jrtfs, res, true);
       
   368     }
       
   369 
       
   370     private JrtPath checkPath(Path path) {
       
   371         Objects.requireNonNull(path);
       
   372         if (!(path instanceof JrtPath))
       
   373             throw new ProviderMismatchException("path class: " +
       
   374                 path.getClass());
       
   375         return (JrtPath) path;
       
   376     }
       
   377 
       
   378     // create offset list if not already created
       
   379     private void initOffsets() {
       
   380         if (this.offsets == null) {
       
   381             int len = path.length();
       
   382             // count names
       
   383             int count = 0;
       
   384             int off = 0;
       
   385             while (off < len) {
       
   386                 char c = path.charAt(off++);
       
   387                 if (c != '/') {
       
   388                     count++;
       
   389                     off = path.indexOf('/', off);
       
   390                     if (off == -1)
       
   391                         break;
       
   392                 }
       
   393             }
       
   394             // populate offsets
       
   395             int[] offsets = new int[count];
       
   396             count = 0;
       
   397             off = 0;
       
   398             while (off < len) {
       
   399                 char c = path.charAt(off);
       
   400                 if (c == '/') {
       
   401                     off++;
       
   402                 } else {
       
   403                     offsets[count++] = off++;
       
   404                     off = path.indexOf('/', off);
       
   405                     if (off == -1)
       
   406                         break;
       
   407                 }
       
   408             }
       
   409             this.offsets = offsets;
       
   410         }
       
   411     }
       
   412 
       
   413     private volatile String resolved;
       
   414 
       
   415     final String getResolvedPath() {
       
   416         String r = resolved;
       
   417         if (r == null) {
       
   418             if (isAbsolute()) {
       
   419                 r = getResolved();
       
   420             } else {
       
   421                 r = toAbsolutePath().getResolvedPath();
       
   422             }
       
   423             resolved = r;
       
   424         }
       
   425         return r;
       
   426     }
       
   427 
       
   428     // removes redundant slashs, replace "\" to separator "/"
       
   429     // and check for invalid characters
       
   430     private static String normalize(String path) {
       
   431         int len = path.length();
       
   432         if (len == 0) {
       
   433             return path;
       
   434         }
       
   435         char prevC = 0;
       
   436         for (int i = 0; i < len; i++) {
       
   437             char c = path.charAt(i);
       
   438             if (c == '\\' || c == '\u0000') {
       
   439                 return normalize(path, i);
       
   440             }
       
   441             if (c == '/' && prevC == '/') {
       
   442                 return normalize(path, i - 1);
       
   443             }
       
   444             prevC = c;
       
   445         }
       
   446         if (prevC == '/' && len > 1) {
       
   447             return path.substring(0, len - 1);
       
   448         }
       
   449         return path;
       
   450     }
       
   451 
       
   452     private static String normalize(String path, int off) {
       
   453         int len = path.length();
       
   454         StringBuilder to = new StringBuilder(len);
       
   455         to.append(path, 0, off);
       
   456         char prevC = 0;
       
   457         while (off < len) {
       
   458             char c = path.charAt(off++);
       
   459             if (c == '\\') {
       
   460                 c = '/';
       
   461             }
       
   462             if (c == '/' && prevC == '/') {
       
   463                 continue;
       
   464             }
       
   465             if (c == '\u0000') {
       
   466                 throw new InvalidPathException(path,
       
   467                         "Path: NUL character not allowed");
       
   468             }
       
   469             to.append(c);
       
   470             prevC = c;
       
   471         }
       
   472         len = to.length();
       
   473         if (len > 1 && to.charAt(len - 1) == '/') {
       
   474             to.deleteCharAt(len - 1);
       
   475         }
       
   476         return to.toString();
       
   477     }
       
   478 
       
   479     // Remove DotSlash(./) and resolve DotDot (..) components
       
   480     private String getResolved() {
       
   481         if (path.length() == 0) {
       
   482             return path;
       
   483         }
       
   484         if (path.indexOf('.') == -1) {
       
   485             return path;
       
   486         }
       
   487         int length = path.length();
       
   488         char[] to = new char[length];
       
   489         int nc = getNameCount();
       
   490         int[] lastM = new int[nc];
       
   491         int lastMOff = -1;
       
   492         int m = 0;
       
   493         for (int i = 0; i < nc; i++) {
       
   494             int n = offsets[i];
       
   495             int len = (i == offsets.length - 1) ? length - n
       
   496                                                 : offsets[i + 1] - n - 1;
       
   497             if (len == 1 && path.charAt(n) == '.') {
       
   498                 if (m == 0 && path.charAt(0) == '/')   // absolute path
       
   499                     to[m++] = '/';
       
   500                 continue;
       
   501             }
       
   502             if (len == 2 && path.charAt(n) == '.' && path.charAt(n + 1) == '.') {
       
   503                 if (lastMOff >= 0) {
       
   504                     m = lastM[lastMOff--];    // retreat
       
   505                     continue;
       
   506                 }
       
   507                 if (path.charAt(0) == '/') {  // "/../xyz" skip
       
   508                     if (m == 0)
       
   509                         to[m++] = '/';
       
   510                 } else {                      // "../xyz" -> "../xyz"
       
   511                     if (m != 0 && to[m-1] != '/')
       
   512                         to[m++] = '/';
       
   513                     while (len-- > 0)
       
   514                         to[m++] = path.charAt(n++);
       
   515                 }
       
   516                 continue;
       
   517             }
       
   518             if (m == 0 && path.charAt(0) == '/' ||   // absolute path
       
   519                 m != 0 && to[m-1] != '/') {   // not the first name
       
   520                 to[m++] = '/';
       
   521             }
       
   522             lastM[++lastMOff] = m;
       
   523             while (len-- > 0)
       
   524                 to[m++] = path.charAt(n++);
       
   525         }
       
   526         if (m > 1 && to[m - 1] == '/')
       
   527             m--;
       
   528         return (m == to.length) ? new String(to) : new String(to, 0, m);
       
   529     }
       
   530 
       
   531     @Override
       
   532     public final String toString() {
       
   533         return path;
       
   534     }
       
   535 
       
   536     @Override
       
   537     public final int hashCode() {
       
   538         return path.hashCode();
       
   539     }
       
   540 
       
   541     @Override
       
   542     public final boolean equals(Object obj) {
       
   543         return obj instanceof JrtPath &&
       
   544                this.path.equals(((JrtPath) obj).path);
       
   545     }
       
   546 
       
   547     @Override
       
   548     public final int compareTo(Path other) {
       
   549         final JrtPath o = checkPath(other);
       
   550         return path.compareTo(o.path);
       
   551     }
       
   552 
       
   553     @Override
       
   554     public final WatchKey register(
       
   555             WatchService watcher,
       
   556             WatchEvent.Kind<?>[] events,
       
   557             WatchEvent.Modifier... modifiers) {
       
   558         Objects.requireNonNull(watcher, "watcher");
       
   559         Objects.requireNonNull(events, "events");
       
   560         Objects.requireNonNull(modifiers, "modifiers");
       
   561         throw new UnsupportedOperationException();
       
   562     }
       
   563 
       
   564     @Override
       
   565     public final WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) {
       
   566         return register(watcher, events, new WatchEvent.Modifier[0]);
       
   567     }
       
   568 
       
   569     @Override
       
   570     public final File toFile() {
       
   571         throw new UnsupportedOperationException();
       
   572     }
       
   573 
       
   574     @Override
       
   575     public final Iterator<Path> iterator() {
       
   576         return new Iterator<Path>() {
       
   577             private int i = 0;
       
   578 
       
   579             @Override
       
   580             public boolean hasNext() {
       
   581                 return (i < getNameCount());
       
   582             }
       
   583 
       
   584             @Override
       
   585             public Path next() {
       
   586                 if (i < getNameCount()) {
       
   587                     Path result = getName(i);
       
   588                     i++;
       
   589                     return result;
       
   590                 } else {
       
   591                     throw new NoSuchElementException();
       
   592                 }
       
   593             }
       
   594 
       
   595             @Override
       
   596             public void remove() {
       
   597                 throw new ReadOnlyFileSystemException();
       
   598             }
       
   599         };
       
   600     }
       
   601 
       
   602     // Helpers for JrtFileSystemProvider and JrtFileSystem
       
   603 
       
   604     final JrtPath readSymbolicLink() throws IOException {
       
   605         if (!jrtfs.isLink(this)) {
       
   606             throw new IOException("not a symbolic link");
       
   607         }
       
   608         return jrtfs.resolveLink(this);
       
   609     }
       
   610 
       
   611     final boolean isHidden() {
       
   612         return false;
       
   613     }
       
   614 
       
   615     final void createDirectory(FileAttribute<?>... attrs)
       
   616             throws IOException {
       
   617         jrtfs.createDirectory(this, attrs);
       
   618     }
       
   619 
       
   620     final InputStream newInputStream(OpenOption... options) throws IOException {
       
   621         if (options.length > 0) {
       
   622             for (OpenOption opt : options) {
       
   623                 if (opt != READ) {
       
   624                     throw new UnsupportedOperationException("'" + opt + "' not allowed");
       
   625                 }
       
   626             }
       
   627         }
       
   628         return jrtfs.newInputStream(this);
       
   629     }
       
   630 
       
   631     final DirectoryStream<Path> newDirectoryStream(Filter<? super Path> filter)
       
   632             throws IOException {
       
   633         return new JrtDirectoryStream(this, filter);
       
   634     }
       
   635 
       
   636     final void delete() throws IOException {
       
   637         jrtfs.deleteFile(this, true);
       
   638     }
       
   639 
       
   640     final void deleteIfExists() throws IOException {
       
   641         jrtfs.deleteFile(this, false);
       
   642     }
       
   643 
       
   644     final JrtFileAttributes getAttributes(LinkOption... options) throws IOException {
       
   645         JrtFileAttributes zfas = jrtfs.getFileAttributes(this, options);
       
   646         if (zfas == null) {
       
   647             throw new NoSuchFileException(toString());
       
   648         }
       
   649         return zfas;
       
   650     }
       
   651 
       
   652     final void setAttribute(String attribute, Object value, LinkOption... options)
       
   653             throws IOException {
       
   654         JrtFileAttributeView.setAttribute(this, attribute, value);
       
   655     }
       
   656 
       
   657     final Map<String, Object> readAttributes(String attributes, LinkOption... options)
       
   658             throws IOException {
       
   659         return JrtFileAttributeView.readAttributes(this, attributes, options);
       
   660     }
       
   661 
       
   662     final void setTimes(FileTime mtime, FileTime atime, FileTime ctime)
       
   663             throws IOException {
       
   664         jrtfs.setTimes(this, mtime, atime, ctime);
       
   665     }
       
   666 
       
   667     final FileStore getFileStore() throws IOException {
       
   668         // each JrtFileSystem only has one root (as requested for now)
       
   669         if (exists()) {
       
   670             return jrtfs.getFileStore(this);
       
   671         }
       
   672         throw new NoSuchFileException(path);
       
   673     }
       
   674 
       
   675     final boolean isSameFile(Path other) throws IOException {
       
   676         if (this == other || this.equals(other)) {
       
   677             return true;
       
   678         }
       
   679         if (other == null || this.getFileSystem() != other.getFileSystem()) {
       
   680             return false;
       
   681         }
       
   682         this.checkAccess();
       
   683         JrtPath o = (JrtPath) other;
       
   684         o.checkAccess();
       
   685         return this.getResolvedPath().equals(o.getResolvedPath()) ||
       
   686                jrtfs.isSameFile(this, o);
       
   687     }
       
   688 
       
   689     final SeekableByteChannel newByteChannel(Set<? extends OpenOption> options,
       
   690                                              FileAttribute<?>... attrs)
       
   691             throws IOException
       
   692     {
       
   693         return jrtfs.newByteChannel(this, options, attrs);
       
   694     }
       
   695 
       
   696     final FileChannel newFileChannel(Set<? extends OpenOption> options,
       
   697             FileAttribute<?>... attrs)
       
   698             throws IOException {
       
   699         return jrtfs.newFileChannel(this, options, attrs);
       
   700     }
       
   701 
       
   702     final void checkAccess(AccessMode... modes) throws IOException {
       
   703         if (modes.length == 0) {    // check if the path exists
       
   704             jrtfs.checkNode(this);  // no need to follow link. the "link" node
       
   705                                     // is built from real node under "/module"
       
   706         } else {
       
   707             boolean w = false;
       
   708             for (AccessMode mode : modes) {
       
   709                 switch (mode) {
       
   710                     case READ:
       
   711                         break;
       
   712                     case WRITE:
       
   713                         w = true;
       
   714                         break;
       
   715                     case EXECUTE:
       
   716                         throw new AccessDeniedException(toString());
       
   717                     default:
       
   718                         throw new UnsupportedOperationException();
       
   719                 }
       
   720             }
       
   721             jrtfs.checkNode(this);
       
   722             if (w && jrtfs.isReadOnly()) {
       
   723                 throw new AccessDeniedException(toString());
       
   724             }
       
   725         }
       
   726     }
       
   727 
       
   728     final boolean exists() {
       
   729         try {
       
   730             return jrtfs.exists(this);
       
   731         } catch (IOException x) {}
       
   732         return false;
       
   733     }
       
   734 
       
   735     final OutputStream newOutputStream(OpenOption... options) throws IOException {
       
   736         if (options.length == 0) {
       
   737             return jrtfs.newOutputStream(this, CREATE_NEW, WRITE);
       
   738         }
       
   739         return jrtfs.newOutputStream(this, options);
       
   740     }
       
   741 
       
   742     final void move(JrtPath target, CopyOption... options) throws IOException {
       
   743         if (this.jrtfs == target.jrtfs) {
       
   744             jrtfs.copyFile(true, this, target, options);
       
   745         } else {
       
   746             copyToTarget(target, options);
       
   747             delete();
       
   748         }
       
   749     }
       
   750 
       
   751     final void copy(JrtPath target, CopyOption... options) throws IOException {
       
   752         if (this.jrtfs == target.jrtfs) {
       
   753             jrtfs.copyFile(false, this, target, options);
       
   754         } else {
       
   755             copyToTarget(target, options);
       
   756         }
       
   757     }
       
   758 
       
   759     private void copyToTarget(JrtPath target, CopyOption... options)
       
   760             throws IOException {
       
   761         boolean replaceExisting = false;
       
   762         boolean copyAttrs = false;
       
   763         for (CopyOption opt : options) {
       
   764             if (opt == REPLACE_EXISTING) {
       
   765                 replaceExisting = true;
       
   766             } else if (opt == COPY_ATTRIBUTES) {
       
   767                 copyAttrs = true;
       
   768             }
       
   769         }
       
   770         // attributes of source file
       
   771         BasicFileAttributes jrtfas = getAttributes();
       
   772         // check if target exists
       
   773         boolean exists;
       
   774         if (replaceExisting) {
       
   775             try {
       
   776                 target.deleteIfExists();
       
   777                 exists = false;
       
   778             } catch (DirectoryNotEmptyException x) {
       
   779                 exists = true;
       
   780             }
       
   781         } else {
       
   782             exists = target.exists();
       
   783         }
       
   784         if (exists) {
       
   785             throw new FileAlreadyExistsException(target.toString());
       
   786         }
       
   787         if (jrtfas.isDirectory()) {
       
   788             // create directory or file
       
   789             target.createDirectory();
       
   790         } else {
       
   791             try (InputStream is = jrtfs.newInputStream(this);
       
   792                  OutputStream os = target.newOutputStream()) {
       
   793                 byte[] buf = new byte[8192];
       
   794                 int n;
       
   795                 while ((n = is.read(buf)) != -1) {
       
   796                     os.write(buf, 0, n);
       
   797                 }
       
   798             }
       
   799         }
       
   800         if (copyAttrs) {
       
   801             BasicFileAttributeView view =
       
   802                 Files.getFileAttributeView(target, BasicFileAttributeView.class);
       
   803             try {
       
   804                 view.setTimes(jrtfas.lastModifiedTime(),
       
   805                               jrtfas.lastAccessTime(),
       
   806                               jrtfas.creationTime());
       
   807             } catch (IOException x) {
       
   808                 try {
       
   809                     target.delete();  // rollback?
       
   810                 } catch (IOException ignore) {}
       
   811                 throw x;
       
   812             }
       
   813         }
       
   814     }
       
   815 }