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