FileDocCategorySizeDatePackage
BTSPPConnectionImpl.javaAPI DocphoneME MR2 API (J2ME)18236Wed May 02 18:00:30 BST 2007com.sun.midp.io.j2me.btspp

BTSPPConnectionImpl.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.btspp;

import javax.bluetooth.BluetoothConnectionException;
import com.sun.midp.io.BluetoothUrl;
import com.sun.midp.jsr082.BluetoothUtils;
import com.sun.kvem.jsr082.bluetooth.BluetoothConnection;
import javax.microedition.io.StreamConnection;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;

/**
 * Bluetooth Serial Port Profile connection implementation.
 */
public class BTSPPConnectionImpl extends BluetoothConnection
        implements StreamConnection {
    
    /** Static initializer. */
    static {
        initialize();
    }

    /**
     * Native static class initializer.
     */
    private native static void initialize();

    /**
     * Native finalizer.
     * Releases all native resources used by this connection.
     */
    private native void finalize();

    /**
     * Stores the address of the remote device connected by this connection.
     * The value is set by the constructor.
     */
    byte[] remoteDeviceAddress;

    /** Lock object for reading from the socket */
    private final Object readerLock = new Object();

    /** Lock object for writing to the socket */
    private final Object writerLock = new Object();

    /**
     * Identidies this connection at native layer, <code>-1<code>
     * if connection is not open.
     */
    private int handle = -1;

    /** Flag to identify if an input stream is opened for this connection. */
    private boolean isOpened = false;

    /** Flag to identify if an output stream is opened for this connection. */
    private boolean osOpened = false;

    /** Open streams counter. */
    private int objects = 1;
    
    /**
     * Constructs an instance and opens connection.
     *
     * @param url keeps connection details
     * @param mode I/O access mode
     * @exception IOException if connection fails
     */
    protected BTSPPConnectionImpl(BluetoothUrl url, int mode)
            throws IOException {
        this(url, mode, null);
    }

    /**
     * Constructs an instance and
     * sets up corresponding native connection handle to it.
     *
     * @param url keeps connection details
     * @param mode I/O access mode
     * @param notif corresponding <code>BTSPPNotifierImpl</code> instance
     *              temporary storing native peer handle
     * @exception IOException if connection fails
     */
    protected BTSPPConnectionImpl(BluetoothUrl url,
            int mode, BTSPPNotifierImpl notif) throws IOException {
        super(url, mode);

        if (notif == null) {
            remoteDeviceAddress = BluetoothUtils.getAddressBytes(url.address);
            doOpen();
        } else {
            remoteDeviceAddress = new byte[6];
            System.arraycopy(notif.peerAddress, 0,  remoteDeviceAddress, 0, 6);

            setThisConnHandle0(notif);
        }

        setRemoteDevice();
    }

    /**
     * Retrieves native connection handle from temporary storage
     * inside <code>BTSPPNotifierImpl</code> instance
     * and sets it to this <code>BTSPPConnectionImpl</code> instance.
     *
     * Note: the method sets native connection handle directly to
     * <code>handle<code> field of <code>BTSPPConnectionImpl</code> object.
     *
     * @param notif reference to corresponding <code>BTSPPNotifierImpl</code>
     *              instance storing native peer handle
     */
    private native void setThisConnHandle0(BTSPPNotifierImpl notif);

    /**
     * Retrieves remote address for the connection.
     *
     * @return remote address
     */
    public String getRemoteDeviceAddress() {
        return BluetoothUtils.getAddressString(remoteDeviceAddress);
    }

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

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

    /**
     * Open and return an input stream for a connection.
     *
     * @return An input stream
     * @throws IOException if an I/O error occurs
     */
    public final InputStream openInputStream() throws IOException {
        checkOpen();
        checkReadMode();
        synchronized (this) {
            if (isOpened) {
                throw new IOException("No more input streams");
            }
            isOpened = true;
            objects++;
        }
        return new SPPInputStream();
    }

    /**
     * Open and return an output stream for a connection.
     *
     * @return An output stream
     * @throws IOException if an I/O error occurs
     */
    public final OutputStream openOutputStream() throws IOException {
        checkOpen();
        checkWriteMode();
        synchronized (this) {
            if (osOpened) {
                throw new IOException("No more output streams");
            }
            osOpened = true;
            objects++;
        }
        return new SPPOutputStream();
    }

    /**
     * Input stream implementation for BTSPPConnection
     */
    private final class SPPInputStream extends InputStream {
        /** Indicates whether the stream is closed. */
        private boolean isClosed;

        /**
         * Reads the next byte of data from the input stream.
         *
         * @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 {
            byte[] buf = new byte[1];
            int res = read(buf);
            if (res != -1)
                res = buf[0] & 0xFF;
            return res;
        }

        /**
         * Reads some number of bytes from the input stream and stores them into
         * the buffer array <code>buf</code>.
         *
         * @param      buf   the buffer into which the data is read.
         * @return     the total number of bytes read into the buffer, or
         *             <code>-1</code> is 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(byte[], int, int)
         */
        public int read(byte[] buf) throws IOException {
            return read(buf, 0, buf.length);
        }

        /**
         * 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.
         * @param      buf     the buffer into which the data is read.
         * @param      off   the start offset in array <code>buf</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[] buf, int off, int len) throws IOException {
            if (isClosed) {
                throw new IOException("Stream is closed");
            }
            
            if ((off < 0) || (len < 0) || (off + len > buf.length)) {
                throw new IndexOutOfBoundsException();
            } else if (len == 0) {
                return 0;
            }

            int res;
    
            /*
             * Multiple threads blocked on read operation may
             * return results interleaved arbitrarily. From an
             * application perspective, the results would be
             * indeterministic. So "reader locks" are introduced
             * for "read" operation from the same handle.
             */
            synchronized (readerLock) {
                res = receive0(buf, off, len);
            }
    
            // convert the 'end of data' to the 'end of stream'
            if (res == 0) {
                res = -1;
            }
    
            return res;
        }

        /**
         * 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.
         *
         * @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 {
            if (isClosed) {
                throw new IOException("Stream is closed");
            }
            return available0();
        }

        /**
         * 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 {
            synchronized (BTSPPConnectionImpl.this) {
                if (isClosed) {
                    return;
                }
                isClosed = true;
                objects--;
                if (objects == 0) {
                    close0();
                }
            }
        }
    }

    /**
     * Output stream implementation for BTSPPConnection
     */
    private final class SPPOutputStream extends OutputStream {
        /** Indicates whether the stream is closed. */
        private boolean isClosed;

        /**
         * Writes the specified byte to this output stream.
         * @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 {
            write(new byte[] { (byte)b });
        }

        /**
         * Writes <code>size</code> bytes from the specified byte array
         * starting at offset <code>offset</code> to this output stream.
         * @param      buf the data.
         * @param      offset the start offset in the data.
         * @param      size 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[] buf, int offset, int size)
                throws IOException {
            if (isClosed) {
                throw new IOException("Stream is closed");
            }
         
            if (size < 0 || offset < 0 || offset + size > buf.length) {
                throw new IndexOutOfBoundsException();
            }
            
            /*
             * Multiple threads blocked on write operation may return results
             * interleaved arbitrarily. From an application perspective, the
             * results would be indeterministic. So "writer locks" are
             * introduced for "write" operation to the same socket.
             */
            synchronized (writerLock) {
                while (size > 0) {
                    int res = send0(buf, offset, size);
    
                    if (res <= 0) {
                        throw new IOException("Data send failed");
                    }
    
                    offset  += res;
                    size -= res;
                }
            }
        }

        /**
         * Closes this output stream and releases any system resources
         * associated with this stream.
         * @exception  IOException  if an I/O error occurs.
         */
        public void close() throws IOException {
            synchronized (BTSPPConnectionImpl.this) {
                if (isClosed) {
                    return;
                }
                isClosed = true;
                objects--;
                if (objects == 0) {
                    close0();
                }
            }
        }
    }

    /**
     * Closes this connection.
     * @throws IOException if I/O error.
     */
    public void close() throws IOException {
        synchronized (this) {
            if (isClosed()) {
                return;
            }
            resetRemoteDevice();
            objects--;
            if (objects == 0) {
                close0();
            }
        }
    }

    /**
     * Closes client connection.
     *
     * Note: the method gets native connection handle directly from
     * <code>handle<code> field of <code>L2CAPConnectionImpl</code> object.
     *
     * @throws IOException if any I/O error occurs
     */
    private native void close0() throws IOException;

    /**
     * Reads data from a packet received via Bluetooth stack.
     *
     * Note: the method gets native connection handle directly from
     * <code>handle<code> field of <code>BTSPPConnectionImpl</code> object.
     *
     * @param buf the buffer to read to
     * @param off the start offset in array <code>buf</code>
     *               at which the data to be written
     * @param size the maximum number of bytes to read,
     *             the rest of the packet is discarded.
     * @return total number of bytes read into the buffer,
     *             <code>0</code> indicates end-of-data,
     *             <code>-1</code> if there is no data available at this moment
     * @throws IOException if an I/O error occurs
     */
    private native int receive0(byte[] buf, int off, int size)
        throws IOException;

    /**
     * Returns the number of bytes available to be read from the connection
     * without blocking.
     *
     * Note: the method gets native connection handle directly from
     * <code>handle<code> field of <code>BTSPPConnectionImpl</code> object.
     *
     * @return the number of available bytes
     * @throws IOException if any I/O error occurs
     */
    private native int available0() throws IOException;
    
    /**
     * Sends the specified data via Bluetooth stack.
     *
     * Note: the method gets native connection handle directly from
     * <code>handle<code> field of <code>L2CAPConnectionImpl</code> object.
     *
     * @param buf the data to send
     * @param off the offset into the data buffer
     * @param size the size of data in the buffer
     * @return total number of send bytes,
     *         or <code>-1</code> if nothing is send
     * @throws IOException if an I/O error occurs
     */
    private native int send0(byte[] buf, int off, int size) throws IOException;
    

    /** Opens client connection. */
    private void doOpen() throws IOException {
        /*
         * create native connection object
         * Note: the method <code>create0</code> sets resulting native
         * connection handle directly to the field <code>handle<code>.
         */
        create0(url.authenticate, url.encrypt, url.master);

        byte[] address = BluetoothUtils.getAddressBytes(url.address);

        try {
            // establish connection
            connect0(address, url.port);
        } catch (IOException e) {
            throw new BluetoothConnectionException(
                BluetoothConnectionException.FAILED_NOINFO,
                e.getMessage());

        }
    }

    /**
     * Creates a client connection object.
     *
     * Note: the method gets native connection handle directly from
     * <code>handle<code> field of <code>BTSPPConnectionImpl</code> object.
     *
     * @param auth   <code>true</code> if authication is required
     * @param enc    <code>true</code> indicates
     *                what connection must be encrypted
     * @param master <code>true</code> if client requires to be
     *               a connection's master
     * @throws IOException if any I/O error occurs
     */
    private native void create0(boolean auth, boolean enc, boolean master)
        throws IOException;

    /**
     * Starts client connection establishment.
     *
     * Note: the method gets native connection handle directly from
     * <code>handle<code> field of <code>BTSPPConnectionImpl</code> object.
     *
     * @param addr bluetooth address of device to connect to
     * @param cn Channel number (CN) value
     * @throws IOException if any I/O error occurs
     */
    private native void connect0(byte[] addr, int cn) throws IOException;
    
}