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