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