FileDocCategorySizeDatePackage
Protocol.javaAPI DocphoneME MR2 API (J2ME)46409Wed May 02 18:00:28 BST 2007com.sun.midp.io.j2me.file

Protocol.java

/*
 *   
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program 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. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */

package com.sun.midp.io.j2me.file;

import com.sun.midp.security.Permissions;
import com.sun.midp.main.Configuration;
import com.sun.midp.io.*;

import javax.microedition.io.file.*;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.util.Enumeration;
import javax.microedition.io.*;
import com.sun.midp.security.*;

import java.util.Vector;

import com.sun.midp.midlet.MIDletSuite;
import com.sun.midp.midlet.Scheduler;

/**
 * This class implements the necessary functionality
 * for a File connection.
 */
public class Protocol extends ConnectionBaseAdapter implements FileConnection {

    /** Security token for using FileConnection API from PIM */
    private SecurityToken classSecurityToken;

    /** Stores file connection mode */
    private int mode;

    /** File name string */
    private String fileName;

    /** File path string including root filesystem */
    private String filePath;

    /** Root filesystem for the file */
    private String fileRoot;

    /** File original URL */
    private String fileURL;

    /** A peer to the native file */
    private BaseFileHandler fileHandler;

    /** Indicates if there is a need to try to load alternative file handler */
    private static boolean hasOtherFileHandler = true;

    /** Input stream associated with this connection */
    InputStream fis;

    /** Output stream associated with this connection */
    OutputStream fos;

    /** Separator for file path components */
    private static String sep;

    /** Static initialization of file separator */
    static {
        char[] t = new char[1];
        t[0]= DefaultFileHandler.getFileSeparator();
        sep = new String(t);
        if (sep == null) {
            throw new
                NullPointerException("Undefined \"file.separator\" property");
        }
    }

    /**
     * Constructor for file connection implementation.
     */
    public Protocol() {
        connectionOpen = false;
        fileHandler = null;
    }

    /**
     * Opens the file connection.
     * @param name URL path fragment
     * @param mode access mode
     * @param timeouts flag to indicate that timeouts allowed
     * @return an opened Connection
     * @throws IOException if some other kind of I/O error occurs.
     */
    public Connection openPrim(String name, int mode, boolean timeouts)
            throws IOException {
        return openPrimImpl(name, mode, timeouts, true);
    }

    /**
     * Opens the file connection and receive security token.
     * @param token security token from PIM
     * @param name URL path fragment
     * @return an opened Connection
     * @throws IOException if some other kind of I/O error occurs.
     */
    public Connection openPrim(SecurityToken token, String name)
            throws IOException {
        return openPrim(token, name, Connector.READ_WRITE);
    }

    /**
     * Opens the file connection and receive security token.
     * @param token security token from PIM
     * @param name URL path fragment
     * @param mode access mode
     * @return an opened Connection
     *  @throws IOException if some other kind of I/O error occurs.
     */
    public Connection openPrim(SecurityToken token, String name, int mode)
            throws IOException {
        classSecurityToken = token;
        return openPrim(name, mode, false);
    }

    // JAVADOC COMMENT ELIDED
    public boolean isOpen() {
        return connectionOpen;
    }

    /**
     * Open and return an input stream for a connection.  The connection's
     * target must already exist and be accessible for the input stream to be
     * created.
     *
     * @return  An open input stream
     * @exception  IOException if an I/O error occurs, if the method is invoked
     *          on a directory, if the connection's target does not
     *          yet exist, or the connection's target is not accessible.
     * @exception  IllegalModeException if the application does have read
     *             access
     *          to the connection's target but has opened the connection in
     *          <code>Connector.WRITE</code> mode.
     * @exception  SecurityException   If the application is not granted read
     *          access to the connection's target.
     *
     */
    public InputStream openInputStream() throws IOException {

        checkReadPermission();

        try {
            ensureOpenAndConnected();
        } catch (ConnectionClosedException e) {
            throw new IOException(e.getMessage());
        }

        // IOException when target file doesn't exist
        if (!fileHandler.exists()) {
            throw new IOException("Target file doesn't exist");
        }

        if (!fileHandler.canRead()) { // no read access
            throw new SecurityException("No read access");
        }

        fileHandler.openForRead();

        fis = super.openInputStream();

        return fis;
    }

