WindowsPath.java revision 14643:0dbd91df691e
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 26package sun.nio.fs; 27 28import java.nio.file.*; 29import java.nio.file.attribute.*; 30import java.io.*; 31import java.net.URI; 32import java.util.*; 33import java.lang.ref.WeakReference; 34 35import static sun.nio.fs.WindowsNativeDispatcher.*; 36import static sun.nio.fs.WindowsConstants.*; 37 38/** 39 * Windows implementation of Path 40 */ 41 42class 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} 880