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.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 com.sun.nio.file.ExtendedWatchEventModifier;
  36 
  37 import static sun.nio.fs.WindowsNativeDispatcher.*;
  38 import static sun.nio.fs.WindowsConstants.*;
  39 
  40 /**
  41  * Windows implementation of Path
  42  */
  43 
  44 class WindowsPath implements Path {
  45 
  46     // The maximum path that does not require long path prefix. On Windows
  47     // the maximum path is 260 minus 1 (NUL) but for directories it is 260
  48     // minus 12 minus 1 (to allow for the creation of a 8.3 file in the
  49     // directory).
  50     private static final int MAX_PATH = 247;
  51 
  52     // Maximum extended-length path
  53     private static final int MAX_LONG_PATH = 32000;
  54 
  55     // FIXME - eliminate this reference to reduce space
  56     private final WindowsFileSystem fs;
  57 
  58     // path type
  59     private final WindowsPathType type;
  60     // root component (may be empty)
  61     private final String root;
  62     // normalized path
  63     private final String path;
  64 
  65     // the path to use in Win32 calls. This differs from path for relative
  66     // paths and has a long path prefix for all paths longer than MAX_PATH.
  67     private volatile WeakReference<String> pathForWin32Calls;
  68 
  69     // offsets into name components (computed lazily)
  70     private volatile Integer[] offsets;
  71 
  72     // computed hash code (computed lazily, no need to be volatile)
  73     private int hash;
  74 
  75 
  76     /**
  77      * Initializes a new instance of this class.
  78      */
  79     private WindowsPath(WindowsFileSystem fs,
  80                         WindowsPathType type,
  81                         String root,
  82                         String path)
  83     {
  84         this.fs = fs;
  85         this.type = type;
  86         this.root = root;
  87         this.path = path;
  88     }
  89 
  90     /**
  91      * Creates a Path by parsing the given path.
  92      */
  93     static WindowsPath parse(WindowsFileSystem fs, String path) {
  94         WindowsPathParser.Result result = WindowsPathParser.parse(path);
  95         return new WindowsPath(fs, result.type(), result.root(), result.path());
  96     }
  97 
  98     /**
  99      * Creates a Path from a given path that is known to be normalized.
 100      */
 101     static WindowsPath createFromNormalizedPath(WindowsFileSystem fs,
 102                                                 String path,
 103                                                 BasicFileAttributes attrs)
 104     {
 105         try {
 106             WindowsPathParser.Result result =
 107                 WindowsPathParser.parseNormalizedPath(path);
 108             if (attrs == null) {
 109                 return new WindowsPath(fs,
 110                                        result.type(),
 111                                        result.root(),
 112                                        result.path());
 113             } else {
 114                 return new WindowsPathWithAttributes(fs,
 115                                                      result.type(),
 116                                                      result.root(),
 117                                                      result.path(),
 118                                                      attrs);
 119             }
 120         } catch (InvalidPathException x) {
 121             throw new AssertionError(x.getMessage());
 122         }
 123     }
 124 
 125     /**
 126      * Creates a WindowsPath from a given path that is known to be normalized.
 127      */
 128     static WindowsPath createFromNormalizedPath(WindowsFileSystem fs,
 129                                                 String path)
 130     {
 131         return createFromNormalizedPath(fs, path, null);
 132     }
 133 
 134     /**
 135      * Special implementation with attached/cached attributes (used to quicken
 136      * file tree traversal)
 137      */
 138     private static class WindowsPathWithAttributes
 139         extends WindowsPath implements BasicFileAttributesHolder
 140     {
 141         final WeakReference<BasicFileAttributes> ref;
 142 
 143         WindowsPathWithAttributes(WindowsFileSystem fs,
 144                                   WindowsPathType type,
 145                                   String root,
 146                                   String path,
 147                                   BasicFileAttributes attrs)
 148         {
 149             super(fs, type, root, path);
 150             ref = new WeakReference<BasicFileAttributes>(attrs);
 151         }
 152 
 153         @Override
 154         public BasicFileAttributes get() {
 155             return ref.get();
 156         }
 157 
 158         @Override
 159         public void invalidate() {
 160             ref.clear();
 161         }
 162 
 163         // no need to override equals/hashCode.
 164     }
 165 
 166     // use this message when throwing exceptions
 167     String getPathForExceptionMessage() {
 168         return path;
 169     }
 170 
 171     // use this path for permission checks
 172     String getPathForPermissionCheck() {
 173         return path;
 174     }
 175 
 176     // use this path for Win32 calls
 177     // This method will prefix long paths with \\?\ or \\?\UNC as required.
 178     String getPathForWin32Calls() throws WindowsException {
 179         // short absolute paths can be used directly
 180         if (isAbsolute() && path.length() <= MAX_PATH)
 181             return path;
 182 
 183         // return cached values if available
 184         WeakReference<String> ref = pathForWin32Calls;
 185         String resolved = (ref != null) ? ref.get() : null;
 186         if (resolved != null) {
 187             // Win32 path already available
 188             return resolved;
 189         }
 190 
 191         // resolve against default directory
 192         resolved = getAbsolutePath();
 193 
 194         // Long paths need to have "." and ".." removed and be prefixed with
 195         // "\\?\". Note that it is okay to remove ".." even when it follows
 196         // a link - for example, it is okay for foo/link/../bar to be changed
 197         // to foo/bar. The reason is that Win32 APIs to access foo/link/../bar
 198         // will access foo/bar anyway (which differs to Unix systems)
 199         if (resolved.length() > MAX_PATH) {
 200             if (resolved.length() > MAX_LONG_PATH) {
 201                 throw new WindowsException("Cannot access file with path exceeding "
 202                     + MAX_LONG_PATH + " characters");
 203             }
 204             resolved = addPrefixIfNeeded(GetFullPathName(resolved));
 205         }
 206 
 207         // cache the resolved path (except drive relative paths as the working
 208         // directory on removal media devices can change during the lifetime
 209         // of the VM)
 210         if (type != WindowsPathType.DRIVE_RELATIVE) {
 211             synchronized (path) {
 212                 pathForWin32Calls = new WeakReference<String>(resolved);
 213             }
 214         }
 215         return resolved;
 216     }
 217 
 218     // return this path resolved against the file system's default directory
 219     private String getAbsolutePath() throws WindowsException {
 220         if (isAbsolute())
 221             return path;
 222 
 223         // Relative path ("foo" for example)
 224         if (type == WindowsPathType.RELATIVE) {
 225             String defaultDirectory = getFileSystem().defaultDirectory();
 226             if (isEmpty())
 227                 return defaultDirectory;
 228             if (defaultDirectory.endsWith("\\")) {
 229                 return defaultDirectory + path;
 230             } else {
 231                 StringBuilder sb =
 232                     new StringBuilder(defaultDirectory.length() + path.length() + 1);
 233                 return sb.append(defaultDirectory).append('\\').append(path).toString();
 234             }
 235         }
 236 
 237         // Directory relative path ("\foo" for example)
 238         if (type == WindowsPathType.DIRECTORY_RELATIVE) {
 239             String defaultRoot = getFileSystem().defaultRoot();
 240             return defaultRoot + path.substring(1);
 241         }
 242 
 243         // Drive relative path ("C:foo" for example).
 244         if (isSameDrive(root, getFileSystem().defaultRoot())) {
 245             // relative to default directory
 246             String remaining = path.substring(root.length());
 247             String defaultDirectory = getFileSystem().defaultDirectory();
 248             String result;
 249             if (defaultDirectory.endsWith("\\")) {
 250                 result = defaultDirectory + remaining;
 251             } else {
 252                 result = defaultDirectory + "\\" + remaining;
 253             }
 254             return result;
 255         } else {
 256             // relative to some other drive
 257             String wd;
 258             try {
 259                 int dt = GetDriveType(root + "\\");
 260                 if (dt == DRIVE_UNKNOWN || dt == DRIVE_NO_ROOT_DIR)
 261                     throw new WindowsException("");
 262                 wd = GetFullPathName(root + ".");
 263             } catch (WindowsException x) {
 264                 throw new WindowsException("Unable to get working directory of drive '" +
 265                     Character.toUpperCase(root.charAt(0)) + "'");
 266             }
 267             String result = wd;
 268             if (wd.endsWith("\\")) {
 269                 result += path.substring(root.length());
 270             } else {
 271                 if (path.length() > root.length())
 272                     result += "\\" + path.substring(root.length());
 273             }
 274             return result;
 275         }
 276     }
 277 
 278     // returns true if same drive letter
 279     private static boolean isSameDrive(String root1, String root2) {
 280         return Character.toUpperCase(root1.charAt(0)) ==
 281                Character.toUpperCase(root2.charAt(0));
 282     }
 283 
 284     // Add long path prefix to path if required
 285     static String addPrefixIfNeeded(String path) {
 286         if (path.length() > MAX_PATH) {
 287             if (path.startsWith("\\\\")) {
 288                 path = "\\\\?\\UNC" + path.substring(1, path.length());
 289             } else {
 290                 path = "\\\\?\\" + path;
 291             }
 292         }
 293         return path;
 294     }
 295 
 296     @Override
 297     public WindowsFileSystem getFileSystem() {
 298         return fs;
 299     }
 300 
 301     // -- Path operations --
 302 
 303     private boolean isEmpty() {
 304         return path.length() == 0;
 305     }
 306 
 307     private WindowsPath emptyPath() {
 308         return new WindowsPath(getFileSystem(), WindowsPathType.RELATIVE, "", "");
 309     }
 310 
 311     @Override
 312     public Path getFileName() {
 313         int len = path.length();
 314         // represents empty path
 315         if (len == 0)
 316             return this;
 317         // represents root component only
 318         if (root.length() == len)
 319             return null;
 320         int off = path.lastIndexOf('\\');
 321         if (off < root.length())
 322             off = root.length();
 323         else
 324             off++;
 325         return new WindowsPath(getFileSystem(), WindowsPathType.RELATIVE, "", path.substring(off));
 326     }
 327 
 328     @Override
 329     public WindowsPath getParent() {
 330         // represents root component only
 331         if (root.length() == path.length())
 332             return null;
 333         int off = path.lastIndexOf('\\');
 334         if (off < root.length())
 335             return getRoot();
 336         else
 337             return new WindowsPath(getFileSystem(),
 338                                    type,
 339                                    root,
 340                                    path.substring(0, off));
 341     }
 342 
 343     @Override
 344     public WindowsPath getRoot() {
 345         if (root.length() == 0)
 346             return null;
 347         return new WindowsPath(getFileSystem(), type, root, root);
 348     }
 349 
 350     // package-private
 351     WindowsPathType type() {
 352         return type;
 353     }
 354 
 355     // package-private
 356     boolean isUnc() {
 357         return type == WindowsPathType.UNC;
 358     }
 359 
 360     boolean needsSlashWhenResolving() {
 361         if (path.endsWith("\\"))
 362             return false;
 363         return path.length() > root.length();
 364     }
 365 
 366     @Override
 367     public boolean isAbsolute() {
 368         return type == WindowsPathType.ABSOLUTE || type == WindowsPathType.UNC;
 369     }
 370 
 371     static WindowsPath toWindowsPath(Path path) {
 372         if (path == null)
 373             throw new NullPointerException();
 374         if (!(path instanceof WindowsPath)) {
 375             throw new ProviderMismatchException();
 376         }
 377         return (WindowsPath)path;
 378     }
 379 
 380     @Override
 381     public WindowsPath relativize(Path obj) {
 382         WindowsPath other = toWindowsPath(obj);
 383         if (this.equals(other))
 384             return emptyPath();
 385 
 386         // can only relativize paths of the same type
 387         if (this.type != other.type)
 388             throw new IllegalArgumentException("'other' is different type of Path");
 389 
 390         // can only relativize paths if root component matches
 391         if (!this.root.equalsIgnoreCase(other.root))
 392             throw new IllegalArgumentException("'other' has different root");
 393 
 394         // this path is the empty path
 395         if (this.isEmpty())
 396             return other;
 397 
 398         int bn = this.getNameCount();
 399         int cn = other.getNameCount();
 400 
 401         // skip matching names
 402         int n = (bn > cn) ? cn : bn;
 403         int i = 0;
 404         while (i < n) {
 405             if (!this.getName(i).equals(other.getName(i)))
 406                 break;
 407             i++;
 408         }
 409 
 410         // append ..\ for remaining names in the base
 411         StringBuilder result = new StringBuilder();
 412         for (int j=i; j<bn; j++) {
 413             result.append("..\\");
 414         }
 415 
 416         // append remaining names in child
 417         for (int j=i; j<cn; j++) {
 418             result.append(other.getName(j).toString());
 419             result.append("\\");
 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 (modifiers[i++] == ExtendedWatchEventModifier.FILE_TREE) {
 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 }