    /**
     * Open and return an output stream for a connection. The output stream
     * is positioned at the start of the file. Writing data to the output
     * stream overwrites the contents of the files (i.e. does not insert data).
     * Writing data to output streams beyond the current end of file
     * automatically extends the file size.  The connection's target must
     * already exist and be accessible for the output stream to be created.
     * {@link #openOutputStream(long)} should be used to position an output
     * stream to a different position in the file.
     * <P>
     * Changes made to a file through an output stream may not be immediately
     * made to the actual file residing on the file system because
     * platform and implementation specific use of caching and buffering of the
     * data.  Stream contents and file length extensions are not necessarily
     * visible outside of the application immediately unless
     * <code>flush()</code> is called on the stream.? The returned output
     *  stream is automatically and synchronously flushed when it is closed.
     * </P>
     *
     * @return  An open output stream
     * @exception  IOException  If an I/O error occurs, if the method is
     *             invoked on
     *          a directory, the file does not yet exist, or the connection's
     *          target is not accessible.
     * @exception  IllegalModeException if the application does have write
     *             access
     *          to the connection's target but has opened the connection in
     *          <code>Connector.READ</code> mode.
     * @exception  SecurityException    If the application is not granted write
     *          access to the connection's target.
     * @see #openOutputStream(long)
     *
     */
    public OutputStream openOutputStream() throws IOException {
        return openOutputStream(0);
    }

    // JAVADOC COMMENT ELIDED
    public OutputStream openOutputStream(long byteOffset) throws IOException {
        if (byteOffset < 0) {
            throw new IllegalArgumentException("Offset has a negative value");
        }

        checkWritePermission();

        try {
            ensureOpenAndConnected();
        } catch (ConnectionClosedException e) {
            throw new IOException(e.getMessage());
        }

        // IOException when target file doesn't exist
        if (!fileHandler.exists()) {
            throw new IOException("Target file doesn't exist");
        }

        if (!fileHandler.canWrite()) {
            // no write access
            throw new SecurityException("No write access");
        }

        fileHandler.openForWrite();
        fileHandler.positionForWrite(byteOffset);

        fos = super.openOutputStream();

        return fos;
    }

    // JAVADOC COMMENT ELIDED
    public long totalSize() {
        long size = -1;

        try {
            checkRootReadPermission();

            ensureOpenAndConnected();

            size = fileHandler.totalSize();
        } catch (IOException e) {
            size = -1;
        }

        return size;
    }

    // JAVADOC COMMENT ELIDED
    public long availableSize() {
        long size = -1;

        try {
            checkRootReadPermission();

            ensureOpenAndConnected();

            size = fileHandler.availableSize();
        } catch (IOException e) {
            size = -1;
        }

        return size;
    }

    // JAVADOC COMMENT ELIDED
    public long usedSize() {
        long size = -1;

        try {
            checkRootReadPermission();

            ensureOpenAndConnected();

            size = fileHandler.usedSize();
        } catch (IOException e) {
            size = -1;
        }

        return size;
    }

    // JAVADOC COMMENT ELIDED
    public long directorySize(boolean includeSubDirs) throws IOException {
        long size = 0;

        // Permissions and ensureOpenAndConnected called by exists()
        if (exists()) {
            if (!isDirectory()) {
                throw new
                    IOException("directorySize is not invoked on directory");
            }
        } else {
            return -1L;
        }

        try {
            size = fileHandler.directorySize(includeSubDirs);
        } catch (IOException e) {
            size = -1;
        }

        return size;
    }

    // JAVADOC COMMENT ELIDED
    public long fileSize() throws IOException {
        long size = -1;

        checkReadPermission();

        if (isDirectory()) {
            throw new IOException("fileSize invoked on a directory");
        }

        try {
            ensureOpenAndConnected();

            size = fileHandler.fileSize();
        } catch (IOException e) {
            size = -1;
        }

        return size;
    }

    // JAVADOC COMMENT ELIDED
    public boolean canRead() {
        boolean res = false;

        try {
            checkReadPermission();

            ensureOpenAndConnected();

            res = fileHandler.canRead();
        } catch (IOException e) {
            res = false;
        }

        return res;
    }

    // JAVADOC COMMENT ELIDED
    public boolean canWrite() {
        boolean res = false;

        try {
            checkReadPermission();

            ensureOpenAndConnected();

            res = fileHandler.canWrite();
        } catch (IOException e) {
            res = false;
        }

        return res;
    }

    // JAVADOC COMMENT ELIDED
    public boolean isHidden() {
        boolean res = false;

        try {
            checkReadPermission();

            ensureOpenAndConnected();

            res = fileHandler.isHidden();
        } catch (IOException e) {
            res = false;
        }

        return res;
    }

    // JAVADOC COMMENT ELIDED
    public void setReadable(boolean readable) throws IOException {
        checkWritePermission();

        ensureOpenAndConnected();

        fileHandler.setReadable(readable);
    }

    // JAVADOC COMMENT ELIDED
    public void setWritable(boolean writable) throws IOException {
        checkWritePermission();

        ensureOpenAndConnected();

        fileHandler.setWritable(writable);
    }

