/*
*
*
* Copyright 1990-2009 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.jsr082.bluetooth.btspp;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import javax.microedition.io.StreamConnection;
import javax.bluetooth.BluetoothConnectionException;
import com.sun.jsr082.bluetooth.BluetoothUrl;
import com.sun.jsr082.bluetooth.BluetoothUtils;
import com.sun.jsr082.bluetooth.BluetoothConnection;
/*
* 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.
*/
protected 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
*/
protected 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
*/
protected 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;
}