1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18// BEGIN android-note
19// We've dropped Windows support, except where it's exposed: we still support
20// non-Unix separators in serialized File objects, for example, but we don't
21// have any code for UNC paths or case-insensitivity.
22// We've also changed the JNI interface to better match what the Java actually wants.
23// (The JNI implementation is also much simpler.)
24// Some methods have been rewritten to reduce unnecessary allocation.
25// Some duplication has been factored out.
26// END android-note
27
28package java.io;
29
30import java.net.URI;
31import java.net.URISyntaxException;
32import java.net.URL;
33import java.nio.ByteBuffer;
34import java.nio.charset.Charset;
35import java.security.AccessController;
36import java.security.SecureRandom;
37import java.util.ArrayList;
38import java.util.List;
39
40import org.apache.harmony.luni.util.DeleteOnExit;
41import org.apache.harmony.luni.util.Msg;
42import org.apache.harmony.luni.util.PriviAction;
43import org.apache.harmony.luni.util.Util;
44
45/**
46 * An "abstract" representation of a file system entity identified by a
47 * pathname. The pathname may be absolute (relative to the root directory
48 * of the file system) or relative to the current directory in which the program
49 * is running.
50 * <p>
51 * The actual file referenced by a {@code File} may or may not exist. It may
52 * also, despite the name {@code File}, be a directory or other non-regular
53 * file.
54 * <p>
55 * This class provides limited functionality for getting/setting file
56 * permissions, file type, and last modified time.
57 * <p>
58 * Although Java doesn't specify a character encoding for filenames, on Android
59 * Java strings are converted to UTF-8 byte sequences when sending filenames to
60 * the operating system, and byte sequences returned by the operating system
61 * (from the various {@code list} methods) are converted to Java strings by
62 * decoding them as UTF-8 byte sequences.
63 *
64 * @see java.io.Serializable
65 * @see java.lang.Comparable
66 */
67public class File implements Serializable, Comparable<File> {
68
69    private static final long serialVersionUID = 301077366599181567L;
70
71    private static final String EMPTY_STRING = ""; //$NON-NLS-1$
72
73    // Caches the UTF-8 Charset for newCString.
74    private static final Charset UTF8 = Charset.forName("UTF-8");
75
76    /**
77     * The system dependent file separator character.
78     * This field is initialized from the system property "file.separator".
79     * Later changes to that property will have no effect on this field or this class.
80     */
81    public static final char separatorChar;
82
83    /**
84     * The system dependent file separator string.
85     * This field is a single-character string equal to String.valueOf(separatorChar).
86     */
87    public static final String separator;
88
89    /**
90     * The system dependent path separator character.
91     * This field is initialized from the system property "path.separator".
92     * Later changes to that property will have no effect on this field or this class.
93     */
94    public static final char pathSeparatorChar;
95
96    /**
97     * The system dependent path separator string.
98     * This field is a single-character string equal to String.valueOf(pathSeparatorChar).
99     */
100    public static final String pathSeparator;
101
102    /* Temp file counter */
103    private static int counter;
104
105    /**
106     * The path we return from getPath. This is almost the path we were
107     * given, but without duplicate adjacent slashes and without trailing
108     * slashes (except for the special case of the root directory). This
109     * path may be the empty string.
110     */
111    private String path;
112
113    /**
114     * The cached UTF-8 byte sequence corresponding to 'path'.
115     * This is suitable for direct use by our JNI, and includes a trailing NUL.
116     * For non-absolute paths, the "user.dir" property is prepended: that is,
117     * this byte sequence usually represents an absolute path (the exception
118     * being if the user overwrites the "user.dir" property with a non-absolute
119     * path).
120     */
121    transient byte[] pathBytes;
122
123    static {
124        // The default protection domain grants access to these properties.
125        separatorChar = System.getProperty("file.separator", "/").charAt(0); //$NON-NLS-1$ //$NON-NLS-2$
126        pathSeparatorChar = System.getProperty("path.separator", ":").charAt(0); //$NON-NLS-1$//$NON-NLS-2$
127        separator = String.valueOf(separatorChar);
128        pathSeparator = String.valueOf(pathSeparatorChar);
129    }
130
131    /**
132     * Constructs a new file using the specified directory and name.
133     *
134     * @param dir
135     *            the directory where the file is stored.
136     * @param name
137     *            the file's name.
138     * @throws NullPointerException
139     *             if {@code name} is {@code null}.
140     */
141    public File(File dir, String name) {
142        this(dir == null ? null : dir.getPath(), name);
143    }
144
145    /**
146     * Constructs a new file using the specified path.
147     *
148     * @param path
149     *            the path to be used for the file.
150     */
151    public File(String path) {
152        init(path);
153    }
154
155    /**
156     * Constructs a new File using the specified directory path and file name,
157     * placing a path separator between the two.
158     *
159     * @param dirPath
160     *            the path to the directory where the file is stored.
161     * @param name
162     *            the file's name.
163     * @throws NullPointerException
164     *             if {@code name} is {@code null}.
165     */
166    public File(String dirPath, String name) {
167        if (name == null) {
168            throw new NullPointerException();
169        }
170        if (dirPath == null || dirPath.length() == 0) {
171            init(name);
172        } else if (name.length() == 0) {
173            init(dirPath);
174        } else {
175            init(join(dirPath, name));
176        }
177    }
178
179    /**
180     * Constructs a new File using the path of the specified URI. {@code uri}
181     * needs to be an absolute and hierarchical Unified Resource Identifier with
182     * file scheme and non-empty path component, but with undefined authority,
183     * query or fragment components.
184     *
185     * @param uri
186     *            the Unified Resource Identifier that is used to construct this
187     *            file.
188     * @throws IllegalArgumentException
189     *             if {@code uri} does not comply with the conditions above.
190     * @see #toURI
191     * @see java.net.URI
192     */
193    public File(URI uri) {
194        // check pre-conditions
195        checkURI(uri);
196        init(uri.getPath());
197    }
198
199    private void init(String dirtyPath) {
200        // Keep a copy of the cleaned-up string path.
201        this.path = fixSlashes(dirtyPath);
202        // Cache the UTF-8 bytes we need for the JNI.
203        // TODO: we shouldn't do this caching at all; the RI demonstrably doesn't.
204        if (path.length() > 0 && path.charAt(0) == separatorChar) { // http://b/2486943
205            this.pathBytes = newCString(path);
206            return;
207        }
208        String userDir = AccessController.doPrivileged(
209            new PriviAction<String>("user.dir")); //$NON-NLS-1$
210        this.pathBytes = newCString(path.length() == 0 ? userDir : join(userDir, path));
211    }
212
213    private byte[] newCString(String s) {
214        ByteBuffer buffer = UTF8.encode(s);
215        // Add a trailing NUL, because this byte[] is going to be used as a char*.
216        int byteCount = buffer.limit() + 1;
217        byte[] bytes = new byte[byteCount];
218        buffer.get(bytes, 0, byteCount - 1);
219        // This is an awful mistake, because '\' is a perfectly acceptable
220        // character on Linux/Android. But we've shipped so many versions
221        // that behaved like this, I'm too scared to change it.
222        for (int i = 0; i < bytes.length; ++i) {
223            if (bytes[i] == '\\') {
224                bytes[i] = '/';
225            }
226        }
227        return bytes;
228    }
229
230    // Removes duplicate adjacent slashes and any trailing slash.
231    private String fixSlashes(String origPath) {
232        // Remove duplicate adjacent slashes.
233        boolean lastWasSlash = false;
234        char[] newPath = origPath.toCharArray();
235        int length = newPath.length;
236        int newLength = 0;
237        for (int i = 0; i < length; ++i) {
238            char ch = newPath[i];
239            if (ch == '/') {
240                if (!lastWasSlash) {
241                    newPath[newLength++] = separatorChar;
242                    lastWasSlash = true;
243                }
244            } else {
245                newPath[newLength++] = ch;
246                lastWasSlash = false;
247            }
248        }
249        // Remove any trailing slash (unless this is the root of the file system).
250        if (lastWasSlash && newLength > 1) {
251            newLength--;
252        }
253        // Reuse the original string if possible.
254        return (newLength != length) ? new String(newPath, 0, newLength) : origPath;
255    }
256
257    // Joins two path components, adding a separator only if necessary.
258    private String join(String prefix, String suffix) {
259        int prefixLength = prefix.length();
260        boolean haveSlash = (prefixLength > 0 && prefix.charAt(prefixLength - 1) == separatorChar);
261        if (!haveSlash) {
262            haveSlash = (suffix.length() > 0 && suffix.charAt(0) == separatorChar);
263        }
264        return haveSlash ? (prefix + suffix) : (prefix + separatorChar + suffix);
265    }
266
267    @SuppressWarnings("nls")
268    private void checkURI(URI uri) {
269        if (!uri.isAbsolute()) {
270            throw new IllegalArgumentException(Msg.getString("K031a", uri));
271        } else if (!uri.getRawSchemeSpecificPart().startsWith("/")) {
272            throw new IllegalArgumentException(Msg.getString("K031b", uri));
273        }
274
275        String temp = uri.getScheme();
276        if (temp == null || !temp.equals("file")) {
277            throw new IllegalArgumentException(Msg.getString("K031c", uri));
278        }
279
280        temp = uri.getRawPath();
281        if (temp == null || temp.length() == 0) {
282            throw new IllegalArgumentException(Msg.getString("K031d", uri));
283        }
284
285        if (uri.getRawAuthority() != null) {
286            throw new IllegalArgumentException(Msg.getString("K031e",
287                    new String[] { "authority", uri.toString() }));
288        }
289
290        if (uri.getRawQuery() != null) {
291            throw new IllegalArgumentException(Msg.getString("K031e",
292                    new String[] { "query", uri.toString() }));
293        }
294
295        if (uri.getRawFragment() != null) {
296            throw new IllegalArgumentException(Msg.getString("K031e",
297                    new String[] { "fragment", uri.toString() }));
298        }
299    }
300
301    /**
302     * Lists the file system roots. The Java platform may support zero or more
303     * file systems, each with its own platform-dependent root. Further, the
304     * canonical pathname of any file on the system will always begin with one
305     * of the returned file system roots.
306     *
307     * @return the array of file system roots.
308     */
309    public static File[] listRoots() {
310        return new File[] { new File("/") };
311    }
312
313    /**
314     * Indicates whether the current context is allowed to read from this file.
315     *
316     * @return {@code true} if this file can be read, {@code false} otherwise.
317     * @throws SecurityException
318     *             if a {@code SecurityManager} is installed and it denies the
319     *             read request.
320     */
321    public boolean canRead() {
322        if (path.length() == 0) {
323            return false;
324        }
325        SecurityManager security = System.getSecurityManager();
326        if (security != null) {
327            security.checkRead(path);
328        }
329        return isReadableImpl(pathBytes);
330    }
331
332    private native boolean isReadableImpl(byte[] filePath);
333
334    /**
335     * Indicates whether the current context is allowed to write to this file.
336     *
337     * @return {@code true} if this file can be written, {@code false}
338     *         otherwise.
339     * @throws SecurityException
340     *             if a {@code SecurityManager} is installed and it denies the
341     *             write request.
342     */
343    public boolean canWrite() {
344        if (path.length() == 0) {
345            return false;
346        }
347        SecurityManager security = System.getSecurityManager();
348        if (security != null) {
349            security.checkWrite(path);
350        }
351        return isWritableImpl(pathBytes);
352    }
353
354    private native boolean isWritableImpl(byte[] filePath);
355
356    /**
357     * Returns the relative sort ordering of the paths for this file and the
358     * file {@code another}. The ordering is platform dependent.
359     *
360     * @param another
361     *            a file to compare this file to
362     * @return an int determined by comparing the two paths. Possible values are
363     *         described in the Comparable interface.
364     * @see Comparable
365     */
366    public int compareTo(File another) {
367        return this.getPath().compareTo(another.getPath());
368    }
369
370    /**
371     * Deletes this file. Directories must be empty before they will be deleted.
372     *
373     * @return {@code true} if this file was deleted, {@code false} otherwise.
374     * @throws SecurityException
375     *             if a {@code SecurityManager} is installed and it denies the
376     *             request.
377     * @see java.lang.SecurityManager#checkDelete
378     */
379    public boolean delete() {
380        if (path.length() == 0) {
381            return false;
382        }
383        SecurityManager security = System.getSecurityManager();
384        if (security != null) {
385            security.checkDelete(path);
386        }
387        return deleteImpl(pathBytes);
388    }
389
390    private native boolean deleteImpl(byte[] filePath);
391
392    /**
393     * Schedules this file to be automatically deleted once the virtual machine
394     * terminates. This will only happen when the virtual machine terminates
395     * normally as described by the Java Language Specification section 12.9.
396     *
397     * @throws SecurityException
398     *             if a {@code SecurityManager} is installed and it denies the
399     *             request.
400     */
401    public void deleteOnExit() {
402        SecurityManager security = System.getSecurityManager();
403        if (security != null) {
404            security.checkDelete(path);
405        }
406        DeleteOnExit.getInstance().addFile(getAbsoluteName());
407    }
408
409    /**
410     * Compares {@code obj} to this file and returns {@code true} if they
411     * represent the <em>same</em> object using a path specific comparison.
412     *
413     * @param obj
414     *            the object to compare this file with.
415     * @return {@code true} if {@code obj} is the same as this object,
416     *         {@code false} otherwise.
417     */
418    @Override
419    public boolean equals(Object obj) {
420        if (!(obj instanceof File)) {
421            return false;
422        }
423        return path.equals(((File) obj).getPath());
424    }
425
426    /**
427     * Returns a boolean indicating whether this file can be found on the
428     * underlying file system.
429     *
430     * @return {@code true} if this file exists, {@code false} otherwise.
431     * @throws SecurityException
432     *             if a {@code SecurityManager} is installed and it denies read
433     *             access to this file.
434     * @see #getPath
435     * @see java.lang.SecurityManager#checkRead(FileDescriptor)
436     */
437    public boolean exists() {
438        if (path.length() == 0) {
439            return false;
440        }
441        SecurityManager security = System.getSecurityManager();
442        if (security != null) {
443            security.checkRead(path);
444        }
445        return existsImpl(pathBytes);
446    }
447
448    private native boolean existsImpl(byte[] filePath);
449
450    /**
451     * Returns the absolute path of this file.
452     *
453     * @return the absolute file path.
454     */
455    public String getAbsolutePath() {
456        return Util.toUTF8String(pathBytes, 0, pathBytes.length - 1);
457    }
458
459    /**
460     * Returns a new file constructed using the absolute path of this file.
461     *
462     * @return a new file from this file's absolute path.
463     * @see java.lang.SecurityManager#checkPropertyAccess
464     */
465    public File getAbsoluteFile() {
466        return new File(this.getAbsolutePath());
467    }
468
469    /**
470     * Returns the absolute path of this file with all references resolved. An
471     * <em>absolute</em> path is one that begins at the root of the file
472     * system. The canonical path is one in which all references have been
473     * resolved. For the cases of '..' and '.', where the file system supports
474     * parent and working directory respectively, these are removed and replaced
475     * with a direct directory reference. If the file does not exist,
476     * getCanonicalPath() may not resolve any references and simply returns an
477     * absolute path name or throws an IOException.
478     *
479     * @return the canonical path of this file.
480     * @throws IOException
481     *             if an I/O error occurs.
482     */
483    public String getCanonicalPath() throws IOException {
484        // BEGIN android-removed
485        //     Caching the canonical path is bogus. Users facing specific
486        //     performance problems can perform their own caching, with
487        //     eviction strategies that are appropriate for their application.
488        //     A VM-wide cache with no mechanism to evict stale elements is a
489        //     disservice to applications that need up-to-date data.
490        // String canonPath = FileCanonPathCache.get(absPath);
491        // if (canonPath != null) {
492        //     return canonPath;
493        // }
494        // END android-removed
495
496        byte[] result = pathBytes;
497        if(separatorChar == '/') {
498            // resolve the full path first
499            result = resolveLink(result, result.length, false);
500            // resolve the parent directories
501            result = resolve(result);
502        }
503        int numSeparators = 1;
504        for (int i = 0; i < result.length; i++) {
505            if (result[i] == separatorChar) {
506                numSeparators++;
507            }
508        }
509        int sepLocations[] = new int[numSeparators];
510        int rootLoc = 0;
511        if (separatorChar != '/') {
512            if (result[0] == '\\') {
513                rootLoc = (result.length > 1 && result[1] == '\\') ? 1 : 0;
514            } else {
515                rootLoc = 2; // skip drive i.e. c:
516            }
517        }
518        byte newResult[] = new byte[result.length + 1];
519        int newLength = 0, lastSlash = 0, foundDots = 0;
520        sepLocations[lastSlash] = rootLoc;
521        for (int i = 0; i <= result.length; i++) {
522            if (i < rootLoc) {
523                newResult[newLength++] = result[i];
524            } else {
525                if (i == result.length || result[i] == separatorChar) {
526                    if (i == result.length && foundDots == 0) {
527                        break;
528                    }
529                    if (foundDots == 1) {
530                        /* Don't write anything, just reset and continue */
531                        foundDots = 0;
532                        continue;
533                    }
534                    if (foundDots > 1) {
535                        /* Go back N levels */
536                        lastSlash = lastSlash > (foundDots - 1) ? lastSlash
537                                - (foundDots - 1) : 0;
538                        newLength = sepLocations[lastSlash] + 1;
539                        foundDots = 0;
540                        continue;
541                    }
542                    sepLocations[++lastSlash] = newLength;
543                    newResult[newLength++] = (byte) separatorChar;
544                    continue;
545                }
546                if (result[i] == '.') {
547                    foundDots++;
548                    continue;
549                }
550                /* Found some dots within text, write them out */
551                if (foundDots > 0) {
552                    for (int j = 0; j < foundDots; j++) {
553                        newResult[newLength++] = (byte) '.';
554                    }
555                }
556                newResult[newLength++] = result[i];
557                foundDots = 0;
558            }
559        }
560        // remove trailing slash
561        if (newLength > (rootLoc + 1)
562                && newResult[newLength - 1] == separatorChar) {
563            newLength--;
564        }
565        newResult[newLength] = 0;
566        newResult = getCanonImpl(newResult);
567        newLength = newResult.length;
568
569        // BEGIN android-changed
570        //     caching the canonical path is completely bogus
571        return Util.toUTF8String(newResult, 0, newLength);
572        // FileCanonPathCache.put(absPath, canonPath);
573        // return canonPath;
574        // END android-changed
575    }
576
577    /*
578     * Resolve symbolic links in the parent directories.
579     */
580    private byte[] resolve(byte[] newResult) throws IOException {
581        int last = 1, nextSize, linkSize;
582        byte[] linkPath = newResult, bytes;
583        boolean done, inPlace;
584        for (int i = 1; i <= newResult.length; i++) {
585            if (i == newResult.length || newResult[i] == separatorChar) {
586                done = i >= newResult.length - 1;
587                // if there is only one segment, do nothing
588                if (done && linkPath.length == 1) {
589                    return newResult;
590                }
591                inPlace = false;
592                if (linkPath == newResult) {
593                    bytes = newResult;
594                    // if there are no symbolic links, terminate the C string
595                    // instead of copying
596                    if (!done) {
597                        inPlace = true;
598                        newResult[i] = '\0';
599                    }
600                } else {
601                    nextSize = i - last + 1;
602                    linkSize = linkPath.length;
603                    if (linkPath[linkSize - 1] == separatorChar) {
604                        linkSize--;
605                    }
606                    bytes = new byte[linkSize + nextSize];
607                    System.arraycopy(linkPath, 0, bytes, 0, linkSize);
608                    System.arraycopy(newResult, last - 1, bytes, linkSize,
609                            nextSize);
610                    // the full path has already been resolved
611                }
612                if (done) {
613                    return bytes;
614                }
615                linkPath = resolveLink(bytes, inPlace ? i : bytes.length, true);
616                if (inPlace) {
617                    newResult[i] = '/';
618                }
619                last = i + 1;
620            }
621        }
622        throw new InternalError();
623    }
624
625    /*
626     * Resolve a symbolic link. While the path resolves to an existing path,
627     * keep resolving. If an absolute link is found, resolve the parent
628     * directories if resolveAbsolute is true.
629     */
630    private byte[] resolveLink(byte[] pathBytes, int length,
631            boolean resolveAbsolute) throws IOException {
632        boolean restart = false;
633        byte[] linkBytes, temp;
634        do {
635            linkBytes = getLinkImpl(pathBytes);
636            if (linkBytes == pathBytes) {
637                break;
638            }
639            if (linkBytes[0] == separatorChar) {
640                // link to an absolute path, if resolving absolute paths,
641                // resolve the parent dirs again
642                restart = resolveAbsolute;
643                pathBytes = linkBytes;
644            } else {
645                int last = length - 1;
646                while (pathBytes[last] != separatorChar) {
647                    last--;
648                }
649                last++;
650                temp = new byte[last + linkBytes.length];
651                System.arraycopy(pathBytes, 0, temp, 0, last);
652                System.arraycopy(linkBytes, 0, temp, last, linkBytes.length);
653                pathBytes = temp;
654            }
655            length = pathBytes.length;
656        } while (existsImpl(pathBytes));
657        // resolve the parent directories
658        if (restart) {
659            return resolve(pathBytes);
660        }
661        return pathBytes;
662    }
663
664    private native byte[] getLinkImpl(byte[] filePath);
665
666    /**
667     * Returns a new file created using the canonical path of this file.
668     * Equivalent to {@code new File(this.getCanonicalPath())}.
669     *
670     * @return the new file constructed from this file's canonical path.
671     * @throws IOException
672     *             if an I/O error occurs.
673     * @see java.lang.SecurityManager#checkPropertyAccess
674     */
675    public File getCanonicalFile() throws IOException {
676        return new File(getCanonicalPath());
677    }
678
679    private native byte[] getCanonImpl(byte[] filePath);
680
681    /**
682     * Returns the name of the file or directory represented by this file.
683     *
684     * @return this file's name or an empty string if there is no name part in
685     *         the file's path.
686     */
687    public String getName() {
688        int separatorIndex = path.lastIndexOf(separator);
689        return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1,
690                path.length());
691    }
692
693    /**
694     * Returns the pathname of the parent of this file. This is the path up to
695     * but not including the last name. {@code null} is returned if there is no
696     * parent.
697     *
698     * @return this file's parent pathname or {@code null}.
699     */
700    public String getParent() {
701        int length = path.length(), firstInPath = 0;
702        if (separatorChar == '\\' && length > 2 && path.charAt(1) == ':') {
703            firstInPath = 2;
704        }
705        int index = path.lastIndexOf(separatorChar);
706        if (index == -1 && firstInPath > 0) {
707            index = 2;
708        }
709        if (index == -1 || path.charAt(length - 1) == separatorChar) {
710            return null;
711        }
712        if (path.indexOf(separatorChar) == index
713                && path.charAt(firstInPath) == separatorChar) {
714            return path.substring(0, index + 1);
715        }
716        return path.substring(0, index);
717    }
718
719    /**
720     * Returns a new file made from the pathname of the parent of this file.
721     * This is the path up to but not including the last name. {@code null} is
722     * returned when there is no parent.
723     *
724     * @return a new file representing this file's parent or {@code null}.
725     */
726    public File getParentFile() {
727        String tempParent = getParent();
728        if (tempParent == null) {
729            return null;
730        }
731        return new File(tempParent);
732    }
733
734    /**
735     * Returns the path of this file.
736     *
737     * @return this file's path.
738     */
739    public String getPath() {
740        return path;
741    }
742
743    /**
744     * Returns an integer hash code for the receiver. Any two objects for which
745     * {@code equals} returns {@code true} must return the same hash code.
746     *
747     * @return this files's hash value.
748     * @see #equals
749     */
750    @Override
751    public int hashCode() {
752        return getPath().hashCode() ^ 1234321;
753    }
754
755    /**
756     * Indicates if this file's pathname is absolute. Whether a pathname is
757     * absolute is platform specific. On Android, absolute paths start with
758     * the character '/'.
759     *
760     * @return {@code true} if this file's pathname is absolute, {@code false}
761     *         otherwise.
762     * @see #getPath
763     */
764    public boolean isAbsolute() {
765        return path.length() > 0 && path.charAt(0) == separatorChar;
766    }
767
768    /**
769     * Indicates if this file represents a <em>directory</em> on the
770     * underlying file system.
771     *
772     * @return {@code true} if this file is a directory, {@code false}
773     *         otherwise.
774     * @throws SecurityException
775     *             if a {@code SecurityManager} is installed and it denies read
776     *             access to this file.
777     */
778    public boolean isDirectory() {
779        if (path.length() == 0) {
780            return false;
781        }
782        SecurityManager security = System.getSecurityManager();
783        if (security != null) {
784            security.checkRead(path);
785        }
786        return isDirectoryImpl(pathBytes);
787    }
788
789    private native boolean isDirectoryImpl(byte[] filePath);
790
791    /**
792     * Indicates if this file represents a <em>file</em> on the underlying
793     * file system.
794     *
795     * @return {@code true} if this file is a file, {@code false} otherwise.
796     * @throws SecurityException
797     *             if a {@code SecurityManager} is installed and it denies read
798     *             access to this file.
799     */
800    public boolean isFile() {
801        if (path.length() == 0) {
802            return false;
803        }
804        SecurityManager security = System.getSecurityManager();
805        if (security != null) {
806            security.checkRead(path);
807        }
808        return isFileImpl(pathBytes);
809    }
810
811    private native boolean isFileImpl(byte[] filePath);
812
813    /**
814     * Returns whether or not this file is a hidden file as defined by the
815     * operating system. The notion of "hidden" is system-dependent. For Unix
816     * systems a file is considered hidden if its name starts with a ".". For
817     * Windows systems there is an explicit flag in the file system for this
818     * purpose.
819     *
820     * @return {@code true} if the file is hidden, {@code false} otherwise.
821     * @throws SecurityException
822     *             if a {@code SecurityManager} is installed and it denies read
823     *             access to this file.
824     */
825    public boolean isHidden() {
826        if (path.length() == 0) {
827            return false;
828        }
829        SecurityManager security = System.getSecurityManager();
830        if (security != null) {
831            security.checkRead(path);
832        }
833        return getName().startsWith(".");
834    }
835
836    /**
837     * Returns the time when this file was last modified, measured in
838     * milliseconds since January 1st, 1970, midnight.
839     * Returns 0 if the file does not exist.
840     *
841     * @return the time when this file was last modified.
842     * @throws SecurityException
843     *             if a {@code SecurityManager} is installed and it denies read
844     *             access to this file.
845     */
846    public long lastModified() {
847        if (path.length() == 0) {
848            return 0;
849        }
850        SecurityManager security = System.getSecurityManager();
851        if (security != null) {
852            security.checkRead(path);
853        }
854        return lastModifiedImpl(pathBytes);
855    }
856
857    private native long lastModifiedImpl(byte[] filePath);
858
859    /**
860     * Sets the time this file was last modified, measured in milliseconds since
861     * January 1st, 1970, midnight.
862     *
863     * @param time
864     *            the last modification time for this file.
865     * @return {@code true} if the operation is successful, {@code false}
866     *         otherwise.
867     * @throws IllegalArgumentException
868     *             if {@code time < 0}.
869     * @throws SecurityException
870     *             if a {@code SecurityManager} is installed and it denies write
871     *             access to this file.
872     */
873    public boolean setLastModified(long time) {
874        if (path.length() == 0) {
875            return false;
876        }
877        if (time < 0) {
878            throw new IllegalArgumentException(Msg.getString("K006a")); //$NON-NLS-1$
879        }
880        SecurityManager security = System.getSecurityManager();
881        if (security != null) {
882            security.checkWrite(path);
883        }
884        return setLastModifiedImpl(pathBytes, time);
885    }
886
887    private native boolean setLastModifiedImpl(byte[] path, long time);
888
889    /**
890     * Marks this file or directory to be read-only as defined by the operating
891     * system.
892     *
893     * @return {@code true} if the operation is successful, {@code false}
894     *         otherwise.
895     * @throws SecurityException
896     *             if a {@code SecurityManager} is installed and it denies write
897     *             access to this file.
898     */
899    public boolean setReadOnly() {
900        if (path.length() == 0) {
901            return false;
902        }
903        SecurityManager security = System.getSecurityManager();
904        if (security != null) {
905            security.checkWrite(path);
906        }
907        return setReadOnlyImpl(pathBytes);
908    }
909
910    private native boolean setReadOnlyImpl(byte[] path);
911
912    /**
913     * Returns the length of this file in bytes.
914     * Returns 0 if the file does not exist.
915     * The result for a directory is not defined.
916     *
917     * @return the number of bytes in this file.
918     * @throws SecurityException
919     *             if a {@code SecurityManager} is installed and it denies read
920     *             access to this file.
921     */
922    public long length() {
923        SecurityManager security = System.getSecurityManager();
924        if (security != null) {
925            security.checkRead(path);
926        }
927        return lengthImpl(pathBytes);
928    }
929
930    private native long lengthImpl(byte[] filePath);
931
932    /**
933     * Returns an array of strings with the file names in the directory
934     * represented by this file. The result is {@code null} if this file is not
935     * a directory.
936     * <p>
937     * The entries {@code .} and {@code ..} representing the current and parent
938     * directory are not returned as part of the list.
939     *
940     * @return an array of strings with file names or {@code null}.
941     * @throws SecurityException
942     *             if a {@code SecurityManager} is installed and it denies read
943     *             access to this file.
944     * @see #isDirectory
945     * @see java.lang.SecurityManager#checkRead(FileDescriptor)
946     */
947    public String[] list() {
948        SecurityManager security = System.getSecurityManager();
949        if (security != null) {
950            security.checkRead(path);
951        }
952        if (path.length() == 0) {
953            return null;
954        }
955        return listImpl(pathBytes);
956    }
957
958    private native String[] listImpl(byte[] path);
959
960    /**
961     * Gets a list of the files in the directory represented by this file. This
962     * list is then filtered through a FilenameFilter and the names of files
963     * with matching names are returned as an array of strings. Returns
964     * {@code null} if this file is not a directory. If {@code filter} is
965     * {@code null} then all filenames match.
966     * <p>
967     * The entries {@code .} and {@code ..} representing the current and parent
968     * directories are not returned as part of the list.
969     *
970     * @param filter
971     *            the filter to match names against, may be {@code null}.
972     * @return an array of files or {@code null}.
973     * @throws SecurityException
974     *             if a {@code SecurityManager} is installed and it denies read
975     *             access to this file.
976     * @see #getPath
977     * @see #isDirectory
978     * @see java.lang.SecurityManager#checkRead(FileDescriptor)
979     */
980    public String[] list(FilenameFilter filter) {
981        String[] filenames = list();
982        if (filter == null || filenames == null) {
983            return filenames;
984        }
985        List<String> result = new ArrayList<String>(filenames.length);
986        for (String filename : filenames) {
987            if (filter.accept(this, filename)) {
988                result.add(filename);
989            }
990        }
991        return result.toArray(new String[result.size()]);
992    }
993
994    /**
995     * Returns an array of files contained in the directory represented by this
996     * file. The result is {@code null} if this file is not a directory. The
997     * paths of the files in the array are absolute if the path of this file is
998     * absolute, they are relative otherwise.
999     *
1000     * @return an array of files or {@code null}.
1001     * @throws SecurityException
1002     *             if a {@code SecurityManager} is installed and it denies read
1003     *             access to this file.
1004     * @see #list
1005     * @see #isDirectory
1006     */
1007    public File[] listFiles() {
1008        return filenamesToFiles(list());
1009    }
1010
1011    /**
1012     * Gets a list of the files in the directory represented by this file. This
1013     * list is then filtered through a FilenameFilter and files with matching
1014     * names are returned as an array of files. Returns {@code null} if this
1015     * file is not a directory. If {@code filter} is {@code null} then all
1016     * filenames match.
1017     * <p>
1018     * The entries {@code .} and {@code ..} representing the current and parent
1019     * directories are not returned as part of the list.
1020     *
1021     * @param filter
1022     *            the filter to match names against, may be {@code null}.
1023     * @return an array of files or {@code null}.
1024     * @throws SecurityException
1025     *             if a {@code SecurityManager} is installed and it denies read
1026     *             access to this file.
1027     * @see #list(FilenameFilter filter)
1028     * @see #getPath
1029     * @see #isDirectory
1030     * @see java.lang.SecurityManager#checkRead(FileDescriptor)
1031     */
1032    public File[] listFiles(FilenameFilter filter) {
1033        return filenamesToFiles(list(filter));
1034    }
1035
1036    /**
1037     * Gets a list of the files in the directory represented by this file. This
1038     * list is then filtered through a FileFilter and matching files are
1039     * returned as an array of files. Returns {@code null} if this file is not a
1040     * directory. If {@code filter} is {@code null} then all files match.
1041     * <p>
1042     * The entries {@code .} and {@code ..} representing the current and parent
1043     * directories are not returned as part of the list.
1044     *
1045     * @param filter
1046     *            the filter to match names against, may be {@code null}.
1047     * @return an array of files or {@code null}.
1048     * @throws SecurityException
1049     *             if a {@code SecurityManager} is installed and it denies read
1050     *             access to this file.
1051     * @see #getPath
1052     * @see #isDirectory
1053     * @see java.lang.SecurityManager#checkRead(FileDescriptor)
1054     */
1055    public File[] listFiles(FileFilter filter) {
1056        File[] files = listFiles();
1057        if (filter == null || files == null) {
1058            return files;
1059        }
1060        List<File> result = new ArrayList<File>(files.length);
1061        for (File file : files) {
1062            if (filter.accept(file)) {
1063                result.add(file);
1064            }
1065        }
1066        return result.toArray(new File[result.size()]);
1067    }
1068
1069    /**
1070     * Converts a String[] containing filenames to a File[].
1071     * Note that the filenames must not contain slashes.
1072     * This method is to remove duplication in the implementation
1073     * of File.list's overloads.
1074     */
1075    private File[] filenamesToFiles(String[] filenames) {
1076        if (filenames == null) {
1077            return null;
1078        }
1079        int count = filenames.length;
1080        File[] result = new File[count];
1081        for (int i = 0; i < count; ++i) {
1082            result[i] = new File(this, filenames[i]);
1083        }
1084        return result;
1085    }
1086
1087    /**
1088     * Creates the directory named by the trailing filename of this file. Does
1089     * not create the complete path required to create this directory.
1090     *
1091     * @return {@code true} if the directory has been created, {@code false}
1092     *         otherwise.
1093     * @throws SecurityException
1094     *             if a {@code SecurityManager} is installed and it denies write
1095     *             access for this file.
1096     * @see #mkdirs
1097     */
1098    public boolean mkdir() {
1099        SecurityManager security = System.getSecurityManager();
1100        if (security != null) {
1101            security.checkWrite(path);
1102        }
1103        return mkdirImpl(pathBytes);
1104    }
1105
1106    private native boolean mkdirImpl(byte[] filePath);
1107
1108    /**
1109     * Creates the directory named by the trailing filename of this file,
1110     * including the complete directory path required to create this directory.
1111     *
1112     * @return {@code true} if the necessary directories have been created,
1113     *         {@code false} if the target directory already exists or one of
1114     *         the directories can not be created.
1115     * @throws SecurityException
1116     *             if a {@code SecurityManager} is installed and it denies write
1117     *             access for this file.
1118     * @see #mkdir
1119     */
1120    public boolean mkdirs() {
1121        /* If the terminal directory already exists, answer false */
1122        if (exists()) {
1123            return false;
1124        }
1125
1126        /* If the receiver can be created, answer true */
1127        if (mkdir()) {
1128            return true;
1129        }
1130
1131        String parentDir = getParent();
1132        /* If there is no parent and we were not created, answer false */
1133        if (parentDir == null) {
1134            return false;
1135        }
1136
1137        /* Otherwise, try to create a parent directory and then this directory */
1138        return (new File(parentDir).mkdirs() && mkdir());
1139    }
1140
1141    /**
1142     * Creates a new, empty file on the file system according to the path
1143     * information stored in this file.
1144     *
1145     * @return {@code true} if the file has been created, {@code false} if it
1146     *         already exists.
1147     * @throws IOException if it's not possible to create the file.
1148     * @throws SecurityException
1149     *             if a {@code SecurityManager} is installed and it denies write
1150     *             access for this file.
1151     */
1152    public boolean createNewFile() throws IOException {
1153        SecurityManager security = System.getSecurityManager();
1154        if (security != null) {
1155            security.checkWrite(path);
1156        }
1157        if (path.length() == 0) {
1158            throw new IOException(Msg.getString("KA012")); //$NON-NLS-1$
1159        }
1160        return createNewFileImpl(pathBytes);
1161    }
1162
1163    private native boolean createNewFileImpl(byte[] filePath);
1164
1165    /**
1166     * Creates an empty temporary file using the given prefix and suffix as part
1167     * of the file name. If suffix is {@code null}, {@code .tmp} is used. This
1168     * method is a convenience method that calls
1169     * {@link #createTempFile(String, String, File)} with the third argument
1170     * being {@code null}.
1171     *
1172     * @param prefix
1173     *            the prefix to the temp file name.
1174     * @param suffix
1175     *            the suffix to the temp file name.
1176     * @return the temporary file.
1177     * @throws IOException
1178     *             if an error occurs when writing the file.
1179     */
1180    public static File createTempFile(String prefix, String suffix)
1181            throws IOException {
1182        return createTempFile(prefix, suffix, null);
1183    }
1184
1185    /**
1186     * Creates an empty temporary file in the given directory using the given
1187     * prefix and suffix as part of the file name.
1188     *
1189     * @param prefix
1190     *            the prefix to the temp file name.
1191     * @param suffix
1192     *            the suffix to the temp file name.
1193     * @param directory
1194     *            the location to which the temp file is to be written, or
1195     *            {@code null} for the default location for temporary files,
1196     *            which is taken from the "java.io.tmpdir" system property. It
1197     *            may be necessary to set this property to an existing, writable
1198     *            directory for this method to work properly.
1199     * @return the temporary file.
1200     * @throws IllegalArgumentException
1201     *             if the length of {@code prefix} is less than 3.
1202     * @throws IOException
1203     *             if an error occurs when writing the file.
1204     */
1205    @SuppressWarnings("nls")
1206    public static File createTempFile(String prefix, String suffix,
1207            File directory) throws IOException {
1208        // Force a prefix null check first
1209        if (prefix.length() < 3) {
1210            throw new IllegalArgumentException(Msg.getString("K006b"));
1211        }
1212        String newSuffix = suffix == null ? ".tmp" : suffix;
1213        File tmpDirFile;
1214        if (directory == null) {
1215            String tmpDir = AccessController.doPrivileged(
1216                new PriviAction<String>("java.io.tmpdir", "."));
1217            tmpDirFile = new File(tmpDir);
1218        } else {
1219            tmpDirFile = directory;
1220        }
1221        File result;
1222        do {
1223            result = genTempFile(prefix, newSuffix, tmpDirFile);
1224        } while (!result.createNewFile());
1225        return result;
1226    }
1227
1228    private static File genTempFile(String prefix, String suffix, File directory) {
1229        if (counter == 0) {
1230            // TODO: this doesn't make a lot of sense. SecureRandom for the seed, but then always just add one?
1231            int newInt = new SecureRandom().nextInt();
1232            counter = ((newInt / 65535) & 0xFFFF) + 0x2710;
1233        }
1234        StringBuilder newName = new StringBuilder();
1235        newName.append(prefix);
1236        newName.append(counter++);
1237        newName.append(suffix);
1238        return new File(directory, newName.toString());
1239    }
1240
1241    /**
1242     * Renames this file to the name represented by the {@code dest} file. This
1243     * works for both normal files and directories.
1244     *
1245     * @param dest
1246     *            the file containing the new name.
1247     * @return {@code true} if the File was renamed, {@code false} otherwise.
1248     * @throws SecurityException
1249     *             if a {@code SecurityManager} is installed and it denies write
1250     *             access for this file or the {@code dest} file.
1251     */
1252    public boolean renameTo(java.io.File dest) {
1253        if (path.length() == 0 || dest.path.length() == 0) {
1254            return false;
1255        }
1256        SecurityManager security = System.getSecurityManager();
1257        if (security != null) {
1258            security.checkWrite(path);
1259            security.checkWrite(dest.path);
1260        }
1261        return renameToImpl(pathBytes, dest.pathBytes);
1262    }
1263
1264    private native boolean renameToImpl(byte[] pathExist, byte[] pathNew);
1265
1266    /**
1267     * Returns a string containing a concise, human-readable description of this
1268     * file.
1269     *
1270     * @return a printable representation of this file.
1271     */
1272    @Override
1273    public String toString() {
1274        return path;
1275    }
1276
1277    /**
1278     * Returns a Uniform Resource Identifier for this file. The URI is system
1279     * dependent and may not be transferable between different operating / file
1280     * systems.
1281     *
1282     * @return an URI for this file.
1283     */
1284    @SuppressWarnings("nls")
1285    public URI toURI() {
1286        String name = getAbsoluteName();
1287        try {
1288            if (!name.startsWith("/")) {
1289                // start with sep.
1290                return new URI("file", null, new StringBuilder(
1291                        name.length() + 1).append('/').append(name).toString(),
1292                        null, null);
1293            } else if (name.startsWith("//")) {
1294                return new URI("file", "", name, null); // UNC path
1295            }
1296            return new URI("file", null, name, null, null);
1297        } catch (URISyntaxException e) {
1298            // this should never happen
1299            return null;
1300        }
1301    }
1302
1303    /**
1304     * Returns a Uniform Resource Locator for this file. The URL is system
1305     * dependent and may not be transferable between different operating / file
1306     * systems.
1307     *
1308     * @return a URL for this file.
1309     * @throws java.net.MalformedURLException
1310     *             if the path cannot be transformed into a URL.
1311     */
1312    @SuppressWarnings("nls")
1313    public URL toURL() throws java.net.MalformedURLException {
1314        String name = getAbsoluteName();
1315        if (!name.startsWith("/")) {
1316            // start with sep.
1317            return new URL(
1318                    "file", EMPTY_STRING, -1, new StringBuilder(name.length() + 1) //$NON-NLS-1$
1319                            .append('/').append(name).toString(), null);
1320        } else if (name.startsWith("//")) {
1321            return new URL("file:" + name); // UNC path
1322        }
1323        return new URL("file", EMPTY_STRING, -1, name, null);
1324    }
1325
1326    private String getAbsoluteName() {
1327        File f = getAbsoluteFile();
1328        String name = f.getPath();
1329
1330        if (f.isDirectory() && name.charAt(name.length() - 1) != separatorChar) {
1331            // Directories must end with a slash
1332            name = new StringBuilder(name.length() + 1).append(name)
1333                    .append('/').toString();
1334        }
1335        if (separatorChar != '/') { // Must convert slashes.
1336            name = name.replace(separatorChar, '/');
1337        }
1338        return name;
1339    }
1340
1341    private void writeObject(ObjectOutputStream stream) throws IOException {
1342        stream.defaultWriteObject();
1343        stream.writeChar(separatorChar);
1344    }
1345
1346    private void readObject(ObjectInputStream stream) throws IOException,
1347            ClassNotFoundException {
1348        stream.defaultReadObject();
1349        char inSeparator = stream.readChar();
1350        init(path.replace(inSeparator, separatorChar));
1351    }
1352}
1353