    // JAVADOC COMMENT ELIDED
    public void setHidden(boolean hidden) throws IOException {
        checkWritePermission();

        ensureOpenAndConnected();

        fileHandler.setHidden(hidden);
    }

    // JAVADOC COMMENT ELIDED
    public Enumeration list() throws IOException {
        return listInternal(null, false);
    }

    // JAVADOC COMMENT ELIDED
    public Enumeration list(String filter, boolean includeHidden)
        throws IOException {

        if (filter == null) {
            throw new NullPointerException("List filter is null");
        }

        return listInternal(EscapedUtil.getUnescapedString(filter),
            includeHidden);
    }

    // JAVADOC COMMENT ELIDED
    public void create() throws IOException {
        checkWritePermission();

        ensureOpenAndConnected();

        if (fileName.charAt(fileName.length() - 1) == '/') {
            throw new IOException("Can not create directory");
        }

        fileHandler.create();
    }

    // JAVADOC COMMENT ELIDED
    public void mkdir() throws IOException {
        checkWritePermission();

        ensureOpenAndConnected();

        fileHandler.mkdir();
    }

    // JAVADOC COMMENT ELIDED
    public boolean exists() {
        boolean res = false;

        try {
            checkReadPermission();

            ensureOpenAndConnected();

            res = fileHandler.exists();
        } catch (IOException e) {
            res = false;
        }

        return res;
    }

    // JAVADOC COMMENT ELIDED
    public boolean isDirectory() {
        boolean res = false;

        try {
            checkReadPermission();

            ensureOpenAndConnected();

            res = fileHandler.isDirectory();
        } catch (IOException e) {
            res = false;
        }

        return res;
    }

    // JAVADOC COMMENT ELIDED
    public void delete() throws java.io.IOException {
        checkWritePermission();

        ensureOpenAndConnected();

        try {
            if (fis != null) {
                fis.close();
                fis = null;
            }
        } catch (IOException e) {
            // Ignore silently
        }

        try {
            if (fos != null) {
                fos.close();
                fos = null;
            }
        } catch (IOException e) {
            // Ignore silently
        }

        try {
            fileHandler.closeForReadWrite();
        } catch (IOException e) {
            // Ignore silently
        }

        fileHandler.delete();
    }


    // JAVADOC COMMENT ELIDED
    public void rename(String newName) throws IOException {
        checkWritePermission();

        newName = EscapedUtil.getUnescapedString(newName);
        // Following line will throw NullPointerException if newName is null
        int dirindex = newName.indexOf('/');
        if (dirindex != -1 && dirindex != (newName.length() - 1)) {
            throw new
              IllegalArgumentException("New name contains path specification");
        }

        if (!"/".equals(sep) && newName.indexOf(sep) != -1) {
            throw new
              IllegalArgumentException("New name contains path specification");
        }

        ensureOpenAndConnected();
        checkIllegalChars(newName);

        try {
            if (fis != null) {
                fis.close();
                fis = null;
            }
        } catch (IOException e) {
            // Ignore silently
        }

        try {
            if (fos != null) {
                fos.close();
                fos = null;
            }
        } catch (IOException e) {
            // Ignore silently
        }

        try {
            fileHandler.closeForReadWrite();
        } catch (IOException e) {
            // Ignore silently
        }

        fileHandler.rename(filePath + newName);

        fileName = newName;
        fileURL = "file://" + filePath + fileName;
    }

    // JAVADOC COMMENT ELIDED
    public void truncate(long byteOffset) throws IOException {
        checkWritePermission();

        ensureOpenAndConnected();

        if (byteOffset < 0) {
            throw new IllegalArgumentException("offset is negative");
        }

        try {
            if (fos != null) {
                fos.flush();
            }
        } catch (IOException e) {
            // Ignore silently
        }

        fileHandler.truncate(byteOffset);
    }

