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