FileDocCategorySizeDatePackage
ConnectionBaseAdapter.javaAPI DocJ2ME MIDP 2.025864Thu Nov 07 12:02:20 GMT 2002com.sun.midp.io

ConnectionBaseAdapter.java

/*
 * @(#)ConnectionBaseAdapter.java	1.25 02/10/14 @(#)
 *
 * Copyright (c) 2000-2002 Sun Microsystems, Inc.  All rights reserved.
 * PROPRIETARY/CONFIDENTIAL
 * Use is subject to license terms.
 */

package com.sun.midp.io;

import com.sun.cldc.io.ConnectionBaseInterface;
import com.sun.cldc.io.GeneralBase;

import com.sun.midp.midlet.*;

import com.sun.midp.security.*;

import java.io.*;

import javax.microedition.io.*;

/**
 * Protocol classes extend this class to gain some of the common functionality
 * needed to implement a CLDC Generic Connection.
 * <p>
 * The common functionality includes:</p>
 * <ul>
 * <li>Supplies the input and output stream classes for a StreamConnection</li>
 * <li>Limits the number of streams opened according to mode, but the limit
 * can be overridden. Read-write allows 1 input and 1 output, write-only
 * allows 1 output, read-only allows 1 input</li>
 * <li>Only "disconnects" when the connection and all streams are closed</li>
 * <li>Throws I/O exceptions when used after being closed</li>
 * <li>Provides a more efficient implementation of
 * {@link InputStream#read(byte[], int, int)}, which is called by
 * {@link InputStream#read()}
 * <li>Provides a more efficient implementation of
 * {@link OutputStream#write(byte[], int, int)}, which is called by
 * {@link OutputStream#write(int)}
 * </ul>
 * <p align="center">
 * <b>Class Relationship Diagram</b></p>
 * <p align="center">
 * <img src="doc-files/ConnectionBaseAdapter.gif" border=0></p>
 *
 * @author  Stephen Flores
 * @version 3.0 9/1/2000
 */