    // JAVADOC COMMENT ELIDED
    public void setFileConnection(String fileName) throws IOException {
        ensureOpenAndConnected();

        // Note: permissions are checked by openPrim method

        // Following line will throw NullPointerException if fileName is null
        int dirindex = fileName.indexOf('/');
        if (dirindex != -1 && dirindex != (fileName.length() - 1)) {
            throw new IllegalArgumentException(
                "Contains any path specification");
        }

        if (fileName.equals("..") && this.fileName.length() == 0) {
            throw new IOException(
                "Cannot set FileConnection to '..' from a file system root");
        }

        if (!"/".equals(sep) && fileName.indexOf(sep) != -1) {
            throw new
            IllegalArgumentException("Contains any path specification");
        }

        checkIllegalChars(fileName);

        // According to the spec, the current FileConnection object must refer
        // to a directory.
        // Check this right here in order to avoid IllegalModeException instead
        // of IOException.
        if (!fileHandler.isDirectory()) {
            throw new IOException("Not a directory");
        }

        String origPath = filePath, origName = this.fileName;

        String tmp_sep;
        // Note: security checks are performed before any object state changes
        if (fileName.equals("..")) {
            // go one directory up
            openPrim("//" + filePath, mode, false);
        } else {
            int fileNameLen = this.fileName.length();
            if (fileNameLen == 0 || this.fileName.charAt(fileNameLen - 1) == '/') {
                tmp_sep = "";
            } else {
                tmp_sep = "/";
            }
            // go deeper in directory structure
            openPrimImpl("//" + filePath 
                     + this.fileName + tmp_sep + fileName,
                     mode, false, false);
        }

        // Old file connection must be a directory. It can not have open
        // streams so no need to close it. Just reset it to null
        fileHandler = null;

        // Reconnect to the new target
        ensureOpenAndConnected();

        // At this point we are already refer to the new file
        if (!fileHandler.exists()) {
            // Revert to an old file
            openPrim("//" + origPath + origName, mode, false);
            fileHandler = null;

            throw new IllegalArgumentException("New target does not exists");
        }
    }

    /**
     * Spec is not consistent: sometimes it requires IOException
     * and sometimes IllegalArgumentException in case of illegal chars
     * in the filename
     * @param name URL path fragment
     * @throws IOException if name contains unsupported characters
     */
    private void checkIllegalChars(String name) throws IOException {

        String illegalChars = fileHandler.illegalFileNameChars();
        for (int i = 0; i < illegalChars.length(); i++) {
            if (name.indexOf(illegalChars.charAt(i)) != -1) {
                throw new
                    IOException("Contains characters invalid for a filename");
            }
        }
    }

    // JAVADOC COMMENT ELIDED
    public String getName() {
        String name = fileName;

        try {
            if (exists()) {
                int lastPos = name.length() - 1;
                if (isDirectory()) {
                    if (!name.equals("") && name.charAt(lastPos) != '/') 
                        name += '/';
                } else {
                    if (name.charAt(lastPos) == '/')
                        name = name.substring(0, lastPos);
                }
            }
        } catch (SecurityException e) {
            // According to spec should silently ignore any exceptions
        } catch (IllegalModeException e) {
            // According to spec should silently ignore any exceptions
        } catch (ConnectionClosedException e) {
            // According to spec should silently ignore any exceptions
        }

        return name;
    }

    // JAVADOC COMMENT ELIDED
    public String getPath() {
        return filePath;
    }

    // JAVADOC COMMENT ELIDED
    public String getURL() {
        String url = EscapedUtil.getEscapedString(fileURL);

        try {
            if (exists()) {
                int lastPos = url.length() - 1;
                if (isDirectory()) {
                    if (url.charAt(lastPos) != '/') {
                        url += '/';
                    }
                } else {
                    if (url.charAt(lastPos) == '/') {
                        url = url.substring(0, lastPos);
                    }
                }
            }
        } catch (SecurityException e) {
            // According to spec should silently ignore any exceptions
        } catch (IllegalModeException e) {
            // According to spec should silently ignore any exceptions
        } catch (ConnectionClosedException e) {
            // According to spec should silently ignore any exceptions
        }

        return url;
    }

    // JAVADOC COMMENT ELIDED
    public long lastModified() {
        long res = 0;

        try {
            checkReadPermission();

            ensureOpenAndConnected();

            res =  fileHandler.lastModified();
        } catch (IOException e) {
            res = 0;
        }

        return res;
    }

    /**
     * Reads up to <code>len</code> bytes of data from the input stream into
     * an array of bytes, blocks until at least one byte is available.
     *
     * @param      b     the buffer into which the data is read.
     * @param      off   the start offset in array <code>b</code>
     *                   at which the data is written.
     * @param      len   the maximum number of bytes to read.
     * @return     the total number of bytes read into the buffer, or
     *             <code>-1</code> if there is no more data because the end of
     *             the stream has been reached.
     * @exception  IOException  if an I/O error occurs.
     */
    protected int readBytes(byte b[], int off, int len)
        throws IOException {

        checkReadPermission();

        ensureConnected();

        int readBytes = fileHandler.read(b, off, len);
        // return '-1' instead of '0' as stream specification requires
        // in case the end of the stream has been reached
        return (readBytes > 0) ? readBytes : -1;
    }

