1 /*
   2  * Copyright (c) 2014, 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 
  26 package jdk.internal.jrtfs;
  27 
  28 import java.io.*;
  29 import java.net.URI;
  30 import java.net.URISyntaxException;
  31 import java.nio.channels.*;
  32 import java.nio.file.*;
  33 import java.nio.file.DirectoryStream.Filter;
  34 import java.nio.file.attribute.*;
  35 import java.util.*;
  36 import static java.nio.file.StandardOpenOption.*;
  37 import static java.nio.file.StandardCopyOption.*;
  38 
  39 final class JrtPath implements Path {
  40 
  41     private final JrtFileSystem jrtfs;
  42     private final byte[] path;
  43     private volatile int[] offsets;
  44     private int hashcode = 0;  // cached hashcode (created lazily)
  45 
  46     JrtPath(JrtFileSystem jrtfs, byte[] path) {
  47         this(jrtfs, path, false);
  48     }
  49 
  50     JrtPath(JrtFileSystem jrtfs, byte[] path, boolean normalized) {
  51         this.jrtfs = jrtfs;
  52         if (normalized)
  53             this.path = path;
  54         else
  55             this.path = normalize(path);
  56     }
  57 
  58     byte[] getName() {
  59         return path;
  60     }
  61 
  62     @Override
  63     public JrtPath getRoot() {
  64         if (this.isAbsolute())
  65             return jrtfs.getRootPath();
  66         else
  67             return null;
  68     }
  69 
  70     @Override
  71     public Path getFileName() {
  72         initOffsets();
  73         int count = offsets.length;
  74         if (count == 0)
  75             return null;  // no elements so no name
  76         if (count == 1 && path[0] != '/')
  77             return this;
  78         int lastOffset = offsets[count-1];
  79         int len = path.length - lastOffset;
  80         byte[] result = new byte[len];
  81         System.arraycopy(path, lastOffset, result, 0, len);
  82         return new JrtPath(jrtfs, result);
  83     }
  84 
  85     @Override
  86     public JrtPath getParent() {
  87         initOffsets();
  88         int count = offsets.length;
  89         if (count == 0)    // no elements so no parent
  90             return null;
  91         int len = offsets[count-1] - 1;
  92         if (len <= 0)      // parent is root only (may be null)
  93             return getRoot();
  94         byte[] result = new byte[len];
  95         System.arraycopy(path, 0, result, 0, len);
  96         return new JrtPath(jrtfs, result);
  97     }
  98 
  99     @Override
 100     public int getNameCount() {
 101         initOffsets();
 102         return offsets.length;
 103     }
 104 
 105     @Override
 106     public JrtPath getName(int index) {
 107         initOffsets();
 108         if (index < 0 || index >= offsets.length)
 109             throw new IllegalArgumentException();
 110         int begin = offsets[index];
 111         int len;
 112         if (index == (offsets.length-1))
 113             len = path.length - begin;
 114         else
 115             len = offsets[index+1] - begin - 1;
 116         // construct result
 117         byte[] result = new byte[len];
 118         System.arraycopy(path, begin, result, 0, len);
 119         return new JrtPath(jrtfs, result);
 120     }
 121 
 122     @Override
 123     public JrtPath subpath(int beginIndex, int endIndex) {
 124         initOffsets();
 125         if (beginIndex < 0 ||
 126             beginIndex >=  offsets.length ||
 127             endIndex > offsets.length ||
 128             beginIndex >= endIndex)
 129             throw new IllegalArgumentException();
 130 
 131         // starting offset and length
 132         int begin = offsets[beginIndex];
 133         int len;
 134         if (endIndex == offsets.length)
 135             len = path.length - begin;
 136         else
 137             len = offsets[endIndex] - begin - 1;
 138         // construct result
 139         byte[] result = new byte[len];
 140         System.arraycopy(path, begin, result, 0, len);
 141         return new JrtPath(jrtfs, result);
 142     }
 143 
 144     @Override
 145     public JrtPath toRealPath(LinkOption... options) throws IOException {
 146         JrtPath realPath = new JrtPath(jrtfs, getResolvedPath()).toAbsolutePath();
 147         realPath = JrtFileSystem.followLinks(options)? jrtfs.resolveLink(this) : realPath;
 148         realPath.checkAccess();
 149         return realPath;
 150     }
 151 
 152     JrtPath readSymbolicLink() throws IOException {
 153         if (! jrtfs.isLink(this)) {
 154            throw new IOException("not a symbolic link");
 155         }
 156 
 157         return jrtfs.resolveLink(this);
 158     }
 159 
 160     boolean isHidden() {
 161         return false;
 162     }
 163 
 164     @Override
 165     public JrtPath toAbsolutePath() {
 166         if (isAbsolute()) {
 167             return this;
 168         } else {
 169             //add / bofore the existing path
 170             byte[] tmp = new byte[path.length + 1];
 171             tmp[0] = '/';
 172             System.arraycopy(path, 0, tmp, 1, path.length);
 173             return (JrtPath) new JrtPath(jrtfs, tmp).normalize();
 174         }
 175     }
 176 
 177     @Override
 178     public URI toUri() {
 179         try {
 180             return new URI("jrt",
 181                            JrtFileSystem.getString(toAbsolutePath().path),
 182                            null);
 183         } catch (URISyntaxException ex) {
 184             throw new AssertionError(ex);
 185         }
 186     }
 187 
 188     private boolean equalsNameAt(JrtPath other, int index) {
 189         int mbegin = offsets[index];
 190         int mlen;
 191         if (index == (offsets.length-1))
 192             mlen = path.length - mbegin;
 193         else
 194             mlen = offsets[index + 1] - mbegin - 1;
 195         int obegin = other.offsets[index];
 196         int olen;
 197         if (index == (other.offsets.length - 1))
 198             olen = other.path.length - obegin;
 199         else
 200             olen = other.offsets[index + 1] - obegin - 1;
 201         if (mlen != olen)
 202             return false;
 203         int n = 0;
 204         while(n < mlen) {
 205             if (path[mbegin + n] != other.path[obegin + n])
 206                 return false;
 207             n++;
 208         }
 209         return true;
 210     }
 211 
 212     @Override
 213     public Path relativize(Path other) {
 214         final JrtPath o = checkPath(other);
 215         if (o.equals(this))
 216             return new JrtPath(getFileSystem(), new byte[0], true);
 217         if (/* this.getFileSystem() != o.getFileSystem() || */
 218             this.isAbsolute() != o.isAbsolute()) {
 219             throw new IllegalArgumentException();
 220         }
 221         int mc = this.getNameCount();
 222         int oc = o.getNameCount();
 223         int n = Math.min(mc, oc);
 224         int i = 0;
 225         while (i < n) {
 226             if (!equalsNameAt(o, i))
 227                 break;
 228             i++;
 229         }
 230         int dotdots = mc - i;
 231         int len = dotdots * 3 - 1;
 232         if (i < oc)
 233             len += (o.path.length - o.offsets[i] + 1);
 234         byte[] result = new byte[len];
 235 
 236         int pos = 0;
 237         while (dotdots > 0) {
 238             result[pos++] = (byte)'.';
 239             result[pos++] = (byte)'.';
 240             if (pos < len)       // no tailing slash at the end
 241                 result[pos++] = (byte)'/';
 242             dotdots--;
 243         }
 244         if (i < oc)
 245             System.arraycopy(o.path, o.offsets[i],
 246                              result, pos,
 247                              o.path.length - o.offsets[i]);
 248         return new JrtPath(getFileSystem(), result);
 249     }
 250 
 251     @Override
 252     public JrtFileSystem getFileSystem() {
 253         return jrtfs;
 254     }
 255 
 256     @Override
 257     public boolean isAbsolute() {
 258         return (this.path.length > 0 && path[0] == '/');
 259     }
 260 
 261     @Override
 262     public JrtPath resolve(Path other) {
 263         final JrtPath o = checkPath(other);
 264         if (o.isAbsolute())
 265             return o;
 266         byte[] res;
 267         if (this.path[path.length - 1] == '/') {
 268             res = new byte[path.length + o.path.length];
 269             System.arraycopy(path, 0, res, 0, path.length);
 270             System.arraycopy(o.path, 0, res, path.length, o.path.length);
 271         } else {
 272             res = new byte[path.length + 1 + o.path.length];
 273             System.arraycopy(path, 0, res, 0, path.length);
 274             res[path.length] = '/';
 275             System.arraycopy(o.path, 0, res, path.length + 1, o.path.length);
 276         }
 277         return new JrtPath(jrtfs, res);
 278     }
 279 
 280     @Override
 281     public Path resolveSibling(Path other) {
 282         if (other == null)
 283             throw new NullPointerException();
 284         Path parent = getParent();
 285         return (parent == null) ? other : parent.resolve(other);
 286     }
 287 
 288     @Override
 289     public boolean startsWith(Path other) {
 290         final JrtPath o = checkPath(other);
 291         if (o.isAbsolute() != this.isAbsolute() ||
 292             o.path.length > this.path.length)
 293             return false;
 294         int olast = o.path.length;
 295         for (int i = 0; i < olast; i++) {
 296             if (o.path[i] != this.path[i])
 297                 return false;
 298         }
 299         olast--;
 300         return o.path.length == this.path.length ||
 301                o.path[olast] == '/' ||
 302                this.path[olast + 1] == '/';
 303     }
 304 
 305     @Override
 306     public boolean endsWith(Path other) {
 307         final JrtPath o = checkPath(other);
 308         int olast = o.path.length - 1;
 309         if (olast > 0 && o.path[olast] == '/')
 310             olast--;
 311         int last = this.path.length - 1;
 312         if (last > 0 && this.path[last] == '/')
 313             last--;
 314         if (olast == -1)    // o.path.length == 0
 315             return last == -1;
 316         if ((o.isAbsolute() &&(!this.isAbsolute() || olast != last)) ||
 317             (last < olast))
 318             return false;
 319         for (; olast >= 0; olast--, last--) {
 320             if (o.path[olast] != this.path[last])
 321                 return false;
 322         }
 323         return o.path[olast + 1] == '/' ||
 324                last == -1 || this.path[last] == '/';
 325     }
 326 
 327     @Override
 328     public JrtPath resolve(String other) {
 329         return resolve(getFileSystem().getPath(other));
 330     }
 331 
 332     @Override
 333     public final Path resolveSibling(String other) {
 334         return resolveSibling(getFileSystem().getPath(other));
 335     }
 336 
 337     @Override
 338     public final boolean startsWith(String other) {
 339         return startsWith(getFileSystem().getPath(other));
 340     }
 341 
 342     @Override
 343     public final boolean endsWith(String other) {
 344         return endsWith(getFileSystem().getPath(other));
 345     }
 346 
 347     @Override
 348     public Path normalize() {
 349         byte[] res = getResolved();
 350         if (res == path)    // no change
 351             return this;
 352         return new JrtPath(jrtfs, res, true);
 353     }
 354 
 355     private JrtPath checkPath(Path path) {
 356         if (path == null)
 357             throw new NullPointerException();
 358         if (!(path instanceof JrtPath))
 359             throw new ProviderMismatchException();
 360         return (JrtPath) path;
 361     }
 362 
 363     // create offset list if not already created
 364     private void initOffsets() {
 365         if (offsets == null) {
 366             int count, index;
 367             // count names
 368             count = 0;
 369             index = 0;
 370             while (index < path.length) {
 371                 byte c = path[index++];
 372                 if (c != '/') {
 373                     count++;
 374                     while (index < path.length && path[index] != '/')
 375                         index++;
 376                 }
 377             }
 378             // populate offsets
 379             int[] result = new int[count];
 380             count = 0;
 381             index = 0;
 382             while (index < path.length) {
 383                 byte c = path[index];
 384                 if (c == '/') {
 385                     index++;
 386                 } else {
 387                     result[count++] = index++;
 388                     while (index < path.length && path[index] != '/')
 389                         index++;
 390                 }
 391             }
 392             synchronized (this) {
 393                 if (offsets == null)
 394                     offsets = result;
 395             }
 396         }
 397     }
 398 
 399     // resolved path for locating jrt entry inside the jrt file,
 400     // the result path does not contain ./ and .. components
 401     // resolved bytes will always start with '/'
 402     private volatile byte[] resolved = null;
 403     byte[] getResolvedPath() {
 404         byte[] r = resolved;
 405         if (r == null) {
 406             if (isAbsolute())
 407                 r = getResolved();
 408             else
 409                 r = toAbsolutePath().getResolvedPath();
 410             resolved = r;
 411         }
 412         return resolved;
 413     }
 414 
 415     // removes redundant slashs, replace "\" to separator "/"
 416     // and check for invalid characters
 417     private static byte[] normalize(byte[] path) {
 418         if (path.length == 0)
 419             return path;
 420         byte prevC = 0;
 421         for (int i = 0; i < path.length; i++) {
 422             byte c = path[i];
 423             if (c == '\\')
 424                 return normalize(path, i);
 425             if (c == (byte)'/' && prevC == '/')
 426                 return normalize(path, i - 1);
 427             if (c == '\u0000')
 428                 throw new InvalidPathException(JrtFileSystem.getString(path),
 429                                                "Path: nul character not allowed");
 430             prevC = c;
 431         }
 432 
 433         if (path.length > 1 && path[path.length - 1] == '/') {
 434             return Arrays.copyOf(path, path.length - 1);
 435         }
 436 
 437         return path;
 438     }
 439 
 440     private static byte[] normalize(byte[] path, int off) {
 441         byte[] to = new byte[path.length];
 442         int n = 0;
 443         while (n < off) {
 444             to[n] = path[n];
 445             n++;
 446         }
 447         int m = n;
 448         byte prevC = 0;
 449         while (n < path.length) {
 450             byte c = path[n++];
 451             if (c == (byte)'\\')
 452                 c = (byte)'/';
 453             if (c == (byte)'/' && prevC == (byte)'/')
 454                 continue;
 455             if (c == '\u0000')
 456                 throw new InvalidPathException(JrtFileSystem.getString(path),
 457                                                "Path: nul character not allowed");
 458             to[m++] = c;
 459             prevC = c;
 460         }
 461         if (m > 1 && to[m - 1] == '/')
 462             m--;
 463         return (m == to.length)? to : Arrays.copyOf(to, m);
 464     }
 465 
 466     // Remove DotSlash(./) and resolve DotDot (..) components
 467     private byte[] getResolved() {
 468         if (path.length == 0)
 469             return path;
 470         for (int i = 0; i < path.length; i++) {
 471             byte c = path[i];
 472             if (c == (byte)'.')
 473                 return resolve0();
 474         }
 475 
 476         return path;
 477     }
 478 
 479     // TBD: performance, avoid initOffsets
 480     private byte[] resolve0() {
 481         byte[] to = new byte[path.length];
 482         int nc = getNameCount();
 483         int[] lastM = new int[nc];
 484         int lastMOff = -1;
 485         int m = 0;
 486         for (int i = 0; i < nc; i++) {
 487             int n = offsets[i];
 488             int len = (i == offsets.length - 1)?
 489                       (path.length - n):(offsets[i + 1] - n - 1);
 490             if (len == 1 && path[n] == (byte)'.') {
 491                 if (m == 0 && path[0] == '/')   // absolute path
 492                     to[m++] = '/';
 493                 continue;
 494             }
 495             if (len == 2 && path[n] == '.' && path[n + 1] == '.') {
 496                 if (lastMOff >= 0) {
 497                     m = lastM[lastMOff--];  // retreat
 498                     continue;
 499                 }
 500                 if (path[0] == '/') {  // "/../xyz" skip
 501                     if (m == 0)
 502                         to[m++] = '/';
 503                 } else {               // "../xyz" -> "../xyz"
 504                     if (m != 0 && to[m-1] != '/')
 505                         to[m++] = '/';
 506                     while (len-- > 0)
 507                         to[m++] = path[n++];
 508                 }
 509                 continue;
 510             }
 511             if (m == 0 && path[0] == '/' ||   // absolute path
 512                 m != 0 && to[m-1] != '/') {   // not the first name
 513                 to[m++] = '/';
 514             }
 515             lastM[++lastMOff] = m;
 516             while (len-- > 0)
 517                 to[m++] = path[n++];
 518         }
 519         if (m > 1 && to[m - 1] == '/')
 520             m--;
 521         return (m == to.length)? to : Arrays.copyOf(to, m);
 522     }
 523 
 524     @Override
 525     public String toString() {
 526         return JrtFileSystem.getString(path);
 527     }
 528 
 529     @Override
 530     public int hashCode() {
 531         int h = hashcode;
 532         if (h == 0)
 533             hashcode = h = Arrays.hashCode(path);
 534         return h;
 535     }
 536 
 537     @Override
 538     public boolean equals(Object obj) {
 539         return obj != null &&
 540                obj instanceof JrtPath &&
 541                this.jrtfs == ((JrtPath)obj).jrtfs &&
 542                compareTo((Path) obj) == 0;
 543     }
 544 
 545     @Override
 546     public int compareTo(Path other) {
 547         final JrtPath o = checkPath(other);
 548         int len1 = this.path.length;
 549         int len2 = o.path.length;
 550 
 551         int n = Math.min(len1, len2);
 552         byte v1[] = this.path;
 553         byte v2[] = o.path;
 554 
 555         int k = 0;
 556         while (k < n) {
 557             int c1 = v1[k] & 0xff;
 558             int c2 = v2[k] & 0xff;
 559             if (c1 != c2)
 560                 return c1 - c2;
 561             k++;
 562         }
 563         return len1 - len2;
 564     }
 565 
 566     @Override
 567     public WatchKey register(
 568             WatchService watcher,
 569             WatchEvent.Kind<?>[] events,
 570             WatchEvent.Modifier... modifiers) {
 571         if (watcher == null || events == null || modifiers == null) {
 572             throw new NullPointerException();
 573         }
 574         throw new UnsupportedOperationException();
 575     }
 576 
 577     @Override
 578     public WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) {
 579         return register(watcher, events, new WatchEvent.Modifier[0]);
 580     }
 581 
 582     @Override
 583     public final File toFile() {
 584         throw new UnsupportedOperationException();
 585     }
 586 
 587     @Override
 588     public Iterator<Path> iterator() {
 589         return new Iterator<Path>() {
 590             private int i = 0;
 591 
 592             @Override
 593             public boolean hasNext() {
 594                 return (i < getNameCount());
 595             }
 596 
 597             @Override
 598             public Path next() {
 599                 if (i < getNameCount()) {
 600                     Path result = getName(i);
 601                     i++;
 602                     return result;
 603                 } else {
 604                     throw new NoSuchElementException();
 605                 }
 606             }
 607 
 608             @Override
 609             public void remove() {
 610                 throw new ReadOnlyFileSystemException();
 611             }
 612         };
 613     }
 614 
 615     /////////////////////////////////////////////////////////////////////
 616     // Helpers for JrtFileSystemProvider and JrtFileSystem
 617 
 618     int getPathLength() {
 619         return path.length;
 620     }
 621 
 622 
 623     void createDirectory(FileAttribute<?>... attrs)
 624         throws IOException
 625     {
 626         jrtfs.createDirectory(getResolvedPath(), attrs);
 627     }
 628 
 629     InputStream newInputStream(OpenOption... options) throws IOException
 630     {
 631         if (options.length > 0) {
 632             for (OpenOption opt : options) {
 633                 if (opt != READ)
 634                     throw new UnsupportedOperationException("'" + opt + "' not allowed");
 635             }
 636         }
 637         return jrtfs.newInputStream(getResolvedPath());
 638     }
 639 
 640     DirectoryStream<Path> newDirectoryStream(Filter<? super Path> filter)
 641         throws IOException
 642     {
 643         return new JrtDirectoryStream(this, filter);
 644     }
 645 
 646     void delete() throws IOException {
 647         jrtfs.deleteFile(getResolvedPath(), true);
 648     }
 649 
 650     void deleteIfExists() throws IOException {
 651         jrtfs.deleteFile(getResolvedPath(), false);
 652     }
 653 
 654     JrtFileAttributes getAttributes(LinkOption... options) throws IOException
 655     {
 656         JrtFileAttributes zfas = jrtfs.getFileAttributes(getResolvedPath(), options);
 657         if (zfas == null)
 658             throw new NoSuchFileException(toString());
 659         return zfas;
 660     }
 661 
 662     void setAttribute(String attribute, Object value, LinkOption... options)
 663         throws IOException
 664     {
 665         String type;
 666         String attr;
 667         int colonPos = attribute.indexOf(':');
 668         if (colonPos == -1) {
 669             type = "basic";
 670             attr = attribute;
 671         } else {
 672             type = attribute.substring(0, colonPos++);
 673             attr = attribute.substring(colonPos);
 674         }
 675         JrtFileAttributeView view = JrtFileAttributeView.get(this, type, options);
 676         if (view == null)
 677             throw new UnsupportedOperationException("view <" + view + "> is not supported");
 678         view.setAttribute(attr, value);
 679     }
 680 
 681     void setTimes(FileTime mtime, FileTime atime, FileTime ctime)
 682         throws IOException
 683     {
 684         jrtfs.setTimes(getResolvedPath(), mtime, atime, ctime);
 685     }
 686 
 687     Map<String, Object> readAttributes(String attributes, LinkOption... options)
 688         throws IOException
 689 
 690     {
 691         String view;
 692         String attrs;
 693         int colonPos = attributes.indexOf(':');
 694         if (colonPos == -1) {
 695             view = "basic";
 696             attrs = attributes;
 697         } else {
 698             view = attributes.substring(0, colonPos++);
 699             attrs = attributes.substring(colonPos);
 700         }
 701         JrtFileAttributeView jrtfv = JrtFileAttributeView.get(this, view, options);
 702         if (jrtfv == null) {
 703             throw new UnsupportedOperationException("view not supported");
 704         }
 705         return jrtfv.readAttributes(attrs);
 706     }
 707 
 708     FileStore getFileStore() throws IOException {
 709         // each JrtFileSystem only has one root (as requested for now)
 710         if (exists())
 711             return jrtfs.getFileStore(this);
 712         throw new NoSuchFileException(JrtFileSystem.getString(path));
 713     }
 714 
 715     boolean isSameFile(Path other) throws IOException {
 716         if (this.equals(other))
 717             return true;
 718         if (other == null ||
 719             this.getFileSystem() != other.getFileSystem())
 720             return false;
 721         this.checkAccess();
 722         JrtPath path = (JrtPath)other;
 723         path.checkAccess();
 724         return Arrays.equals(this.getResolvedPath(), path.getResolvedPath()) ||
 725             jrtfs.isSameFile(this, (JrtPath)other);
 726     }
 727 
 728     SeekableByteChannel newByteChannel(Set<? extends OpenOption> options,
 729                                        FileAttribute<?>... attrs)
 730         throws IOException
 731     {
 732         return jrtfs.newByteChannel(getResolvedPath(), options, attrs);
 733     }
 734 
 735 
 736     FileChannel newFileChannel(Set<? extends OpenOption> options,
 737                                FileAttribute<?>... attrs)
 738         throws IOException
 739     {
 740         return jrtfs.newFileChannel(getResolvedPath(), options, attrs);
 741     }
 742 
 743     void checkAccess(AccessMode... modes) throws IOException {
 744         boolean w = false;
 745         boolean x = false;
 746         for (AccessMode mode : modes) {
 747             switch (mode) {
 748                 case READ:
 749                     break;
 750                 case WRITE:
 751                     w = true;
 752                     break;
 753                 case EXECUTE:
 754                     x = true;
 755                     break;
 756                 default:
 757                     throw new UnsupportedOperationException();
 758             }
 759         }
 760         JrtFileAttributes attrs = jrtfs.getFileAttributes(getResolvedPath());
 761         if (attrs == null && (path.length != 1 || path[0] != '/'))
 762             throw new NoSuchFileException(toString());
 763         if (w) {
 764             if (jrtfs.isReadOnly())
 765                 throw new AccessDeniedException(toString());
 766         }
 767         if (x)
 768             throw new AccessDeniedException(toString());
 769     }
 770 
 771     boolean exists() {
 772         if (isAbsolute())
 773             return true;
 774         try {
 775             return jrtfs.exists(getResolvedPath());
 776         } catch (IOException x) {}
 777         return false;
 778     }
 779 
 780     OutputStream newOutputStream(OpenOption... options) throws IOException
 781     {
 782         if (options.length == 0)
 783             return jrtfs.newOutputStream(getResolvedPath(),
 784                                        CREATE_NEW, WRITE);
 785         return jrtfs.newOutputStream(getResolvedPath(), options);
 786     }
 787 
 788     void move(JrtPath target, CopyOption... options)
 789         throws IOException
 790     {
 791         if (this.jrtfs == target.jrtfs)
 792         {
 793             jrtfs.copyFile(true,
 794                          getResolvedPath(), target.getResolvedPath(),
 795                          options);
 796         } else {
 797             copyToTarget(target, options);
 798             delete();
 799         }
 800     }
 801 
 802     void copy(JrtPath target, CopyOption... options)
 803         throws IOException
 804     {
 805         if (this.jrtfs == target.jrtfs)
 806             jrtfs.copyFile(false,
 807                          getResolvedPath(), target.getResolvedPath(),
 808                          options);
 809         else
 810             copyToTarget(target, options);
 811     }
 812 
 813     private void copyToTarget(JrtPath target, CopyOption... options)
 814         throws IOException
 815     {
 816         boolean replaceExisting = false;
 817         boolean copyAttrs = false;
 818         for (CopyOption opt : options) {
 819             if (opt == REPLACE_EXISTING)
 820                 replaceExisting = true;
 821             else if (opt == COPY_ATTRIBUTES)
 822                 copyAttrs = true;
 823         }
 824         // attributes of source file
 825         JrtFileAttributes jrtfas = getAttributes();
 826         // check if target exists
 827         boolean exists;
 828         if (replaceExisting) {
 829             try {
 830                 target.deleteIfExists();
 831                 exists = false;
 832             } catch (DirectoryNotEmptyException x) {
 833                 exists = true;
 834             }
 835         } else {
 836             exists = target.exists();
 837         }
 838         if (exists)
 839             throw new FileAlreadyExistsException(target.toString());
 840 
 841         if (jrtfas.isDirectory()) {
 842             // create directory or file
 843             target.createDirectory();
 844         } else {
 845             try (InputStream is = jrtfs.newInputStream(getResolvedPath()); OutputStream os = target.newOutputStream()) {
 846                 byte[] buf = new byte[8192];
 847                 int n;
 848                 while ((n = is.read(buf)) != -1) {
 849                     os.write(buf, 0, n);
 850                 }
 851             }
 852         }
 853         if (copyAttrs) {
 854             BasicFileAttributeView view =
 855                 JrtFileAttributeView.get(target, BasicFileAttributeView.class);
 856             try {
 857                 view.setTimes(jrtfas.lastModifiedTime(),
 858                               jrtfas.lastAccessTime(),
 859                               jrtfas.creationTime());
 860             } catch (IOException x) {
 861                 // rollback?
 862                 try {
 863                     target.delete();
 864                 } catch (IOException ignore) { }
 865                 throw x;
 866             }
 867         }
 868     }
 869 }