/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package java.io;
import android.system.ErrnoException;
import android.system.StructStat;
import android.system.StructStatVfs;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import libcore.io.DeleteOnExit;
import libcore.io.IoUtils;
import libcore.io.Libcore;
import static android.system.OsConstants.*;
/**
* An "abstract" representation of a file system entity identified by a
* pathname. The pathname may be absolute (relative to the root directory
* of the file system) or relative to the current directory in which the program
* is running.
*
* <p>The actual file referenced by a {@code File} may or may not exist. It may
* also, despite the name {@code File}, be a directory or other non-regular
* file.
*
* <p>This class provides limited functionality for getting/setting file
* permissions, file type, and last modified time.
*
* <p>On Android strings are converted to UTF-8 byte sequences when sending filenames to
* the operating system, and byte sequences returned by the operating system (from the
* various {@code list} methods) are converted to strings by decoding them as UTF-8
* byte sequences.
*
* @see java.io.Serializable
* @see java.lang.Comparable
*/
public class File implements Serializable, Comparable<File> {
private static final long serialVersionUID = 301077366599181567L;
/**
* The system-dependent character used to separate components in filenames ('/').
* Use of this (rather than hard-coding '/') helps portability to other operating systems.
*
* <p>This field is initialized from the system property "file.separator".
* Later changes to that property will have no effect on this field or this class.
*/
public static final char separatorChar;
/**
* The system-dependent string used to separate components in filenames ('/').
* See {@link #separatorChar}.
*/
public static final String separator;
/**
* The system-dependent character used to separate components in search paths (':').
* This is used to split such things as the PATH environment variable and classpath
* system properties into lists of directories to be searched.
*
* <p>This field is initialized from the system property "path.separator".
* Later changes to that property will have no effect on this field or this class.
*/
public static final char pathSeparatorChar;
/**
* The system-dependent string used to separate components in search paths (":").
* See {@link #pathSeparatorChar}.
*/
public static final String pathSeparator;
/**
* The path we return from getPath. This is almost the path we were
* given, but without duplicate adjacent slashes and without trailing
* slashes (except for the special case of the root directory). This
* path may be the empty string.
*
* This can't be final because we override readObject.
*/
private String path;
static {
separatorChar = System.getProperty("file.separator", "/").charAt(0);
pathSeparatorChar = System.getProperty("path.separator", ":").charAt(0);
separator = String.valueOf(separatorChar);
pathSeparator = String.valueOf(pathSeparatorChar);
}
/**
* Constructs a new file using the specified directory and name.
*
* @param dir
* the directory where the file is stored.
* @param name
* the file's name.
* @throws NullPointerException
* if {@code name} is {@code null}.
*/
public File(File dir, String name) {
this(dir == null ? null : dir.getPath(), name);
}
/**
* Constructs a new file using the specified path.
*
* @param path
* the path to be used for the file.
* @throws NullPointerException
* if {@code path} is {@code null}.
*/
public File(String path) {
this.path = fixSlashes(path);
}
/**
* Constructs a new File using the specified directory path and file name,
* placing a path separator between the two.
*
* @param dirPath
* the path to the directory where the file is stored.
* @param name
* the file's name.
* @throws NullPointerException
* if {@code name == null}.
*/
public File(String dirPath, String name) {
if (name == null) {
throw new NullPointerException("name == null");
}
if (dirPath == null || dirPath.isEmpty()) {
this.path = fixSlashes(name);
} else if (name.isEmpty()) {
this.path = fixSlashes(dirPath);
} else {
this.path = fixSlashes(join(dirPath, name));
}
}
/**
* Constructs a new File using the path of the specified URI. {@code uri}
* needs to be an absolute and hierarchical Unified Resource Identifier with
* file scheme and non-empty path component, but with undefined authority,
* query or fragment components.
*
* @param uri
* the Unified Resource Identifier that is used to construct this
* file.
* @throws NullPointerException
* if {@code uri == null}.
* @throws IllegalArgumentException
* if {@code uri} does not comply with the conditions above.
* @see #toURI
* @see java.net.URI
*/
public File(URI uri) {
// check pre-conditions
checkURI(uri);
this.path = fixSlashes(uri.getPath());
}
// Removes duplicate adjacent slashes and any trailing slash.
private static String fixSlashes(String origPath) {
// Remove duplicate adjacent slashes.
boolean lastWasSlash = false;
char[] newPath = origPath.toCharArray();
int length = newPath.length;
int newLength = 0;
for (int i = 0; i < length; ++i) {
char ch = newPath[i];
if (ch == '/') {
if (!lastWasSlash) {
newPath[newLength++] = separatorChar;
lastWasSlash = true;
}
} else {
newPath[newLength++] = ch;
lastWasSlash = false;
}
}
// Remove any trailing slash (unless this is the root of the file system).
if (lastWasSlash && newLength > 1) {
newLength--;
}
// Reuse the original string if possible.
return (newLength != length) ? new String(newPath, 0, newLength) : origPath;
}
// Joins two path components, adding a separator only if necessary.
private static String join(String prefix, String suffix) {
int prefixLength = prefix.length();
boolean haveSlash = (prefixLength > 0 && prefix.charAt(prefixLength - 1) == separatorChar);
if (!haveSlash) {
haveSlash = (suffix.length() > 0 && suffix.charAt(0) == separatorChar);
}
return haveSlash ? (prefix + suffix) : (prefix + separatorChar + suffix);
}
private static void checkURI(URI uri) {
if (!uri.isAbsolute()) {
throw new IllegalArgumentException("URI is not absolute: " + uri);
} else if (!uri.getRawSchemeSpecificPart().startsWith("/")) {
throw new IllegalArgumentException("URI is not hierarchical: " + uri);
}
if (!"file".equals(uri.getScheme())) {
throw new IllegalArgumentException("Expected file scheme in URI: " + uri);
}
String rawPath = uri.getRawPath();
if (rawPath == null || rawPath.isEmpty()) {
throw new IllegalArgumentException("Expected non-empty path in URI: " + uri);
}
if (uri.getRawAuthority() != null) {
throw new IllegalArgumentException("Found authority in URI: " + uri);
}
if (uri.getRawQuery() != null) {
throw new IllegalArgumentException("Found query in URI: " + uri);
}
if (uri.getRawFragment() != null) {
throw new IllegalArgumentException("Found fragment in URI: " + uri);
}
}
/**
* Returns the file system roots. On Android and other Unix systems, there is
* a single root, {@code /}.
*/
public static File[] listRoots() {
return new File[] { new File("/") };
}
/**
* Tests whether or not this process is allowed to execute this file.
* Note that this is a best-effort result; the only way to be certain is
* to actually attempt the operation.
*
* @return {@code true} if this file can be executed, {@code false} otherwise.
* @since 1.6
*/
public boolean canExecute() {
return doAccess(X_OK);
}
/**
* Indicates whether the current context is allowed to read from this file.
*
* @return {@code true} if this file can be read, {@code false} otherwise.
*/
public boolean canRead() {
return doAccess(R_OK);
}
/**
* Indicates whether the current context is allowed to write to this file.
*
* @return {@code true} if this file can be written, {@code false}
* otherwise.
*/
public boolean canWrite() {
return doAccess(W_OK);
}
private boolean doAccess(int mode) {
try {
return Libcore.os.access(path, mode);
} catch (ErrnoException errnoException) {
return false;
}
}
/**
* Returns the relative sort ordering of the paths for this file and the
* file {@code another}. The ordering is platform dependent.
*
* @param another
* a file to compare this file to
* @return an int determined by comparing the two paths. Possible values are
* described in the Comparable interface.
* @see Comparable
*/
public int compareTo(File another) {
return this.getPath().compareTo(another.getPath());
}
/**
* Deletes this file. Directories must be empty before they will be deleted.
*
* <p>Note that this method does <i>not</i> throw {@code IOException} on failure.
* Callers must check the return value.
*
* @return {@code true} if this file was deleted, {@code false} otherwise.
*/
public boolean delete() {
try {
Libcore.os.remove(path);
return true;
} catch (ErrnoException errnoException) {
return false;
}
}
/**
* Schedules this file to be automatically deleted when the VM terminates normally.
*
* <p><i>Note that on Android, the application lifecycle does not include VM termination,
* so calling this method will not ensure that files are deleted</i>. Instead, you should
* use the most appropriate out of:
* <ul>
* <li>Use a {@code finally} clause to manually invoke {@link #delete}.
* <li>Maintain your own set of files to delete, and process it at an appropriate point
* in your application's lifecycle.
* <li>Use the Unix trick of deleting the file as soon as all readers and writers have
* opened it. No new readers/writers will be able to access the file, but all existing
* ones will still have access until the last one closes the file.
* </ul>
*/
public void deleteOnExit() {
DeleteOnExit.getInstance().addFile(getAbsolutePath());
}
/**
* Compares {@code obj} to this file and returns {@code true} if they
* represent the <em>same</em> object using a path specific comparison.
*
* @param obj
* the object to compare this file with.
* @return {@code true} if {@code obj} is the same as this object,
* {@code false} otherwise.
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof File)) {
return false;
}
return path.equals(((File) obj).getPath());
}
/**
* Returns a boolean indicating whether this file can be found on the
* underlying file system.
*
* @return {@code true} if this file exists, {@code false} otherwise.
*/
public boolean exists() {
return doAccess(F_OK);
}
/**
* Returns the absolute path of this file. An absolute path is a path that starts at a root
* of the file system. On Android, there is only one root: {@code /}.
*
* <p>A common use for absolute paths is when passing paths to a {@code Process} as
* command-line arguments, to remove the requirement implied by relative paths, that the
* child must have the same working directory as its parent.
*/
public String getAbsolutePath() {
if (isAbsolute()) {
return path;
}
String userDir = System.getProperty("user.dir");
return path.isEmpty() ? userDir : join(userDir, path);
}
/**
* Returns a new file constructed using the absolute path of this file.
* Equivalent to {@code new File(this.getAbsolutePath())}.
*/
public File getAbsoluteFile() {
return new File(getAbsolutePath());
}
/**
* Returns the canonical path of this file.
* An <i>absolute</i> path is one that begins at the root of the file system.
* A <i>canonical</i> path is an absolute path with symbolic links
* and references to "." or ".." resolved. If a path element does not exist (or
* is not searchable), there is a conflict between interpreting canonicalization
* as a textual operation (where "a/../b" is "b" even if "a" does not exist) .
*
* <p>Most callers should use {@link #getAbsolutePath} instead. A canonical path is
* significantly more expensive to compute, and not generally useful. The primary
* use for canonical paths is determining whether two paths point to the same file by
* comparing the canonicalized paths.
*
* <p>It can be actively harmful to use a canonical path, specifically because
* canonicalization removes symbolic links. It's wise to assume that a symbolic link
* is present for a reason, and that that reason is because the link may need to change.
* Canonicalization removes this layer of indirection. Good code should generally avoid
* caching canonical paths.
*
* @return the canonical path of this file.
* @throws IOException
* if an I/O error occurs.
*/
public String getCanonicalPath() throws IOException {
return canonicalizePath(getAbsolutePath());
}
private static native String canonicalizePath(String path);
/**
* Returns a new file created using the canonical path of this file.
* Equivalent to {@code new File(this.getCanonicalPath())}.
*
* @return the new file constructed from this file's canonical path.
* @throws IOException
* if an I/O error occurs.
*/
public File getCanonicalFile() throws IOException {
return new File(getCanonicalPath());
}
/**
* Returns the name of the file or directory represented by this file.
*
* @return this file's name or an empty string if there is no name part in
* the file's path.
*/
public String getName() {
int separatorIndex = path.lastIndexOf(separator);
return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1, path.length());
}
/**
* Returns the pathname of the parent of this file. This is the path up to
* but not including the last name. {@code null} is returned if there is no
* parent.
*
* @return this file's parent pathname or {@code null}.
*/
public String getParent() {
int length = path.length(), firstInPath = 0;
if (separatorChar == '\\' && length > 2 && path.charAt(1) == ':') {
firstInPath = 2;
}
int index = path.lastIndexOf(separatorChar);
if (index == -1 && firstInPath > 0) {
index = 2;
}
if (index == -1 || path.charAt(length - 1) == separatorChar) {
return null;
}
if (path.indexOf(separatorChar) == index
&& path.charAt(firstInPath) == separatorChar) {
return path.substring(0, index + 1);
}
return path.substring(0, index);
}
/**
* Returns a new file made from the pathname of the parent of this file.
* This is the path up to but not including the last name. {@code null} is
* returned when there is no parent.
*
* @return a new file representing this file's parent or {@code null}.
*/
public File getParentFile() {
String tempParent = getParent();
if (tempParent == null) {
return null;
}
return new File(tempParent);
}
/**
* Returns the path of this file.
*/
public String getPath() {
return path;
}
/**
* Returns an integer hash code for the receiver. Any two objects for which
* {@code equals} returns {@code true} must return the same hash code.
*
* @return this files's hash value.
* @see #equals
*/
@Override
public int hashCode() {
return getPath().hashCode() ^ 1234321;
}
/**
* Indicates if this file's pathname is absolute. Whether a pathname is
* absolute is platform specific. On Android, absolute paths start with
* the character '/'.
*
* @return {@code true} if this file's pathname is absolute, {@code false}
* otherwise.
* @see #getPath
*/
public boolean isAbsolute() {
return path.length() > 0 && path.charAt(0) == separatorChar;
}
/**
* Indicates if this file represents a <em>directory</em> on the
* underlying file system.
*
* @return {@code true} if this file is a directory, {@code false}
* otherwise.
*/
public boolean isDirectory() {
try {
return S_ISDIR(Libcore.os.stat(path).st_mode);
} catch (ErrnoException errnoException) {
// The RI returns false on error. (Even for errors like EACCES or ELOOP.)
return false;
}
}
/**
* Indicates if this file represents a <em>file</em> on the underlying
* file system.
*
* @return {@code true} if this file is a file, {@code false} otherwise.
*/
public boolean isFile() {
try {
return S_ISREG(Libcore.os.stat(path).st_mode);
} catch (ErrnoException errnoException) {
// The RI returns false on error. (Even for errors like EACCES or ELOOP.)
return false;
}
}
/**
* Returns whether or not this file is a hidden file as defined by the
* operating system. The notion of "hidden" is system-dependent. For Unix
* systems a file is considered hidden if its name starts with a ".". For
* Windows systems there is an explicit flag in the file system for this
* purpose.
*
* @return {@code true} if the file is hidden, {@code false} otherwise.
*/
public boolean isHidden() {
if (path.isEmpty()) {
return false;
}
return getName().startsWith(".");
}
/**
* Returns the time when this file was last modified, measured in
* milliseconds since January 1st, 1970, midnight.
* Returns 0 if the file does not exist.
*
* @return the time when this file was last modified.
*/
public long lastModified() {
try {
return Libcore.os.stat(path).st_mtime * 1000L;
} catch (ErrnoException errnoException) {
// The RI returns 0 on error. (Even for errors like EACCES or ELOOP.)
return 0;
}
}
/**
* Sets the time this file was last modified, measured in milliseconds since
* January 1st, 1970, midnight.
*
* <p>Note that this method does <i>not</i> throw {@code IOException} on failure.
* Callers must check the return value.
*
* @param time
* the last modification time for this file.
* @return {@code true} if the operation is successful, {@code false}
* otherwise.
* @throws IllegalArgumentException
* if {@code time < 0}.
*/
public boolean setLastModified(long time) {
if (time < 0) {
throw new IllegalArgumentException("time < 0");
}
return setLastModifiedImpl(path, time);
}
private static native boolean setLastModifiedImpl(String path, long time);
/**
* Equivalent to setWritable(false, false).
*
* @see #setWritable(boolean, boolean)
*/
public boolean setReadOnly() {
return setWritable(false, false);
}
/**
* Manipulates the execute permissions for the abstract path designated by
* this file.
*
* <p>Note that this method does <i>not</i> throw {@code IOException} on failure.
* Callers must check the return value.
*
* @param executable
* To allow execute permission if true, otherwise disallow
* @param ownerOnly
* To manipulate execute permission only for owner if true,
* otherwise for everyone. The manipulation will apply to
* everyone regardless of this value if the underlying system
* does not distinguish owner and other users.
* @return true if and only if the operation succeeded. If the user does not
* have permission to change the access permissions of this abstract
* pathname the operation will fail. If the underlying file system
* does not support execute permission and the value of executable
* is false, this operation will fail.
* @since 1.6
*/
public boolean setExecutable(boolean executable, boolean ownerOnly) {
return doChmod(ownerOnly ? S_IXUSR : (S_IXUSR | S_IXGRP | S_IXOTH), executable);
}
/**
* Equivalent to setExecutable(executable, true).
* @see #setExecutable(boolean, boolean)
* @since 1.6
*/
public boolean setExecutable(boolean executable) {
return setExecutable(executable, true);
}
/**
* Manipulates the read permissions for the abstract path designated by this
* file.
*
* @param readable
* To allow read permission if true, otherwise disallow
* @param ownerOnly
* To manipulate read permission only for owner if true,
* otherwise for everyone. The manipulation will apply to
* everyone regardless of this value if the underlying system
* does not distinguish owner and other users.
* @return true if and only if the operation succeeded. If the user does not
* have permission to change the access permissions of this abstract
* pathname the operation will fail. If the underlying file system
* does not support read permission and the value of readable is
* false, this operation will fail.
* @since 1.6
*/
public boolean setReadable(boolean readable, boolean ownerOnly) {
return doChmod(ownerOnly ? S_IRUSR : (S_IRUSR | S_IRGRP | S_IROTH), readable);
}
/**
* Equivalent to setReadable(readable, true).
* @see #setReadable(boolean, boolean)
* @since 1.6
*/
public boolean setReadable(boolean readable) {
return setReadable(readable, true);
}
/**
* Manipulates the write permissions for the abstract path designated by this
* file.
*
* @param writable
* To allow write permission if true, otherwise disallow
* @param ownerOnly
* To manipulate write permission only for owner if true,
* otherwise for everyone. The manipulation will apply to
* everyone regardless of this value if the underlying system
* does not distinguish owner and other users.
* @return true if and only if the operation succeeded. If the user does not
* have permission to change the access permissions of this abstract
* pathname the operation will fail.
* @since 1.6
*/
public boolean setWritable(boolean writable, boolean ownerOnly) {
return doChmod(ownerOnly ? S_IWUSR : (S_IWUSR | S_IWGRP | S_IWOTH), writable);
}
/**
* Equivalent to setWritable(writable, true).
* @see #setWritable(boolean, boolean)
* @since 1.6
*/
public boolean setWritable(boolean writable) {
return setWritable(writable, true);
}
private boolean doChmod(int mask, boolean set) {
try {
StructStat sb = Libcore.os.stat(path);
int newMode = set ? (sb.st_mode | mask) : (sb.st_mode & ~mask);
Libcore.os.chmod(path, newMode);
return true;
} catch (ErrnoException errnoException) {
return false;
}
}
/**
* Returns the length of this file in bytes.
* Returns 0 if the file does not exist.
* The result for a directory is not defined.
*
* @return the number of bytes in this file.
*/
public long length() {
try {
return Libcore.os.stat(path).st_size;
} catch (ErrnoException errnoException) {
// The RI returns 0 on error. (Even for errors like EACCES or ELOOP.)
return 0;
}
}
/**
* Returns an array of strings with the file names in the directory
* represented by this file. The result is {@code null} if this file is not
* a directory.
* <p>
* The entries {@code .} and {@code ..} representing the current and parent
* directory are not returned as part of the list.
*
* @return an array of strings with file names or {@code null}.
*/
public String[] list() {
return listImpl(path);
}
private static native String[] listImpl(String path);
/**
* Gets a list of the files in the directory represented by this file. This
* list is then filtered through a FilenameFilter and the names of files
* with matching names are returned as an array of strings. Returns
* {@code null} if this file is not a directory. If {@code filter} is
* {@code null} then all filenames match.
* <p>
* The entries {@code .} and {@code ..} representing the current and parent
* directories are not returned as part of the list.
*
* @param filter
* the filter to match names against, may be {@code null}.
* @return an array of files or {@code null}.
*/
public String[] list(FilenameFilter filter) {
String[] filenames = list();
if (filter == null || filenames == null) {
return filenames;
}
List<String> result = new ArrayList<String>(filenames.length);
for (String filename : filenames) {
if (filter.accept(this, filename)) {
result.add(filename);
}
}
return result.toArray(new String[result.size()]);
}
/**
* Returns an array of files contained in the directory represented by this
* file. The result is {@code null} if this file is not a directory. The
* paths of the files in the array are absolute if the path of this file is
* absolute, they are relative otherwise.
*
* @return an array of files or {@code null}.
*/
public File[] listFiles() {
return filenamesToFiles(list());
}
/**
* Gets a list of the files in the directory represented by this file. This
* list is then filtered through a FilenameFilter and files with matching
* names are returned as an array of files. Returns {@code null} if this
* file is not a directory. If {@code filter} is {@code null} then all
* filenames match.
* <p>
* The entries {@code .} and {@code ..} representing the current and parent
* directories are not returned as part of the list.
*
* @param filter
* the filter to match names against, may be {@code null}.
* @return an array of files or {@code null}.
*/
public File[] listFiles(FilenameFilter filter) {
return filenamesToFiles(list(filter));
}
/**
* Gets a list of the files in the directory represented by this file. This
* list is then filtered through a FileFilter and matching files are
* returned as an array of files. Returns {@code null} if this file is not a
* directory. If {@code filter} is {@code null} then all files match.
* <p>
* The entries {@code .} and {@code ..} representing the current and parent
* directories are not returned as part of the list.
*
* @param filter
* the filter to match names against, may be {@code null}.
* @return an array of files or {@code null}.
*/
public File[] listFiles(FileFilter filter) {
File[] files = listFiles();
if (filter == null || files == null) {
return files;
}
List<File> result = new ArrayList<File>(files.length);
for (File file : files) {
if (filter.accept(file)) {
result.add(file);
}
}
return result.toArray(new File[result.size()]);
}
/**
* Converts a String[] containing filenames to a File[].
* Note that the filenames must not contain slashes.
* This method is to remove duplication in the implementation
* of File.list's overloads.
*/
private File[] filenamesToFiles(String[] filenames) {
if (filenames == null) {
return null;
}
int count = filenames.length;
File[] result = new File[count];
for (int i = 0; i < count; ++i) {
result[i] = new File(this, filenames[i]);
}
return result;
}
/**
* Creates the directory named by this file, assuming its parents exist.
* Use {@link #mkdirs} if you also want to create missing parents.
*
* <p>Note that this method does <i>not</i> throw {@code IOException} on failure.
* Callers must check the return value. Note also that this method returns
* false if the directory already existed. If you want to know whether the
* directory exists on return, either use {@code (f.mkdir() || f.isDirectory())}
* or simply ignore the return value from this method and simply call {@link #isDirectory}.
*
* @return {@code true} if the directory was created,
* {@code false} on failure or if the directory already existed.
*/
public boolean mkdir() {
try {
mkdirErrno();
return true;
} catch (ErrnoException errnoException) {
return false;
}
}
private void mkdirErrno() throws ErrnoException {
// On Android, we don't want default permissions to allow global access.
Libcore.os.mkdir(path, S_IRWXU);
}
/**
* Creates the directory named by this file, creating missing parent
* directories if necessary.
* Use {@link #mkdir} if you don't want to create missing parents.
*
* <p>Note that this method does <i>not</i> throw {@code IOException} on failure.
* Callers must check the return value. Note also that this method returns
* false if the directory already existed. If you want to know whether the
* directory exists on return, either use {@code (f.mkdirs() || f.isDirectory())}
* or simply ignore the return value from this method and simply call {@link #isDirectory}.
*
* @return {@code true} if the directory was created,
* {@code false} on failure or if the directory already existed.
*/
public boolean mkdirs() {
return mkdirs(false);
}
private boolean mkdirs(boolean resultIfExists) {
try {
// Try to create the directory directly.
mkdirErrno();
return true;
} catch (ErrnoException errnoException) {
if (errnoException.errno == ENOENT) {
// If the parent was missing, try to create it and then try again.
File parent = getParentFile();
return parent != null && parent.mkdirs(true) && mkdir();
} else if (errnoException.errno == EEXIST) {
return resultIfExists;
}
return false;
}
}
/**
* Creates a new, empty file on the file system according to the path
* information stored in this file. This method returns true if it creates
* a file, false if the file already existed. Note that it returns false
* even if the file is not a file (because it's a directory, say).
*
* <p>This method is not generally useful. For creating temporary files,
* use {@link #createTempFile} instead. For reading/writing files, use {@link FileInputStream},
* {@link FileOutputStream}, or {@link RandomAccessFile}, all of which can create files.
*
* <p>Note that this method does <i>not</i> throw {@code IOException} if the file
* already exists, even if it's not a regular file. Callers should always check the
* return value, and may additionally want to call {@link #isFile}.
*
* @return true if the file has been created, false if it
* already exists.
* @throws IOException if it's not possible to create the file.
*/
public boolean createNewFile() throws IOException {
FileDescriptor fd = null;
try {
// On Android, we don't want default permissions to allow global access.
fd = Libcore.os.open(path, O_RDWR | O_CREAT | O_EXCL, 0600);
return true;
} catch (ErrnoException errnoException) {
if (errnoException.errno == EEXIST) {
// The file already exists.
return false;
}
throw errnoException.rethrowAsIOException();
} finally {
IoUtils.close(fd); // TODO: should we suppress IOExceptions thrown here?
}
}
/**
* Creates an empty temporary file using the given prefix and suffix as part
* of the file name. If {@code suffix} is null, {@code .tmp} is used. This
* method is a convenience method that calls
* {@link #createTempFile(String, String, File)} with the third argument
* being {@code null}.
*
* @param prefix
* the prefix to the temp file name.
* @param suffix
* the suffix to the temp file name.
* @return the temporary file.
* @throws IOException
* if an error occurs when writing the file.
*/
public static File createTempFile(String prefix, String suffix) throws IOException {
return createTempFile(prefix, suffix, null);
}
/**
* Creates an empty temporary file in the given directory using the given
* prefix and suffix as part of the file name. If {@code suffix} is null, {@code .tmp} is used.
*
* <p>Note that this method does <i>not</i> call {@link #deleteOnExit}, but see the
* documentation for that method before you call it manually.
*
* @param prefix
* the prefix to the temp file name.
* @param suffix
* the suffix to the temp file name.
* @param directory
* the location to which the temp file is to be written, or
* {@code null} for the default location for temporary files,
* which is taken from the "java.io.tmpdir" system property. It
* may be necessary to set this property to an existing, writable
* directory for this method to work properly.
* @return the temporary file.
* @throws IllegalArgumentException
* if the length of {@code prefix} is less than 3.
* @throws IOException
* if an error occurs when writing the file.
*/
public static File createTempFile(String prefix, String suffix, File directory)
throws IOException {
// Force a prefix null check first
if (prefix.length() < 3) {
throw new IllegalArgumentException("prefix must be at least 3 characters");
}
if (suffix == null) {
suffix = ".tmp";
}
File tmpDirFile = directory;
if (tmpDirFile == null) {
String tmpDir = System.getProperty("java.io.tmpdir", ".");
tmpDirFile = new File(tmpDir);
}
File result;
do {
result = new File(tmpDirFile, prefix + Math.randomIntInternal() + suffix);
} while (!result.createNewFile());
return result;
}
/**
* Renames this file to {@code newPath}. This operation is supported for both
* files and directories.
*
* <p>Many failures are possible. Some of the more likely failures include:
* <ul>
* <li>Write permission is required on the directories containing both the source and
* destination paths.
* <li>Search permission is required for all parents of both paths.
* <li>Both paths be on the same mount point. On Android, applications are most likely to hit
* this restriction when attempting to copy between internal storage and an SD card.
* </ul>
*
* <p>Note that this method does <i>not</i> throw {@code IOException} on failure.
* Callers must check the return value.
*
* @param newPath the new path.
* @return true on success.
*/
public boolean renameTo(File newPath) {
try {
Libcore.os.rename(path, newPath.path);
return true;
} catch (ErrnoException errnoException) {
return false;
}
}
/**
* Returns a string containing a concise, human-readable description of this
* file.
*
* @return a printable representation of this file.
*/
@Override
public String toString() {
return path;
}
/**
* Returns a Uniform Resource Identifier for this file. The URI is system
* dependent and may not be transferable between different operating / file
* systems.
*
* @return an URI for this file.
*/
public URI toURI() {
String name = getAbsoluteName();
try {
if (!name.startsWith("/")) {
// start with sep.
return new URI("file", null, "/" + name, null, null);
} else if (name.startsWith("//")) {
return new URI("file", "", name, null); // UNC path
}
return new URI("file", null, name, null, null);
} catch (URISyntaxException e) {
// this should never happen
return null;
}
}
/**
* Returns a Uniform Resource Locator for this file. The URL is system
* dependent and may not be transferable between different operating / file
* systems.
*
* @return a URL for this file.
* @throws java.net.MalformedURLException
* if the path cannot be transformed into a URL.
* @deprecated Use {@link #toURI} and {@link java.net.URI#toURL} to
* correctly escape illegal characters.
*/
@Deprecated
public URL toURL() throws java.net.MalformedURLException {
String name = getAbsoluteName();
if (!name.startsWith("/")) {
// start with sep.
return new URL("file", "", -1, "/" + name, null);
} else if (name.startsWith("//")) {
return new URL("file:" + name); // UNC path
}
return new URL("file", "", -1, name, null);
}
// TODO: is this really necessary, or can it be replaced with getAbsolutePath?
private String getAbsoluteName() {
File f = getAbsoluteFile();
String name = f.getPath();
if (f.isDirectory() && name.charAt(name.length() - 1) != separatorChar) {
// Directories must end with a slash
name = name + "/";
}
if (separatorChar != '/') { // Must convert slashes.
name = name.replace(separatorChar, '/');
}
return name;
}
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
stream.writeChar(separatorChar);
}
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
char inSeparator = stream.readChar();
this.path = fixSlashes(path.replace(inSeparator, separatorChar));
}
/**
* Returns the total size in bytes of the partition containing this path.
* Returns 0 if this path does not exist.
*
* @since 1.6
*/
public long getTotalSpace() {
try {
StructStatVfs sb = Libcore.os.statvfs(path);
return sb.f_blocks * sb.f_bsize; // total block count * block size in bytes.
} catch (ErrnoException errnoException) {
return 0;
}
}
/**
* Returns the number of usable free bytes on the partition containing this path.
* Returns 0 if this path does not exist.
*
* <p>Note that this is likely to be an optimistic over-estimate and should not
* be taken as a guarantee your application can actually write this many bytes.
* On Android (and other Unix-based systems), this method returns the number of free bytes
* available to non-root users, regardless of whether you're actually running as root,
* and regardless of any quota or other restrictions that might apply to the user.
* (The {@code getFreeSpace} method returns the number of bytes potentially available to root.)
*
* @since 1.6
*/
public long getUsableSpace() {
try {
StructStatVfs sb = Libcore.os.statvfs(path);
return sb.f_bavail * sb.f_bsize; // non-root free block count * block size in bytes.
} catch (ErrnoException errnoException) {
return 0;
}
}
/**
* Returns the number of free bytes on the partition containing this path.
* Returns 0 if this path does not exist.
*
* <p>Note that this is likely to be an optimistic over-estimate and should not
* be taken as a guarantee your application can actually write this many bytes.
*
* @since 1.6
*/
public long getFreeSpace() {
try {
StructStatVfs sb = Libcore.os.statvfs(path);
return sb.f_bfree * sb.f_bsize; // free block count * block size in bytes.
} catch (ErrnoException errnoException) {
return 0;
}
}
}