    /**
     * Returns the number of bytes that can be read (or skipped over) from
     * this input stream without blocking by the next caller of a method for
     * this input stream.  The next caller might be the same thread or
     * another thread. This classes implementation always returns
     * <code>0</code>. It is up to subclasses to override this method.
     *
     * @return     the number of bytes that can be read from this input stream
     *             without blocking.
     * @exception  IOException  if an I/O error occurs.
     *
     *     public int available() throws IOException {
     *    return 0;
     *}
     */
    /**
     * Writes <code>len</code> bytes from the specified byte array
     * starting at offset <code>off</code> to this output stream.
     * <p>
     * Polling the native code is done here to allow for simple
     * asynchronous native code to be written. Not all implementations
     * work this way (they block in the native code) but the same
     * Java code works for both.
     *
     * @param      b     the data.
     * @param      off   the start offset in the data.
     * @param      len   the number of bytes to write.
     * @return     number of bytes written
     * @exception  IOException  if an I/O error occurs. In particular,
     *             an <code>IOException</code> is thrown if the output
     *             stream is closed.
     */
    protected int writeBytes(byte b[], int off, int len)
        throws IOException {
        checkWritePermission();

        ensureConnected();

        return fileHandler.write(b, off, len);
    }

    /**
     * Forces any buffered output bytes to be written out.
     * The general contract of <code>flush</code> is
     * that calling it is an indication that, if any bytes previously
     * written that have been buffered by the connection,
     * should immediately be written to their intended destination.
     * <p>
     * The <code>flush</code> method of <code>ConnectionBaseAdapter</code>
     * does nothing.
     *
     * @exception  IOException  if an I/O error occurs.
     */
    protected void flush() throws IOException {
        checkWritePermission();

        ensureConnected();

        fileHandler.flush();
    }

    /**
     * Called once by each child input stream.
     * If the input stream is marked open, it will be marked closed and
     * the if the connection and output stream are closed the disconnect
     * method will be called.
     *
     * @exception IOException if the subclass throws one
     */
    protected void closeInputStream() throws IOException {
        maxIStreams++;
        fileHandler.closeForRead();
        super.closeInputStream();
    }

    /**
     * Called once by each child output stream.
     * If the output stream is marked open, it will be marked closed and
     * the if the connection and input stream are closed the disconnect
     * method will be called.
     *
     * @exception IOException if the subclass throws one
     */
    protected void closeOutputStream() throws IOException {
        maxOStreams++;
        flush();
        fileHandler.closeForWrite();
        super.closeOutputStream();
    }

    /**
     * Free up the connection resources.
     *
     * @exception  IOException  if an I/O error occurs.
     */
    protected void disconnect() throws IOException {
        try {
            if (fileHandler != null) {
                fileHandler.close();
            }
        } finally {
            fileHandler = null;
        }
    }

    // In order to compile against MIDP's ConnectionBaseAdapter
    /**
     * Establishes the connection.
     * @param name URL path fragment
     * @param mode access mode
     * @param timeouts flag to indicate that timeouts allowed
     * @throws IOException if an error occurs
     */
    protected void connect(String name, int mode, boolean timeouts)
        throws IOException {}

    /**
     * Checks that the connection is already open.
     * @throws IOException if the connection is closed
     */
    protected void ensureConnected() throws IOException {
        if (!isRoot(fileRoot)) {
            throw new IOException("Root is not accessible");
        }

        if (fileHandler == null) {
            fileHandler = getFileHandler();

            fileHandler.connect(fileRoot, filePath + fileName);

            fileHandler.createPrivateDir(fileRoot);
        }
    }

    /**
     * Opens the file connection.
     * @param name URL path fragment
     * @param mode access mode
     * @param timeouts flag to indicate that timeouts allowed
     * @param unescape flag to indicate whether URL must be unescaped
     * @return an opened Connection
     * @throws IOException if some other kind of I/O error occurs.
     */
    private Connection openPrimImpl(String name, int mode, boolean timeouts, boolean unescape)
            throws IOException {

        if (!name.startsWith("//")) {
            throw new IllegalArgumentException("Missing protocol separator");
        }

        int rootStart = name.indexOf('/', 2);

        if (rootStart == -1) {
            throw new IllegalArgumentException("Malformed File URL");
        }

        /* The string must be a valid URL path separated by "/" */
		
        if (name.indexOf("/../", rootStart) != -1 ||
            name.indexOf("/./", rootStart) != -1 ||
            name.endsWith("/..") ||
            name.endsWith("/.") ||
            !"/".equals(sep) && name.indexOf(sep, rootStart) != -1 ||
            name.indexOf('\\') != -1) {
                throw new
                    IllegalArgumentException("/. or /.. is not supported "
                    + "or other illegal characters found");
        }

        if (unescape) {
            name = EscapedUtil.getUnescapedString(name);
        }
        String fileURL = "file:" + name;

        // Perform security checks before any object state changes since
        // this method is used not only by Connector.open() but
        // by FileConnection.setFileConnection() too.
        switch (mode) {
        case Connector.READ:
            checkReadPermission(fileURL, mode);
            maxOStreams = 0;
            break;
        case Connector.WRITE:
            checkWritePermission(fileURL, mode);
            maxIStreams = 0;
            break;
        case Connector.READ_WRITE:
            checkReadPermission(fileURL, mode);
            checkWritePermission(fileURL, mode);
            break;
        default:
            throw new IllegalArgumentException("Invalid mode");
        }

        this.fileURL = fileURL;
        this.mode = mode;

        int nameLength = name.length();
        int pathStart = name.indexOf('/', rootStart + 1);

        if (pathStart == -1) {
            throw new IllegalArgumentException("Root is not specified");
        }

        if (pathStart == (nameLength - 1)) {
            fileName = "";
            fileRoot = name.substring(rootStart + 1);
            filePath = name.substring(rootStart);
        } else {
            fileRoot = name.substring(rootStart + 1, pathStart + 1);

            int fileStart = name.lastIndexOf('/', nameLength - 2);

            if (fileStart <= pathStart) {
                fileName = name.substring(pathStart + 1);
                filePath = name.substring(rootStart, pathStart + 1);
            } else {
                filePath = name.substring(rootStart, fileStart + 1);
                fileName = name.substring(fileStart + 1);
            }
        }

        connectionOpen = true;
        return this;
    }

