KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > io > Win32FileSystem


1 /*
2  * @(#)Win32FileSystem.java 1.15 02/10/01
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package java.io;
9
10 import java.security.AccessController JavaDoc;
11 import sun.security.action.GetPropertyAction;
12
13
14 class Win32FileSystem extends FileSystem JavaDoc {
15
16     private final char slash;
17     private final char altSlash;
18     private final char semicolon;
19
20     public Win32FileSystem() {
21     slash = ((String JavaDoc) AccessController.doPrivileged(
22               new GetPropertyAction("file.separator"))).charAt(0);
23     semicolon = ((String JavaDoc) AccessController.doPrivileged(
24               new GetPropertyAction("path.separator"))).charAt(0);
25     altSlash = (this.slash == '\\') ? '/' : '\\';
26     }
27
28     private boolean isSlash(char c) {
29     return (c == '\\') || (c == '/');
30     }
31
32     private boolean isLetter(char c) {
33     return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
34     }
35
36     private String JavaDoc slashify(String JavaDoc p) {
37     if ((p.length() > 0) && (p.charAt(0) != slash)) return slash + p;
38     else return p;
39     }
40
41
42     /* -- Normalization and construction -- */
43
44     public char getSeparator() {
45     return slash;
46     }
47
48     public char getPathSeparator() {
49     return semicolon;
50     }
51
52     /* A normal Win32 pathname contains no duplicate slashes, except possibly
53        for a UNC prefix, and does not end with a slash. It may be the empty
54        string. Normalized Win32 pathnames have the convenient property that
55        the length of the prefix almost uniquely identifies the type of the path
56        and whether it is absolute or relative:
57
58            0 relative to both drive and directory
59        1 drive-relative (begins with '\\')
60        2 absolute UNC (if first char is '\\'),
61             else directory-relative (has form "z:foo")
62        3 absolute local pathname (begins with "z:\\")
63      */

64
65     private int normalizePrefix(String JavaDoc path, int len, StringBuffer JavaDoc sb) {
66     int src = 0;
67     while ((src < len) && isSlash(path.charAt(src))) src++;
68     char c;
69     if ((len - src >= 2)
70         && isLetter(c = path.charAt(src))
71         && path.charAt(src + 1) == ':') {
72         /* Remove leading slashes if followed by drive specifier.
73            This hack is necessary to support file URLs containing drive
74            specifiers (e.g., "file://c:/path"). As a side effect,
75            "/c:/path" can be used as an alternative to "c:/path". */

76         sb.append(c);
77         sb.append(':');
78         src += 2;
79     } else {
80         src = 0;
81         if ((len >= 2)
82         && isSlash(path.charAt(0))
83         && isSlash(path.charAt(1))) {
84         /* UNC pathname: Retain first slash; leave src pointed at
85            second slash so that further slashes will be collapsed
86            into the second slash. The result will be a pathname
87            beginning with "\\\\" followed (most likely) by a host
88            name. */

89         src = 1;
90         sb.append(slash);
91         }
92     }
93     return src;
94     }
95
96     /* Normalize the given pathname, whose length is len, starting at the given
97        offset; everything before this offset is already normal. */

