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