1 /* 2 * Copyright (c) 2008, 2013, 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 26 package sun.nio.fs; 27 28 import java.nio.*; 29 import java.nio.file.*; 30 import java.nio.charset.*; 31 import java.io.*; 32 import java.net.URI; 33 import java.util.*; 34 import java.lang.ref.SoftReference; 35 36 import static sun.nio.fs.UnixNativeDispatcher.*; 37 import static sun.nio.fs.UnixConstants.*; 38 39 /** 40 * Solaris/Linux implementation of java.nio.file.Path 41 */ 42 43 class UnixPath implements Path { 44 private static ThreadLocal<SoftReference<CharsetEncoder>> encoder = 45 new ThreadLocal<SoftReference<CharsetEncoder>>(); 46 47 // FIXME - eliminate this reference to reduce space 48 private final UnixFileSystem fs; 49 50 // internal representation 51 private final byte[] path; 52 53 // String representation (created lazily) 54 private volatile String stringValue; 55 56 // cached hashcode (created lazily, no need to be volatile) 57 private int hash; 58 59 // array of offsets of elements in path (created lazily) 60 private volatile int[] offsets; 61 62 UnixPath(UnixFileSystem fs, byte[] path) { 63 this.fs = fs; 64 this.path = path; 65 } 66 67 UnixPath(UnixFileSystem fs, String input) { 68 // removes redundant slashes and checks for invalid characters 69 this(fs, encode(fs, normalizeAndCheck(input))); 70 } 71 72 // package-private 73 // removes redundant slashes and check input for invalid characters 74 static String normalizeAndCheck(String input) { 75 int n = input.length(); 76 char prevChar = 0; 77 for (int i=0; i < n; i++) { 78 char c = input.charAt(i); 79 if ((c == '/') && (prevChar == '/')) 80 return normalize(input, n, i - 1); 81 checkNotNul(input, c); 82 prevChar = c; 83 } 84 if (prevChar == '/') 85 return normalize(input, n, n - 1); 86 return input; 87 } 88 89 private static void checkNotNul(String input, char c) { 90 if (c == '\u0000') 91 throw new InvalidPathException(input, "Nul character not allowed"); 92 } 93 94 private static String normalize(String input, int len, int off) { 95 if (len == 0) 96 return input; 97 int n = len; 98 while ((n > 0) && (input.charAt(n - 1) == '/')) n--; 99 if (n == 0) 100 return "/"; 101 StringBuilder sb = new StringBuilder(input.length()); 102 if (off > 0) 103 sb.append(input.substring(0, off)); 104 char prevChar = 0; 105 for (int i=off; i < n; i++) { 106 char c = input.charAt(i); 107 if ((c == '/') && (prevChar == '/')) 108 continue; 109 checkNotNul(input, c); 110 sb.append(c); 111 prevChar = c; 112 } 113 return sb.toString(); 114 } 115 116 // encodes the given path-string into a sequence of bytes 117 private static byte[] encode(UnixFileSystem fs, String input) { 118 SoftReference<CharsetEncoder> ref = encoder.get(); 119 CharsetEncoder ce = (ref != null) ? ref.get() : null; 120 if (ce == null) { 121 ce = Util.jnuEncoding().newEncoder() 122 .onMalformedInput(CodingErrorAction.REPORT) 123 .onUnmappableCharacter(CodingErrorAction.REPORT); 124 encoder.set(new SoftReference<CharsetEncoder>(ce)); 125 } 126 127 char[] ca = fs.normalizeNativePath(input.toCharArray()); 128 129 // size output buffer for worse-case size 130 byte[] ba = new byte[(int)(ca.length * (double)ce.maxBytesPerChar())]; 131 132 // encode 133 ByteBuffer bb = ByteBuffer.wrap(ba); 134 CharBuffer cb = CharBuffer.wrap(ca); 135 ce.reset(); 136 CoderResult cr = ce.encode(cb, bb, true); 137 boolean error; 138 if (!cr.isUnderflow()) { 139 error = true; 140 } else { 141 cr = ce.flush(bb); 142 error = !cr.isUnderflow(); 143 } 144 if (error) { 145 throw new InvalidPathException(input, 146 "Malformed input or input contains unmappable characters"); 147 } 148 149 // trim result to actual length if required 150 int len = bb.position(); 151 if (len != ba.length) 152 ba = Arrays.copyOf(ba, len); 153 154 return ba; 155 } 156 157 // package-private 158 byte[] asByteArray() { 159 return path; 160 } 161 162 // use this path when making system/library calls 163 byte[] getByteArrayForSysCalls() { 164 // resolve against default directory if required (chdir allowed or 165 // file system default directory is not working directory) 166 if (getFileSystem().needToResolveAgainstDefaultDirectory()) { 167 return resolve(getFileSystem().defaultDirectory(), path); 168 } else { 169 if (!isEmpty()) { 170 return path; 171 } else { 172 // empty path case will access current directory 173 byte[] here = { '.' }; 174 return here; 175 } 176 } 177 } 178 179 // use this message when throwing exceptions 180 String getPathForExceptionMessage() { 181 return toString(); 182 } 183 184 // use this path for permission checks 185 String getPathForPermissionCheck() { 186 if (getFileSystem().needToResolveAgainstDefaultDirectory()) { 187 return Util.toString(getByteArrayForSysCalls()); 188 } else { 189 return toString(); 190 } 191 } 192 193 // Checks that the given file is a UnixPath 194 static UnixPath toUnixPath(Path obj) { 195 if (obj == null) 196 throw new NullPointerException(); 197 if (!(obj instanceof UnixPath)) 198 throw new ProviderMismatchException(); 199 return (UnixPath)obj; 200 } 201 202 // create offset list if not already created 203 private void initOffsets() { 204 if (offsets == null) { 205 int count, index; 206 207 // count names 208 count = 0; 209 index = 0; 210 if (isEmpty()) { 211 // empty path has one name 212 count = 1; 213 } else { 214 while (index < path.length) { 215 byte c = path[index++]; 216 if (c != '/') { 217 count++; 218 while (index < path.length && path[index] != '/') 219 index++; 220 } 221 } 222 } 223 224 // populate offsets 225 int[] result = new int[count]; 226 count = 0; 227 index = 0; 228 while (index < path.length) { 229 byte c = path[index]; 230 if (c == '/') { 231 index++; 232 } else { 233 result[count++] = index++; 234 while (index < path.length && path[index] != '/') 235 index++; 236 } 237 } 238 synchronized (this) { 239 if (offsets == null) 240 offsets = result; 241 } 242 } 243 } 244 245 // returns {@code true} if this path is an empty path 246 private boolean isEmpty() { 247 return path.length == 0; 248 } 249 250 // returns an empty path 251 private UnixPath emptyPath() { 252 return new UnixPath(getFileSystem(), new byte[0]); 253 } 254 255 @Override 256 public UnixFileSystem getFileSystem() { 257 return fs; 258 } 259 260 @Override 261 public UnixPath getRoot() { 262 if (path.length > 0 && path[0] == '/') { 263 return getFileSystem().rootDirectory(); 264 } else { 265 return null; 266 } 267 } 268 269 @Override 270 public UnixPath getFileName() { 271 initOffsets(); 272 273 int count = offsets.length; 274 275 // no elements so no name 276 if (count == 0) 277 return null; 278 279 // one name element and no root component 280 if (count == 1 && path.length > 0 && path[0] != '/') 281 return this; 282 283 int lastOffset = offsets[count-1]; 284 int len = path.length - lastOffset; 285 byte[] result = new byte[len]; 286 System.arraycopy(path, lastOffset, result, 0, len); 287 return new UnixPath(getFileSystem(), result); 288 } 289 290 @Override 291 public UnixPath getParent() { 292 initOffsets(); 293 294 int count = offsets.length; 295 if (count == 0) { 296 // no elements so no parent 297 return null; 298 } 299 int len = offsets[count-1] - 1; 300 if (len <= 0) { 301 // parent is root only (may be null) 302 return getRoot(); 303 } 304 byte[] result = new byte[len]; 305 System.arraycopy(path, 0, result, 0, len); 306 return new UnixPath(getFileSystem(), result); 307 } 308 309 @Override 310 public int getNameCount() { 311 initOffsets(); 312 return offsets.length; 313 } 314 315 @Override 316 public UnixPath getName(int index) { 317 initOffsets(); 318 if (index < 0) 319 throw new IllegalArgumentException(); 320 if (index >= offsets.length) 321 throw new IllegalArgumentException(); 322 323 int begin = offsets[index]; 324 int len; 325 if (index == (offsets.length-1)) { 326 len = path.length - begin; 327 } else { 328 len = offsets[index+1] - begin - 1; 329 } 330 331 // construct result 332 byte[] result = new byte[len]; 333 System.arraycopy(path, begin, result, 0, len); 334 return new UnixPath(getFileSystem(), result); 335 } 336 337 @Override 338 public UnixPath subpath(int beginIndex, int endIndex) { 339 initOffsets(); 340 341 if (beginIndex < 0) 342 throw new IllegalArgumentException(); 343 if (beginIndex >= offsets.length) 344 throw new IllegalArgumentException(); 345 if (endIndex > offsets.length) 346 throw new IllegalArgumentException(); 347 if (beginIndex >= endIndex) { 348 throw new IllegalArgumentException(); 349 } 350 351 // starting offset and length 352 int begin = offsets[beginIndex]; 353 int len; 354 if (endIndex == offsets.length) { 355 len = path.length - begin; 356 } else { 357 len = offsets[endIndex] - begin - 1; 358 } 359 360 // construct result 361 byte[] result = new byte[len]; 362 System.arraycopy(path, begin, result, 0, len); 363 return new UnixPath(getFileSystem(), result); 364 } 365 366 @Override 367 public boolean isAbsolute() { 368 return (path.length > 0 && path[0] == '/'); 369 } 370 371 // Resolve child against given base 372 private static byte[] resolve(byte[] base, byte[] child) { 373 int baseLength = base.length; 374 int childLength = child.length; 375 if (childLength == 0) 376 return base; 377 if (baseLength == 0 || child[0] == '/') 378 return child; 379 byte[] result; 380 if (baseLength == 1 && base[0] == '/') { 381 result = new byte[childLength + 1]; 382 result[0] = '/'; 383 System.arraycopy(child, 0, result, 1, childLength); 384 } else { 385 result = new byte[baseLength + 1 + childLength]; 386 System.arraycopy(base, 0, result, 0, baseLength); 387 result[base.length] = '/'; 388 System.arraycopy(child, 0, result, baseLength+1, childLength); 389 } 390 return result; 391 } 392 393 @Override 394 public UnixPath resolve(Path obj) { 395 byte[] other = toUnixPath(obj).path; 396 if (other.length > 0 && other[0] == '/') 397 return ((UnixPath)obj); 398 byte[] result = resolve(path, other); 399 return new UnixPath(getFileSystem(), result); 400 } 401 402 UnixPath resolve(byte[] other) { 403 return resolve(new UnixPath(getFileSystem(), other)); 404 } 405 406 @Override 407 public UnixPath relativize(Path obj) { 408 UnixPath other = toUnixPath(obj); 409 if (other.equals(this)) 410 return emptyPath(); 411 412 // can only relativize paths of the same type 413 if (this.isAbsolute() != other.isAbsolute()) 414 throw new IllegalArgumentException("'other' is different type of Path"); 415 416 // this path is the empty path 417 if (this.isEmpty()) 418 return other; 419 420 int bn = this.getNameCount(); 421 int cn = other.getNameCount(); 422 423 // skip matching names 424 int n = (bn > cn) ? cn : bn; 425 int i = 0; 426 while (i < n) { 427 if (!this.getName(i).equals(other.getName(i))) 428 break; 429 i++; 430 } 431 432 int dotdots = bn - i; 433 if (i < cn) { 434 // remaining name components in other 435 UnixPath remainder = other.subpath(i, cn); 436 if (dotdots == 0) 437 return remainder; 438 439 // other is the empty path 440 boolean isOtherEmpty = other.isEmpty(); 441 442 // result is a "../" for each remaining name in base 443 // followed by the remaining names in other. If the remainder is 444 // the empty path then we don't add the final trailing slash. 445 int len = dotdots*3 + remainder.path.length; 446 if (isOtherEmpty) { 447 assert remainder.isEmpty(); 448 len--; 449 } 450 byte[] result = new byte[len]; 451 int pos = 0; 452 while (dotdots > 0) { 453 result[pos++] = (byte)'.'; 454 result[pos++] = (byte)'.'; 455 if (isOtherEmpty) { 456 if (dotdots > 1) result[pos++] = (byte)'/'; 457 } else { 458 result[pos++] = (byte)'/'; 459 } 460 dotdots--; 461 } 462 System.arraycopy(remainder.path, 0, result, pos, remainder.path.length); 463 return new UnixPath(getFileSystem(), result); 464 } else { 465 // no remaining names in other so result is simply a sequence of ".." 466 byte[] result = new byte[dotdots*3 - 1]; 467 int pos = 0; 468 while (dotdots > 0) { 469 result[pos++] = (byte)'.'; 470 result[pos++] = (byte)'.'; 471 // no tailing slash at the end 472 if (dotdots > 1) 473 result[pos++] = (byte)'/'; 474 dotdots--; 475 } 476 return new UnixPath(getFileSystem(), result); 477 } 478 } 479 480 @Override 481 public Path normalize() { 482 final int count = getNameCount(); 483 if (count == 0 || isEmpty()) 484 return this; 485 486 boolean[] ignore = new boolean[count]; // true => ignore name 487 int[] size = new int[count]; // length of name 488 int remaining = count; // number of names remaining 489 boolean hasDotDot = false; // has at least one .. 490 boolean isAbsolute = isAbsolute(); 491 492 // first pass: 493 // 1. compute length of names 494 // 2. mark all occurrences of "." to ignore 495 // 3. and look for any occurrences of ".." 496 for (int i=0; i<count; i++) { 497 int begin = offsets[i]; 498 int len; 499 if (i == (offsets.length-1)) { 500 len = path.length - begin; 501 } else { 502 len = offsets[i+1] - begin - 1; 503 } 504 size[i] = len; 505 506 if (path[begin] == '.') { 507 if (len == 1) { 508 ignore[i] = true; // ignore "." 509 remaining--; 510 } 511 else { 512 if (path[begin+1] == '.') // ".." found 513 hasDotDot = true; 514 } 515 } 516 } 517 518 // multiple passes to eliminate all occurrences of name/.. 519 if (hasDotDot) { 520 int prevRemaining; 521 do { 522 prevRemaining = remaining; 523 int prevName = -1; 524 for (int i=0; i<count; i++) { 525 if (ignore[i]) 526 continue; 527 528 // not a ".." 529 if (size[i] != 2) { 530 prevName = i; 531 continue; 532 } 533 534 int begin = offsets[i]; 535 if (path[begin] != '.' || path[begin+1] != '.') { 536 prevName = i; 537 continue; 538 } 539 540 // ".." found 541 if (prevName >= 0) { 542 // name/<ignored>/.. found so mark name and ".." to be 543 // ignored 544 ignore[prevName] = true; 545 ignore[i] = true; 546 remaining = remaining - 2; 547 prevName = -1; 548 } else { 549 // Case: /<ignored>/.. so mark ".." as ignored 550 if (isAbsolute) { 551 boolean hasPrevious = false; 552 for (int j=0; j<i; j++) { 553 if (!ignore[j]) { 554 hasPrevious = true; 555 break; 556 } 557 } 558 if (!hasPrevious) { 559 // all proceeding names are ignored 560 ignore[i] = true; 561 remaining--; 562 } 563 } 564 } 565 } 566 } while (prevRemaining > remaining); 567 } 568 569 // no redundant names 570 if (remaining == count) 571 return this; 572 573 // corner case - all names removed 574 if (remaining == 0) { 575 return isAbsolute ? getFileSystem().rootDirectory() : emptyPath(); 576 } 577 578 // compute length of result 579 int len = remaining - 1; 580 if (isAbsolute) 581 len++; 582 583 for (int i=0; i<count; i++) { 584 if (!ignore[i]) 585 len += size[i]; 586 } 587 byte[] result = new byte[len]; 588 589 // copy names into result 590 int pos = 0; 591 if (isAbsolute) 592 result[pos++] = '/'; 593 for (int i=0; i<count; i++) { 594 if (!ignore[i]) { 595 System.arraycopy(path, offsets[i], result, pos, size[i]); 596 pos += size[i]; 597 if (--remaining > 0) { 598 result[pos++] = '/'; 599 } 600 } 601 } 602 return new UnixPath(getFileSystem(), result); 603 } 604 605 @Override 606 public boolean startsWith(Path other) { 607 if (!(Objects.requireNonNull(other) instanceof UnixPath)) 608 return false; 609 UnixPath that = (UnixPath)other; 610 611 // other path is longer 612 if (that.path.length > path.length) 613 return false; 614 615 int thisOffsetCount = getNameCount(); 616 int thatOffsetCount = that.getNameCount(); 617 618 // other path has no name elements 619 if (thatOffsetCount == 0 && this.isAbsolute()) { 620 return that.isEmpty() ? false : true; 621 } 622 623 // given path has more elements that this path 624 if (thatOffsetCount > thisOffsetCount) 625 return false; 626 627 // same number of elements so must be exact match 628 if ((thatOffsetCount == thisOffsetCount) && 629 (path.length != that.path.length)) { 630 return false; 631 } 632 633 // check offsets of elements match 634 for (int i=0; i<thatOffsetCount; i++) { 635 Integer o1 = offsets[i]; 636 Integer o2 = that.offsets[i]; 637 if (!o1.equals(o2)) 638 return false; 639 } 640 641 // offsets match so need to compare bytes 642 int i=0; 643 while (i < that.path.length) { 644 if (this.path[i] != that.path[i]) 645 return false; 646 i++; 647 } 648 649 // final check that match is on name boundary 650 if (i < path.length && this.path[i] != '/') 651 return false; 652 653 return true; 654 } 655 656 @Override 657 public boolean endsWith(Path other) { 658 if (!(Objects.requireNonNull(other) instanceof UnixPath)) 659 return false; 660 UnixPath that = (UnixPath)other; 661 662 int thisLen = path.length; 663 int thatLen = that.path.length; 664 665 // other path is longer 666 if (thatLen > thisLen) 667 return false; 668 669 // other path is the empty path 670 if (thisLen > 0 && thatLen == 0) 671 return false; 672 673 // other path is absolute so this path must be absolute 674 if (that.isAbsolute() && !this.isAbsolute()) 675 return false; 676 677 int thisOffsetCount = getNameCount(); 678 int thatOffsetCount = that.getNameCount(); 679 680 // given path has more elements that this path 681 if (thatOffsetCount > thisOffsetCount) { 682 return false; 683 } else { 684 // same number of elements 685 if (thatOffsetCount == thisOffsetCount) { 686 if (thisOffsetCount == 0) 687 return true; 688 int expectedLen = thisLen; 689 if (this.isAbsolute() && !that.isAbsolute()) 690 expectedLen--; 691 if (thatLen != expectedLen) 692 return false; 693 } else { 694 // this path has more elements so given path must be relative 695 if (that.isAbsolute()) 696 return false; 697 } 698 } 699 700 // compare bytes 701 int thisPos = offsets[thisOffsetCount - thatOffsetCount]; 702 int thatPos = that.offsets[0]; 703 if ((thatLen - thatPos) != (thisLen - thisPos)) 704 return false; 705 while (thatPos < thatLen) { 706 if (this.path[thisPos++] != that.path[thatPos++]) 707 return false; 708 } 709 710 return true; 711 } 712 713 @Override 714 public int compareTo(Path other) { 715 int len1 = path.length; 716 int len2 = ((UnixPath) other).path.length; 717 718 int n = Math.min(len1, len2); 719 byte v1[] = path; 720 byte v2[] = ((UnixPath) other).path; 721 722 int k = 0; 723 while (k < n) { 724 int c1 = v1[k] & 0xff; 725 int c2 = v2[k] & 0xff; 726 if (c1 != c2) { 727 return c1 - c2; 728 } 729 k++; 730 } 731 return len1 - len2; 732 } 733 734 @Override 735 public boolean equals(Object ob) { 736 if ((ob != null) && (ob instanceof UnixPath)) { 737 return compareTo((Path)ob) == 0; 738 } 739 return false; 740 } 741 742 @Override 743 public int hashCode() { 744 // OK if two or more threads compute hash 745 int h = hash; 746 if (h == 0) { 747 for (int i = 0; i< path.length; i++) { 748 h = 31*h + (path[i] & 0xff); 749 } 750 hash = h; 751 } 752 return h; 753 } 754 755 @Override 756 public String toString() { 757 // OK if two or more threads create a String 758 if (stringValue == null) { 759 stringValue = fs.normalizeJavaPath(Util.toString(path)); // platform encoding 760 } 761 return stringValue; 762 } 763 764 // -- file operations -- 765 766 // package-private 767 int openForAttributeAccess(boolean followLinks) throws IOException { 768 int flags = O_RDONLY; 769 if (!followLinks) { 770 if (O_NOFOLLOW == 0) 771 throw new IOException("NOFOLLOW_LINKS is not supported on this platform"); 772 flags |= O_NOFOLLOW; 773 } 774 try { 775 return open(this, flags, 0); 776 } catch (UnixException x) { 777 // HACK: EINVAL instead of ELOOP on Solaris 10 prior to u4 (see 6460380) 778 if (getFileSystem().isSolaris() && x.errno() == EINVAL) 779 x.setError(ELOOP); 780 781 if (x.errno() == ELOOP) 782 throw new FileSystemException(getPathForExceptionMessage(), null, 783 x.getMessage() + " or unable to access attributes of symbolic link"); 784 785 x.rethrowAsIOException(this); 786 return -1; // keep compile happy 787 } 788 } 789 790 void checkRead() { 791 SecurityManager sm = System.getSecurityManager(); 792 if (sm != null) 793 sm.checkRead(getPathForPermissionCheck()); 794 } 795 796 void checkWrite() { 797 SecurityManager sm = System.getSecurityManager(); 798 if (sm != null) 799 sm.checkWrite(getPathForPermissionCheck()); 800 } 801 802 void checkDelete() { 803 SecurityManager sm = System.getSecurityManager(); 804 if (sm != null) 805 sm.checkDelete(getPathForPermissionCheck()); 806 } 807 808 @Override 809 public UnixPath toAbsolutePath() { 810 if (isAbsolute()) { 811 return this; 812 } 813 // The path is relative so need to resolve against default directory, 814 // taking care not to reveal the user.dir 815 SecurityManager sm = System.getSecurityManager(); 816 if (sm != null) { 817 sm.checkPropertyAccess("user.dir"); 818 } 819 return new UnixPath(getFileSystem(), 820 resolve(getFileSystem().defaultDirectory(), path)); 821 } 822 823 @Override 824 public Path toRealPath(LinkOption... options) throws IOException { 825 checkRead(); 826 827 UnixPath absolute = toAbsolutePath(); 828 829 // if resolving links then use realpath 830 if (Util.followLinks(options)) { 831 try { 832 byte[] rp = realpath(absolute); 833 return new UnixPath(getFileSystem(), rp); 834 } catch (UnixException x) { 835 x.rethrowAsIOException(this); 836 } 837 } 838 839 // if not resolving links then eliminate "." and also ".." 840 // where the previous element is not a link. 841 UnixPath result = fs.rootDirectory(); 842 for (int i=0; i<absolute.getNameCount(); i++) { 843 UnixPath element = absolute.getName(i); 844 845 // eliminate "." 846 if ((element.asByteArray().length == 1) && (element.asByteArray()[0] == '.')) 847 continue; 848 849 // cannot eliminate ".." if previous element is a link 850 if ((element.asByteArray().length == 2) && (element.asByteArray()[0] == '.') && 851 (element.asByteArray()[1] == '.')) 852 { 853 UnixFileAttributes attrs = null; 854 try { 855 attrs = UnixFileAttributes.get(result, false); 856 } catch (UnixException x) { 857 x.rethrowAsIOException(result); 858 } 859 if (!attrs.isSymbolicLink()) { 860 result = result.getParent(); 861 if (result == null) { 862 result = fs.rootDirectory(); 863 } 864 continue; 865 } 866 } 867 result = result.resolve(element); 868 } 869 870 // check file exists (without following links) 871 try { 872 UnixFileAttributes.get(result, false); 873 } catch (UnixException x) { 874 x.rethrowAsIOException(result); 875 } 876 return result; 877 } 878 879 @Override 880 public URI toUri() { 881 return UnixUriUtils.toUri(this); 882 } 883 884 @Override 885 public WatchKey register(WatchService watcher, 886 WatchEvent.Kind<?>[] events, 887 WatchEvent.Modifier... modifiers) 888 throws IOException 889 { 890 if (watcher == null) 891 throw new NullPointerException(); 892 if (!(watcher instanceof AbstractWatchService)) 893 throw new ProviderMismatchException(); 894 checkRead(); 895 return ((AbstractWatchService)watcher).register(this, events, modifiers); 896 } 897 }