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