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     @Override
 379     public WindowsPath relativize(Path obj) {
 380         WindowsPath other = toWindowsPath(obj);
 381         if (this.equals(other))
 382             return emptyPath();
 383 
 384         // can only relativize paths of the same type
 385         if (this.type != other.type)
 386             throw new IllegalArgumentException("'other' is different type of Path");
 387 
 388         // can only relativize paths if root component matches
 389         if (!this.root.equalsIgnoreCase(other.root))
 390             throw new IllegalArgumentException("'other' has different root");
 391 
 392         // this path is the empty path
 393         if (this.isEmpty())
 394             return other;
 395 
 396         int bn = this.getNameCount();
 397         int cn = other.getNameCount();
 398 
 399         // skip matching names
 400         int n = (bn > cn) ? cn : bn;
 401         int i = 0;
 402         while (i < n) {
 403             if (!this.getName(i).equals(other.getName(i)))
 404                 break;
 405             i++;
 406         }
 407 
 408         // append ..\ for remaining names in the base
 409         StringBuilder result = new StringBuilder();
 410         for (int j=i; j<bn; j++) {
 411             result.append("..\\");
 412         }
 413 
 414         // append remaining names in child
 415         if (!other.isEmpty()) {
 416             for (int j=i; j<cn; j++) {
 417                 result.append(other.getName(j).toString());
 418                 result.append("\\");
 419             }
 420         }
 421 
 422         // drop trailing slash in result
 423         result.setLength(result.length()-1);
 424         return createFromNormalizedPath(getFileSystem(), result.toString());
 425     }
 426 
 427     @Override
 428     public Path normalize() {
 429         final int count = getNameCount();
 430         if (count == 0 || isEmpty())
 431             return this;
 432 
 433         boolean[] ignore = new boolean[count];      // true => ignore name
 434         int remaining = count;                      // number of names remaining
 435 
 436         // multiple passes to eliminate all occurrences of "." and "name/.."
 437         int prevRemaining;
 438         do {
 439             prevRemaining = remaining;
 440             int prevName = -1;
 441             for (int i=0; i<count; i++) {
 442                 if (ignore[i])
 443                     continue;
 444 
 445                 String name = elementAsString(i);
 446 
 447                 // not "." or ".."
 448                 if (name.length() > 2) {
 449                     prevName = i;
 450                     continue;
 451                 }
 452 
 453                 // "." or something else
 454                 if (name.length() == 1) {
 455                     // ignore "."
 456                     if (name.charAt(0) == '.') {
 457                         ignore[i] = true;
 458                         remaining--;
 459                     } else {
 460                         prevName = i;
 461                     }
 462                     continue;
 463                 }
 464 
 465                 // not ".."
 466                 if (name.charAt(0) != '.' || name.charAt(1) != '.') {
 467                     prevName = i;
 468                     continue;
 469                 }
 470 
 471                 // ".." found
 472                 if (prevName >= 0) {
 473                     // name/<ignored>/.. found so mark name and ".." to be
 474                     // ignored
 475                     ignore[prevName] = true;
 476                     ignore[i] = true;
 477                     remaining = remaining - 2;
 478                     prevName = -1;
 479                 } else {
 480                     // Cases:
 481                     //    C:\<ignored>\..
 482                     //    \\server\\share\<ignored>\..
 483                     //    \<ignored>..
 484                     if (isAbsolute() || type == WindowsPathType.DIRECTORY_RELATIVE) {
 485                         boolean hasPrevious = false;
 486                         for (int j=0; j<i; j++) {
 487                             if (!ignore[j]) {
 488                                 hasPrevious = true;
 489                                 break;
 490                             }
 491                         }
 492                         if (!hasPrevious) {
 493                             // all proceeding names are ignored
 494                             ignore[i] = true;
 495                             remaining--;
 496                         }
 497                     }
 498                 }
 499             }
 500         } while (prevRemaining > remaining);
 501 
 502         // no redundant names
 503         if (remaining == count)
 504             return this;
 505 
 506         // corner case - all names removed
 507         if (remaining == 0) {
 508             return (root.length() == 0) ? emptyPath() : getRoot();
 509         }
 510 
 511         // re-constitute the path from the remaining names.
 512         StringBuilder result = new StringBuilder();
 513         if (root != null)
 514             result.append(root);
 515         for (int i=0; i<count; i++) {
 516             if (!ignore[i]) {
 517                 result.append(getName(i));
 518                 result.append("\\");
 519             }
 520         }
 521 
 522         // drop trailing slash in result
 523         result.setLength(result.length()-1);
 524         return createFromNormalizedPath(getFileSystem(), result.toString());
 525     }
 526 
 527     @Override
 528     public WindowsPath resolve(Path obj) {
 529         WindowsPath other = toWindowsPath(obj);
 530         if (other.isEmpty())
 531             return this;
 532         if (other.isAbsolute())
 533             return other;
 534 
 535         switch (other.type) {
 536             case RELATIVE: {
 537                 String result;
 538                 if (path.endsWith("\\") || (root.length() == path.length())) {
 539                     result = path + other.path;
 540                 } else {
 541                     result = path + "\\" + other.path;
 542                 }
 543                 return new WindowsPath(getFileSystem(), type, root, result);
 544             }
 545 
 546             case DIRECTORY_RELATIVE: {
 547                 String result;
 548                 if (root.endsWith("\\")) {
 549                     result = root + other.path.substring(1);
 550                 } else {
 551                     result = root + other.path;
 552                 }
 553                 return createFromNormalizedPath(getFileSystem(), result);
 554             }
 555 
 556             case DRIVE_RELATIVE: {
 557                 if (!root.endsWith("\\"))
 558                     return other;
 559                 // if different roots then return other
 560                 String thisRoot = root.substring(0, root.length()-1);
 561                 if (!thisRoot.equalsIgnoreCase(other.root))
 562                     return other;
 563                 // same roots
 564                 String remaining = other.path.substring(other.root.length());
 565                 String result;
 566                 if (path.endsWith("\\")) {
 567                     result = path + remaining;
 568                 } else {
 569                     result = path + "\\" + remaining;
 570                 }
 571                 return createFromNormalizedPath(getFileSystem(), result);
 572             }
 573 
 574             default:
 575                 throw new AssertionError();
 576         }
 577     }
 578 
 579     // generate offset array
 580     private void initOffsets() {
 581         if (offsets == null) {
 582             ArrayList<Integer> list = new ArrayList<>();
 583             if (isEmpty()) {
 584                 // empty path considered to have one name element
 585                 list.add(0);
 586             } else {
 587                 int start = root.length();
 588                 int off = root.length();
 589                 while (off < path.length()) {
 590                     if (path.charAt(off) != '\\') {
 591                         off++;
 592                     } else {
 593                         list.add(start);
 594                         start = ++off;
 595                     }
 596                 }
 597                 if (start != off)
 598                     list.add(start);
 599             }
 600             synchronized (this) {
 601                 if (offsets == null)
 602                     offsets = list.toArray(new Integer[list.size()]);
 603             }
 604         }
 605     }
 606 
 607     @Override
 608     public int getNameCount() {
 609         initOffsets();
 610         return offsets.length;
 611     }
 612 
 613     private String elementAsString(int i) {
 614         initOffsets();
 615         if (i == (offsets.length-1))
 616             return path.substring(offsets[i]);
 617         return path.substring(offsets[i], offsets[i+1]-1);
 618     }
 619 
 620     @Override
 621     public WindowsPath getName(int index) {
 622         initOffsets();
 623         if (index < 0 || index >= offsets.length)
 624             throw new IllegalArgumentException();
 625         return new WindowsPath(getFileSystem(), WindowsPathType.RELATIVE, "", elementAsString(index));
 626     }
 627 
 628     @Override
 629     public WindowsPath subpath(int beginIndex, int endIndex) {
 630         initOffsets();
 631         if (beginIndex < 0)
 632             throw new IllegalArgumentException();
 633         if (beginIndex >= offsets.length)
 634             throw new IllegalArgumentException();
 635         if (endIndex > offsets.length)
 636             throw new IllegalArgumentException();
 637         if (beginIndex >= endIndex)
 638             throw new IllegalArgumentException();
 639 
 640         StringBuilder sb = new StringBuilder();
 641         Integer[] nelems = new Integer[endIndex - beginIndex];
 642         for (int i = beginIndex; i < endIndex; i++) {
 643             nelems[i-beginIndex] = sb.length();
 644             sb.append(elementAsString(i));
 645             if (i != (endIndex-1))
 646                 sb.append("\\");
 647         }
 648         return new WindowsPath(getFileSystem(), WindowsPathType.RELATIVE, "", sb.toString());
 649     }
 650 
 651     @Override
 652     public boolean startsWith(Path obj) {
 653         if (!(Objects.requireNonNull(obj) instanceof WindowsPath))
 654             return false;
 655         WindowsPath other = (WindowsPath)obj;
 656 
 657         // if this path has a root component the given path's root must match
 658         if (!this.root.equalsIgnoreCase(other.root)) {
 659             return false;
 660         }
 661 
 662         // empty path starts with itself
 663         if (other.isEmpty())
 664             return this.isEmpty();
 665 
 666         // roots match so compare elements
 667         int thisCount = getNameCount();
 668         int otherCount = other.getNameCount();
 669         if (otherCount <= thisCount) {
 670             while (--otherCount >= 0) {
 671                 String thisElement = this.elementAsString(otherCount);
 672                 String otherElement = other.elementAsString(otherCount);
 673                 // FIXME: should compare in uppercase
 674                 if (!thisElement.equalsIgnoreCase(otherElement))
 675                     return false;
 676             }
 677             return true;
 678         }
 679         return false;
 680     }
 681 
 682     @Override
 683     public boolean endsWith(Path obj) {
 684         if (!(Objects.requireNonNull(obj) instanceof WindowsPath))
 685             return false;
 686         WindowsPath other = (WindowsPath)obj;
 687 
 688         // other path is longer
 689         if (other.path.length() > this.path.length()) {
 690             return false;
 691         }
 692 
 693         // empty path ends in itself
 694         if (other.isEmpty()) {
 695             return this.isEmpty();
 696         }
 697 
 698         int thisCount = this.getNameCount();
 699         int otherCount = other.getNameCount();
 700 
 701         // given path has more elements that this path
 702         if (otherCount > thisCount) {
 703             return false;
 704         }
 705 
 706         // compare roots
 707         if (other.root.length() > 0) {
 708             if (otherCount < thisCount)
 709                 return false;
 710             // FIXME: should compare in uppercase
 711             if (!this.root.equalsIgnoreCase(other.root))
 712                 return false;
 713         }
 714 
 715         // match last 'otherCount' elements
 716         int off = thisCount - otherCount;
 717         while (--otherCount >= 0) {
 718             String thisElement = this.elementAsString(off + otherCount);
 719             String otherElement = other.elementAsString(otherCount);
 720             // FIXME: should compare in uppercase
 721             if (!thisElement.equalsIgnoreCase(otherElement))
 722                 return false;
 723         }
 724         return true;
 725     }
 726 
 727     @Override
 728     public int compareTo(Path obj) {
 729         if (obj == null)
 730             throw new NullPointerException();
 731         String s1 = path;
 732         String s2 = ((WindowsPath)obj).path;
 733         int n1 = s1.length();
 734         int n2 = s2.length();
 735         int min = Math.min(n1, n2);
 736         for (int i = 0; i < min; i++) {
 737             char c1 = s1.charAt(i);
 738             char c2 = s2.charAt(i);
 739              if (c1 != c2) {
 740                  c1 = Character.toUpperCase(c1);
 741                  c2 = Character.toUpperCase(c2);
 742                  if (c1 != c2) {
 743                      return c1 - c2;
 744                  }
 745              }
 746         }
 747         return n1 - n2;
 748     }
 749 
 750     @Override
 751     public boolean equals(Object obj) {
 752         if ((obj != null) && (obj instanceof WindowsPath)) {
 753             return compareTo((Path)obj) == 0;
 754         }
 755         return false;
 756     }
 757 
 758     @Override
 759     public int hashCode() {
 760         // OK if two or more threads compute hash
 761         int h = hash;
 762         if (h == 0) {
 763             for (int i = 0; i< path.length(); i++) {
 764                 h = 31*h + Character.toUpperCase(path.charAt(i));
 765             }
 766             hash = h;
 767         }
 768         return h;
 769     }
 770 
 771     @Override
 772     public String toString() {
 773         return path;
 774     }
 775 
 776     // -- file operations --
 777 
 778     // package-private
 779     long openForReadAttributeAccess(boolean followLinks)
 780         throws WindowsException
 781     {
 782         int flags = FILE_FLAG_BACKUP_SEMANTICS;
 783         if (!followLinks)
 784             flags |= FILE_FLAG_OPEN_REPARSE_POINT;
 785         return CreateFile(getPathForWin32Calls(),
 786                           FILE_READ_ATTRIBUTES,
 787                           (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
 788                           0L,
 789                           OPEN_EXISTING,
 790                           flags);
 791     }
 792 
 793     void checkRead() {
 794         SecurityManager sm = System.getSecurityManager();
 795         if (sm != null) {
 796             sm.checkRead(getPathForPermissionCheck());
 797         }
 798     }
 799 
 800     void checkWrite() {
 801         SecurityManager sm = System.getSecurityManager();
 802         if (sm != null) {
 803             sm.checkWrite(getPathForPermissionCheck());
 804         }
 805     }
 806 
 807     void checkDelete() {
 808         SecurityManager sm = System.getSecurityManager();
 809         if (sm != null) {
 810             sm.checkDelete(getPathForPermissionCheck());
 811         }
 812     }
 813 
 814     @Override
 815     public URI toUri() {
 816         return WindowsUriSupport.toUri(this);
 817     }
 818 
 819     @Override
 820     public WindowsPath toAbsolutePath() {
 821         if (isAbsolute())
 822             return this;
 823 
 824         // permission check as per spec
 825         SecurityManager sm = System.getSecurityManager();
 826         if (sm != null) {
 827             sm.checkPropertyAccess("user.dir");
 828         }
 829 
 830         try {
 831             return createFromNormalizedPath(getFileSystem(), getAbsolutePath());
 832         } catch (WindowsException x) {
 833             throw new IOError(new IOException(x.getMessage()));
 834         }
 835     }
 836 
 837     @Override
 838     public WindowsPath toRealPath(LinkOption... options) throws IOException {
 839         checkRead();
 840         String rp = WindowsLinkSupport.getRealPath(this, Util.followLinks(options));
 841         return createFromNormalizedPath(getFileSystem(), rp);
 842     }
 843 
 844     @Override
 845     public WatchKey register(WatchService watcher,
 846                              WatchEvent.Kind<?>[] events,
 847                              WatchEvent.Modifier... modifiers)
 848         throws IOException
 849     {
 850         if (watcher == null)
 851             throw new NullPointerException();
 852         if (!(watcher instanceof WindowsWatchService))
 853             throw new ProviderMismatchException();
 854 
 855         // When a security manager is set then we need to make a defensive
 856         // copy of the modifiers and check for the Windows specific FILE_TREE
 857         // modifier. When the modifier is present then check that permission
 858         // has been granted recursively.
 859         SecurityManager sm = System.getSecurityManager();
 860         if (sm != null) {
 861             boolean watchSubtree = false;
 862             final int ml = modifiers.length;
 863             if (ml > 0) {
 864                 modifiers = Arrays.copyOf(modifiers, ml);
 865                 int i=0;
 866                 while (i < ml) {
 867                     if (ExtendedOptions.FILE_TREE.matches(modifiers[i++])) {
 868                         watchSubtree = true;
 869                         break;
 870                     }
 871                 }
 872             }
 873             String s = getPathForPermissionCheck();
 874             sm.checkRead(s);
 875             if (watchSubtree)
 876                 sm.checkRead(s + "\\-");
 877         }
 878 
 879         return ((WindowsWatchService)watcher).register(this, events, modifiers);
 880     }
 881 }