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 }