98     private String JavaDoc normalize(String JavaDoc path, int len, int off) {
99     if (len == 0) return path;
100     if (off < 3) off = 0; /* Avoid fencepost cases with UNC pathnames */
101     int src;
102     char slash = this.slash;
103     StringBuffer JavaDoc sb = new StringBuffer JavaDoc(len);
104
105     if (off == 0) {
106         /* Complete normalization, including prefix */
107         src = normalizePrefix(path, len, sb);
108     } else {
109         /* Partial normalization */
110         src = off;
111         sb.append(path.substring(0, off));
112     }
113
114     /* Remove redundant slashes from the remainder of the path, forcing all
115        slashes into the preferred slash */

116     while (src < len) {
117         char c = path.charAt(src++);
118         if (isSlash(c)) {
119         while ((src < len) && isSlash(path.charAt(src))) src++;
120         if (src == len) {
121             /* Check for trailing separator */
122             int sn = sb.length();
123             if ((sn == 2) && (sb.charAt(1) == ':')) {
124             /* "z:\\" */
125             sb.append(slash);
126             break;
127             }
128             if (sn == 0) {
129             /* "\\" */
130             sb.append(slash);
131             break;
132             }
133             if ((sn == 1) && (isSlash(sb.charAt(0)))) {
134             /* "\\\\" is not collapsed to "\\" because "\\\\" marks
135                    the beginning of a UNC pathname. Even though it is
136                    not, by itself, a valid UNC pathname, we leave it as
137                    is in order to be consistent with the win32 APIs,
138                    which treat this case as an invalid UNC pathname
139                    rather than as an alias for the root directory of
140                    the current drive. */

141             sb.append(slash);
142             break;
143             }
144             /* Path does not denote a root directory, so do not append
145                trailing slash */

146             break;
147         } else {
148             sb.append(slash);
149         }
150         } else {
151         sb.append(c);
152         }
153     }
154
155     String JavaDoc rv = sb.toString();
156     return rv;
157     }
158
159     /* Check that the given pathname is normal. If not, invoke the real
160        normalizer on the part of the pathname that requires normalization.
161        This way we iterate through the whole pathname string only once. */

