/*********************************************************************************
* TotalCross Software Development Kit *
* Copyright (C) 1998, 1999 Wabasoft <www.wabasoft.com> *
* Copyright (C) 2000-2012 SuperWaba Ltda. *
* All Rights Reserved *
* *
* This library and virtual machine is distributed in the hope that it will *
* be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
* *
* This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 *
* A copy of this license is located in file license.txt at the root of this *
* SDK or can be downloaded here: *
* http://www.gnu.org/licenses/lgpl-3.0.txt *
* *
*********************************************************************************/
package totalcross.io;
import java.net.URISyntaxException;
import totalcross.sys.*;
import totalcross.util.*;
/**
* File represents a file or directory.
* <p>
* Note that writing to a storage card can be 5x slower than writing to the main memory.
* <p>
* Here is an example showing data being read from a file:
*
* <pre>
* File file = new File("/temp/tempfile", File.READ_WRITE);
* byte b[] = new byte[10];
* file.readBytes(b, 0, 10);
* file.close();
* file = new File("/temp/tempfile"); // opens in DONT_OPEN mode
* file.delete();
* </pre>
*
* When creating a new file, you may start the path using the alias "device/", which evaluates to the platform's base
* user directory:
* <ul>
* <li>PalmOS, WinCE and iPhone - "/" (root)
* <li>BlackBerry - "/store/home/user/"
* <li>Java - "/" (current directory)
* <li>Win32 - "/" (root of the current drive)
* <li>iOS - "/private/var/" in a installation using .deb (on a jailbroken device) or in the documents folder of the application in a installation
* using .ipa
* <li>Android - "/data/data/totalcross.app.<mainclass name>" or "/data/data/totalcross.app.<application id> if using single package
* </ul>
* The alias is ALWAYS relative to the built in storage, regardless of the value passed to the argument slot.<br>
*
* On iOS and Android, if you don't specify a path, the file will be open in device/ path.
*/
public class File extends RandomAccessStream
{
/** The path that represents this file */
protected String path;
/** stores a java.io.File. */
private Object fileRef;
/** Stores a java.io.RandomAccessFile. */
private Object fileEx;
/** Stores the mode used to open the file. */
private int mode;
/** Stores the slot number passed in the constructor. */
private int slot;
public static final int INVALID = 0;
/**
* The DONT_OPEN mode allows the exists(), rename(), delete(), listFiles(), createDir(), and isDir() methods to be
* called without requiring the file to be open for reading or writing.
*
* @see #File(String)
* @see #File(String,int)
* @see #File(String,int,int)
* @see #exists()
* @see #rename(String)
* @see #delete()
* @see #listFiles()
* @see #createDir()
* @see #isDir()
*/
public static final int DONT_OPEN = 1;
/**
* Read-write open mode. Works only for files, must not be used for folders.
*
* @see #File(String,int)
* @see #File(String,int,int)
*/
public static final int READ_WRITE = 2;
/**
* Read-only open mode. Works only for files, must not be used for folders.
*
* @see #File(String,int)
* @see #File(String,int,int)
* @since TotalCross 1.38
*/
public static final int READ_ONLY = 3;
/**
* Used to create a file if one does not exist; if the file exists, it is not erased, and the mode is changed to
* READ_WRITE.
*
* @see #File(String,int)
* @see #File(String,int,int)
*/
public static final int CREATE = 4;
/**
* Create an empty file; destroys the file if it exists, then the mode is changed to READ_WRITE.
*
* @see #File(String,int)
* @see #File(String,int,int)
*/
public static final int CREATE_EMPTY = 5;
/**
* Used in the setTime method, in parameter whichTime. This sets all times at once.
*
* @see #setTime(byte, totalcross.sys.Time)
*/
public static final byte TIME_ALL = (byte) 0xF;
/**
* Used in the setTime method. These values are platform independent, and can be ORed together in the setTime method.
*
* @see #setTime(byte, totalcross.sys.Time)
*/
public static final byte TIME_CREATED = (byte) 1;
/**
* Used in the setTime method. These values are platform independent, and can be ORed together in the setTime method.
*
* @see #setTime(byte, totalcross.sys.Time)
*/
public static final byte TIME_MODIFIED = (byte) 2;
/**
* Used in the setTime method. These values are platform independent, and can be ORed together in the setTime method.
*
* @see #setTime(byte, totalcross.sys.Time)
*/
public static final byte TIME_ACCESSED = (byte) 4;
/**
* Used in the getAttributes and setAttributes method. These values are platform independent.
*
* @see #setAttributes(int)
* @see #getAttributes()
*/
public static final int ATTR_ARCHIVE = 1;
/**
* Used in the getAttributes and setAttributes method. These values are platform independent.
*
* @see #setAttributes(int)
* @see #getAttributes()
*/
public static final int ATTR_HIDDEN = 2;
/**
* Used in the getAttributes and setAttributes method. These values are platform independent. Palm specific: Avoid
* using this attribute on a file located on the Built-in storage, because some devices do not allow this attribute
* to be changed after it is first set. This results in a read-only file that cannot be changed or deleted.
*
* @see #setAttributes(int)
* @see #getAttributes()
*/
public static final int ATTR_READ_ONLY = 4;
/**
* Used in the getAttributes and setAttributes method. These values are platform independent.
*
* @see #setAttributes(int)
* @see #getAttributes()
*/
public static final int ATTR_SYSTEM = 8;
/**
* These are the volumes that getCardVolume search to find the available one. <br>
* <br>
* To access the card in Android devices, prefix the path with <code>/sdcard</code>. Be sure that the sdcard is NOT MOUNTED, otherwise your application will not have access to it.
*
* @see #getCardVolume()
*/
public static String[] winceVols = { "/Storage Card2/", "/Storage Card1/", "/SD Card/", "/Storage Card/",
"/SD-MMCard/", "/CF Card/" }; // guich@572_3
/**
* Opens a file with the given name, mode and in the given card number.
* <p>
* Note that it's not advised to use accentuated characters in the file name. Also, the slash / MUST be the path separator. It is not forbidden
* to use the backslash \, but its support might be discontinued in the future to increase performance. Note
* also that some OSes may not allow the creation of files in the ROOT directory.
*
* @param path
* the file's path. Always use slashes (/) instead of backslashes (\\).
* <br>
* To access the card in Android devices, prefix the path with <code>/sdcard</code>. Be sure that the sdcard is NOT MOUNTED, otherwise your application will not have access to it.
* @param mode
* one of open modes.
* @param slot
* The card slot number. This currently works only on Palm OS devices, because other OSes use a different
* approach to specify the card. The number may be -1 to use the last available card, or a number between 0
* and the number of cards supported by the device. Usually, slot 0 is the main memory (CAUTION: cannot be
* used with File!), slot 1 is the NVFS volume, slot 2 the external card volume. This may vary on some Palm
* devices, so use prefer using the Settings.nvfsVolume property. You can find the available slots using
* this code:
*
* <pre>
* for (int i = 0; i < 10; i++)
* if (File.isCardInserted(i))
* add(new Label("found " + i), LEFT, AFTER); // Zire 22 returns 1 only
* </pre>
*
* @since SuperWaba 5.52
* @see #File(String)
* @see #File(String,int)
* @see #DONT_OPEN
* @see #READ_WRITE
* @see #READ_ONLY
* @see #CREATE
* @see #CREATE_EMPTY
* @see totalcross.sys.Settings#nvfsVolume
* @deprecated TotalCross 2 no longer uses slot
*/
public File(String path, int mode, int slot) throws IllegalArgumentIOException, FileNotFoundException, IOException
{
if (mode == 8) mode = CREATE_EMPTY; // keep compatibility
if (path == null)
throw new java.lang.NullPointerException("Argument 'path' cannot have a null value");
if (path.length() == 0 || path.length() > 255)
throw new IllegalArgumentIOException("path", path);
if (mode < DONT_OPEN || mode > CREATE_EMPTY)
throw new IllegalArgumentIOException("mode", Convert.toString(mode));
if (slot < -1)
throw new IllegalArgumentIOException("slot", Convert.toString(slot));
path = Convert.normalizePath(path);
if (path.startsWith("device/")) // flsobral@tc110_108: added support for the alias "device/".
path = Convert.appendPath(Settings.appPath,path.substring(6)); // guich@tc310: in desktop was using the root folder of current drive
this.path = path;
this.mode = mode; // remove the sequential flag
this.slot = slot;
create();
}
/**
* Creates a file with the given path, mode and slot=-1.
*
* @param path
* the file's path
* @param mode
* one of open modes
* @throws IllegalArgumentIOException
* @throws FileNotFoundException
* @throws IOException
* @see #File(String)
* @see #File(String,int,int)
*/
public File(String path, int mode) throws IllegalArgumentIOException, FileNotFoundException, IOException
{
this(path, mode, -1);
}
/**
* Opens a file with the given path and mode=DONT_OPEN and slot=-1. This constructor is useful for directory
* manipulation and/or check if file exists. No read/write operation can be done with a file created in this mode.
*
* @param path
* the file's path
* @throws IllegalArgumentIOException
* @throws FileNotFoundException
* @throws IOException
* @see #File(String, int, int)
*/
public File(String path) throws IllegalArgumentIOException, IOException
{
this(path, DONT_OPEN, -1);
}
final private void create() throws FileNotFoundException, IOException
{
java.net.URI pathURI = null;
if (path.length() >= 2 && path.charAt(1) == ':') // absolute path
{
try
{
path = path.replaceAll("\"", "");
pathURI = new java.net.URI(("file:///" + path.replaceAll(" ", "%20")));
}
catch (URISyntaxException e)
{
}
}
java.io.File fileRef4Java = null;
try
{
if (pathURI != null)
fileRef4Java = new java.io.File(pathURI);
}
catch (IllegalArgumentException e)
{
/*
* The path may contain characters that may not be correctly interpreted when converted to URI. Maybe we can
* replace them with escape codes, but for now we'll just ignore this error and try again using the path as
* provided.
*/
}
if (fileRef4Java == null)
fileRef4Java = new java.io.File(path);
if (mode != DONT_OPEN)
{
if (mode != CREATE && mode != CREATE_EMPTY && !fileRef4Java.exists())
throw new FileNotFoundException(path);
if (mode == CREATE_EMPTY)
try
{
if (fileRef4Java.exists())
fileRef4Java.delete();
}
catch (java.lang.SecurityException e)
{
throw new IOException(e.getMessage());
}
try
{
fileEx = new java.io.RandomAccessFile(fileRef4Java, mode == READ_ONLY ? "r" : "rw");
/*
* Attempts to get an exclusive lock for this file, using reflection to call methods from JDK 1.4
* ((java.io.RandomAccessFile) fileEx).getChannel().tryLock();
*/
if (mode != READ_ONLY)
try
{
// RandomAccessFile.getChannel()
java.lang.reflect.Method getChannel = fileEx.getClass().getMethod("getChannel");
Object fileChannel = getChannel.invoke(fileEx);
// FileChannel.tryLock() -> returns null if the file is already locked.
java.lang.reflect.Method tryLock = fileChannel.getClass().getMethod("tryLock");
if (tryLock.invoke(fileChannel) == null)
{
// close everything and throw IOException.
((java.io.RandomAccessFile) fileEx).close();
fileEx = null;
mode = INVALID;
throw new IOException("Cannot access the file because it is already in use");
}
}
catch (java.lang.NoSuchMethodException e)
{
((java.io.RandomAccessFile) fileEx).close();
fileEx = null;
mode = INVALID;
throw new IOException(e.getMessage());
}
catch (java.lang.IllegalAccessException e)
{
((java.io.RandomAccessFile) fileEx).close();
fileEx = null;
mode = INVALID;
throw new IOException(e.getMessage());
}
catch (java.lang.reflect.InvocationTargetException e)
{
((java.io.RandomAccessFile) fileEx).close();
fileEx = null;
mode = INVALID;
throw new IOException("Cannot access the file because it is already in use");
}
}
catch (java.io.FileNotFoundException e)
{
boolean wasCreate = mode == CREATE || mode == CREATE_EMPTY;
fileEx = null;
mode = INVALID;
if (wasCreate)
throw new IOException("Folder not found or there's already a folder with the same name of the file: "+path);
throw new FileNotFoundException(path);
}
catch (java.io.IOException e)
{
fileEx = null;
mode = INVALID;
throw new IOException(e.getMessage());
}
}
this.fileRef = fileRef4Java;
}
/**
* Can be used to verify if a card is inserted into the given slot. Only works on Palm OS and Android devices. In all
* other platforms, always returns true.
*
* @param slot
* The slot number, or -1 to use the last slot number (which, in most devices, will be the only slot
* available).
* @since SuperWaba 5.52
*/
final public static boolean isCardInserted(int slot) throws IllegalArgumentIOException
{
if (slot < -1)
throw new IllegalArgumentIOException("slot", Convert.toString(slot));
return true;
}
/**
* Closes the file.
*
* @throws IOException
* If a file is closed more than once,
*/
public void close() throws IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
try
{
if (mode != DONT_OPEN)
{
java.io.RandomAccessFile fileEx4Java = (java.io.RandomAccessFile) fileEx;
try
{
fileEx4Java.close();
}
catch (java.io.IOException e)
{
throw new IOException(e.getMessage());
}
}
}
finally
{
fileRef = null;
fileEx = null;
mode = INVALID;
}
}
/**
* Flushes a file. This causes any pending data to be written to disk. Calling this method too much may decrease the
* performance. Has no effect on JavaSE.
*/
public void flush() throws IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode == DONT_OPEN)
throw new IOException("Operation cannot be used in DONT_OPEN mode");
if (mode == READ_ONLY)
throw new IOException("Operation cannot be used in READ_WRITE mode");
// do nothing
}
/**
* Recursively creates a directory that is represented by the current file. The file must have been open in DONT_OPEN
* mode (remember that the constructor File(String) already opens the file in DONT_OPEN mode).
*
* @throws IOException
* If the file was closed, or if it was open in anything else than DONT_OPEN, or if the directory already
* exists, or if the directories could not be created.
* <p>
* Example:
*
* <pre>
* new File("/my/new/recursive/folder").createDir();
* </pre>
*/
final public void createDir() throws IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode != DONT_OPEN)
throw new IOException("Operation can ONLY be used in DONT_OPEN mode");
java.io.File fileRef4Java = (java.io.File) fileRef;
try
{
if (fileRef4Java.exists())
throw new IOException("Directory already exists");
if (!fileRef4Java.mkdirs())
throw new IOException("Could not create all the directories listed on the path");
}
catch (java.lang.SecurityException e)
{
throw new IOException(e.getMessage());
}
}
/**
* Deletes the file or directory (which must be empty). The file is automatically closed before it is deleted. The
* file could have been opened in any of the available modes, except READ_ONLY. Example:
*
* <pre>
* new File("/my/file.c").delete();
* </pre>
*/
final public void delete() throws FileNotFoundException, IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode == READ_ONLY)
throw new IOException("Operation cannot be used in READ_ONLY mode");
java.io.File fileRef4Java = (java.io.File) fileRef;
if (mode != DONT_OPEN)
this.close();
try
{
if (!fileRef4Java.exists())
throw new FileNotFoundException(path);
if (!fileRef4Java.delete())
throw new IOException("Could not remove the file. Possible reason: "
+ (fileRef4Java.isDirectory() ? "The directory is not empty" : "The file is in use"));
}
catch (java.lang.SecurityException e)
{
throw new IOException(e.getMessage());
}
}
/**
* Returns true if the file exists and false otherwise.<br>
* <br>
* Example:
*
* <pre>
* if (new File("dummy.txt").exists())
* ...
* </pre>
*
* @throws IOException
*/
final public boolean exists() throws IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
java.io.File fileRef4Java = (java.io.File) fileRef;
try
{
return fileRef4Java.exists();
}
catch (java.lang.SecurityException e)
{
throw new IOException(e.getMessage());
}
}
/**
* Returns the size of the file in bytes. If the file is a directory (ends with slash, not backslash), it returns the
* amount of free space in bytes of the card/file system (also works on JDK 1.6 and above). <br>
* <br>
* If the total amount is greater than 2 GB, 2 GB is returned. In other cases, if the file is not opened, an
* exception will be thrown. <br>
* <br>
* Examples:
*
* <pre>
* int freeSpace;
* if (Settings.platform.equals("Win32") || Settings.platform.equals("Java"))
* freeSpace = new File("c:\\").getSize();
* else if (Settings.platform.equals("PalmOS"))
* freeSpace = new File("\\", 1).getSize(); // hidden volume
* else
* freeSpace = new File("\\").getSize(); // WinCE and Posix
* </pre>
*/
final public int getSize() throws IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (path.endsWith("/")) // a folder?
try
{
long size = ((java.io.File) fileRef).getFreeSpace();
return (int) (size > 2147483647 ? 2147483647 : size);
}
catch (Throwable t)
{
return 1024 * 1024; // 1mb
}
if (mode == DONT_OPEN)
throw new IOException("The file can't be open in the DONT_OPEN mode to get its size.");
java.io.File fileRef4Java = (java.io.File) fileRef;
try
{
long size = fileRef4Java.length();
return (int) (size > 2147483647 ? 2147483647 : size);
}
catch (Throwable t)
{
throw new IOException(t.getMessage());
}
}
/** Return the file's path passed in the constructor. */
public String getPath()
{
return path;
}
/**
* Returns the file's parent, or null if its the root.
*
* @throws IOException
*/
public File getParent() throws IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (path.equals("/"))
return null;
java.io.File fileRef4Java = (java.io.File) fileRef;
return new File(fileRef4Java.getParent());
}
/**
* Returns true if the file is a directory and false otherwise. The file must have been open in DONT_OPEN mode.
*
* @throws IOException
*/
final public boolean isDir() throws IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode != DONT_OPEN)
return false;
java.io.File fileRef4Java = (java.io.File) fileRef;
try
{
return fileRef4Java.isDirectory();
}
catch (java.lang.SecurityException e)
{
throw new IOException(e.getMessage());
}
}
/**
* Lists the files contained in a directory. The strings returned are the names of the files and directories
* contained within this directory. Paths are suffixed by a slash.
*/
final public String[] listFiles() throws IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode != DONT_OPEN)
throw new IOException("Operation can ONLY be used in DONT_OPEN mode");
java.io.File fileRef4Java = (java.io.File) fileRef;
if (!fileRef4Java.exists())
throw new FileNotFoundException(path);
if (!fileRef4Java.isDirectory())
throw new IOException("File is not a directory: " + path);
try
{
String[] files = fileRef4Java.list();
path = path.replace('\\', '/');
String pathWithSlash = path.endsWith("/") ? path : (path + '/'); // guich@564_5: check if the path ends with /
if (files != null) // guich@550_24
for (int i = 0; i < files.length; i++)
if (new java.io.File(pathWithSlash + files[i]).isDirectory()) // guich@554_9: add the path separator
files[i] += '/'; // add a / to a directory end
return files;
}
catch (java.lang.SecurityException e)
{
throw new IOException(e.getMessage());
}
}
/** If this is a file, returns true if the file has 0 bytes. If this is a folder, returns true if there are
* no files nor folders inside of it.
* @since TotalCross 1.27
*/
final public boolean isEmpty() throws IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
java.io.File fileRef4Java = (java.io.File) fileRef;
if (fileRef4Java.isDirectory())
{
String[] list = fileRef4Java.list();
return list == null || list.length == 0;
}
else
{
return fileRef4Java.length() == 0;
}
}
final public int readBytes(byte b[], int off, int len) throws IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode == DONT_OPEN)
throw new IOException("Operation cannot be used in DONT_OPEN mode");
if (b == null)
throw new java.lang.NullPointerException("Argument 'b' cannot have a null value");
if (off < 0 || len < 0 || off + len > b.length)
throw new java.lang.ArrayIndexOutOfBoundsException();
if (len == 0)
return 0; // flsobral@tc113_43: return 0 if asked to read 0.
java.io.RandomAccessFile fileEx4Java = (java.io.RandomAccessFile) fileEx;
try
{
int ret = fileEx4Java.read(b, off, len);
pos += ret;
return ret;
}
catch (java.io.IOException e)
{
throw new IOException(e.getMessage());
}
}
/**
* Renames the file. You must give the full directory specification for the file, to keep compatibility between all
* platforms. WinCE platform lets you move a file using rename, while Palm OS does not let you move the file. File is
* automatically closed prior to renaming. After this operation, this File object is invalid.
*
* Cannot be used in READ_ONLY mode.
*
* @param path
* the new name of the file.
*/
final public void rename(String path) throws IllegalArgumentIOException, IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode == READ_ONLY)
throw new IOException("Operation cannot be used in READ_ONLY mode");
if (path == null)
throw new java.lang.NullPointerException("Argument 'path' cannot have a null value");
if (path.length() == 0 || path.length() > 255)
throw new IllegalArgumentIOException("path", path);
path = path.replace('\\', '/');
java.io.File fileRef4Java = (java.io.File) fileRef;
this.close();
try
{
fileRef4Java.renameTo(new java.io.File(path));
}
catch (java.lang.SecurityException e)
{
throw new IOException(e.getMessage());
}
}
public int getPos() throws totalcross.io.IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode == DONT_OPEN)
throw new IOException("Operation cannot be used in DONT_OPEN mode");
return pos;
}
/**
* Sets the file pointer for read and write operations to the given position. The position passed is an absolute
* position, in bytes, from the beginning of the file. To set the position to just after the end of the file, you can
* call:
*
* <pre>
* file.setPos(file.getSize());
* </pre>
*
* Note: if you plan to change the file size using setPos, you must write something on the new size to effectively
* change the size. For example, on some devices if you call setPos and then read (assuming that the new pos is past
* the end of the file, the read method will fail. Here's a code that will change the size for sure:
*
* <pre>
* private static byte[] zeros = new byte[4096];
*
* public void setSize(int newSize)
* {
* int size = f.getSize();
* f.setPos(newSize - 1); // note: setPos(1) makes the file 2 bytes long (0, 1)
* f.setPos(size);
* for (int dif = newSize - size, n = 0; dif > 0; dif -= n)
* n = f.writeBytes(zeros, 0, dif > zeros.length ? zeros.length : dif);
* }
* </pre>
*/
final public void setPos(int pos) throws IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode == DONT_OPEN)
throw new IOException("Operation cannot be used in DONT_OPEN mode");
if (pos < 0)
throw new IOException("Argument 'pos' cannot be negative");
java.io.RandomAccessFile fileEx4Java = (java.io.RandomAccessFile) fileEx;
try
{
if (pos > fileEx4Java.length()) // guich@568_8: growing? force file size
fileEx4Java.setLength(pos + 1); // suppose the file is empty. seeking to pos 8 makes the file with 9 bytes (0-8).
fileEx4Java.seek(pos);
this.pos = pos;
}
catch (java.io.IOException e)
{
throw new IOException(e.getMessage());
}
}
final public void setPos(int offset, int origin) throws IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode == DONT_OPEN)
throw new IOException("Operation cannot be used in DONT_OPEN mode");
try
{
java.io.RandomAccessFile fileEx4Java = (java.io.RandomAccessFile) fileEx;
int newPos;
int fileLen = (int) fileEx4Java.length();
switch (origin)
{
case SEEK_SET:
newPos = offset;
break;
case SEEK_CUR:
newPos = pos + offset;
break;
case SEEK_END:
newPos = fileLen + offset - 1;
break;
default:
throw new IllegalArgumentException();
}
if (newPos < 0)
throw new IOException();
if (newPos >= fileLen)
fileEx4Java.setLength(newPos + 1);
fileEx4Java.seek(newPos);
pos = newPos;
}
catch (java.io.IOException e)
{
throw new IOException(e.getMessage());
}
}
final public int writeBytes(byte b[], int off, int len) throws IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode == DONT_OPEN)
throw new IOException("Operation cannot be used in DONT_OPEN mode");
if (mode == READ_ONLY)
throw new IOException("Operation cannot be used in READ_ONLY mode");
if (b == null)
throw new java.lang.NullPointerException("Argument 'b' cannot have a null value");
if (off < 0 || len < 0 || off + len > b.length)
throw new java.lang.ArrayIndexOutOfBoundsException();
if (len == 0)
return 0;
java.io.RandomAccessFile fileEx4Java = (java.io.RandomAccessFile) fileEx;
try
{
fileEx4Java.write(b, off, len);
pos += len;
return len;
}
catch (java.io.IOException e)
{
throw new IOException(e.getMessage());
}
}
/**
* Sets the attributes of the opened file - cannot be used with <code>DONT_OPEN</code>.<br>
* Platform specific notes:
* <ul>
* <li>JDK - This method has no effect on any file when running on JDK, it only checks if the object state and the
* received argument are valid.
* <li>BLACKBERRY - Supports only <code>ATTR_HIDDEN</code> and <code>ATTR_READ_ONLY</code>.
* <li>LINUX, IPHONE, and ANDROID - The attributes <code>ATTR_HIDDEN</code> and <code>ATTR_ARCHIVE</code> are not
* supported by Unix based systems. Using them will not throw an exception, but it will have no effect on the file.
* <li>PALMOS - Avoid using the attribute <code>ATTR_READ_ONLY</code> on files located in the device's internal
* storage. Marking a file as read only affects also its attributes, which means it can't be undone. The only way to
* remove a file marked as read only is performing a hard reset.
* </ul>
*
* @param attr
* one ore more ATTR_xxx constants ORed together.
* @see #ATTR_ARCHIVE
* @see #ATTR_HIDDEN
* @see #ATTR_READ_ONLY
* @see #ATTR_SYSTEM
*/
final public void setAttributes(int attr) throws IllegalArgumentIOException, IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode == DONT_OPEN)
throw new IOException("Operation cannot be used in DONT_OPEN mode");
if (mode == READ_ONLY)
throw new IOException("Operation cannot be used in READ_ONLY mode");
if (attr < 0 || attr > 15) // the user may reset all attributes, so 0 is a valid number
throw new IllegalArgumentIOException("attr", null);
}
/**
* Gets this file attributes. The file must be opened in a mode different of DONT_OPEN.
* <p>
* This method does not work on desktop, but the arguments are still checked.
*
* @return The file attributes ORed together.
* @see #ATTR_ARCHIVE
* @see #ATTR_HIDDEN
* @see #ATTR_READ_ONLY
* @see #ATTR_SYSTEM
*/
final public int getAttributes() throws IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode == DONT_OPEN)
throw new IOException("Operation cannot be used in DONT_OPEN mode");
return 0;
}
/**
* Sets the time attribute of the opened filed - cannot be used with <code>DONT_OPEN</code> or <code>READ_ONLY</code>.<br>
* Platform specific notes:
* <ul>
* <li>JDK - This method has no effect on any file when running on JDK, it only checks if the object state and the
* received arguments are valid.
* <li>WINCE - Supports only <code>TIME_MODIFIED</code> if the file is stored on the device's non-volatile memory. If the file is stored in an
* external FAT storage, it also supports <code>TIME_CREATED</code>.
* <li>BLACKBERRY - Not supported.
* <li>LINUX, IPHONE and ANDROID - Unix based systems do not keep record of the file's creation time. Attempting to
* do so will not thrown an exception, but it will have no effect on the file.
* </ul>
*
* @param whichTime
* One or more of the TIME_xxx constants, ORed together.
* @param time
* The new time.
*
* @see #TIME_ALL
* @see #TIME_ACCESSED
* @see #TIME_CREATED
* @see #TIME_MODIFIED
*/
final public void setTime(byte whichTime, totalcross.sys.Time time) throws IllegalArgumentIOException, IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode == DONT_OPEN)
throw new IOException("Operation cannot be used in DONT_OPEN mode");
if (mode == READ_ONLY)
throw new IOException("Operation cannot be used in READ_ONLY mode");
if (time == null)
throw new java.lang.NullPointerException("Argument 'time' cannot have a null value");
if (whichTime != 0x1 && whichTime != 0x2 && whichTime != 0x4 && whichTime != 0xF)
throw new IllegalArgumentIOException("whichTime", null);
}
/**
* Retrieves the specified time attribute of the opened file - cannot be used with <code>DONT_OPEN</code>.<br>
* <ul>
* <li>JDK - If the object state and the received argument are valid, it will always return the time of the last
* modification.
* <li>WINCE - Supports only <code>TIME_MODIFIED</code> if the file is stored on the device's non-volatile memory. If the file is stored in an
* external FAT storage, it also supports <code>TIME_CREATED</code>.
* <li>BLACKBERRY - Supports only <code>TIME_MODIFIED</code>.
* <li>LINUX, IPHONE and ANDROID - Unix based systems do not keep record of the file's creation time. Using the
* constant <code>TIME_CREATED</code> will return the last time the file was changed, which is updated when changes
* are made to the file's inode (owner, permissions, etc.), and also when the contents of the file are modified.<br>
* The constant <code>TIME_MODIFIED</code> returns the last time the contents of the file were modified.<br>
* </ul>
*
* @param whichTime
* value must be <code>TIME_ACCESSED</code>, <code>TIME_CREATED</code> or <code>TIME_MODIFIED</code>. Any
* other value will result in an exception.
*
* @see #TIME_ACCESSED
* @see #TIME_CREATED
* @see #TIME_MODIFIED
*/
final public totalcross.sys.Time getTime(byte whichTime) throws IllegalArgumentIOException, IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode == DONT_OPEN)
throw new IOException("Operation cannot be used in DONT_OPEN mode");
if (whichTime != 0x1 && whichTime != 0x2 && whichTime != 0x4)
throw new IllegalArgumentIOException("whichTime", null);
java.util.Calendar cal = java.util.Calendar.getInstance();
java.io.File fileRef4Java = (java.io.File) fileRef;
cal.setTime(new java.util.Date(fileRef4Java.lastModified()));
return new totalcross.sys.Time(cal.get(java.util.Calendar.YEAR), cal.get(java.util.Calendar.MONTH) + 1,
cal.get(java.util.Calendar.DATE), cal.get(java.util.Calendar.HOUR_OF_DAY),
cal.get(java.util.Calendar.MINUTE), cal.get(java.util.Calendar.SECOND),
cal.get(java.util.Calendar.MILLISECOND));
}
/**
* Returns the volume File for the Windows CE and Pocket PC, and BlackBerry devices. On these devices, the volume has a special
* folder name, but since there's no system call that informs this, we must just test the existence of each folder,
* returning the first one that exists. You can set the winceVols string array to the ones you want to be searched. <br>
* <br>
* To access the card on Android devices, prefix the path with <code>/sdcard</code>. Be sure that the sdcard is NOT MOUNTED, otherwise your application will not have access to it.
* Some android devices have more than one sdcard, an internal and an external ones. On such devices, /sdcard is the internal one; to find the external path, you must get into the device
* because there's no API to get it. For example, on Galaxy devices, it is /mnt/extSdCard.
*
* @return The File object which references the volume, ended with backslash, or null if none found.
* @see #winceVols
*/
final public static File getCardVolume() throws IOException
{
return null;
}
/**
* Sets the file size, growing the size or truncating it.
*
* @param newSize
* The new file size.
*
* @since SuperWaba 5.83
*/
final public void setSize(int newSize) throws IOException
{
if (mode == INVALID)
throw new IOException("Invalid file handle");
if (mode == DONT_OPEN)
throw new IOException("Operation cannot be used in DONT_OPEN mode");
if (mode == READ_ONLY)
throw new IOException("Operation cannot be used in READ_ONLY mode");
java.io.RandomAccessFile fileEx4Java = (java.io.RandomAccessFile) fileEx;
try
{
fileEx4Java.setLength(newSize); // suppose the file is empty. seeking to pos 8 makes the file with 9 bytes (0-8).
fileEx4Java.seek(newSize);
}
catch (java.io.IOException e)
{
throw new IOException(e.getMessage());
}
}
/**
* Returns the card serial number for the given slot.
* <p>
* This method only works on Palm OS.
*
* @param slot
* The slot number, or -1 to use the last slot (which is usually an external card if the device has such
* slot).
* @since TotalCross 1.0
*/
final public static String getCardSerialNumber(int slot) throws IllegalArgumentIOException, IOException
{
if (slot < -1)
throw new IllegalArgumentIOException("slot", Convert.toString(slot));
return null;
}
protected void finalize()
{
try
{
if (mode != INVALID)
this.close();
}
catch (Throwable t)
{
}
}
/**
* Returns the slot number passed in the constructor, or -1 if no slot was given.
*
* @since TotalCross 1.0
*/
public int getSlot()
{
return slot;
}
private static void listFiles(String dir, Vector files, boolean recursive) throws IOException // guich@tc115_92
{
String[] list = new File(dir).listFiles();
if (list != null)
for (int i = 0; i < list.length; i++)
{
String p = list[i];
String full = Convert.appendPath(dir, p);
files.addElement(full);
if (recursive && p.endsWith("/"))
listFiles(full, files, recursive);
}
}
/**
* Returns a recursive list of all files inside the given directory (including it). The array is sorted upon return.
*
* @since TotalCross 1.15
*/
public static String[] listFiles(String dir) throws IOException // guich@tc115_92
{
return listFiles(dir, true);
}
/**
* Lists all the files in the specified directory, and also the files in the subdirectories if recursive is true.
*
* @param dir
* @param recursive
* @throws IOException
*/
public static String[] listFiles(String dir, boolean recursive) throws IOException
{
Vector files = new Vector(50);
dir = Convert.appendPath(dir, "/");
files.addElement(dir);
listFiles(dir, files, recursive);
files.qsort();
return (String[]) files.toObjectArray();
}
/**
* Deletes a directory and all its subdirectories and files.
* If you have problems trying to recreate the directory, be sure to call <code>Vm.gc()</code>
* after calling this method.
*
* @since TotalCross 1.15
*/
public static void deleteDir(String dir) throws IOException // guich@tc115_92
{
String[] files = listFiles(dir);
for (int i = files.length; --i >= 0;)
new File((String) files[i]).delete();
}
/** List the root drives. If there are no roots, returns null.
* Works on Win32, Java, and Blackberry platforms.
* @since TotalCross 1.22
*/
public static String[] listRoots() // fabio@tc122_14
{
java.io.File[] roots = java.io.File.listRoots();
if (roots == null)
return null;
String[] result = new String[roots.length];
for (int i = roots.length; --i >= 0;)
result[i] = roots[i].getPath();
return result;
}
/** Copies the current file to the given one.
* You must close both files after calling this method.
* Here's a sample of how to copy a file:
* <pre>
File src = new File(srcFileName,File.READ_WRITE);
File dest = new File(destFileName,File.CREATE_EMPTY);
src.copyTo(dest);
src.close();
dest.close();
* </pre>
* This method is thread-safe.
* @see #moveTo(File)
* @since TotalCross 1.27
*/
public void copyTo(File dest) throws IOException // guich@tc126_8
{
try {setPos(0);} catch (IOException ioe) {}
try {dest.setPos(0);} catch (IOException ioe) {}
byte[] buf = new byte[4096];
int n = 0;
while ((n=readBytes(buf, 0, buf.length)) > 0)
dest.writeBytes(buf,0,n);
}
/** Moves the current file to the given one (the original file is deleted).
* You must explicitly close the destination file after this operation is done.
* Here's a sample of how to move a file:
* <pre>
File src = new File(srcFileName,File.READ_WRITE);
File dest = new File(destFileName,File.CREATE_EMPTY);
src.moveTo(dest);
// src.close(); - not needed! src was deleted
dest.close();
* </pre>
* This method is thread-safe.
* @see #copyTo(File)
* @since TotalCross 1.27
*/
public void moveTo(File dest) throws IOException // guich@tc126_8
{
if (mode == READ_ONLY)
throw new IOException("Operation cannot be used in READ_ONLY mode");
copyTo(dest);
delete();
}
/** A handy method to call copyTo creating two File instances and closing them.
* The target file is erased if it exists.
* This method is thread-safe. If you want to have more control, use the copyTo method
* @see #copyTo(File)
* @since TotalCross 1.27
*/
public static void copy(String src, String dst) throws IOException // guich@tc126_43
{
File fin=null,fout=null;
try
{
fin = new File(src,File.READ_ONLY);
fout = new File(dst,File.CREATE_EMPTY);
fin.copyTo(fout);
}
finally
{
try {if (fin != null) fin.close();} catch (Exception e) {}
try {if (fout != null) fout.close();} catch (Exception e) {}
}
}
/** A handy method to call moveTo creating two File instances and closing them.
* The target file is erased if it exists.
* This method is thread-safe. If you want to have more control, use the other moveTo method
* @see #moveTo(File)
* @since TotalCross 1.27
*/
public static void move(String src, String dst) throws IOException // guich@tc126_43
{
File fin=null,fout=null;
try
{
fin = new File(src,File.READ_WRITE);
fout = new File(dst,File.CREATE_EMPTY);
fin.moveTo(fout);
}
finally
{
try {if (fout != null) fout.close();} catch (Exception e) {}
}
}
/** Applies the given permissions to this file. Works only on Unix-based operating systems: Linux, Android, and iOS. On JDK 1.6, the first number (user) is applied to all groups.
* Below you see a table with some chmod values (r = read, w = write, x = execute).
* <pre>
* Number Permission
000 ---------
400 r--------
444 r--r--r--
600 rw-------
620 rw--w----
640 rw-r-----
644 rw-r--r--
645 rw-r--r-x
646 rw-r--rw-
650 rw-r-x---
660 rw-rw----
661 rw-rw---x
662 rw-rw--w-
663 rw-rw--wx
664 rw-rw-r--
666 rw-rw-r--
700 rwx------
750 rwxr-x---
755 rwxr-xr-x
777 rwxrwxrwx
* </pre>
* The numbers represents a group of 3. The first number is the permission for <i>user</i>, the second number for <i>group</i>, and the third number for <i>others</i>
* These are the possible permission values for each number:
* <pre>
Permission Binary Decimal
--- 000 0
--x 001 1
-w- 010 2
-wx 011 3
r-- 100 4
r-x 101 5
rw- 110 6
rwx 111 7
* </pre>
* Failing to change the permission returns -1.
* <br><br>
* Here's a sample:
* <pre>
try
{
// testing in a folder
File f = new File(Settings.appPath);
add(new Label("mods of appPath = "+f.chmod(-1)),CENTER,CENTER);
// testing in a file
String name = "test";
f = new File(Settings.appPath+'/'+name,File.CREATE_EMPTY);
int m0 = f.chmod(777); // change it
int m1 = f.chmod(-1); // retrieve the changed value
add(new Label("mods of "+name+" = "+m0+" -> "+m1+" (777)"),CENTER,AFTER+5);
}
catch (Exception ee)
{
MessageBox.showException(ee,true);
}
* </pre>
* @param mod The modifiers you want to set in DECIMAL, or -1 to just return the current ones.
* @return The modifiers that were set before you called this method (or the current modifiers, if -1 is being passed). Some platforms may return more than 3 digits, indicating extra attributes (for example, if it's a file or a directory).
* @since TotalCross 1.27
*/
public int chmod(int mod) throws IOException // guich@tc126_16
{
if (mode == INVALID)
throw new IOException("Invalid file handle"); //flsobral@tc126: object must be valid.
java.io.File f = (java.io.File) fileRef;
if (!f.exists())
throw new FileNotFoundException(path); //flsobral@tc126: throw exception if the file does not exist.
try
{
int current = (f.canExecute() ? 1 : 0) | (f.canWrite() ? 2 : 0) | (f.canRead() ? 4 : 0 );
current = current + current*10 + current*100;
mod /= 100; // 753 -> 7
f.setExecutable((mod & 1) != 0, false);
f.setWritable ((mod & 2) != 0, false);
f.setReadable ((mod & 4) != 0, false);
return current;
}
catch (java.lang.SecurityException e)
{
throw new IOException(e.getMessage()); //flsobral@tc126: throw exception on error.
}
}
/** Reads the entire file into a byte array and closes itself. A handy method that can be used like this:
* <pre>
* byte[] bytes = new File(...,File.READ_ONLY).readAndClose();
* </pre>
* The only drawback is that this method consumes lots of memory if the file is big; use it carefully.
* @since TotalCross 1.53
*/
public byte[] readAndClose() throws IOException
{
byte[] ret = read();
close();
return ret;
}
/** Writes byte array to this file and closes itself. A handy method that can be used like this:
* <pre>
* new File(...,File.CREATE_EMPTY).writeAndClose(Vm.getFile("myfile.txt"));
* </pre>
* The only drawback is that this method consumes lots of memory if the file is big; use it carefully.
* @since TotalCross 1.53
*/
public void writeAndClose(byte[] bytes) throws IOException
{
writeBytes(bytes, 0, bytes.length);
close();
}
/** Reads the file and returns a byte array with its contents.
* @since TotalCross 3.1
*/
public byte[] read() throws IOException
{
int len = getSize();
byte[] ret = new byte[len];
readBytes(ret,0,len);
return ret;
}
}