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