1 /*
   2  * Copyright (c) 2008, 2016, 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.file.*;
  29 import java.nio.file.attribute.*;
  30 import java.io.*;
  31 import java.net.URI;
  32 import java.util.*;
  33 import java.lang.ref.WeakReference;
  34 
  35 import static sun.nio.fs.WindowsNativeDispatcher.*;
  36 import static sun.nio.fs.WindowsConstants.*;
  37 
  38 /**
  39  * Windows implementation of Path
  40  */
  41 
  42 class WindowsPath implements Path {
  43 
  44     // The maximum path that does not require long path prefix. On Windows
  45     // the maximum path is 260 minus 1 (NUL) but for directories it is 260
  46     // minus 12 minus 1 (to allow for the creation of a 8.3 file in the
  47     // directory).
  48     private static final int MAX_PATH = 247;
  49 
  50     // Maximum extended-length path
  51     private static final int MAX_LONG_PATH = 32000;
  52 
  53     // FIXME - eliminate this reference to reduce space
  54     private final WindowsFileSystem fs;
  55 
  56     // path type
  57     private final WindowsPathType type;
  58     // root component (may be empty)
  59     private final String root;
  60     // normalized path
  61     private final String path;
  62 
  63     // the path to use in Win32 calls. This differs from path for relative
  64     // paths and has a long path prefix for all paths longer than MAX_PATH.
  65     private volatile WeakReference<String> pathForWin32Calls;
  66 
  67     // offsets into name components (computed lazily)
  68     private volatile Integer[] offsets;
  69 
  70     // computed hash code (computed lazily, no need to be volatile)
  71     private int hash;
  72 
  73 
  74     /**
  75      * Initializes a new instance of this class.
  76      */
  77     private WindowsPath(WindowsFileSystem fs,
  78                         WindowsPathType type,
  79                         String root,
  80                         String path)
  81     {
  82         this.fs = fs;
  83         this.type = type;
  84         this.root = root;
  85         this.path = path;
  86     }
  87 
  88     /**
  89      * Creates a Path by parsing the given path.
  90      */
  91     static WindowsPath parse(WindowsFileSystem fs, String path) {
  92         WindowsPathParser.Result result = WindowsPathParser.parse(path);
  93         return new WindowsPath(fs, result.type(), result.root(), result.path());
  94     }
  95 
  96     /**
  97      * Creates a Path from a given path that is known to be normalized.
  98      */
  99     static WindowsPath createFromNormalizedPath(WindowsFileSystem fs,
 100                                                 String path,
 101                                                 BasicFileAttributes attrs)
 102     {
 103         try {
 104             WindowsPathParser.Result result =
 105                 WindowsPathParser.parseNormalizedPath(path);
 106             if (attrs == null) {
 107                 return new WindowsPath(fs,
 108                                        result.type(),
 109                                        result.root(),
 110                                        result.path());
 111             } else {
 112                 return new WindowsPathWithAttributes(fs,
 113                                                      result.type(),
 114                                                      result.root(),
 115                                                      result.path(),
 116                                                      attrs);
 117             }
 118         } catch (InvalidPathException x) {
 119             throw new AssertionError(x.getMessage());
 120         }
 121     }
 122 
 123     /**
 124      * Creates a WindowsPath from a given path that is known to be normalized.
 125      */
 126     static WindowsPath createFromNormalizedPath(WindowsFileSystem fs,
 127                                                 String path)
 128     {
 129         return createFromNormalizedPath(fs, path, null);
 130     }
 131 
 132     /**
 133      * Special implementation with attached/cached attributes (used to quicken
 134      * file tree traversal)
 135      */
 136     private static class WindowsPathWithAttributes
 137         extends WindowsPath implements BasicFileAttributesHolder
 138     {
 139         final WeakReference<BasicFileAttributes> ref;
 140 
 141         WindowsPathWithAttributes(WindowsFileSystem fs,
 142                                   WindowsPathType type,
 143                                   String root,
 144                                   String path,
 145                                   BasicFileAttributes attrs)
 146         {
 147             super(fs, type, root, path);
 148             ref = new WeakReference<BasicFileAttributes>(attrs);
 149         }
 150 
 151         @Override
 152         public BasicFileAttributes get() {
 153             return ref.get();
 154         }
 155 
 156         @Override
 157         public void invalidate() {
 158             ref.clear();
 159         }
 160 
 161         // no need to override equals/hashCode.
 162     }
 163 
 164     // use this message when throwing exceptions
 165     String getPathForExceptionMessage() {
 166         return path;
 167     }
 168 
 169     // use this path for permission checks
 170     String getPathForPermissionCheck() {
 171         return path;
 172     }
 173 
 174     // use this path for Win32 calls
 175     // This method will prefix long paths with \\?\ or \\?\UNC as required.
 176     String getPathForWin32Calls() throws WindowsException {
 177         // short absolute paths can be used directly
 178         if (isAbsolute() && path.length() <= MAX_PATH)
 179             return path;
 180 
 181         // return cached values if available
 182         WeakReference<String> ref = pathForWin32Calls;
 183         String resolved = (ref != null) ? ref.get() : null;
 184         if (resolved != null) {
 185             // Win32 path already available
 186             return resolved;
 187         }
 188 
 189         // resolve against default directory
 190         resolved = getAbsolutePath();
 191 
 192         // Long paths need to have "." and ".." removed and be prefixed with
 193         // "\\?\". Note that it is okay to remove ".." even when it follows
 194         // a link - for example, it is okay for foo/link/../bar to be changed
 195         // to foo/bar. The reason is that Win32 APIs to access foo/link/../bar
 196         // will access foo/bar anyway (which differs to Unix systems)
 197         if (resolved.length() > MAX_PATH) {
 198             if (resolved.length() > MAX_LONG_PATH) {
 199                 throw new WindowsException("Cannot access file with path exceeding "
 200                     + MAX_LONG_PATH + " characters");
 201             }
 202             resolved = addPrefixIfNeeded(GetFullPathName(resolved));
 203         }
 204 
 205         // cache the resolved path (except drive relative paths as the working
 206         // directory on removal media devices can change during the lifetime
 207         // of the VM)
 208         if (type != WindowsPathType.DRIVE_RELATIVE) {
 209             synchronized (path) {
 210                 pathForWin32Calls = new WeakReference<String>(resolved);
 211             }
 212         }
 213         return resolved;
 214     }
 215 
 216     // return this path resolved against the file system's default directory
 217     private String getAbsolutePath() throws WindowsException {
 218         if (isAbsolute())
 219             return path;
 220 
 221         // Relative path ("foo" for example)
 222         if (type == WindowsPathType.RELATIVE) {
 223             String defaultDirectory = getFileSystem().defaultDirectory();
 224             if (isEmpty())
 225                 return defaultDirectory;
 226             if (defaultDirectory.endsWith("\\")) {
 227                 return defaultDirectory + path;
 228             } else {
 229                 StringBuilder sb =
 230                     new StringBuilder(defaultDirectory.length() + path.length() + 1);
 231                 return sb.append(defaultDirectory).append('\\').append(path).toString();
 232             }
 233         }
 234 
 235         // Directory relative path ("\foo" for example)
 236         if (type == WindowsPathType.DIRECTORY_RELATIVE) {
 237             String defaultRoot = getFileSystem().defaultRoot();
 238             return defaultRoot + path.substring(1);
 239         }
 240 
 241         // Drive relative path ("C:foo" for example).
 242         if (isSameDrive(root, getFileSystem().defaultRoot())) {
 243             // relative to default directory
 244             String remaining = path.substring(root.length());
 245             String defaultDirectory = getFileSystem().defaultDirectory();
 246             if (remaining.length() == 0) {
 247                 return defaultDirectory;
 248             } else if (defaultDirectory.endsWith("\\")) {
 249                  return defaultDirectory + remaining;
 250             } else {
 251                 return defaultDirectory + "\\" + remaining;
 252             }
 253         } else {
 254             // relative to some other drive
 255             String wd;
 256             try {
 257                 int dt = GetDriveType(root + "\\");
 258                 if (dt == DRIVE_UNKNOWN || dt == DRIVE_NO_ROOT_DIR)
 259                     throw new WindowsException("");
 260                 wd = GetFullPathName(root + ".");
 261             } catch (WindowsException x) {
 262                 throw new WindowsException("Unable to get working directory of drive '" +
 263                     Character.toUpperCase(root.charAt(0)) + "'");
 264             }
 265             String result = wd;
 266             if (wd.endsWith("\\")) {
 267                 result += path.substring(root.length());
 268             } else {
 269                 if (path.length() > root.length())
 270                     result += "\\" + path.substring(root.length());
 271             }
 272             return result;
 273         }
 274     }
 275 
 276     // returns true if same drive letter
 277     private static boolean isSameDrive(String root1, String root2) {
 278         return Character.toUpperCase(root1.charAt(0)) ==
 279                Character.toUpperCase(root2.charAt(0));
 280     }
 281 
 282     // Add long path prefix to path if required
 283     static String addPrefixIfNeeded(String path) {
 284         if (path.length() > MAX_PATH) {
 285             if (path.startsWith("\\\\")) {
 286                 path = "\\\\?\\UNC" + path.substring(1, path.length());
 287             } else {
 288                 path = "\\\\?\\" + path;
 289             }
 290         }
 291         return path;
 292     }
 293 
 294     @Override
 295     public WindowsFileSystem getFileSystem() {
 296         return fs;
 297     }
 298 
 299     // -- Path operations --
 300 
 301     private boolean isEmpty() {
 302         return path.length() == 0;
 303     }
 304 
 305     private WindowsPath emptyPath() {
 306         return new WindowsPath(getFileSystem(), WindowsPathType.RELATIVE, "", "");
 307     }
 308 
 309     @Override
 310     public Path getFileName() {
 311         int len = path.length();
 312         // represents empty path
 313         if (len == 0)
 314             return this;
 315         // represents root component only
 316         if (root.length() == len)
 317             return null;
 318         int off = path.lastIndexOf('\\');
 319         if (off < root.length())
 320             off = root.length();
 321         else
 322             off++;
 323         return new WindowsPath(getFileSystem(), WindowsPathType.RELATIVE, "", path.substring(off));
 324     }
 325 
 326     @Override
 327     public WindowsPath getParent() {
 328         // represents root component only
 329         if (root.length() == path.length())
 330             return null;
 331         int off = path.lastIndexOf('\\');
 332         if (off < root.length())
 333             return getRoot();
 334         else
 335             return new WindowsPath(getFileSystem(),
 336                                    type,
 337                                    root,
 338                                    path.substring(0, off));
 339     }
 340 
 341     @Override
 342     public WindowsPath getRoot() {
 343         if (root.length() == 0)
 344             return null;
 345         return new WindowsPath(getFileSystem(), type, root, root);
 346     }
 347 
 348     // package-private
 349     WindowsPathType type() {
 350         return type;
 351     }
 352 
 353     // package-private
 354     boolean isUnc() {
 355         return type == WindowsPathType.UNC;
 356     }
 357 
 358     boolean needsSlashWhenResolving() {
 359         if (path.endsWith("\\"))
 360             return false;
 361         return path.length() > root.length();
 362     }
 363 
 364     @Override
 365     public boolean isAbsolute() {
 366         return type == WindowsPathType.ABSOLUTE || type == WindowsPathType.UNC;
 367     }
 368 
 369     static WindowsPath toWindowsPath(Path path) {
 370         if (path == null)
 371             throw new NullPointerException();
 372         if (!(path instanceof WindowsPath)) {
 373             throw new ProviderMismatchException();
 374         }
 375         return (WindowsPath)path;
 376     }
 377 
 378     // return true if this path has "." or ".."
 379     private boolean hasDotOrDotDot() {
 380         int n = getNameCount();
 381         for (int i=0; i<n; i++) {
 382             String name = elementAsString(i);
 383             if (name.length() == 1 && name.charAt(0) == '.')
 384                 return true;
 385             if (name.length() == 2
 386                     && name.charAt(0) == '.' && name.charAt(1) == '.')
 387                 return true;
 388         }
 389         return false;
 390     }
 391 
 392     @Override
 393     public WindowsPath relativize(Path obj) {
 394         WindowsPath child = toWindowsPath(obj);
 395         if (this.equals(child))
 396             return emptyPath();
 397 
 398         // can only relativize paths of the same type
 399         if (this.type != child.type)
 400             throw new IllegalArgumentException("'other' is different type of Path");
 401 
 402         // can only relativize paths if root component matches
 403         if (!this.root.equalsIgnoreCase(child.root))
 404             throw new IllegalArgumentException("'other' has different root");
 405 
 406         // this path is the empty path
 407         if (this.isEmpty())
 408             return child;
 409 
 410 
 411         WindowsPath base = this;
 412         if (base.hasDotOrDotDot() || child.hasDotOrDotDot()) {
 413             base = base.normalize();
 414             child = child.normalize();
 415         }
 416 
 417         int baseCount = base.getNameCount();
 418         int childCount = child.getNameCount();
 419 
 420         // skip matching names
 421         int n = Math.min(baseCount, childCount);
 422         int i = 0;
 423         while (i < n) {
 424             if (!base.getName(i).equals(child.getName(i)))
 425                 break;
 426             i++;
 427         }
 428 
 429         // remaining elements in child
 430         WindowsPath childRemaining;
 431         boolean isChildEmpty;
 432         if (i == childCount) {
 433             childRemaining = emptyPath();
 434             isChildEmpty = true;
 435         } else {
 436             childRemaining = child.subpath(i, childCount);
 437             isChildEmpty = childRemaining.isEmpty();
 438         }
 439 
 440         // matched all of base
 441         if (i == baseCount) {
 442             return childRemaining;
 443         }
 444 
 445         // the remainder of base cannot contain ".."
 446         WindowsPath baseRemaining = base.subpath(i, baseCount);
 447         if (baseRemaining.hasDotOrDotDot()) {
 448             throw new IllegalArgumentException("Unable to compute relative "
 449                     + " path from " + this + " to " + obj);
 450         }
 451         if (baseRemaining.isEmpty())
 452             return childRemaining;
 453 
 454         // number of ".." needed
 455         int dotdots = baseRemaining.getNameCount();
 456         if (dotdots == 0) {
 457             return childRemaining;
 458         }
 459 
 460         StringBuilder result = new StringBuilder();
 461         for (int j=0; j<dotdots; j++) {
 462             result.append("..\\");
 463         }
 464 
 465         // append remaining names in child
 466         if (!isChildEmpty) {
 467             for (int j=0; j<childRemaining.getNameCount(); j++) {
 468                 result.append(childRemaining.getName(j).toString());
 469                 result.append("\\");
 470             }
 471         }
 472 
 473         // drop trailing slash
 474         result.setLength(result.length()-1);
 475         return createFromNormalizedPath(getFileSystem(), result.toString());
 476     }
 477 
 478     @Override
 479     public WindowsPath normalize() {
 480         final int count = getNameCount();
 481         if (count == 0 || isEmpty())
 482             return this;
 483 
 484         boolean[] ignore = new boolean[count];      // true => ignore name
 485         int remaining = count;                      // number of names remaining
 486 
 487         // multiple passes to eliminate all occurrences of "." and "name/.."
 488         int prevRemaining;
 489         do {
 490             prevRemaining = remaining;
 491             int prevName = -1;
 492             for (int i=0; i<count; i++) {
 493                 if (ignore[i])
 494                     continue;
 495 
 496                 String name = elementAsString(i);
 497 
 498                 // not "." or ".."
 499                 if (name.length() > 2) {
 500                     prevName = i;
 501                     continue;
 502                 }
 503 
 504                 // "." or something else
 505                 if (name.length() == 1) {
 506                     // ignore "."
 507                     if (name.charAt(0) == '.') {
 508                         ignore[i] = true;
 509                         remaining--;
 510                     } else {
 511                         prevName = i;
 512                     }
 513                     continue;
 514                 }
 515 
 516                 // not ".."
 517                 if (name.charAt(0) != '.' || name.charAt(1) != '.') {
 518                     prevName = i;
 519                     continue;
 520                 }
 521 
 522                 // ".." found
 523                 if (prevName >= 0) {
 524                     // name/<ignored>/.. found so mark name and ".." to be
 525                     // ignored
 526                     ignore[prevName] = true;
 527                     ignore[i] = true;
 528                     remaining = remaining - 2;
 529                     prevName = -1;
 530                 } else {
 531                     // Cases:
 532                     //    C:\<ignored>\..
 533                     //    \\server\\share\<ignored>\..
 534                     //    \<ignored>..
 535                     if (isAbsolute() || type == WindowsPathType.DIRECTORY_RELATIVE) {
 536                         boolean hasPrevious = false;
 537                         for (int j=0; j<i; j++) {
 538                             if (!ignore[j]) {
 539                                 hasPrevious = true;
 540                                 break;
 541                             }
 542                         }
 543                         if (!hasPrevious) {
 544                             // all proceeding names are ignored
 545                             ignore[i] = true;
 546                             remaining--;
 547                         }
 548                     }
 549                 }
 550             }
 551         } while (prevRemaining > remaining);
 552 
 553         // no redundant names
 554         if (remaining == count)
 555             return this;
 556 
 557         // corner case - all names removed
 558         if (remaining == 0) {
 559             return (root.length() == 0) ? emptyPath() : getRoot();
 560         }
 561 
 562         // re-constitute the path from the remaining names.
 563         StringBuilder result = new StringBuilder();
 564         if (root != null)
 565             result.append(root);
 566         for (int i=0; i<count; i++) {
 567             if (!ignore[i]) {
 568                 result.append(getName(i));
 569                 result.append("\\");
 570             }
 571         }
 572 
 573         // drop trailing slash in result
 574         result.setLength(result.length()-1);
 575         return createFromNormalizedPath(getFileSystem(), result.toString());
 576     }
 577 
 578     @Override
 579     public WindowsPath resolve(Path obj) {
 580         WindowsPath other = toWindowsPath(obj);
 581         if (other.isEmpty())
 582             return this;
 583         if (other.isAbsolute())
 584             return other;
 585 
 586         switch (other.type) {
 587             case RELATIVE: {
 588                 String result;
 589                 if (path.endsWith("\\") || (root.length() == path.length())) {
 590                     result = path + other.path;
 591                 } else {
 592                     result = path + "\\" + other.path;
 593                 }
 594                 return new WindowsPath(getFileSystem(), type, root, result);
 595             }
 596 
 597             case DIRECTORY_RELATIVE: {
 598                 String result;
 599                 if (root.endsWith("\\")) {
 600                     result = root + other.path.substring(1);
 601                 } else {
 602                     result = root + other.path;
 603                 }
 604                 return createFromNormalizedPath(getFileSystem(), result);
 605             }
 606 
 607             case DRIVE_RELATIVE: {
 608                 if (!root.endsWith("\\"))
 609                     return other;
 610                 // if different roots then return other
 611                 String thisRoot = root.substring(0, root.length()-1);
 612                 if (!thisRoot.equalsIgnoreCase(other.root))
 613                     return other;
 614                 // same roots
 615                 String remaining = other.path.substring(other.root.length());
 616                 String result;
 617                 if (path.endsWith("\\")) {
 618                     result = path + remaining;
 619                 } else {
 620                     result = path + "\\" + remaining;
 621                 }
 622                 return createFromNormalizedPath(getFileSystem(), result);
 623             }
 624 
 625             default:
 626                 throw new AssertionError();
 627         }
 628     }
 629 
 630     // generate offset array
 631     private void initOffsets() {
 632         if (offsets == null) {
 633             ArrayList<Integer> list = new ArrayList<>();
 634             if (isEmpty()) {
 635                 // empty path considered to have one name element
 636                 list.add(0);
 637             } else {
 638                 int start = root.length();
 639                 int off = root.length();
 640                 while (off < path.length()) {
 641                     if (path.charAt(off) != '\\') {
 642                         off++;
 643                     } else {
 644                         list.add(start);
 645                         start = ++off;
 646                     }
 647                 }
 648                 if (start != off)
 649                     list.add(start);
 650             }
 651             synchronized (this) {
 652                 if (offsets == null)
 653                     offsets = list.toArray(new Integer[list.size()]);
 654             }
 655         }
 656     }
 657 
 658     @Override
 659     public int getNameCount() {
 660         initOffsets();
 661         return offsets.length;
 662     }
 663 
 664     private String elementAsString(int i) {
 665         initOffsets();
 666         if (i == (offsets.length-1))
 667             return path.substring(offsets[i]);
 668         return path.substring(offsets[i], offsets[i+1]-1);
 669     }
 670 
 671     @Override
 672     public WindowsPath getName(int index) {
 673         initOffsets();
 674         if (index < 0 || index >= offsets.length)
 675             throw new IllegalArgumentException();
 676         return new WindowsPath(getFileSystem(), WindowsPathType.RELATIVE, "", elementAsString(index));
 677     }
 678 
 679     @Override
 680     public WindowsPath subpath(int beginIndex, int endIndex) {
 681         initOffsets();
 682         if (beginIndex < 0)
 683             throw new IllegalArgumentException();
 684         if (beginIndex >= offsets.length)
 685             throw new IllegalArgumentException();
 686         if (endIndex > offsets.length)
 687             throw new IllegalArgumentException();
 688         if (beginIndex >= endIndex)
 689             throw new IllegalArgumentException();
 690 
 691         StringBuilder sb = new StringBuilder();
 692         Integer[] nelems = new Integer[endIndex - beginIndex];
 693         for (int i = beginIndex; i < endIndex; i++) {
 694             nelems[i-beginIndex] = sb.length();
 695             sb.append(elementAsString(i));
 696             if (i != (endIndex-1))
 697                 sb.append("\\");
 698         }
 699         return new WindowsPath(getFileSystem(), WindowsPathType.RELATIVE, "", sb.toString());
 700     }
 701 
 702     @Override
 703     public boolean startsWith(Path obj) {
 704         if (!(Objects.requireNonNull(obj) instanceof WindowsPath))
 705             return false;
 706         WindowsPath other = (WindowsPath)obj;
 707 
 708         // if this path has a root component the given path's root must match
 709         if (!this.root.equalsIgnoreCase(other.root)) {
 710             return false;
 711         }
 712 
 713         // empty path starts with itself
 714         if (other.isEmpty())
 715             return this.isEmpty();
 716 
 717         // roots match so compare elements
 718         int thisCount = getNameCount();
 719         int otherCount = other.getNameCount();
 720         if (otherCount <= thisCount) {
 721             while (--otherCount >= 0) {
 722                 String thisElement = this.elementAsString(otherCount);
 723                 String otherElement = other.elementAsString(otherCount);
 724                 // FIXME: should compare in uppercase
 725                 if (!thisElement.equalsIgnoreCase(otherElement))
 726                     return false;
 727             }
 728             return true;
 729         }
 730         return false;
 731     }
 732 
 733     @Override
 734     public boolean endsWith(Path obj) {
 735         if (!(Objects.requireNonNull(obj) instanceof WindowsPath))
 736             return false;
 737         WindowsPath other = (WindowsPath)obj;
 738 
 739         // other path is longer
 740         if (other.path.length() > this.path.length()) {
 741             return false;
 742         }
 743 
 744         // empty path ends in itself
 745         if (other.isEmpty()) {
 746             return this.isEmpty();
 747         }
 748 
 749         int thisCount = this.getNameCount();
 750         int otherCount = other.getNameCount();
 751 
 752         // given path has more elements that this path
 753         if (otherCount > thisCount) {
 754             return false;
 755         }
 756 
 757         // compare roots
 758         if (other.root.length() > 0) {
 759             if (otherCount < thisCount)
 760                 return false;
 761             // FIXME: should compare in uppercase
 762             if (!this.root.equalsIgnoreCase(other.root))
 763                 return false;
 764         }
 765 
 766         // match last 'otherCount' elements
 767         int off = thisCount - otherCount;
 768         while (--otherCount >= 0) {
 769             String thisElement = this.elementAsString(off + otherCount);
 770             String otherElement = other.elementAsString(otherCount);
 771             // FIXME: should compare in uppercase
 772             if (!thisElement.equalsIgnoreCase(otherElement))
 773                 return false;
 774         }
 775         return true;
 776     }
 777 
 778     @Override
 779     public int compareTo(Path obj) {
 780         if (obj == null)
 781             throw new NullPointerException();
 782         String s1 = path;
 783         String s2 = ((WindowsPath)obj).path;
 784         int n1 = s1.length();
 785         int n2 = s2.length();
 786         int min = Math.min(n1, n2);
 787         for (int i = 0; i < min; i++) {
 788             char c1 = s1.charAt(i);
 789             char c2 = s2.charAt(i);
 790              if (c1 != c2) {
 791                  c1 = Character.toUpperCase(c1);
 792                  c2 = Character.toUpperCase(c2);
 793                  if (c1 != c2) {
 794                      return c1 - c2;
 795                  }
 796              }
 797         }
 798         return n1 - n2;
 799     }
 800 
 801     @Override
 802     public boolean equals(Object obj) {
 803         if ((obj != null) && (obj instanceof WindowsPath)) {
 804             return compareTo((Path)obj) == 0;
 805         }
 806         return false;
 807     }
 808 
 809     @Override
 810     public int hashCode() {
 811         // OK if two or more threads compute hash
 812         int h = hash;
 813         if (h == 0) {
 814             for (int i = 0; i< path.length(); i++) {
 815                 h = 31*h + Character.toUpperCase(path.charAt(i));
 816             }
 817             hash = h;
 818         }
 819         return h;
 820     }
 821 
 822     @Override
 823     public String toString() {
 824         return path;
 825     }
 826 
 827     // -- file operations --
 828 
 829     // package-private
 830     long openForReadAttributeAccess(boolean followLinks)
 831         throws WindowsException
 832     {
 833         int flags = FILE_FLAG_BACKUP_SEMANTICS;
 834         if (!followLinks)
 835             flags |= FILE_FLAG_OPEN_REPARSE_POINT;
 836         return CreateFile(getPathForWin32Calls(),
 837                           FILE_READ_ATTRIBUTES,
 838                           (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
 839                           0L,
 840                           OPEN_EXISTING,
 841                           flags);
 842     }
 843 
 844     void checkRead() {
 845         SecurityManager sm = System.getSecurityManager();
 846         if (sm != null) {
 847             sm.checkRead(getPathForPermissionCheck());
 848         }
 849     }
 850 
 851     void checkWrite() {
 852         SecurityManager sm = System.getSecurityManager();
 853         if (sm != null) {
 854             sm.checkWrite(getPathForPermissionCheck());
 855         }
 856     }
 857 
 858     void checkDelete() {
 859         SecurityManager sm = System.getSecurityManager();
 860         if (sm != null) {
 861             sm.checkDelete(getPathForPermissionCheck());
 862         }
 863     }
 864 
 865     @Override
 866     public URI toUri() {
 867         return WindowsUriSupport.toUri(this);
 868     }
 869 
 870     @Override
 871     public WindowsPath toAbsolutePath() {
 872         if (isAbsolute())
 873             return this;
 874 
 875         // permission check as per spec
 876         SecurityManager sm = System.getSecurityManager();
 877         if (sm != null) {
 878             sm.checkPropertyAccess("user.dir");
 879         }
 880 
 881         try {
 882             return createFromNormalizedPath(getFileSystem(), getAbsolutePath());
 883         } catch (WindowsException x) {
 884             throw new IOError(new IOException(x.getMessage()));
 885         }
 886     }
 887 
 888     @Override
 889     public WindowsPath toRealPath(LinkOption... options) throws IOException {
 890         checkRead();
 891         String rp = WindowsLinkSupport.getRealPath(this, Util.followLinks(options));
 892         return createFromNormalizedPath(getFileSystem(), rp);
 893     }
 894 
 895     @Override
 896     public WatchKey register(WatchService watcher,
 897                              WatchEvent.Kind<?>[] events,
 898                              WatchEvent.Modifier... modifiers)
 899         throws IOException
 900     {
 901         if (watcher == null)
 902             throw new NullPointerException();
 903         if (!(watcher instanceof WindowsWatchService))
 904             throw new ProviderMismatchException();
 905 
 906         // When a security manager is set then we need to make a defensive
 907         // copy of the modifiers and check for the Windows specific FILE_TREE
 908         // modifier. When the modifier is present then check that permission
 909         // has been granted recursively.
 910         SecurityManager sm = System.getSecurityManager();
 911         if (sm != null) {
 912             boolean watchSubtree = false;
 913             final int ml = modifiers.length;
 914             if (ml > 0) {
 915                 modifiers = Arrays.copyOf(modifiers, ml);
 916                 int i=0;
 917                 while (i < ml) {
 918                     if (ExtendedOptions.FILE_TREE.matches(modifiers[i++])) {
 919                         watchSubtree = true;
 920                         break;
 921                     }
 922                 }
 923             }
 924             String s = getPathForPermissionCheck();
 925             sm.checkRead(s);
 926             if (watchSubtree)
 927                 sm.checkRead(s + "\\-");
 928         }
 929 
 930         return ((WindowsWatchService)watcher).register(this, events, modifiers);
 931     }
 932 }