162     public String JavaDoc normalize(String JavaDoc path) {
163     int n = path.length();
164     char slash = this.slash;
165     char altSlash = this.altSlash;
166     char prev = 0;
167     for (int i = 0; i < n; i++) {
168         char c = path.charAt(i);
169         if (c == altSlash)
170         return normalize(path, n, (prev == slash) ? i - 1 : i);
171         if ((c == slash) && (prev == slash) && (i > 1))
172         return normalize(path, n, i - 1);
173         if ((c == ':') && (i > 1))
174         return normalize(path, n, 0);
175         prev = c;
176     }
177     if (prev == slash) return normalize(path, n, n - 1);
178     return path;
179     }
180
181     public int prefixLength(String JavaDoc path) {
182     char slash = this.slash;
183     int n = path.length();
184     if (n == 0) return 0;
185     char c0 = path.charAt(0);
186     char c1 = (n > 1) ? path.charAt(1) : 0;
187     if (c0 == slash) {
188         if (c1 == slash) return 2; /* Absolute UNC pathname "\\\\foo" */
189         return 1; /* Drive-relative "\\foo" */
190     }
191     if (isLetter(c0) && (c1 == ':')) {
192         if ((n > 2) && (path.charAt(2) == slash))
193         return 3; /* Absolute local pathname "z:\\foo" */
194         return 2; /* Directory-relative "z:foo" */
195     }
196     return 0; /* Completely relative */
197     }
198
199     public String JavaDoc resolve(String JavaDoc parent, String JavaDoc child) {
200         int pn = parent.length();
201         if (pn == 0) return child;
202         int cn = child.length();
203         if (cn == 0) return parent;
204
205         String JavaDoc c = child;
206         int childStart = 0;
207         int parentEnd = pn;
208
209         if ((cn > 1) && (c.charAt(0) == slash)) {
210             if (c.charAt(1) == slash) {
211                 /* Drop prefix when child is a UNC pathname */
212                 childStart = 2;
213             } else {
214                 /* Drop prefix when child is drive-relative */
215                 childStart = 1;
216
217             }
218             if (cn == childStart) { // Child is double slash
219
if (parent.charAt(pn - 1) == slash)
220                     return parent.substring(0, pn - 1);
221                 return parent;
222             }
223         }
224
225         if (parent.charAt(pn - 1) == slash)
226             parentEnd--;
227
228         int strlen = parentEnd + cn - childStart;
229         char[] theChars = null;
230         if (child.charAt(childStart) == slash) {
231             theChars = new char[strlen];
232             parent.getChars(0, parentEnd, theChars, 0);
233             child.getChars(childStart, cn, theChars, parentEnd);
234         } else {
235             theChars = new char[strlen + 1];
236             parent.getChars(0, parentEnd, theChars, 0);
237             theChars[parentEnd] = slash;
238             child.getChars(childStart, cn, theChars, parentEnd + 1);
239         }
240         return new String JavaDoc(theChars);
241     }
242
243     public String JavaDoc getDefaultParent() {
244     return ("" + slash);
245     }
246
247     public String JavaDoc fromURIPath(String JavaDoc path) {
248     String JavaDoc p = path;
249     if ((p.length() > 2) && (p.charAt(2) == ':')) {
250         // "/c:/foo" --> "c:/foo"
251
p = p.substring(1);
252         // "c:/foo/" --> "c:/foo", but "c:/" --> "c:/"
253
if ((p.length() > 3) && p.endsWith("/"))
254         p = p.substring(0, p.length() - 1);
255     } else if ((p.length() > 1) && p.endsWith("/")) {
256         // "/foo/" --> "/foo"
257
p = p.substring(0, p.length() - 1);
258     }
259     return p;
260     }
261
262
263
264     /* -- Path operations -- */
265
266     public boolean isAbsolute(File JavaDoc f) {
267     int pl = f.getPrefixLength();
268     return (((pl == 2) && (f.getPath().charAt(0) == slash))
269         || (pl == 3));
270     }
271
272     protected native String JavaDoc getDriveDirectory(int drive);
273
274     private static String JavaDoc[] driveDirCache = new String JavaDoc[26];
275
276     private static int driveIndex(char d) {
277     if ((d >= 'a') && (d <= 'z')) return d - 'a';
278     if ((d >= 'A') && (d <= 'Z')) return d - 'A';
279     return -1;
280     }
281
282     private String JavaDoc getDriveDirectory(char drive) {
283     int i = driveIndex(drive);
284     if (i < 0) return null;
285     String JavaDoc s = driveDirCache[i];
286     if (s != null) return s;
287     s = getDriveDirectory(i + 1);
288     driveDirCache[i] = s;
289     return s;
290     }
291
292     private String JavaDoc getUserPath() {
293     /* For both compatibility and security,
294        we must look this up every time */

295     return normalize(System.getProperty("user.dir"));
296     }
297
298     private String JavaDoc getDrive(String JavaDoc path) {
299     int pl = prefixLength(path);
300     return (pl == 3) ? path.substring(0, 2) : null;
301     }
302
303     public String JavaDoc resolve(File JavaDoc f) {
304     String JavaDoc path = f.getPath();
305     int pl = f.getPrefixLength();
306     if ((pl == 2) && (path.charAt(0) == slash))
307         return path; /* UNC */
308     if (pl == 3)
309         return path; /* Absolute local */
310     if (pl == 0)
311         return getUserPath() + slashify(path); /* Completely relative */
312     if (pl == 1) { /* Drive-relative */
313         String JavaDoc up = getUserPath();
314         String JavaDoc ud = getDrive(up);
315         if (ud != null) return ud + path;
316         return up + path; /* User dir is a UNC path */
317     }
318     if (pl == 2) { /* Directory-relative */
319         String JavaDoc up = getUserPath();
320         String JavaDoc ud = getDrive(up);
321         if ((ud != null) && path.startsWith(ud))
322         return up + slashify(path.substring(2));
323         char drive = path.charAt(0);
324         String JavaDoc dir = getDriveDirectory(drive);
325         String JavaDoc np;
326         if (dir != null) {
327         /* When resolving a directory-relative path that refers to a
328            drive other than the current drive, insist that the caller
329            have read permission on the result */

330         String JavaDoc p = drive + (':' + dir + slashify(path.substring(2)));
331         SecurityManager JavaDoc security = System.getSecurityManager();
332         try {
333             if (security != null) security.checkRead(p);
334         } catch (SecurityException JavaDoc x) {
335             /* Don't disclose the drive's directory in the exception */
336             throw new SecurityException JavaDoc("Cannot resolve path " + path);
337         }
338         return p;
339         }
340         return drive + ":" + slashify(path.substring(2)); /* fake it */
341     }
342     throw new InternalError JavaDoc("Unresolvable path: " + path);
343     }
344
345     // Caches for canonicalization results to improve startup performance.
346
// The first cache handles repeated canonicalizations of the same path
347
// name. The prefix cache handles repeated canonicalizations within the
348
// same directory, and must not create results differing from the true
349
// canonicalization algorithm in canonicalize_md.c. For this reason the
350
// prefix cache is conservative and is not used for complex path names.
351
private ExpiringCache JavaDoc cache = new ExpiringCache JavaDoc();
352     private ExpiringCache JavaDoc prefixCache = new ExpiringCache JavaDoc();
353
354     public String JavaDoc canonicalize(String JavaDoc path) throws IOException JavaDoc {
355         // If path is a drive letter only then skip canonicalization
356
int len = path.length();
357         if ((len == 2) &&
358             (isLetter(path.charAt(0))) &&
359             (path.charAt(1) == ':')) {
360             char c = path.charAt(0);
361             if ((c >= 'A') && (c <= 'Z'))
362                 return path;
363             return "" + ((char) (c-32)) + ':';
364         } else if ((len == 3) &&
365                    (isLetter(path.charAt(0))) &&
366                    (path.charAt(1) == ':') &&
367                    (path.charAt(2) == '\\')) {
368             char c = path.charAt(0);
369             if ((c >= 'A') && (c <= 'Z'))
370                 return path;
371             return "" + ((char) (c-32)) + ':' + '\\';
372         }
373         if (!useCanonCaches) {
374             return canonicalize0(path);
375         } else {
376             String JavaDoc res = cache.get(path);
377             if (res == null) {
378         String JavaDoc dir = null;
379         String JavaDoc resDir = null;
380                 if (useCanonPrefixCache) {
381                     dir = parentOrNull(path);
382                     if (dir != null) {
383                         resDir = prefixCache.get(dir);
384                         if (resDir != null) {
385                             // Hit only in prefix cache; full path is canonical,
386
// but we need to get the canonical name of the file
387
// in this directory to get the appropriate capitalization
388
String JavaDoc filename = path.substring(1 + dir.length());
389                             res = canonicalizeWithPrefix(resDir, filename);
390                             cache.put(dir + File.separatorChar + filename, res);
391                         }
392                     }
393                 }
394                 if (res == null) {
395                     res = canonicalize0(path);
396                     cache.put(path, res);
397                     if (useCanonPrefixCache && dir != null) {
398                         resDir = parentOrNull(res);
399                         if (resDir != null) {
400                             File JavaDoc f = new File JavaDoc(res);
401                             if (f.exists() && !f.isDirectory()) {
402                                 prefixCache.put(dir, resDir);
403                             }
404                         }
405                     }
406                 }
407             }
408             assert canonicalize0(path).equalsIgnoreCase(res);
409             return res;
410         }
411     }
412
413     protected native String JavaDoc canonicalize0(String JavaDoc path)
414                                                 throws IOException JavaDoc;
415     protected String JavaDoc canonicalizeWithPrefix(String JavaDoc canonicalPrefix,
416                                             String JavaDoc filename) throws IOException JavaDoc
417     {
418         return canonicalizeWithPrefix0(canonicalPrefix,
419                                        canonicalPrefix + File.separatorChar + filename);
420     }
421     // Run the canonicalization operation assuming that the prefix
422
// (everything up to the last filename) is canonical; just gets
423
// the canonical name of the last element of the path
424
protected native String JavaDoc canonicalizeWithPrefix0(String JavaDoc canonicalPrefix,
425                                                     String JavaDoc pathWithCanonicalPrefix)
426                                                 throws IOException JavaDoc;
427     // Best-effort attempt to get parent of this path; used for
428
// optimization of filename canonicalization. This must return null for
429
// any cases where the code in canonicalize_md.c would throw an
430
// exception or otherwise deal with non-simple pathnames like handling
431
// of "." and "..". It may conservatively return null in other
432
// situations as well. Returning null will cause the underlying
433
// (expensive) canonicalization routine to be called.
434
static String JavaDoc parentOrNull(String JavaDoc path) {
435         if (path == null) return null;
436         char sep = File.separatorChar;
437         char altSep = '/';
438         int last = path.length() - 1;
439         int idx = last;
440         int adjacentDots = 0;
441         int nonDotCount = 0;
442         while (idx > 0) {
443             char c = path.charAt(idx);
444             if (c == '.') {
445                 if (++adjacentDots >= 2) {
446                     // Punt on pathnames containing . and ..
447
return null;
448                 }
449                 if (nonDotCount == 0) {
450                     // Punt on pathnames ending in a .
451
return null;
452                 }
453             } else if (c == sep) {
454                 if (adjacentDots == 1 && nonDotCount == 0) {
455                     // Punt on pathnames containing . and ..
456
return null;
457                 }
458                 if (idx == 0 ||
459                     idx >= last - 1 ||
460                     path.charAt(idx - 1) == sep ||
461                     path.charAt(idx - 1) == altSep) {
462                     // Punt on pathnames containing adjacent slashes
463
// toward the end
464
return null;
465                 }
466                 return path.substring(0, idx);
467             } else if (c == altSep) {
468                 // Punt on pathnames containing both backward and
469
// forward slashes
470
return null;
471             } else if (c == '*' || c == '?') {
472                 // Punt on pathnames containing wildcards
473
return null;
474             } else {
475                 ++nonDotCount;
476                 adjacentDots = 0;
477             }
478             --idx;
479         }
480         return null;
481     }
482
483
484     /* -- Attribute accessors -- */
485
486     public native int getBooleanAttributes(File JavaDoc f);
487     public native boolean checkAccess(File JavaDoc f, boolean write);
488     public native long getLastModifiedTime(File JavaDoc f);
489     public native long getLength(File JavaDoc f);
490
491
492     /* -- File operations -- */
493
494     public native boolean createFileExclusively(String JavaDoc path)
495     throws IOException JavaDoc;
496     public boolean delete(File JavaDoc f) {
497         // Keep canonicalization caches in sync after file deletion
498
// and renaming operations. Could be more clever than this
499
// (i.e., only remove/update affected entries) but probably
500
// not worth it since these entries expire after 30 seconds
501
// anyway.
502
cache.clear();
503         prefixCache.clear();
504         return delete0(f);
505     }
506     protected native boolean delete0(File JavaDoc f);
507     public synchronized native boolean deleteOnExit(File JavaDoc f);
508     public native String JavaDoc[] list(File JavaDoc f);
509     public native boolean createDirectory(File JavaDoc f);
510     public boolean rename(File JavaDoc f1, File JavaDoc f2) {
511         // Keep canonicalization caches in sync after file deletion
512
// and renaming operations. Could be more clever than this
513
// (i.e., only remove/update affected entries) but probably
514
// not worth it since these entries expire after 30 seconds
515
// anyway.
516
cache.clear();
517         prefixCache.clear();
518         return rename0(f1, f2);
519     }
520     protected native boolean rename0(File JavaDoc f1, File JavaDoc f2);
521     public native boolean setLastModifiedTime(File JavaDoc f, long time);
522     public native boolean setReadOnly(File JavaDoc f);
523
524
525     /* -- Filesystem interface -- */
526
527     private boolean access(String JavaDoc path) {
528     try {
529         SecurityManager JavaDoc security = System.getSecurityManager();
530         if (security != null) security.checkRead(path);
531         return true;
532     } catch (SecurityException JavaDoc x) {
533         return false;
534     }
535     }
536
537     private static native int listRoots0();
538
539     public File JavaDoc[] listRoots() {
540     int ds = listRoots0();
541     int n = 0;
542     for (int i = 0; i < 26; i++) {
543         if (((ds >> i) & 1) != 0) {
544         if (!access((char)('A' + i) + ":" + slash))
545             ds &= ~(1 << i);
546         else
547             n++;
548         }
549     }
550     File JavaDoc[] fs = new File JavaDoc[n];
551     int j = 0;
552     char slash = this.slash;
553     for (int i = 0; i < 26; i++) {
554         if (((ds >> i) & 1) != 0)
555         fs[j++] = new File JavaDoc((char)('A' + i) + ":" + slash);
556     }
557     return fs;
558     }
559
560
561     /* -- Basic infrastructure -- */
562
563     public int compare(File JavaDoc f1, File JavaDoc f2) {
564     return f1.getPath().compareToIgnoreCase(f2.getPath());
565     }
566
567     public int hashCode(File JavaDoc f) {
568     /* Could make this more efficient: String.hashCodeIgnoreCase */
569     return f.getPath().toLowerCase().hashCode() ^ 1234321;
570     }
571
572
573     private static native void initIDs();
574
575     static {
576     initIDs();
577     }
578
579 }
580
Popular Tags