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