001: /*
002: * @(#)Win32FileSystem.java 1.26 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: package java.io;
029:
030: import java.security.AccessController;
031: import sun.security.action.GetPropertyAction;
032:
033: class Win32FileSystem extends FileSystem {
034:
035: private final char slash;
036: private final char altSlash;
037: private final char semicolon;
038:
039: public Win32FileSystem() {
040: slash = ((String) AccessController
041: .doPrivileged(new GetPropertyAction("file.separator")))
042: .charAt(0);
043: semicolon = ((String) AccessController
044: .doPrivileged(new GetPropertyAction("path.separator")))
045: .charAt(0);
046: altSlash = (this .slash == '\\') ? '/' : '\\';
047: }
048:
049: private boolean isSlash(char c) {
050: return (c == '\\') || (c == '/');
051: }
052:
053: private boolean isLetter(char c) {
054: return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
055: }
056:
057: private String slashify(String p) {
058: if ((p.length() > 0) && (p.charAt(0) != slash))
059: return slash + p;
060: else
061: return p;
062: }
063:
064: /* -- Normalization and construction -- */
065:
066: public char getSeparator() {
067: return slash;
068: }
069:
070: public char getPathSeparator() {
071: return semicolon;
072: }
073:
074: /* A normal Win32 pathname contains no duplicate slashes, except possibly
075: for a UNC prefix, and does not end with a slash. It may be the empty
076: string. Normalized Win32 pathnames have the convenient property that
077: the length of the prefix almost uniquely identifies the type of the path
078: and whether it is absolute or relative:
079:
080: 0 relative to both drive and directory
081: 1 drive-relative (begins with '\\')
082: 2 absolute UNC (if first char is '\\'),
083: else directory-relative (has form "z:foo")
084: 3 absolute local pathname (begins with "z:\\")
085: */
086:
087: private int normalizePrefix(String path, int len, StringBuffer sb) {
088: int src = 0;
089: while ((src < len) && isSlash(path.charAt(src)))
090: src++;
091: char c;
092: if ((len - src >= 2) && isLetter(c = path.charAt(src))
093: && path.charAt(src + 1) == ':') {
094: /* Remove leading slashes if followed by drive specifier.
095: This is necessary to support file URLs containing drive
096: specifiers (e.g., "file://c:/path"). As a side effect,
097: "/c:/path" can be used as an alternative to "c:/path". */
098: sb.append(c);
099: sb.append(':');
100: src += 2;
101: } else {
102: src = 0;
103: if ((len >= 2) && isSlash(path.charAt(0))
104: && isSlash(path.charAt(1))) {
105: /* UNC pathname: Retain first slash; leave src pointed at
106: second slash so that further slashes will be collapsed
107: into the second slash. The result will be a pathname
108: beginning with "\\\\" followed (most likely) by a host
109: name. */
110: src = 1;
111: sb.append(slash);
112: }
113: }
114: return src;
115: }
116:
117: /* Normalize the given pathname, whose length is len, starting at the given
118: offset; everything before this offset is already normal. */
119: private String normalize(String path, int len, int off) {
120: if (len == 0)
121: return path;
122: if (off < 3)
123: off = 0; /* Avoid fencepost cases with UNC pathnames */
124: int src;
125: char slash = this .slash;
126: StringBuffer sb = new StringBuffer(len);
127:
128: if (off == 0) {
129: /* Complete normalization, including prefix */
130: src = normalizePrefix(path, len, sb);
131: } else {
132: /* Partial normalization */
133: src = off;
134: sb.append(path.substring(0, off));
135: }
136:
137: /* Remove redundant slashes from the remainder of the path, forcing all
138: slashes into the preferred slash */
139: while (src < len) {
140: char c = path.charAt(src++);
141: if (isSlash(c)) {
142: while ((src < len) && isSlash(path.charAt(src)))
143: src++;
144: if (src == len) {
145: /* Check for trailing separator */
146: int sn = sb.length();
147: if ((sn == 2) && (sb.charAt(1) == ':')) {
148: /* "z:\\" */
149: sb.append(slash);
150: break;
151: }
152: if (sn == 0) {
153: /* "\\" */
154: sb.append(slash);
155: break;
156: }
157: if ((sn == 1) && (isSlash(sb.charAt(0)))) {
158: /* "\\\\" is not collapsed to "\\" because "\\\\" marks
159: the beginning of a UNC pathname. Even though it is
160: not, by itself, a valid UNC pathname, we leave it as
161: is in order to be consistent with the win32 APIs,
162: which treat this case as an invalid UNC pathname
163: rather than as an alias for the root directory of
164: the current drive. */
165: sb.append(slash);
166: break;
167: }
168: /* Path does not denote a root directory, so do not append
169: trailing slash */
170: break;
171: } else {
172: sb.append(slash);
173: }
174: } else {
175: sb.append(c);
176: }
177: }
178:
179: String rv = sb.toString();
180: return rv;
181: }
182:
183: /* Check that the given pathname is normal. If not, invoke the real
184: normalizer on the part of the pathname that requires normalization.
185: This way we iterate through the whole pathname string only once. */
186: public String normalize(String path) {
187: int n = path.length();
188: char slash = this .slash;
189: char altSlash = this .altSlash;
190: char prev = 0;
191: for (int i = 0; i < n; i++) {
192: char c = path.charAt(i);
193: if (c == altSlash)
194: return normalize(path, n, (prev == slash) ? i - 1 : i);
195: if ((c == slash) && (prev == slash) && (i > 1))
196: return normalize(path, n, i - 1);
197: if ((c == ':') && (i > 1))
198: return normalize(path, n, 0);
199: prev = c;
200: }
201: if (prev == slash)
202: return normalize(path, n, n - 1);
203: return path;
204: }
205:
206: public int prefixLength(String path) {
207: char slash = this .slash;
208: int n = path.length();
209: if (n == 0)
210: return 0;
211: char c0 = path.charAt(0);
212: char c1 = (n > 1) ? path.charAt(1) : 0;
213: if (c0 == slash) {
214: if (c1 == slash)
215: return 2; /* Absolute UNC pathname "\\\\foo" */
216: return 1; /* Drive-relative "\\foo" */
217: }
218: if (isLetter(c0) && (c1 == ':')) {
219: if ((n > 2) && (path.charAt(2) == slash))
220: return 3; /* Absolute local pathname "z:\\foo" */
221: return 2; /* Directory-relative "z:foo" */
222: }
223: return 0; /* Completely relative */
224: }
225:
226: public String resolve(String parent, String child) {
227: char slash = this .slash;
228: int pn = parent.length();
229: if (pn == 0)
230: return child;
231: int cn = child.length();
232: if (cn == 0)
233: return parent;
234: String c = child;
235: if ((cn > 1) && (c.charAt(0) == slash)) {
236: if (c.charAt(1) == slash) {
237: /* Drop prefix when child is a UNC pathname */
238: c = c.substring(2);
239: } else {
240: /* Drop prefix when child is drive-relative */
241: c = c.substring(1);
242: }
243: }
244: String p = parent;
245: if (p.charAt(pn - 1) == slash)
246: p = p.substring(0, pn - 1);
247: return p + slashify(c);
248: }
249:
250: public String getDefaultParent() {
251: return ("" + slash);
252: }
253:
254: public String fromURIPath(String path) {
255: String p = path;
256: if ((p.length() > 2) && (p.charAt(2) == ':')) {
257: // "/c:/foo" --> "c:/foo"
258: p = p.substring(1);
259: // "c:/foo/" --> "c:/foo", but "c:/" --> "c:/"
260: if ((p.length() > 3) && p.endsWith("/"))
261: p = p.substring(0, p.length() - 1);
262: } else if ((p.length() > 1) && p.endsWith("/")) {
263: // "/foo/" --> "/foo"
264: p = p.substring(0, p.length() - 1);
265: }
266: return p;
267: }
268:
269: /* -- Path operations -- */
270:
271: public boolean isAbsolute(File f) {
272: int pl = f.getPrefixLength();
273: return (((pl == 2) && (f.getPath().charAt(0) == slash)) || (pl == 3));
274: }
275:
276: protected native String getDriveDirectory(int drive);
277:
278: private static String[] driveDirCache = new String[26];
279:
280: private static int driveIndex(char d) {
281: if ((d >= 'a') && (d <= 'z'))
282: return d - 'a';
283: if ((d >= 'A') && (d <= 'Z'))
284: return d - 'A';
285: return -1;
286: }
287:
288: private String getDriveDirectory(char drive) {
289: int i = driveIndex(drive);
290: if (i < 0)
291: return null;
292: String s = driveDirCache[i];
293: if (s != null)
294: return s;
295: s = getDriveDirectory(i + 1);
296: driveDirCache[i] = s;
297: return s;
298: }
299:
300: private String getUserPath() {
301: /* For both compatibility and security,
302: we must look this up every time */
303: return normalize(System.getProperty("user.dir"));
304: }
305:
306: String getDrive(String path) {
307: int pl = prefixLength(path);
308: return (pl == 3) ? path.substring(0, 2) : null;
309: }
310:
311: public String resolve(File f) {
312: String path = f.getPath();
313: int pl = f.getPrefixLength();
314: if ((pl == 2) && (path.charAt(0) == slash))
315: return path; /* UNC */
316: if (pl == 3)
317: return path; /* Absolute local */
318: if (pl == 0)
319: return getUserPath() + slashify(path); /* Completely relative */
320: if (pl == 1) { /* Drive-relative */
321: String up = getUserPath();
322: String ud = getDrive(up);
323: if (ud != null)
324: return ud + path;
325: return up + path; /* User dir is a UNC path */
326: }
327: if (pl == 2) { /* Directory-relative */
328: String up = getUserPath();
329: String ud = getDrive(up);
330: if ((ud != null) && path.startsWith(ud))
331: return up + slashify(path.substring(2));
332: char drive = path.charAt(0);
333: String dir = getDriveDirectory(drive);
334: String np;
335: if (dir != null) {
336: /* When resolving a directory-relative path that refers to a
337: drive other than the current drive, insist that the caller
338: have read permission on the result */
339: String p = drive
340: + (':' + dir + slashify(path.substring(2)));
341: SecurityManager security = System.getSecurityManager();
342: try {
343: if (security != null)
344: security.checkRead(p);
345: } catch (SecurityException x) {
346: /* Don't disclose the drive's directory in the exception */
347: throw new SecurityException("Cannot resolve path "
348: + path);
349: }
350: return p;
351: }
352: return drive + ":" + slashify(path.substring(2)); /* fake it */
353: }
354: throw new InternalError("Unresolvable path: " + path);
355: }
356:
357: // Caches for canonicalization results to improve startup performance.
358: // The first cache handles repeated canonicalizations of the same path
359: // name. The prefix cache handles repeated canonicalizations within the
360: // same directory, and must not create results differing from the true
361: // canonicalization algorithm in canonicalize_md.c. For this reason the
362: // prefix cache is conservative and is not used for complex path names.
363: private ExpiringCache cache = new ExpiringCache();
364: private ExpiringCache prefixCache = new ExpiringCache();
365:
366: public String canonicalize(String path) throws IOException {
367: // *** FIXME - temporary workaround for canonicalize problem FIXME
368: if (path.startsWith("\\\\")) {
369: path = path.substring(1);
370: }
371: // ***END FIXME - temporary workaround for canonicalize problem FIXME
372: if (!useCanonCaches) {
373: return canonicalize0(path);
374: } else {
375: String res = cache.get(path);
376: if (res == null) {
377: String dir = null;
378: String resDir = null;
379: if (useCanonPrefixCache) {
380: dir = parentOrNull(path);
381: if (dir != null) {
382: resDir = prefixCache.get(dir);
383: if (resDir != null) {
384: // Hit only in prefix cache; full path is canonical
385: String filename = path.substring(1 + dir
386: .length());
387: res = resDir + File.separatorChar
388: + filename;
389: cache.put(dir + File.separatorChar
390: + 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 f = new File(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 canonicalize0(String path)
414: throws IOException;
415:
416: // Best-effort attempt to get parent of this path; used for
417: // optimization of filename canonicalization. This must return null for
418: // any cases where the code in canonicalize_md.c would throw an
419: // exception or otherwise deal with non-simple pathnames like handling
420: // of "." and "..". It may conservatively return null in other
421: // situations as well. Returning null will cause the underlying
422: // (expensive) canonicalization routine to be called.
423: static String parentOrNull(String path) {
424: if (path == null)
425: return null;
426: char sep = File.separatorChar;
427: char altSep = '/';
428: int last = path.length() - 1;
429: int idx = last;
430: int adjacentDots = 0;
431: int nonDotCount = 0;
432: while (idx > 0) {
433: char c = path.charAt(idx);
434: if (c == '.') {
435: if (++adjacentDots >= 2) {
436: // Punt on pathnames containing . and ..
437: return null;
438: }
439: if (nonDotCount == 0) {
440: // Punt on pathnames ending in a .
441: return null;
442: }
443: } else if (c == sep) {
444: if (adjacentDots == 1 && nonDotCount == 0) {
445: // Punt on pathnames containing . and ..
446: return null;
447: }
448: if (idx == 0 || idx >= last - 1
449: || path.charAt(idx - 1) == sep
450: || path.charAt(idx - 1) == altSep) {
451: // Punt on pathnames containing adjacent slashes
452: // toward the end
453: return null;
454: }
455: return path.substring(0, idx);
456: } else if (c == altSep) {
457: // Punt on pathnames containing both backward and
458: // forward slashes
459: return null;
460: } else if (c == '*' || c == '?') {
461: // Punt on pathnames containing wildcards
462: return null;
463: } else {
464: ++nonDotCount;
465: adjacentDots = 0;
466: }
467: --idx;
468: }
469: return null;
470: }
471:
472: /* -- Attribute accessors -- */
473:
474: public native int getBooleanAttributes(File f);
475:
476: public native boolean checkAccess(File f, boolean write);
477:
478: public native long getLastModifiedTime(File f);
479:
480: public native long getLength(File f);
481:
482: /* -- File operations -- */
483:
484: public native boolean createFileExclusively(String path)
485: throws IOException;
486:
487: public boolean delete(File f) {
488: // Keep canonicalization caches in sync after file deletion
489: // and renaming operations. Could be more clever than this
490: // (i.e., only remove/update affected entries) but probably
491: // not worth it since these entries expire after 30 seconds
492: // anyway.
493: cache.clear();
494: prefixCache.clear();
495: return delete0(f);
496: }
497:
498: protected native boolean delete0(File f);
499:
500: public synchronized native boolean deleteOnExit(File f);
501:
502: public native String[] list(File f);
503:
504: public native boolean createDirectory(File f);
505:
506: public boolean rename(File f1, File f2) {
507: // Keep canonicalization caches in sync after file deletion
508: // and renaming operations. Could be more clever than this
509: // (i.e., only remove/update affected entries) but probably
510: // not worth it since these entries expire after 30 seconds
511: // anyway.
512: cache.clear();
513: prefixCache.clear();
514: return rename0(f1, f2);
515: }
516:
517: protected native boolean rename0(File f1, File f2);
518:
519: public native boolean setLastModifiedTime(File f, long time);
520:
521: public native boolean setReadOnly(File f);
522:
523: /* -- Filesystem interface -- */
524:
525: private boolean access(String path) {
526: try {
527: SecurityManager security = System.getSecurityManager();
528: if (security != null)
529: security.checkRead(path);
530: return true;
531: } catch (SecurityException x) {
532: return false;
533: }
534: }
535:
536: private static native int listRoots0();
537:
538: public File[] listRoots() {
539: int ds = listRoots0();
540: int n = 0;
541: for (int i = 0; i < 26; i++) {
542: if (((ds >> i) & 1) != 0) {
543: if (!access((char) ('A' + i) + ":" + slash))
544: ds &= ~(1 << i);
545: else
546: n++;
547: }
548: }
549: File[] fs = new File[n];
550: int j = 0;
551: char slash = this .slash;
552: for (int i = 0; i < 26; i++) {
553: if (((ds >> i) & 1) != 0)
554: fs[j++] = new File((char) ('A' + i) + ":" + slash);
555: }
556: return fs;
557: }
558:
559: /* -- Basic infrastructure -- */
560:
561: public int compare(File f1, File f2) {
562: return f1.getPath().compareToIgnoreCase(f2.getPath());
563: }
564:
565: public int hashCode(File f) {
566: /* Could make this more efficient: String.hashCodeIgnoreCase */
567: return f.getPath().toLowerCase().hashCode() ^ 1234321;
568: }
569:
570: private static native void initIDs();
571:
572: static {
573: initIDs();
574: }
575:
576: }
|