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 }