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