src/java.base/share/classes/jdk/internal/jrtfs/JrtPath.java

Print this page




   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package jdk.internal.jrtfs;
  26 
  27 import java.io.*;



  28 import java.net.URI;
  29 import java.net.URISyntaxException;
  30 import java.nio.channels.*;

  31 import java.nio.file.*;
  32 import java.nio.file.DirectoryStream.Filter;
  33 import java.nio.file.attribute.*;
  34 import java.util.*;







  35 import static java.nio.file.StandardOpenOption.*;
  36 import static java.nio.file.StandardCopyOption.*;
  37 
  38 /**
  39  * Base class for Path implementation of jrt file systems.
  40  *
  41  * @implNote This class needs to maintain JDK 8 source compatibility.
  42  *
  43  * It is used internally in the JDK to implement jimage/jrtfs access,
  44  * but also compiled and delivered as part of the jrtfs.jar to support access
  45  * to the jimage file provided by the shipped JDK by tools running on JDK 8.
  46  */
  47 abstract class AbstractJrtPath implements Path {
  48 
  49     protected final AbstractJrtFileSystem jrtfs;
  50     private final byte[] path;
  51     private volatile int[] offsets;
  52     private int hashcode = 0;  // cached hashcode (created lazily)
  53 
  54     AbstractJrtPath(AbstractJrtFileSystem jrtfs, byte[] path) {
  55         this(jrtfs, path, false);

  56         this.resolved = null;
  57     }
  58 
  59     AbstractJrtPath(AbstractJrtFileSystem jrtfs, byte[] path, boolean normalized) {
  60         this.resolved = null;
  61         this.jrtfs = jrtfs;
  62         if (normalized) {
  63             this.path = path;
  64         } else {
  65             this.path = normalize(path);
  66         }
  67     }
  68 
  69     // factory methods to create subtypes of AbstractJrtPath
  70     protected abstract AbstractJrtPath newJrtPath(byte[] path);
  71 
  72     protected abstract AbstractJrtPath newJrtPath(byte[] path, boolean normalized);
  73 
  74     final byte[] getName() {
  75         return path;
  76     }
  77 
  78     @Override
  79     public final AbstractJrtPath getRoot() {
  80         if (this.isAbsolute()) {
  81             return jrtfs.getRootPath();
  82         } else {
  83             return null;
  84         }
  85     }
  86 
  87     @Override
  88     public final AbstractJrtPath getFileName() {
  89         initOffsets();
  90         int count = offsets.length;
  91         if (count == 0) {
  92             return null;  // no elements so no name
  93         }
  94         if (count == 1 && path[0] != '/') {
  95             return this;
  96         }
  97         int lastOffset = offsets[count - 1];
  98         int len = path.length - lastOffset;
  99         byte[] result = new byte[len];
 100         System.arraycopy(path, lastOffset, result, 0, len);
 101         return newJrtPath(result);
 102     }
 103 
 104     @Override
 105     public final AbstractJrtPath getParent() {
 106         initOffsets();
 107         int count = offsets.length;
 108         if (count == 0) // no elements so no parent
 109         {
 110             return null;
 111         }
 112         int len = offsets[count - 1] - 1;
 113         if (len <= 0) // parent is root only (may be null)
 114         {
 115             return getRoot();
 116         }
 117         byte[] result = new byte[len];
 118         System.arraycopy(path, 0, result, 0, len);
 119         return newJrtPath(result);
 120     }
 121 
 122     @Override
 123     public final int getNameCount() {
 124         initOffsets();
 125         return offsets.length;
 126     }
 127 
 128     @Override
 129     public final AbstractJrtPath getName(int index) {
 130         initOffsets();
 131         if (index < 0 || index >= offsets.length) {
 132             throw new IllegalArgumentException();
 133         }
 134         int begin = offsets[index];
 135         int len;
 136         if (index == (offsets.length - 1)) {
 137             len = path.length - begin;
 138         } else {
 139             len = offsets[index + 1] - begin - 1;
 140         }
 141         // construct result
 142         byte[] result = new byte[len];
 143         System.arraycopy(path, begin, result, 0, len);
 144         return newJrtPath(result);
 145     }
 146 
 147     @Override
 148     public final AbstractJrtPath subpath(int beginIndex, int endIndex) {
 149         initOffsets();
 150         if (beginIndex < 0
 151                 || beginIndex >= offsets.length
 152                 || endIndex > offsets.length
 153                 || beginIndex >= endIndex) {
 154             throw new IllegalArgumentException();
 155         }
 156 
 157         // starting offset and length
 158         int begin = offsets[beginIndex];
 159         int len;
 160         if (endIndex == offsets.length) {
 161             len = path.length - begin;
 162         } else {
 163             len = offsets[endIndex] - begin - 1;
 164         }
 165         // construct result
 166         byte[] result = new byte[len];
 167         System.arraycopy(path, begin, result, 0, len);
 168         return newJrtPath(result);
 169     }
 170 
 171     @Override
 172     public final AbstractJrtPath toRealPath(LinkOption... options) throws IOException {
 173         AbstractJrtPath realPath = newJrtPath(getResolvedPath()).toAbsolutePath();
 174         realPath = JrtFileSystem.followLinks(options) ? jrtfs.resolveLink(this) : realPath;
 175         realPath.checkAccess();
 176         return realPath;
 177     }
 178 
 179     final AbstractJrtPath readSymbolicLink() throws IOException {
 180         if (!jrtfs.isLink(this)) {
 181             throw new IOException("not a symbolic link");
 182         }
 183 
 184         return jrtfs.resolveLink(this);
 185     }
 186 
 187     final boolean isHidden() {
 188         return false;
 189     }
 190 
 191     @Override
 192     public final AbstractJrtPath toAbsolutePath() {
 193         if (isAbsolute()) {
 194             return this;
 195         } else {
 196             //add / bofore the existing path
 197             byte[] tmp = new byte[path.length + 1];
 198             tmp[0] = '/';
 199             System.arraycopy(path, 0, tmp, 1, path.length);
 200             return newJrtPath(tmp).normalize();
 201         }
 202     }
 203 
 204     @Override
 205     public final URI toUri() {
 206         try {
 207             return new URI("jrt",
 208                     JrtFileSystem.getString(toAbsolutePath().path),
 209                     null);
 210         } catch (URISyntaxException ex) {
 211             throw new AssertionError(ex);
 212         }
 213     }
 214 
 215     private boolean equalsNameAt(AbstractJrtPath other, int index) {
 216         int mbegin = offsets[index];
 217         int mlen;
 218         if (index == (offsets.length - 1)) {
 219             mlen = path.length - mbegin;
 220         } else {
 221             mlen = offsets[index + 1] - mbegin - 1;
 222         }
 223         int obegin = other.offsets[index];
 224         int olen;
 225         if (index == (other.offsets.length - 1)) {
 226             olen = other.path.length - obegin;
 227         } else {
 228             olen = other.offsets[index + 1] - obegin - 1;
 229         }
 230         if (mlen != olen) {
 231             return false;
 232         }
 233         int n = 0;
 234         while (n < mlen) {
 235             if (path[mbegin + n] != other.path[obegin + n]) {
 236                 return false;
 237             }
 238             n++;
 239         }
 240         return true;
 241     }
 242 
 243     @Override
 244     public final AbstractJrtPath relativize(Path other) {
 245         final AbstractJrtPath o = checkPath(other);
 246         if (o.equals(this)) {
 247             return newJrtPath(new byte[0], true);
 248         }
 249         if (/* this.getFileSystem() != o.getFileSystem() || */this.isAbsolute() != o.isAbsolute()) {



 250             throw new IllegalArgumentException();
 251         }









 252         int mc = this.getNameCount();
 253         int oc = o.getNameCount();
 254         int n = Math.min(mc, oc);
 255         int i = 0;
 256         while (i < n) {
 257             if (!equalsNameAt(o, i)) {
 258                 break;
 259             }
 260             i++;
 261         }
 262         int dotdots = mc - i;
 263         int len = dotdots * 3 - 1;
 264         if (i < oc) {
 265             len += (o.path.length - o.offsets[i] + 1);
 266         }
 267         byte[] result = new byte[len];
 268 
 269         int pos = 0;
 270         while (dotdots > 0) {
 271             result[pos++] = (byte) '.';
 272             result[pos++] = (byte) '.';
 273             if (pos < len) // no tailing slash at the end
 274             {
 275                 result[pos++] = (byte) '/';
 276             }
 277             dotdots--;
 278         }
 279         if (i < oc) {
 280             System.arraycopy(o.path, o.offsets[i],
 281                     result, pos,
 282                     o.path.length - o.offsets[i]);
 283         }
 284         return newJrtPath(result);
 285     }
 286 
 287     @Override
 288     public AbstractJrtFileSystem getFileSystem() {
 289         return jrtfs;
 290     }
 291 
 292     @Override
 293     public final boolean isAbsolute() {
 294         return (this.path.length > 0 && path[0] == '/');
 295     }
 296 
 297     @Override
 298     public final AbstractJrtPath resolve(Path other) {
 299         final AbstractJrtPath o = checkPath(other);
 300         if (o.isAbsolute()) {
 301             return o;
 302         }
 303         byte[] res;
 304         if (this.path[path.length - 1] == '/') {
 305             res = new byte[path.length + o.path.length];
 306             System.arraycopy(path, 0, res, 0, path.length);
 307             System.arraycopy(o.path, 0, res, path.length, o.path.length);
 308         } else {
 309             res = new byte[path.length + 1 + o.path.length];
 310             System.arraycopy(path, 0, res, 0, path.length);
 311             res[path.length] = '/';
 312             System.arraycopy(o.path, 0, res, path.length + 1, o.path.length);
 313         }
 314         return newJrtPath(res);





 315     }
 316 
 317     @Override
 318     public final Path resolveSibling(Path other) {
 319         if (other == null) {
 320             throw new NullPointerException();
 321         }
 322         Path parent = getParent();
 323         return (parent == null) ? other : parent.resolve(other);
 324     }
 325 
 326     @Override
 327     public final boolean startsWith(Path other) {
 328         final AbstractJrtPath o = checkPath(other);
 329         if (o.isAbsolute() != this.isAbsolute()
 330                 || o.path.length > this.path.length) {
 331             return false;
 332         }
 333         int olast = o.path.length;
 334         for (int i = 0; i < olast; i++) {
 335             if (o.path[i] != this.path[i]) {
 336                 return false;
 337             }
 338         }
 339         olast--;
 340         return o.path.length == this.path.length
 341                 || o.path[olast] == '/'
 342                 || this.path[olast + 1] == '/';


 343     }
 344 
 345     @Override
 346     public final boolean endsWith(Path other) {
 347         final AbstractJrtPath o = checkPath(other);
 348         int olast = o.path.length - 1;
 349         if (olast > 0 && o.path[olast] == '/') {



 350             olast--;
 351         }
 352         int last = this.path.length - 1;
 353         if (last > 0 && this.path[last] == '/') {
 354             last--;
 355         }
 356         if (olast == -1) // o.path.length == 0
 357         {
 358             return last == -1;
 359         }
 360         if ((o.isAbsolute() && (!this.isAbsolute() || olast != last))
 361                 || (last < olast)) {
 362             return false;
 363         }
 364         for (; olast >= 0; olast--, last--) {
 365             if (o.path[olast] != this.path[last]) {
 366                 return false;
 367             }
 368         }
 369         return o.path[olast + 1] == '/'
 370                 || last == -1 || this.path[last] == '/';
 371     }
 372 
 373     @Override
 374     public final AbstractJrtPath resolve(String other) {
 375         return resolve(getFileSystem().getPath(other));
 376     }
 377 
 378     @Override
 379     public final Path resolveSibling(String other) {
 380         return resolveSibling(getFileSystem().getPath(other));
 381     }
 382 
 383     @Override
 384     public final boolean startsWith(String other) {
 385         return startsWith(getFileSystem().getPath(other));
 386     }
 387 
 388     @Override
 389     public final boolean endsWith(String other) {
 390         return endsWith(getFileSystem().getPath(other));
 391     }
 392 
 393     @Override
 394     public final AbstractJrtPath normalize() {
 395         byte[] res = getResolved();
 396         if (res == path) // no change
 397         {
 398             return this;
 399         }
 400         return newJrtPath(res, true);
 401     }
 402 
 403     private AbstractJrtPath checkPath(Path path) {
 404         if (path == null) {
 405             throw new NullPointerException();
 406         }
 407         if (!(path instanceof AbstractJrtPath)) {
 408             throw new ProviderMismatchException();
 409         }
 410         return (AbstractJrtPath) path;
 411     }
 412 
 413     // create offset list if not already created
 414     private void initOffsets() {
 415         if (offsets == null) {
 416             int count, index;
 417             // count names
 418             count = 0;
 419             index = 0;
 420             while (index < path.length) {
 421                 byte c = path[index++];
 422                 if (c != '/') {
 423                     count++;
 424                     while (index < path.length && path[index] != '/') {
 425                         index++;
 426                     }
 427                 }
 428             }
 429             // populate offsets
 430             int[] result = new int[count];
 431             count = 0;
 432             index = 0;
 433             while (index < path.length) {
 434                 byte c = path[index];
 435                 if (c == '/') {
 436                     index++;
 437                 } else {
 438                     result[count++] = index++;
 439                     while (index < path.length && path[index] != '/') {
 440                         index++;
 441                     }
 442                 }
 443             }
 444             synchronized (this) {
 445                 if (offsets == null) {
 446                     offsets = result;
 447                 }
 448             }

 449         }
 450     }
 451 
 452     private volatile byte[] resolved;
 453 
 454     final byte[] getResolvedPath() {
 455         byte[] r = resolved;
 456         if (r == null) {
 457             if (isAbsolute()) {
 458                 r = getResolved();
 459             } else {
 460                 r = toAbsolutePath().getResolvedPath();
 461             }
 462             resolved = r;
 463         }
 464         return resolved;
 465     }
 466 
 467     // removes redundant slashs, replace "\" to separator "/"
 468     // and check for invalid characters
 469     private static byte[] normalize(byte[] path) {
 470         if (path.length == 0) {

 471             return path;
 472         }
 473         byte prevC = 0;
 474         for (int i = 0; i < path.length; i++) {
 475             byte c = path[i];
 476             if (c == '\\') {
 477                 return normalize(path, i);
 478             }
 479             if (c == (byte) '/' && prevC == '/') {
 480                 return normalize(path, i - 1);
 481             }
 482             if (c == '\u0000') {
 483                 throw new InvalidPathException(JrtFileSystem.getString(path),
 484                         "Path: nul character not allowed");
 485             }
 486             prevC = c;
 487         }
 488 
 489         if (path.length > 1 && path[path.length - 1] == '/') {
 490             return Arrays.copyOf(path, path.length - 1);
 491         }
 492 
 493         return path;
 494     }
 495 
 496     private static byte[] normalize(byte[] path, int off) {
 497         byte[] to = new byte[path.length];
 498         int n = 0;
 499         while (n < off) {
 500             to[n] = path[n];
 501             n++;
 502         }
 503         int m = n;
 504         byte prevC = 0;
 505         while (n < path.length) {
 506             byte c = path[n++];
 507             if (c == (byte) '\\') {
 508                 c = (byte) '/';
 509             }
 510             if (c == (byte) '/' && prevC == (byte) '/') {
 511                 continue;
 512             }
 513             if (c == '\u0000') {
 514                 throw new InvalidPathException(JrtFileSystem.getString(path),
 515                         "Path: nul character not allowed");
 516             }
 517             to[m++] = c;
 518             prevC = c;
 519         }
 520         if (m > 1 && to[m - 1] == '/') {
 521             m--;

 522         }
 523         return (m == to.length) ? to : Arrays.copyOf(to, m);
 524     }
 525 
 526     // Remove DotSlash(./) and resolve DotDot (..) components
 527     private byte[] getResolved() {
 528         if (path.length == 0) {
 529             return path;
 530         }
 531         for (int i = 0; i < path.length; i++) {
 532             byte c = path[i];
 533             if (c == (byte) '.') {
 534                 return resolve0();
 535             }
 536         }
 537 
 538         return path;
 539     }
 540 
 541     // TBD: performance, avoid initOffsets
 542     private byte[] resolve0() {
 543         byte[] to = new byte[path.length];
 544         int nc = getNameCount();
 545         int[] lastM = new int[nc];
 546         int lastMOff = -1;
 547         int m = 0;
 548         for (int i = 0; i < nc; i++) {
 549             int n = offsets[i];
 550             int len = (i == offsets.length - 1)
 551                     ? (path.length - n) : (offsets[i + 1] - n - 1);
 552             if (len == 1 && path[n] == (byte) '.') {
 553                 if (m == 0 && path[0] == '/') // absolute path
 554                 {
 555                     to[m++] = '/';
 556                 }
 557                 continue;
 558             }
 559             if (len == 2 && path[n] == '.' && path[n + 1] == '.') {
 560                 if (lastMOff >= 0) {
 561                     m = lastM[lastMOff--];  // retreat
 562                     continue;
 563                 }
 564                 if (path[0] == '/') {  // "/../xyz" skip
 565                     if (m == 0) {
 566                         to[m++] = '/';
 567                     }
 568                 } else {               // "../xyz" -> "../xyz"
 569                     if (m != 0 && to[m - 1] != '/') {
 570                         to[m++] = '/';
 571                     }
 572                     while (len-- > 0) {
 573                         to[m++] = path[n++];
 574                     }
 575                 }
 576                 continue;
 577             }
 578             if (m == 0 && path[0] == '/' || // absolute path
 579                     m != 0 && to[m - 1] != '/') {   // not the first name
 580                 to[m++] = '/';
 581             }
 582             lastM[++lastMOff] = m;
 583             while (len-- > 0) {
 584                 to[m++] = path[n++];
 585             }
 586         }
 587         if (m > 1 && to[m - 1] == '/') {
 588             m--;
 589         }
 590         return (m == to.length) ? to : Arrays.copyOf(to, m);
 591     }
 592 
 593     @Override
 594     public final String toString() {
 595         return JrtFileSystem.getString(path);
 596     }
 597 
 598     @Override
 599     public final int hashCode() {
 600         int h = hashcode;
 601         if (h == 0) {
 602             hashcode = h = Arrays.hashCode(path);
 603         }
 604         return h;
 605     }
 606 
 607     @Override
 608     public final boolean equals(Object obj) {
 609         return obj != null
 610                 && obj instanceof AbstractJrtPath
 611                 && this.jrtfs == ((AbstractJrtPath) obj).jrtfs
 612                 && compareTo((Path) obj) == 0;
 613     }
 614 
 615     @Override
 616     public final int compareTo(Path other) {
 617         final AbstractJrtPath o = checkPath(other);
 618         int len1 = this.path.length;
 619         int len2 = o.path.length;
 620 
 621         int n = Math.min(len1, len2);
 622         byte v1[] = this.path;
 623         byte v2[] = o.path;
 624 
 625         int k = 0;
 626         while (k < n) {
 627             int c1 = v1[k] & 0xff;
 628             int c2 = v2[k] & 0xff;
 629             if (c1 != c2) {
 630                 return c1 - c2;
 631             }
 632             k++;
 633         }
 634         return len1 - len2;
 635     }
 636 
 637     @Override
 638     public final WatchKey register(
 639             WatchService watcher,
 640             WatchEvent.Kind<?>[] events,
 641             WatchEvent.Modifier... modifiers) {
 642         if (watcher == null || events == null || modifiers == null) {
 643             throw new NullPointerException();
 644         }
 645         throw new UnsupportedOperationException();
 646     }
 647 
 648     @Override
 649     public final WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) {
 650         return register(watcher, events, new WatchEvent.Modifier[0]);
 651     }
 652 
 653     @Override
 654     public final File toFile() {
 655         throw new UnsupportedOperationException();
 656     }
 657 
 658     @Override
 659     public final Iterator<Path> iterator() {
 660         return new Iterator<Path>() {
 661             private int i = 0;
 662 
 663             @Override
 664             public boolean hasNext() {


 666             }
 667 
 668             @Override
 669             public Path next() {
 670                 if (i < getNameCount()) {
 671                     Path result = getName(i);
 672                     i++;
 673                     return result;
 674                 } else {
 675                     throw new NoSuchElementException();
 676                 }
 677             }
 678 
 679             @Override
 680             public void remove() {
 681                 throw new ReadOnlyFileSystemException();
 682             }
 683         };
 684     }
 685 
 686     /////////////////////////////////////////////////////////////////////
 687     // Helpers for JrtFileSystemProvider and JrtFileSystem
 688     final int getPathLength() {
 689         return path.length;








 690     }
 691 
 692     final void createDirectory(FileAttribute<?>... attrs)
 693             throws IOException {
 694         jrtfs.createDirectory(this, attrs);
 695     }
 696 
 697     final InputStream newInputStream(OpenOption... options) throws IOException {
 698         if (options.length > 0) {
 699             for (OpenOption opt : options) {
 700                 if (opt != READ) {
 701                     throw new UnsupportedOperationException("'" + opt + "' not allowed");
 702                 }
 703             }
 704         }
 705         return jrtfs.newInputStream(this);
 706     }
 707 
 708     final DirectoryStream<Path> newDirectoryStream(Filter<? super Path> filter)
 709             throws IOException {
 710         return new JrtDirectoryStream(this, filter);
 711     }
 712 
 713     final void delete() throws IOException {
 714         jrtfs.deleteFile(this, true);
 715     }
 716 
 717     final void deleteIfExists() throws IOException {
 718         jrtfs.deleteFile(this, false);
 719     }
 720 
 721     final AbstractJrtFileAttributes getAttributes(LinkOption... options) throws IOException {
 722         AbstractJrtFileAttributes zfas = jrtfs.getFileAttributes(this, options);
 723         if (zfas == null) {
 724             throw new NoSuchFileException(toString());
 725         }
 726         return zfas;
 727     }
 728 
 729     final void setAttribute(String attribute, Object value, LinkOption... options)
 730             throws IOException {
 731         String type;
 732         String attr;
 733         int colonPos = attribute.indexOf(':');
 734         if (colonPos == -1) {
 735             type = "basic";
 736             attr = attribute;
 737         } else {
 738             type = attribute.substring(0, colonPos++);
 739             attr = attribute.substring(colonPos);
 740         }
 741         JrtFileAttributeView view = JrtFileAttributeView.get(this, type, options);
 742         if (view == null) {
 743             throw new UnsupportedOperationException("view <" + view + "> is not supported");
 744         }
 745         view.setAttribute(attr, value);
 746     }
 747 
 748     final void setTimes(FileTime mtime, FileTime atime, FileTime ctime)
 749             throws IOException {
 750         jrtfs.setTimes(this, mtime, atime, ctime);
 751     }
 752 
 753     final Map<String, Object> readAttributes(String attributes, LinkOption... options)
 754             throws IOException {
 755         String view;
 756         String attrs;
 757         int colonPos = attributes.indexOf(':');
 758         if (colonPos == -1) {
 759             view = "basic";
 760             attrs = attributes;
 761         } else {
 762             view = attributes.substring(0, colonPos++);
 763             attrs = attributes.substring(colonPos);
 764         }
 765         JrtFileAttributeView jrtfv = JrtFileAttributeView.get(this, view, options);
 766         if (jrtfv == null) {
 767             throw new UnsupportedOperationException("view not supported");
 768         }
 769         return jrtfv.readAttributes(attrs);
 770     }
 771 
 772     final FileStore getFileStore() throws IOException {
 773         // each JrtFileSystem only has one root (as requested for now)
 774         if (exists()) {
 775             return jrtfs.getFileStore(this);
 776         }
 777         throw new NoSuchFileException(JrtFileSystem.getString(path));
 778     }
 779 
 780     final boolean isSameFile(Path other) throws IOException {
 781         if (this.equals(other)) {
 782             return true;
 783         }
 784         if (other == null
 785                 || this.getFileSystem() != other.getFileSystem()) {
 786             return false;
 787         }
 788         this.checkAccess();
 789         AbstractJrtPath target = (AbstractJrtPath) other;
 790         target.checkAccess();
 791         return Arrays.equals(this.getResolvedPath(), target.getResolvedPath())
 792                 || jrtfs.isSameFile(this, target);
 793     }
 794 
 795     final SeekableByteChannel newByteChannel(Set<? extends OpenOption> options,
 796             FileAttribute<?>... attrs)
 797             throws IOException {

 798         return jrtfs.newByteChannel(this, options, attrs);
 799     }
 800 
 801     final FileChannel newFileChannel(Set<? extends OpenOption> options,
 802             FileAttribute<?>... attrs)
 803             throws IOException {
 804         return jrtfs.newFileChannel(this, options, attrs);
 805     }
 806 
 807     final void checkAccess(AccessMode... modes) throws IOException {




 808         boolean w = false;
 809         boolean x = false;
 810         for (AccessMode mode : modes) {
 811             switch (mode) {
 812                 case READ:
 813                     break;
 814                 case WRITE:
 815                     w = true;
 816                     break;
 817                 case EXECUTE:
 818                     x = true;
 819                     break;
 820                 default:
 821                     throw new UnsupportedOperationException();
 822             }
 823         }
 824 
 825         BasicFileAttributes attrs = jrtfs.getFileAttributes(this);
 826         if (attrs == null && (path.length != 1 || path[0] != '/')) {
 827             throw new NoSuchFileException(toString());
 828         }
 829         if (w) {
 830 //            if (jrtfs.isReadOnly())
 831             throw new AccessDeniedException(toString());
 832         }
 833         if (x) {
 834             throw new AccessDeniedException(toString());
 835         }
 836     }
 837 
 838     final boolean exists() {
 839         try {
 840             return jrtfs.exists(this);
 841         } catch (IOException x) {
 842         }
 843         return false;
 844     }
 845 
 846     final OutputStream newOutputStream(OpenOption... options) throws IOException {
 847         if (options.length == 0) {
 848             return jrtfs.newOutputStream(this,
 849                     CREATE_NEW, WRITE);
 850         }
 851         return jrtfs.newOutputStream(this, options);
 852     }
 853 
 854     final void move(AbstractJrtPath target, CopyOption... options)
 855             throws IOException {
 856         if (this.jrtfs == target.jrtfs) {
 857             jrtfs.copyFile(true,
 858                     this, target,
 859                     options);
 860         } else {
 861             copyToTarget(target, options);
 862             delete();
 863         }
 864     }
 865 
 866     final void copy(AbstractJrtPath target, CopyOption... options)
 867             throws IOException {
 868         if (this.jrtfs == target.jrtfs) {
 869             jrtfs.copyFile(false,
 870                     this, target,
 871                     options);
 872         } else {
 873             copyToTarget(target, options);
 874         }
 875     }
 876 
 877     private void copyToTarget(AbstractJrtPath target, CopyOption... options)
 878             throws IOException {
 879         boolean replaceExisting = false;
 880         boolean copyAttrs = false;
 881         for (CopyOption opt : options) {
 882             if (opt == REPLACE_EXISTING) {
 883                 replaceExisting = true;
 884             } else if (opt == COPY_ATTRIBUTES) {
 885                 copyAttrs = true;
 886             }
 887         }
 888         // attributes of source file
 889         BasicFileAttributes jrtfas = getAttributes();
 890         // check if target exists
 891         boolean exists;
 892         if (replaceExisting) {
 893             try {
 894                 target.deleteIfExists();
 895                 exists = false;
 896             } catch (DirectoryNotEmptyException x) {
 897                 exists = true;
 898             }
 899         } else {
 900             exists = target.exists();
 901         }
 902         if (exists) {
 903             throw new FileAlreadyExistsException(target.toString());
 904         }
 905 
 906         if (jrtfas.isDirectory()) {
 907             // create directory or file
 908             target.createDirectory();
 909         } else {
 910             try (InputStream is = jrtfs.newInputStream(this); OutputStream os = target.newOutputStream()) {

 911                 byte[] buf = new byte[8192];
 912                 int n;
 913                 while ((n = is.read(buf)) != -1) {
 914                     os.write(buf, 0, n);
 915                 }
 916             }
 917         }
 918         if (copyAttrs) {
 919             BasicFileAttributeView view
 920                     = JrtFileAttributeView.get(target, BasicFileAttributeView.class);
 921             try {
 922                 view.setTimes(jrtfas.lastModifiedTime(),
 923                         jrtfas.lastAccessTime(),
 924                         jrtfas.creationTime());
 925             } catch (IOException x) {
 926                 // rollback?
 927                 try {
 928                     target.delete();
 929                 } catch (IOException ignore) {
 930                 }
 931                 throw x;
 932             }
 933         }
 934     }
 935 }


   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package jdk.internal.jrtfs;
  26 
  27 import java.io.File;
  28 import java.io.IOException;
  29 import java.io.InputStream;
  30 import java.io.OutputStream;
  31 import java.net.URI;
  32 import java.net.URISyntaxException;
  33 import java.nio.channels.FileChannel;
  34 import java.nio.channels.SeekableByteChannel;
  35 import java.nio.file.*;
  36 import java.nio.file.DirectoryStream.Filter;;
  37 import java.nio.file.attribute.BasicFileAttributes;
  38 import java.nio.file.attribute.BasicFileAttributeView;
  39 import java.nio.file.attribute.FileAttribute;
  40 import java.nio.file.attribute.FileTime;
  41 import java.util.Iterator;
  42 import java.util.Map;
  43 import java.util.NoSuchElementException;
  44 import java.util.Objects;
  45 import java.util.Set;
  46 import static java.nio.file.StandardOpenOption.*;
  47 import static java.nio.file.StandardCopyOption.*;
  48 
  49 /**
  50  * Base class for Path implementation of jrt file systems.
  51  *
  52  * @implNote This class needs to maintain JDK 8 source compatibility.
  53  *
  54  * It is used internally in the JDK to implement jimage/jrtfs access,
  55  * but also compiled and delivered as part of the jrtfs.jar to support access
  56  * to the jimage file provided by the shipped JDK by tools running on JDK 8.
  57  */
  58 final class JrtPath implements Path {
  59 
  60     final JrtFileSystem jrtfs;
  61     private final String path;
  62     private volatile int[] offsets;

  63 
  64     JrtPath(JrtFileSystem jrtfs, String path) {
  65         this.jrtfs = jrtfs;
  66         this.path = normalize(path);
  67         this.resolved = null;
  68     }
  69 
  70     JrtPath(JrtFileSystem jrtfs, String path, boolean normalized) {

  71         this.jrtfs = jrtfs;
  72         this.path = normalized ? path : normalize(path);
  73         this.resolved = null;



  74     }
  75 
  76     final String getName() {





  77         return path;
  78     }
  79 
  80     @Override
  81     public final JrtPath getRoot() {
  82         if (this.isAbsolute()) {
  83             return jrtfs.getRootPath();
  84         } else {
  85             return null;
  86         }
  87     }
  88 
  89     @Override
  90     public final JrtPath getFileName() {
  91         if (path.length() == 0)





  92             return this;
  93         if (path.length() == 1 && path.charAt(0) == '/')
  94             return null;
  95         int off = path.lastIndexOf('/');
  96         if (off == -1)
  97             return this;
  98         return new JrtPath(jrtfs, path.substring(off + 1), true);
  99     }
 100 
 101     @Override
 102     public final JrtPath getParent() {
 103         initOffsets();
 104         int count = offsets.length;
 105         if (count == 0) {     // no elements so no parent

 106             return null;
 107         }
 108         int off = offsets[count - 1] - 1;
 109         if (off <= 0) {       // parent is root only (may be null)

 110             return getRoot();
 111         }
 112         return new JrtPath(jrtfs, path.substring(0, off));


 113     }
 114 
 115     @Override
 116     public final int getNameCount() {
 117         initOffsets();
 118         return offsets.length;
 119     }
 120 
 121     @Override
 122     public final JrtPath getName(int index) {
 123         initOffsets();
 124         if (index < 0 || index >= offsets.length) {
 125             throw new IllegalArgumentException();
 126         }
 127         int begin = offsets[index];
 128         int end;
 129         if (index == (offsets.length - 1)) {
 130             end = path.length();
 131         } else {
 132             end = offsets[index + 1];
 133         }
 134         return new JrtPath(jrtfs, path.substring(begin, end));



 135     }
 136 
 137     @Override
 138     public final JrtPath subpath(int beginIndex, int endIndex) {
 139         initOffsets();
 140         if (beginIndex < 0 || endIndex > offsets.length ||
 141             beginIndex >= endIndex) {


 142             throw new IllegalArgumentException();
 143         }
 144         // starting/ending offsets

 145         int begin = offsets[beginIndex];
 146         int end;
 147         if (endIndex == offsets.length) {
 148             end = path.length();
 149         } else {
 150             end = offsets[endIndex];
 151         }
 152         return new JrtPath(jrtfs, path.substring(begin, end));



 153     }
 154 
 155     @Override
 156     public final JrtPath toRealPath(LinkOption... options) throws IOException {
 157         return jrtfs.toRealPath(this, options);















 158     }
 159 
 160     @Override
 161     public final JrtPath toAbsolutePath() {
 162         if (isAbsolute())
 163             return this;
 164         return new JrtPath(jrtfs, "/" + path, true);






 165     }
 166 
 167     @Override
 168     public final URI toUri() {
 169         try {
 170             return new URI("jrt", toAbsolutePath().path, null);


 171         } catch (URISyntaxException ex) {
 172             throw new AssertionError(ex);
 173         }
 174     }
 175 
 176     private boolean equalsNameAt(JrtPath other, int index) {
 177         int mbegin = offsets[index];
 178         int mlen;
 179         if (index == (offsets.length - 1)) {
 180             mlen = path.length() - mbegin;
 181         } else {
 182             mlen = offsets[index + 1] - mbegin - 1;
 183         }
 184         int obegin = other.offsets[index];
 185         int olen;
 186         if (index == (other.offsets.length - 1)) {
 187             olen = other.path.length() - obegin;
 188         } else {
 189             olen = other.offsets[index + 1] - obegin - 1;
 190         }
 191         if (mlen != olen) {
 192             return false;
 193         }
 194         int n = 0;
 195         while (n < mlen) {
 196             if (path.charAt(mbegin + n) != other.path.charAt(obegin + n)) {
 197                 return false;
 198             }
 199             n++;
 200         }
 201         return true;
 202     }
 203 
 204     @Override
 205     public final JrtPath relativize(Path other) {
 206         final JrtPath o = checkPath(other);
 207         if (o.equals(this)) {
 208             return new JrtPath(jrtfs, "", true);
 209         }
 210         if (path.length() == 0) {
 211             return o;
 212         }
 213         if (jrtfs != o.jrtfs || isAbsolute() != o.isAbsolute()) {
 214             throw new IllegalArgumentException();
 215         }
 216         final String tp = this.path;
 217         final String op = o.path;
 218         if (op.startsWith(tp)) {    // fast path
 219             int off = tp.length();
 220             if (op.charAt(off - 1) == '/')
 221                 return new JrtPath(jrtfs, op.substring(off), true);
 222             if (op.charAt(off) == '/')
 223                 return new JrtPath(jrtfs, op.substring(off + 1), true);
 224         }
 225         int mc = this.getNameCount();
 226         int oc = o.getNameCount();
 227         int n = Math.min(mc, oc);
 228         int i = 0;
 229         while (i < n) {
 230             if (!equalsNameAt(o, i)) {
 231                 break;
 232             }
 233             i++;
 234         }
 235         int dotdots = mc - i;
 236         int len = dotdots * 3 - 1;
 237         if (i < oc) {
 238             len += (o.path.length() - o.offsets[i] + 1);
 239         }
 240         StringBuilder sb  = new StringBuilder(len);


 241         while (dotdots > 0) {
 242             sb.append("..");
 243             if (sb.length() < len) {  // no tailing slash at the end
 244                 sb.append('/');


 245             }
 246             dotdots--;
 247         }
 248         if (i < oc) {
 249             sb.append(o.path, o.offsets[i], o.path.length());


 250         }
 251         return new JrtPath(jrtfs, sb.toString(), true);
 252     }
 253 
 254     @Override
 255     public JrtFileSystem getFileSystem() {
 256         return jrtfs;
 257     }
 258 
 259     @Override
 260     public final boolean isAbsolute() {
 261         return path.length() > 0 && path.charAt(0) == '/';
 262     }
 263 
 264     @Override
 265     public final JrtPath resolve(Path other) {
 266         final JrtPath o = checkPath(other);
 267         if (this.path.length() == 0 || o.isAbsolute()) {
 268             return o;
 269         }
 270         if (o.path.length() == 0) {
 271             return this;








 272         }
 273         StringBuilder sb = new StringBuilder(path.length() + o.path.length());
 274         sb.append(path);
 275         if (path.charAt(path.length() - 1) != '/')
 276             sb.append('/');
 277         sb.append(o.path);
 278         return new JrtPath(jrtfs, sb.toString(), true);
 279     }
 280 
 281     @Override
 282     public final Path resolveSibling(Path other) {
 283         Objects.requireNonNull(other, "other");


 284         Path parent = getParent();
 285         return (parent == null) ? other : parent.resolve(other);
 286     }
 287 
 288     @Override
 289     public final boolean startsWith(Path other) {
 290         if (!(Objects.requireNonNull(other) instanceof JrtPath))


 291             return false;
 292         final JrtPath o = (JrtPath)other;
 293         final String tp = this.path;
 294         final String op = o.path;
 295         if (isAbsolute() != o.isAbsolute() || !tp.startsWith(op)) {
 296             return false;
 297         }
 298         int off = op.length();
 299         if (off == 0) {
 300             return tp.length() == 0;
 301         }
 302         // check match is on name boundary
 303         return tp.length() == off || tp.charAt(off) == '/' ||
 304                off == 0 || op.charAt(off - 1) == '/';
 305     }
 306 
 307     @Override
 308     public final boolean endsWith(Path other) {
 309         if (!(Objects.requireNonNull(other) instanceof JrtPath))
 310             return false;
 311         final JrtPath o = (JrtPath)other;
 312         final JrtPath t = this;
 313         int olast = o.path.length() - 1;
 314         if (olast > 0 && o.path.charAt(olast) == '/') {
 315             olast--;
 316         }
 317         int last = t.path.length() - 1;
 318         if (last > 0 && t.path.charAt(last) == '/') {
 319             last--;
 320         }
 321         if (olast == -1) {  // o.path.length == 0

 322             return last == -1;
 323         }
 324         if ((o.isAbsolute() && (!t.isAbsolute() || olast != last))
 325             || last < olast) {
 326             return false;
 327         }
 328         for (; olast >= 0; olast--, last--) {
 329             if (o.path.charAt(olast) != t.path.charAt(last)) {
 330                 return false;
 331             }
 332         }
 333         return o.path.charAt(olast + 1) == '/' ||
 334                last == -1 || t.path.charAt(last) == '/';
 335     }
 336 
 337     @Override
 338     public final JrtPath resolve(String other) {
 339         return resolve(getFileSystem().getPath(other));
 340     }
 341 
 342     @Override
 343     public final Path resolveSibling(String other) {
 344         return resolveSibling(getFileSystem().getPath(other));
 345     }
 346 
 347     @Override
 348     public final boolean startsWith(String other) {
 349         return startsWith(getFileSystem().getPath(other));
 350     }
 351 
 352     @Override
 353     public final boolean endsWith(String other) {
 354         return endsWith(getFileSystem().getPath(other));
 355     }
 356 
 357     @Override
 358     public final JrtPath normalize() {
 359         String res = getResolved();
 360         if (res == path) {  // no change

 361             return this;
 362         }
 363         return new JrtPath(jrtfs, res, true);
 364     }
 365 
 366     private JrtPath checkPath(Path path) {
 367         Objects.requireNonNull(path);
 368         if (!(path instanceof JrtPath))


 369             throw new ProviderMismatchException();
 370         return (JrtPath) path;

 371     }
 372 
 373     // create offset list if not already created
 374     private void initOffsets() {
 375         if (this.offsets == null) {
 376             int len = path.length();
 377             // count names
 378             int count = 0;
 379             int off = 0;
 380             while (off < len) {
 381                 char c = path.charAt(off++);
 382                 if (c != '/') {
 383                     count++;
 384                     off = path.indexOf('/', off);
 385                     if (off == -1)
 386                         break;
 387                 }
 388             }
 389             // populate offsets
 390             int[] offsets = new int[count];
 391             count = 0;
 392             off = 0;
 393             while (off < len) {
 394                 char c = path.charAt(off);
 395                 if (c == '/') {
 396                     off++;
 397                 } else {
 398                     offsets[count++] = off++;
 399                     off = path.indexOf('/', off);
 400                     if (off == -1)
 401                         break;





 402                 }
 403             }
 404             this.offsets = offsets;
 405         }
 406     }
 407 
 408     private volatile String resolved;
 409 
 410     final String getResolvedPath() {
 411         String r = resolved;
 412         if (r == null) {
 413             if (isAbsolute()) {
 414                 r = getResolved();
 415             } else {
 416                 r = toAbsolutePath().getResolvedPath();
 417             }
 418             resolved = r;
 419         }
 420         return r;
 421     }
 422 
 423     // removes redundant slashs, replace "\" to separator "/"
 424     // and check for invalid characters
 425     private static String normalize(String path) {
 426         int len = path.length();
 427         if (len == 0) {
 428             return path;
 429         }
 430         char prevC = 0;
 431         for (int i = 0; i < len; i++) {
 432             char c = path.charAt(i);
 433             if (c == '\\' || c == '\u0000') {
 434                 return normalize(path, i);
 435             }
 436             if (c == '/' && prevC == '/') {
 437                 return normalize(path, i - 1);
 438             }




 439             prevC = c;
 440         }
 441         if (prevC == '/' && len > 1) {
 442             return path.substring(0, len - 1);

 443         }

 444         return path;
 445     }
 446 
 447     private static String normalize(String path, int off) {
 448         int len = path.length();
 449         StringBuilder to = new StringBuilder(len);
 450         to.append(path, 0, off);
 451         char prevC = 0;
 452         while (off < len) {
 453             char c = path.charAt(off++);
 454             if (c == '\\') {
 455                 c = '/';




 456             }
 457             if (c == '/' && prevC == '/') {
 458                 continue;
 459             }
 460             if (c == '\u0000') {
 461                 throw new InvalidPathException(path,
 462                         "Path: nul character not allowed");
 463             }
 464             to.append(c);
 465             prevC = c;
 466         }
 467         len = to.length();
 468         if (len > 1 && to.charAt(len - 1) == '/') {
 469             to.deleteCharAt(len - 1);
 470         }
 471         return to.toString();
 472     }
 473 
 474     // Remove DotSlash(./) and resolve DotDot (..) components
 475     private String getResolved() {
 476         if (path.length() == 0) {
 477             return path;
 478         }
 479         if (path.indexOf('.') == -1) {






 480             return path;
 481         }
 482         int length = path.length();
 483         char[] to = new char[length];


 484         int nc = getNameCount();
 485         int[] lastM = new int[nc];
 486         int lastMOff = -1;
 487         int m = 0;
 488         for (int i = 0; i < nc; i++) {
 489             int n = offsets[i];
 490             int len = (i == offsets.length - 1) ? length - n
 491                                                 : offsets[i + 1] - n - 1;
 492             if (len == 1 && path.charAt(n) == '.') {
 493                 if (m == 0 && path.charAt(0) == '/')   // absolute path

 494                     to[m++] = '/';

 495                 continue;
 496             }
 497             if (len == 2 && path.charAt(n) == '.' && path.charAt(n + 1) == '.') {
 498                 if (lastMOff >= 0) {
 499                     m = lastM[lastMOff--];    // retreat
 500                     continue;
 501                 }
 502                 if (path.charAt(0) == '/') {  // "/../xyz" skip
 503                     if (m == 0)
 504                         to[m++] = '/';

 505                 } else {                      // "../xyz" -> "../xyz"
 506                     if (m != 0 && to[m-1] != '/')
 507                         to[m++] = '/';
 508                     while (len-- > 0)
 509                         to[m++] = path.charAt(n++);


 510                 }
 511                 continue;
 512             }
 513             if (m == 0 && path.charAt(0) == '/' ||   // absolute path
 514                 m != 0 && to[m-1] != '/') {   // not the first name
 515                 to[m++] = '/';
 516             }
 517             lastM[++lastMOff] = m;
 518             while (len-- > 0)
 519                 to[m++] = path.charAt(n++);

 520         }
 521         if (m > 1 && to[m - 1] == '/')
 522             m--;
 523         return (m == to.length) ? new String(to) : new String(to, 0, m);

 524     }
 525 
 526     @Override
 527     public final String toString() {
 528         return path;
 529     }
 530 
 531     @Override
 532     public final int hashCode() {
 533         return path.hashCode();




 534     }
 535 
 536     @Override
 537     public final boolean equals(Object obj) {
 538         return obj instanceof JrtPath &&
 539                this.path.equals(((JrtPath) obj).path);


 540     }
 541 
 542     @Override
 543     public final int compareTo(Path other) {
 544         final JrtPath o = checkPath(other);
 545         return path.compareTo(o.path);
















 546     }
 547 
 548     @Override
 549     public final WatchKey register(
 550             WatchService watcher,
 551             WatchEvent.Kind<?>[] events,
 552             WatchEvent.Modifier... modifiers) {
 553         Objects.requireNonNull(watcher, "watcher");
 554         Objects.requireNonNull(events, "events");
 555         Objects.requireNonNull(modifiers, "modifiers");
 556         throw new UnsupportedOperationException();
 557     }
 558 
 559     @Override
 560     public final WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) {
 561         return register(watcher, events, new WatchEvent.Modifier[0]);
 562     }
 563 
 564     @Override
 565     public final File toFile() {
 566         throw new UnsupportedOperationException();
 567     }
 568 
 569     @Override
 570     public final Iterator<Path> iterator() {
 571         return new Iterator<Path>() {
 572             private int i = 0;
 573 
 574             @Override
 575             public boolean hasNext() {


 577             }
 578 
 579             @Override
 580             public Path next() {
 581                 if (i < getNameCount()) {
 582                     Path result = getName(i);
 583                     i++;
 584                     return result;
 585                 } else {
 586                     throw new NoSuchElementException();
 587                 }
 588             }
 589 
 590             @Override
 591             public void remove() {
 592                 throw new ReadOnlyFileSystemException();
 593             }
 594         };
 595     }
 596 

 597     // Helpers for JrtFileSystemProvider and JrtFileSystem
 598 
 599     final JrtPath readSymbolicLink() throws IOException {
 600         if (!jrtfs.isLink(this)) {
 601             throw new IOException("not a symbolic link");
 602         }
 603         return jrtfs.resolveLink(this);
 604     }
 605 
 606     final boolean isHidden() {
 607         return false;
 608     }
 609 
 610     final void createDirectory(FileAttribute<?>... attrs)
 611             throws IOException {
 612         jrtfs.createDirectory(this, attrs);
 613     }
 614 
 615     final InputStream newInputStream(OpenOption... options) throws IOException {
 616         if (options.length > 0) {
 617             for (OpenOption opt : options) {
 618                 if (opt != READ) {
 619                     throw new UnsupportedOperationException("'" + opt + "' not allowed");
 620                 }
 621             }
 622         }
 623         return jrtfs.newInputStream(this);
 624     }
 625 
 626     final DirectoryStream<Path> newDirectoryStream(Filter<? super Path> filter)
 627             throws IOException {
 628         return new JrtDirectoryStream(this, filter);
 629     }
 630 
 631     final void delete() throws IOException {
 632         jrtfs.deleteFile(this, true);
 633     }
 634 
 635     final void deleteIfExists() throws IOException {
 636         jrtfs.deleteFile(this, false);
 637     }
 638 
 639     final JrtFileAttributes getAttributes(LinkOption... options) throws IOException {
 640         JrtFileAttributes zfas = jrtfs.getFileAttributes(this, options);
 641         if (zfas == null) {
 642             throw new NoSuchFileException(toString());
 643         }
 644         return zfas;
 645     }
 646 
 647     final void setAttribute(String attribute, Object value, LinkOption... options)
 648             throws IOException {
 649         JrtFileAttributeView.setAttribute(this, attribute, value);














 650     }
 651 
 652     final Map<String, Object> readAttributes(String attributes, LinkOption... options)
 653             throws IOException {
 654         return JrtFileAttributeView.readAttributes(this, attributes, options);
 655     }
 656 
 657     final void setTimes(FileTime mtime, FileTime atime, FileTime ctime)
 658             throws IOException {
 659         jrtfs.setTimes(this, mtime, atime, ctime);














 660     }
 661 
 662     final FileStore getFileStore() throws IOException {
 663         // each JrtFileSystem only has one root (as requested for now)
 664         if (exists()) {
 665             return jrtfs.getFileStore(this);
 666         }
 667         throw new NoSuchFileException(path);
 668     }
 669 
 670     final boolean isSameFile(Path other) throws IOException {
 671         if (this == other || this.equals(other)) {
 672             return true;
 673         }
 674         if (other == null || this.getFileSystem() != other.getFileSystem()) {

 675             return false;
 676         }
 677         this.checkAccess();
 678         JrtPath o = (JrtPath) other;
 679         o.checkAccess();
 680         return this.getResolvedPath().equals(o.getResolvedPath()) ||
 681                jrtfs.isSameFile(this, o);
 682     }
 683 
 684     final SeekableByteChannel newByteChannel(Set<? extends OpenOption> options,
 685                                              FileAttribute<?>... attrs)
 686             throws IOException
 687     {
 688         return jrtfs.newByteChannel(this, options, attrs);
 689     }
 690 
 691     final FileChannel newFileChannel(Set<? extends OpenOption> options,
 692             FileAttribute<?>... attrs)
 693             throws IOException {
 694         return jrtfs.newFileChannel(this, options, attrs);
 695     }
 696 
 697     final void checkAccess(AccessMode... modes) throws IOException {
 698         if (modes.length == 0) {    // check if the path exists
 699             jrtfs.checkNode(this);  // no need to follow link. the "link" node
 700                                     // is built from real node under "/module"
 701         } else {
 702             boolean w = false;

 703             for (AccessMode mode : modes) {
 704                 switch (mode) {
 705                     case READ:
 706                         break;
 707                     case WRITE:
 708                         w = true;
 709                         break;
 710                     case EXECUTE:
 711                         throw new AccessDeniedException(toString());

 712                     default:
 713                         throw new UnsupportedOperationException();
 714                 }
 715             }
 716             jrtfs.checkNode(this);
 717             if (w && jrtfs.isReadOnly()) {





 718                 throw new AccessDeniedException(toString());
 719             }


 720         }
 721     }
 722 
 723     final boolean exists() {
 724         try {
 725             return jrtfs.exists(this);
 726         } catch (IOException x) {}

 727         return false;
 728     }
 729 
 730     final OutputStream newOutputStream(OpenOption... options) throws IOException {
 731         if (options.length == 0) {
 732             return jrtfs.newOutputStream(this, CREATE_NEW, WRITE);

 733         }
 734         return jrtfs.newOutputStream(this, options);
 735     }
 736 
 737     final void move(JrtPath target, CopyOption... options) throws IOException {

 738         if (this.jrtfs == target.jrtfs) {
 739             jrtfs.copyFile(true, this, target, options);


 740         } else {
 741             copyToTarget(target, options);
 742             delete();
 743         }
 744     }
 745 
 746     final void copy(JrtPath target, CopyOption... options) throws IOException {

 747         if (this.jrtfs == target.jrtfs) {
 748             jrtfs.copyFile(false, this, target, options);


 749         } else {
 750             copyToTarget(target, options);
 751         }
 752     }
 753 
 754     private void copyToTarget(JrtPath target, CopyOption... options)
 755             throws IOException {
 756         boolean replaceExisting = false;
 757         boolean copyAttrs = false;
 758         for (CopyOption opt : options) {
 759             if (opt == REPLACE_EXISTING) {
 760                 replaceExisting = true;
 761             } else if (opt == COPY_ATTRIBUTES) {
 762                 copyAttrs = true;
 763             }
 764         }
 765         // attributes of source file
 766         BasicFileAttributes jrtfas = getAttributes();
 767         // check if target exists
 768         boolean exists;
 769         if (replaceExisting) {
 770             try {
 771                 target.deleteIfExists();
 772                 exists = false;
 773             } catch (DirectoryNotEmptyException x) {
 774                 exists = true;
 775             }
 776         } else {
 777             exists = target.exists();
 778         }
 779         if (exists) {
 780             throw new FileAlreadyExistsException(target.toString());
 781         }

 782         if (jrtfas.isDirectory()) {
 783             // create directory or file
 784             target.createDirectory();
 785         } else {
 786             try (InputStream is = jrtfs.newInputStream(this);
 787                  OutputStream os = target.newOutputStream()) {
 788                 byte[] buf = new byte[8192];
 789                 int n;
 790                 while ((n = is.read(buf)) != -1) {
 791                     os.write(buf, 0, n);
 792                 }
 793             }
 794         }
 795         if (copyAttrs) {
 796             BasicFileAttributeView view =
 797                 Files.getFileAttributeView(target, BasicFileAttributeView.class);
 798             try {
 799                 view.setTimes(jrtfas.lastModifiedTime(),
 800                               jrtfas.lastAccessTime(),
 801                               jrtfas.creationTime());
 802             } catch (IOException x) {

 803                 try {
 804                     target.delete();  // rollback?
 805                 } catch (IOException ignore) {}

 806                 throw x;
 807             }
 808         }
 809     }
 810 }