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