    /**
     * Checks if path is a root path.
     * @param root path to be checked
     * @return <code>true</code> if path is a root,
     *                <code>false</code> otherwise.
     */
    private boolean isRoot(String root) {
        Vector r = listRoots(); // retrieve up-to-date list of mounted roots
        for (int i = 0; i < r.size(); i++) {
            String name = (String)r.elementAt(i);
            if (name.equals(root)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Checks that the connection is already open and connected.
     * @throws ConnectionClosedException if the connection is closed
     * @throws IOException if any error occurs while connecting
     */
    protected void ensureOpenAndConnected() throws IOException {
        if (!isOpen()) {
            throw new ConnectionClosedException("Connection is closed");
        }

        ensureConnected();
    }

    /**
     * Checks that the application has permission to read.
     * @param fileURL complete file URL
     * @param mode access mode
     * @throws InterruptedIOException if the permission dialog is
     *                                terminated before completed
     * @throws SecurityException if read is not allowed
     * @throws IllegalModeException if connection is write only
     */
    private final void checkReadPermission(String fileURL, int mode)
            throws InterruptedIOException {

        if (classSecurityToken == null) { // FC permission
            MIDletSuite suite = Scheduler.getScheduler().getMIDletSuite();

            try {
                suite.checkForPermission(Permissions.FILE_CONNECTION_READ,
                    fileURL);
            } catch (InterruptedException ie) {
                throw new InterruptedIOException(
                    "Interrupted while trying to ask the user permission");
            }
        } else { // call from PIM
            classSecurityToken.checkIfPermissionAllowed(
                Permissions.FILE_CONNECTION_READ);
        }

        if (mode == Connector.WRITE) {
            throw new IllegalModeException("Connection is write only");
        }
    }

    /**
     * Checks that the application has permission to read.
     * @throws InterruptedIOException if the permission dialog is
     *                                terminated before completed
     * @throws SecurityException if read is not allowed
     * @throws IllegalModeException if connection is write only
     */
    protected final void checkReadPermission() throws InterruptedIOException {
        checkReadPermission(fileURL, mode);
    }

    /**
     * Checks that the application has permission to read the root path.
     * @throws InterruptedIOException if the permission dialog is
     * terminated before completed
     * @throws SecurityException if read is not allowed
     * @throws IllegalModeException if connection is write only
     */
    protected final void checkRootReadPermission()
            throws InterruptedIOException {

        if (classSecurityToken == null) { // FC permission
            MIDletSuite suite = Scheduler.getScheduler().getMIDletSuite();

            try {
                suite.checkForPermission(Permissions.FILE_CONNECTION_READ,
                                     "file://" + fileRoot);
            } catch (InterruptedException ie) {
                throw new InterruptedIOException(
                    "Interrupted while trying to ask the user permission");
            }
        } else { // call from PIM
            classSecurityToken.checkIfPermissionAllowed
                (Permissions.FILE_CONNECTION_READ);
        }

        if (mode == Connector.WRITE) {
            throw new IllegalModeException("Connection is write only");
        }
    }

    /**
     * Checks that the application has permission to write.
     * @param fileURL complete file URL
     * @param mode access mode
     * @throws InterruptedIOException if the permission dialog is
     * terminated before completed
     * @throws SecurityException if write is not allowed
     * @throws IllegalModeException if connection is read only
     */
    private final void checkWritePermission(String fileURL, int mode)
            throws InterruptedIOException {

        if (classSecurityToken == null) { // FC permission
            MIDletSuite suite = Scheduler.getScheduler().getMIDletSuite();

            try {
                suite.checkForPermission(Permissions.FILE_CONNECTION_WRITE,
                                     fileURL);
            } catch (InterruptedException ie) {
                throw new InterruptedIOException(
                    "Interrupted while trying to ask the user permission");
            }
        } else { // call from PIM
            classSecurityToken.checkIfPermissionAllowed
                (Permissions.FILE_CONNECTION_WRITE);
        }

        if (mode == Connector.READ) {
            throw new IllegalModeException("Connection is read only");
        }
    }

    /**
     * Checks that the application has permission to write.
     * @throws InterruptedIOException if the permission dialog is
     *                                terminated before completed
     * @throws SecurityException if write is not allowed
     * @throws IllegalModeException if connection is read only
     */
    protected final void checkWritePermission() throws InterruptedIOException {
        checkWritePermission(fileURL, mode);
    }


    /**
     * Gets an array of file system roots.
     * @return up-to-date array of file system roots;
     *         empty array is returned if there are no roots available.
     */
    public static Vector listRoots() {
        BaseFileHandler fh = getFileHandler();
        return fh.listRoots();
    }

    /**
     * Gets a filtered list of files and directories contained in a directory.
     * The directory is the connection's target as specified in
     * <code>Connector.open()</code>.
     *
     * @param   filter String against which all files and directories are
     *          matched for retrieval.  An asterisk ("*") can be used as a
     *          wildcard to represent 0 or more occurrences of any character.
     * @param   includeHidden boolean indicating whether files marked as hidden
     *          should be included or not in the list of files and directories
     *          returned.
     * @return  An Enumeration of strings, denoting the files and directories
     *          in the directory matching the filter. Directories are denoted
     *          with a trailing slash "/" in their returned name.  The
     *          Enumeration has zero length if the directory is empty or no
     *      files and/or directories are found matching the given filter.
     *      Any current directory indication (".") and any parent directory
     *      indication ("..") is not included in the list of files and
     *      directories returned.
     * @exception  SecurityException if the security of the application does
     *          not have read access for the directory.
     * @exception  IllegalModeException if the application does have read
     *          access
     *          to the connection's target but has opened the connection in
     *          <code>Connector.WRITE</code> mode.
     * @exception  IOException if invoked on a file, the directory does not
     *      exists, the directory is not accessible, or an I/O error occurs.
     * @exception  ConnectionClosedException if the connection is closed.
     * @exception  IllegalArgumentException if filter contains any path
     *      specification or is an invalid filename for the platform
     *      (e.g. contains characters invalid for a filename on the
     *          platform).
     */
    private Enumeration listInternal(String filter, boolean includeHidden)
        throws IOException {
        checkReadPermission();

        ensureOpenAndConnected();

        if (filter != null) {
            if (filter.indexOf('/') != -1) {
                throw new IllegalArgumentException(
                    "Filter contains any path specification");
            }

            String illegalChars = fileHandler.illegalFileNameChars();
            for (int i = 0; i < illegalChars.length(); i++) {
                if (filter.indexOf(illegalChars.charAt(i)) != -1) {
                    throw new
                        IllegalArgumentException("Filter contains characters "
                            + "invalid for a filename");
                }
            }
        }

        return fileHandler.list(filter, includeHidden).elements();
    }

    /**
     * Gets the file handler.
     * @return handle to current file connection
     */
    private static BaseFileHandler getFileHandler() {
        String def = "com.sun.midp.io.j2me.file.DefaultFileHandler";
        String n = null;
        if (hasOtherFileHandler) {
            n = Configuration.getProperty(
                               "com.sun.midp.io.j2me.fileHandlerImpl");
            if (n == null) {
                hasOtherFileHandler = false;
            }
        }
        if (hasOtherFileHandler) {
            try {
                return (BaseFileHandler) (Class.forName(n)).newInstance();
            } catch (ClassNotFoundException e) {
                hasOtherFileHandler = false;
            } catch (Error e) {
                hasOtherFileHandler = false;
            } catch (IllegalAccessException e) {
                hasOtherFileHandler = false;
            } catch (InstantiationException e) {
                hasOtherFileHandler = false;
            }
        }
        try {
            return (BaseFileHandler) (Class.forName(def)).newInstance();
        } catch (ClassNotFoundException e) {
        } catch (Error e) {
        } catch (IllegalAccessException e) {
        } catch (InstantiationException e) {
        }
        throw new Error("Unable to create FileConnection Handler");
    }
}
/**
 * Utility for escaped character handling.
 */
class EscapedUtil {
    /**
     * Gets the escaped string.
     * @param name string to be processed
     * @return escaped string
     * @throws IllegalArgumentException if encoding not supported
     */
    public static String getEscapedString(String name) {
        try {
            if (name == null) {
                return null;
            }
            byte newName[] = new byte[name.length()*12];
            int nextPlace = 0;
            for (int i = 0; i < name.length(); i++) {
                char c = name.charAt(i);
                if (containsReserved(c)) {
                    char data[] = {c};
                    byte[] reservedBytes = new String(data).getBytes("utf-8");
                    for (int j = 0; j < reservedBytes.length; j++) {
                        newName[nextPlace++] = '%';
                        byte upper = (byte) ((reservedBytes[j] >> 4) & 0xF);
                        if (upper <= 9) {
                            newName[nextPlace++] = (byte) ('0' + upper);
                        } else {
                            newName[nextPlace++] = (byte) ('A' + (upper - 10));
                        }
                        byte lower = (byte) (reservedBytes[j] & 0xF);
                        if (lower <= 9) {
                            newName[nextPlace++] = (byte) ('0' + lower);
                        } else {
                            newName[nextPlace++] = (byte) ('A' + (lower - 10));
                        }
                    }
                } else {
                    newName[nextPlace++] = (byte)c;
                }
            }
            return new String(newName, 0, nextPlace);
        } catch (UnsupportedEncodingException uee) {
            throw new IllegalArgumentException(uee.getMessage());
        }
    }


    /**
     * Gets the unescaped string.
     * <pre>
     *   escaped   = "%" hex hex
     *   hex       = digit | "A" | "B" | "C" | "D" | "E" | "F" |
     *                       "a" | "b" | "c" | "d" | "e" | "f"
     * </pre>
     * @param name string to be processed
     * @return escaped string
     * @throws IllegalArgumentException if encoding not supported
     *
     */
    public static String getUnescapedString(String name) {
        try {
            if (name == null) {
                return null;
            }
            if (name.indexOf("%") == -1) {
                return name;
            } else {
                byte newName[] = new byte[name.length()];
                int nextPlace = 0;
                for (int i = 0; i < name.length(); i++) {
                    char c = name.charAt(i);
                    if (c == '%') {
                        String hexNum = name.substring(i+1, i+3).toUpperCase();
                        if (isHexCharsLegal(hexNum)) {
                            c = hexToChar(hexNum);
                            i = i + 2;
                        } else {
                            throw new IllegalArgumentException("Bad format");
                        }
                    } else if (containsReserved(c)) {
                        throw
                            new IllegalArgumentException("Bad escaped format");
                    }
                    newName[nextPlace++] = (byte)c;
                }
                return new String(newName, 0, nextPlace,  "UTF-8");
            }
        } catch (UnsupportedEncodingException uee) {
            throw new IllegalArgumentException(uee.getMessage());
        }
    }

    /**
     * Checks if the hexadecimal character is valid.
     * @param hexValue string to be checked
     * @return <code>true</code> if all characters are valid
     */
    private static boolean isHexCharsLegal(String hexValue) {
        if ((isDigit(hexValue.charAt(0)) || isABCDEF(hexValue.charAt(0))) &&
            (isDigit(hexValue.charAt(1)) || isABCDEF(hexValue.charAt(1)))) {
            return true;
        } else {
            return false;
        }

    }

    /**
     * Converts one hexadecimal char.
     * @param hexValue string to be processed
     * @return normalized hex value
     */
    private static char hexToChar(String hexValue) {
        char c = 0;
        if (isDigit(hexValue.charAt(0))) {
            c += (hexValue.charAt(0) - '0')*16;
        } else {
            c += (hexValue.charAt(0) - 'A' + 10)*16;
        }

        if (isDigit(hexValue.charAt(1))) {
            c += (hexValue.charAt(01) - '0');
        } else {
            c += (hexValue.charAt(1) - 'A' + 10);
        }
        return c;
    }

    /**
     * Checks if character is decimal digit.
     * @param c character to check
     * @return <code>true</code> if in the range 0..9
     */
    private static boolean isDigit(char c) {
        return (c >= '0' && c <= '9');
    }

    /**
     * Checks if character is hexadecimal digit.
     * @param c character to check
     * @return  <code>true</code> if in the range A..F
     */
    private static boolean isABCDEF(char c) {
        return (c >= 'A' && c <= 'F');
    }

    /**
     * Checks if character is from the reserved character set.
     * @param c character to check
     * @return  <code>true</code> if not in the range A..Z,
     * a..z,..9, or punctuation (forward slash, colon, hyphen,
     * under score, period, exclamation, tilde, asterisk, single quote,
     * left paren or right paren).
     */
    private static boolean containsReserved(char c) {
        return !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
                 (c >= '0' && c <= '9') || ("/:-_.!~*'()".indexOf(c) != -1));
    }

} // End  of EscapeUtil