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