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