public abstract class ConnectionBaseAdapter implements ConnectionBaseInterface,
    StreamConnection {

    /** Flag indicating if the connection is open. */
    protected boolean connectionOpen = false;
    /** Number of input streams that were opened. */
    protected int iStreams = 0;
    /**
     * Maximum number of open input streams. Set this
     * to zero to prevent openInputStream from giving out a stream in
     * write-only mode.
     */
    protected int maxIStreams = 1;
    /** Number of output streams were opened. */
    protected int oStreams = 0;
    /**
     * Maximum number of output streams. Set this
     * to zero to prevent openOutputStream from giving out a stream in
     * read-only mode.
     */
    protected int maxOStreams = 1;
    /** The security permission required by a subclass. -1 for none. */
    protected int requiredPermission = -1;
    /** Protocol name of subclass to prefix name of resource, can be null. */
    protected String protocol;
    /** The subclass needs know that the permission occured. */
    private boolean permissionChecked;

    /**
     * Lets a subclass know that the security check was done.
     * Called by the connect method of subclasses that require permission.
     * <p>
     * This method is needed since the security check must be put off
     * until connection since the name of the resource must be
     * presented to the user in the permission question.
     * An attacker could directly construct a in a subclass of the
     * protocol and then call its connect method. A subclass can detect
     * this attack by calling this method.
     *
     * @exception SecurityException if the check did not happen.
     */
    protected void verifyPermissionCheck() {
        if (permissionChecked) {
            return;
        }

        throw new SecurityException("The permission check was bypassed");
    }

    /**
     * Check for required permission and open a connection to a target.
     *
     * @param name             URL for the connection, without the
     *                         without the protocol part
     * @param mode             I/O access mode, see {@link Connector}
     * @param timeouts         flag to indicate that the caller
     *                         wants timeout exceptions
     * @return                 this Connection object
     *
     * @exception IllegalArgumentException If a parameter is invalid.
     * @exception ConnectionNotFoundException If the connection cannot
     *                                        be found.
     * @exception IOException  If some other kind of I/O error occurs.
     */
    public Connection openPrim(String name, int mode, boolean timeouts)
            throws IOException {
        checkForPermission(name);

        switch (mode) {
        case Connector.READ:
        case Connector.WRITE:
        case Connector.READ_WRITE:
            break;

        default:
            throw new IllegalArgumentException("Illegal mode");
        }

        connect(name, mode, timeouts);
        connectionOpen = true;
        return this;
    }

    /**
     * Check for the required permission, if needed.
     *
     * @param name name of resource to insert into the permission question
     *
     * @exception IOInterruptedException if another thread interrupts the
     *   calling thread while this method is waiting to preempt the
     *   display.
     */
    public void checkForPermission(String name) throws InterruptedIOException {
        Scheduler scheduler;
        MIDletSuite midletSuite;

        if (permissionChecked) {
            return;
        }

        permissionChecked = true;

        if (requiredPermission == -1) {
            return;
        }
            
        scheduler = Scheduler.getScheduler();
        midletSuite = scheduler.getMIDletSuite();

        // there is no suite running when installing from the command line
        if (midletSuite != null) {
            if (protocol != null) {
                name = protocol + ":" + name;
            }

            try {
                midletSuite.checkForPermission(requiredPermission, name);
            } catch (InterruptedException ie) {
                throw new InterruptedIOException(
                    "Interrupted while trying to ask the user permission");
            }
        }
    }

    /**
     * Check for the required permission and open a connection to a target.
     * This method can be used with permissions greater than
     * the current MIDlet suite.
     * 
     * @param token            security token of the calling class
     * @param name             URL for the connection, without the
     *                         without the protocol part
     * @param mode             I/O access mode, see {@link Connector}
     * @param timeouts         flag to indicate that the caller
     *                         wants timeout exceptions
     * @return                 this Connection object
     *
     * @exception IllegalArgumentException If a parameter is invalid.
     * @exception ConnectionNotFoundException If the connection cannot
     *                                        be found.
     * @exception IOException  If some other kind of I/O error occurs.
     */
    public Connection openPrim(SecurityToken token, String name, int mode,
                               boolean timeouts) throws IOException {
        if (requiredPermission != -1) {
            token.checkIfPermissionAllowed(requiredPermission);
        }

        permissionChecked = true;

        return openPrim(name, mode, timeouts);
    }

    /**
     * Check for required permission and open a connection to a target.
     * This method can be used with permissions greater than
     * the current MIDlet suite. Assume read/write and no timeouts.
     * 
     * @param token            security token of the calling class
     * @param name             URL for the connection, without the
     *                         without the protocol part
     * @return                 this Connection object
     *
     * @exception IllegalArgumentException If a parameter is invalid.
     * @exception ConnectionNotFoundException If the connection cannot
     *                                        be found.
     * @exception 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, false);
    }

    /**
     * Returns an input stream.
     *
     * @return     an input stream for writing bytes to this port.
     * @exception  IOException  if an I/O error occurs when creating the
     *                          output stream.
     */
    public InputStream openInputStream() throws IOException {
        InputStream i;

        ensureOpen();

        if (maxIStreams == 0) {
	    throw new IOException("no more input streams available");
        }
        
        i = new BaseInputStream(this);
        maxIStreams--;
        iStreams++;
        return i;
    }

    /**
     * Open and return a data input stream for a connection.
     *
     * @return                 An input stream
     * @exception IOException  If an I/O error occurs
     */
    public DataInputStream openDataInputStream() throws IOException {
        return new DataInputStream(openInputStream());
    }

    /**
     * Returns an output stream.
     *
     * @return     an output stream for writing bytes to this port.
     * @exception  IOException  if an I/O error occurs when creating the
     *                          output stream.
     */
    public OutputStream openOutputStream() throws IOException {
        OutputStream o;

        ensureOpen();

        if (maxOStreams == 0) {
	    throw new IOException("no more output streams available");
        }

        o = new BaseOutputStream(this);
        maxOStreams--;
        oStreams++;
        return o;
    }

    /**
     * Open and return a data output stream for a connection.
     *
     * @return                 An input stream
     * @exception IOException  If an I/O error occurs
     */
    public DataOutputStream openDataOutputStream() throws IOException {
        return new DataOutputStream(openOutputStream());
    }

    /**
     * Close the connection.
     *
     * @exception  IOException  if an I/O error occurs when closing the
     *                          connection.
     */
    public void close() throws IOException {
        if (connectionOpen) {
            connectionOpen = false;
            closeCommon();
        }
    }

    /**
     * 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 {
        iStreams--;
        closeCommon();
    }

    /**
     * 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 {
        oStreams--;
        closeCommon();
    }

    /**
     * Disconnect if the connection and all the streams and the closed.
     *
     * @exception  IOException  if an I/O error occurs when closing the
     *                          connection.
     */
    void closeCommon() throws IOException {
        if (!connectionOpen && iStreams == 0 && oStreams == 0) {
            disconnect();
        }
    }

    /**
     * Check if the connection is open.
     *
     * @exception  IOException  is thrown, if the stream is not open.
     */
    protected void ensureOpen() throws IOException {
        if (!connectionOpen) {
            throw new IOException("Connection closed");
        }
    }

    /**
     * Connect to a target.
     *
     * @param name             URL for the connection, without the protocol
     *                         part
     * @param mode             I/O access mode, see {@link Connector}
     * @param timeouts         flag to indicate that the called wants
     *                         timeout exceptions
     *
     * @exception IllegalArgumentException If a parameter is invalid.
     * @exception ConnectionNotFoundException If the connection cannot be
     *             found.
     * @exception IOException  If some other kind of I/O error occurs.
     */
    protected abstract void connect(String name, int mode, boolean timeouts)
        throws IOException;

    /**
     * Free up the connection resources.
     *
     * @exception  IOException  if an I/O error occurs.
     */
    protected abstract void disconnect() throws IOException;

    /**
     * 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 abstract int readBytes(byte b[], int off, int len)
        throws IOException;

    /**
     * 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 abstract int writeBytes(byte b[], int off, int len)
        throws IOException;

    /**
     * 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 {
    }
}

/**
 * Input stream for the connection
 */
class BaseInputStream extends InputStream {

    /** Pointer to the connection */
    private ConnectionBaseAdapter parent;

    /** Buffer for single char reads */
    byte[] buf = new byte[1];

    /**
     * Constructs a BaseInputStream for a ConnectionBaseAdapter.
     *
     * @param parent pointer to the connection object
     *
     * @exception  IOException  if an I/O error occurs.
     */
    BaseInputStream(ConnectionBaseAdapter parent) throws IOException {
        this.parent = parent;
    }

    /**
     * Check the stream is open
     *
     * @exception  InterruptedIOException  if it is not.
     */
    private void ensureOpen() throws InterruptedIOException {
        if (parent == null) {
            throw new InterruptedIOException("Stream closed");
        }
    }

    /**
     * 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.
     *
     * <p>The <code>available</code> method always returns <code>0</code> if
     * {@link ConnectionBaseAdapter#available()} is
     * not overridden by the subclass.
     *
     * @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 {

        ensureOpen();

        return parent.available();
    }

    /**
     * Reads the next byte of data from the input stream. The value byte is
     * returned as an <code>int</code> in the range <code>0</code> to
     * <code>255</code>. If no byte is available because the end of the stream
     * has been reached, the value <code>-1</code> is returned. This method
     * blocks until input data is available, the end of the stream is detected,
     * or an exception is thrown.
     *
     * @return     the next byte of data, or <code>-1</code> if the end of the
     *             stream is reached.
     * @exception  IOException  if an I/O error occurs.
     */
    public int read() throws IOException {
        if (read(buf, 0, 1) > 0) {
            return (buf[0] & 0xFF);
        }

        return -1;
    }

    /**
     * Reads up to <code>len</code> bytes of data from the input stream into
     * an array of bytes.  An attempt is made to read as many as
     * <code>len</code> bytes, but a smaller number may be read, possibly
     * zero. The number of bytes actually read is returned as an integer.
     *
     * <p> This method blocks until input data is available, end of file is
     * detected, or an exception is thrown.
     *
     * <p> If <code>b</code> is <code>null</code>, a
     * <code>NullPointerException</code> is thrown.
     *
     * <p> If <code>off</code> is negative, or <code>len</code> is negative, or
     * <code>off+len</code> is greater than the length of the array
     * <code>b</code>, then an <code>IndexOutOfBoundsException</code> is
     * thrown.
     *
     * <p> If <code>len</code> is zero, then no bytes are read and
     * <code>0</code> is returned; otherwise, there is an attempt to read at
     * least one byte. If no byte is available because the stream is at end of
     * file, the value <code>-1</code> is returned; otherwise, at least one
     * byte is read and stored into <code>b</code>.
     *
     * <p> The first byte read is stored into element <code>b[off]</code>, the
     * next one into <code>b[off+1]</code>, and so on. The number of bytes read
     * is, at most, equal to <code>len</code>. Let <i>k</i> be the number of
     * bytes actually read; these bytes will be stored in elements
     * <code>b[off]</code> through <code>b[off+</code><i>k</i><code>-1]</code>,
     * leaving elements <code>b[off+</code><i>k</i><code>]</code> through
     * <code>b[off+len-1]</code> unaffected.
     *
     * <p> In every case, elements <code>b[0]</code> through
     * <code>b[off]</code> and elements <code>b[off+len]</code> through
     * <code>b[b.length-1]</code> are unaffected.
     *
     * <p> If the first byte cannot be read for any reason other than end of
     * file, then an <code>IOException</code> is thrown. In particular, an
     * <code>IOException</code> is thrown if the input stream has been closed.
     *
     * @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.
     * @see        java.io.InputStream#read()
     */
    public int read(byte b[], int off, int len) throws IOException {
        int test;

        ensureOpen();

        if (len == 0) {
            return 0;
        }

        /*
         * test the parameters so the subclass will not have to.
         * this will avoid crashes in the native code
         */
        test = b[off] + b[len - 1] + b[off + len - 1];

        return parent.readBytes(b, off, len);
    }

    /**
     * Closes this input stream and releases any system resources associated
     * with the stream.
     *
     * @exception  IOException  if an I/O error occurs.
     */
    public void close() throws IOException {
        if (parent != null) {
            parent.closeInputStream();
            parent = null;
        }
    }
}


/**
 * Output stream for the connection
 */
class BaseOutputStream extends OutputStream {

    /** Pointer to the connection */
    ConnectionBaseAdapter parent;

    /** Buffer for single char writes */
    byte[] buf = new byte[1];

    /**
     * Constructs a BaseOutputStream for an ConnectionBaseAdapter.
     *
     * @param p parent connection
     */
    BaseOutputStream(ConnectionBaseAdapter p) {
        parent = p;
    }

    /**
     * Check the stream is open
     *
     * @exception  InterruptedIOException  if it is not.
     */
    private void ensureOpen() throws InterruptedIOException {
        if (parent == null) {
            throw new InterruptedIOException("Stream closed");
        }
    }

    /**
     * Writes the specified byte to this output stream. The general
     * contract for <code>write</code> is that one byte is written
     * to the output stream. The byte to be written is the eight
     * low-order bits of the argument <code>b</code>. The 24
     * high-order bits of <code>b</code> are ignored.
     *
     * @param      b   the <code>byte</code>.
     * @exception  IOException  if an I/O error occurs. In particular,
     *             an <code>IOException</code> may be thrown if the
     *             output stream has been closed.
     */
    public void write(int b) throws IOException {
        buf[0] = (byte)b;
        write(buf, 0, 1);
    }

    /**
     * Writes <code>len</code> bytes from the specified byte array
     * starting at offset <code>off</code> to this output stream.
     * The general contract for <code>write(b, off, len)</code> is that
     * some of the bytes in the array <code>b</code> are written to the
     * output stream in order; element <code>b[off]</code> is the first
     * byte written and <code>b[off+len-1]</code> is the last byte written
     * by this operation.
     * <p>
     * If <code>b</code> is <code>null</code>, a
     * <code>NullPointerException</code> is thrown.
     * <p>
     * If <code>off</code> is negative, or <code>len</code> is negative, or
     * <code>off+len</code> is greater than the length of the array
     * <code>b</code>, then an <tt>IndexOutOfBoundsException</tt> is thrown.
     *
     * @param      b     the data.
     * @param      off   the start offset in the data.
     * @param      len   the number of bytes to write.
     * @exception  IOException  if an I/O error occurs. In particular,
     *             an <code>IOException</code> is thrown if the output
     *             stream is closed.
     */
    public void write(byte b[], int off, int len)
           throws IOException {
        int test;
        int bytesWritten;

        ensureOpen();

        if (len == 0) {
            return;
        }

        /*
         * test the parameters here so subclases do not have to,
         * this will avoid a crash in the native code
         */
        test = b[off] + b[len - 1] + b[off + len - 1];

        /*
         * 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.
         */
        for (bytesWritten = 0; ; ) {
            try {
                bytesWritten += parent.writeBytes(b, off + bytesWritten,
                                                  len - bytesWritten);
            } finally {
                if (parent == null) {
                    throw new InterruptedIOException("Stream closed");
                }
            }

            if (bytesWritten == len) {
                break;
            }
            
            GeneralBase.iowait(); 
        }
    }

    /**
     * Flushes this output stream and 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 have been buffered by the implementation of the output
     * stream, such bytes should immediately be written to their
     * intended destination.
     *
     * @exception  IOException  if an I/O error occurs.
     */
    public void flush() throws IOException {
        ensureOpen();
        parent.flush();
    }

    /**
     * Closes this output stream and releases any system resources
     * associated with this stream. The general contract of <code>close</code>
     * is that it closes the output stream. A closed stream cannot perform
     * output operations and cannot be reopened.
     *
     * @exception  IOException  if an I/O error occurs.
     */
    public void close() throws IOException {
        if (parent != null) {
            parent.closeOutputStream();
            parent = null;
        }
    }
}