diff options
author | Mark Spruiell <mes@zeroc.com> | 2017-03-30 15:58:21 -0700 |
---|---|---|
committer | Mark Spruiell <mes@zeroc.com> | 2017-03-30 15:58:21 -0700 |
commit | 975309430b7edf9aed1c5829931804f188b5ccbb (patch) | |
tree | 72ea7b998c17b651d035f87ba256ee3a18258f8e /java/src | |
parent | Update bzip2 package version used in CSharp nuget package (diff) | |
download | ice-975309430b7edf9aed1c5829931804f188b5ccbb.tar.bz2 ice-975309430b7edf9aed1c5829931804f188b5ccbb.tar.xz ice-975309430b7edf9aed1c5829931804f188b5ccbb.zip |
adding IceBT for Java8
Diffstat (limited to 'java/src')
13 files changed, 1981 insertions, 0 deletions
diff --git a/java/src/IceBT/build.gradle b/java/src/IceBT/build.gradle new file mode 100644 index 00000000000..017a80c2ab1 --- /dev/null +++ b/java/src/IceBT/build.gradle @@ -0,0 +1,38 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +//sourceCompatibility = iceSourceCompatibility +//targetCompatibility = iceTargetCompatibility +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +project.ext.displayName = "IceBT" +project.ext.description = "Bluetooth support for Ice" + +slice { + java { + set1 { + files = fileTree(dir: "$sliceDir/IceBT", includes:['*.ice'], excludes:["*F.ice"]) + } + } +} + +dependencies { + compile project(':ice') +} + +apply from: "$rootProject.projectDir/gradle/library.gradle" + +jar { + // + // The classes in src/main/java/android/bluetooth are stubs that allow us to compile the IceBT transport + // plug-in without requiring an Android SDK. These classes are excluded from the IceBT JAR file. + // + exclude("android/**") +} diff --git a/java/src/IceBT/src/main/java/android/bluetooth/BluetoothAdapter.java b/java/src/IceBT/src/main/java/android/bluetooth/BluetoothAdapter.java new file mode 100644 index 00000000000..8d00d879d3b --- /dev/null +++ b/java/src/IceBT/src/main/java/android/bluetooth/BluetoothAdapter.java @@ -0,0 +1,55 @@ +// +// This is a placeholder for the Android API. It is not included in the IceBT JAR file. +// + +package android.bluetooth; + +public final class BluetoothAdapter +{ + public boolean cancelDiscovery() + { + return false; + } + + public boolean isEnabled() + { + return false; + } + + public String getAddress() + { + return ""; + } + + public static boolean checkBluetoothAddress(String address) + { + return false; + } + + public static BluetoothAdapter getDefaultAdapter() + { + return null; + } + + public BluetoothDevice getRemoteDevice(byte[] address) + { + return null; + } + + public BluetoothDevice getRemoteDevice(String address) + { + return null; + } + + public BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String name, java.util.UUID uuid) + throws java.io.IOException + { + return null; + } + + public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, java.util.UUID uuid) + throws java.io.IOException + { + return null; + } +} diff --git a/java/src/IceBT/src/main/java/android/bluetooth/BluetoothDevice.java b/java/src/IceBT/src/main/java/android/bluetooth/BluetoothDevice.java new file mode 100644 index 00000000000..7fbee8880de --- /dev/null +++ b/java/src/IceBT/src/main/java/android/bluetooth/BluetoothDevice.java @@ -0,0 +1,25 @@ +// +// This is a placeholder for the Android API. It is not included in the IceBT JAR file. +// + +package android.bluetooth; + +public final class BluetoothDevice +{ + public BluetoothSocket createInsecureRfcommSocketToServiceRecord(java.util.UUID uuid) + throws java.io.IOException + { + return null; + } + + public BluetoothSocket createRfcommSocketToServiceRecord(java.util.UUID uuid) + throws java.io.IOException + { + return null; + } + + public String getAddress() + { + return ""; + } +} diff --git a/java/src/IceBT/src/main/java/android/bluetooth/BluetoothServerSocket.java b/java/src/IceBT/src/main/java/android/bluetooth/BluetoothServerSocket.java new file mode 100644 index 00000000000..c6e6f2a6978 --- /dev/null +++ b/java/src/IceBT/src/main/java/android/bluetooth/BluetoothServerSocket.java @@ -0,0 +1,19 @@ +// +// This is a placeholder for the Android API. It is not included in the IceBT JAR file. +// + +package android.bluetooth; + +public final class BluetoothServerSocket implements java.io.Closeable +{ + public BluetoothSocket accept() + throws java.io.IOException + { + return null; + } + + public void close() + throws java.io.IOException + { + } +} diff --git a/java/src/IceBT/src/main/java/android/bluetooth/BluetoothSocket.java b/java/src/IceBT/src/main/java/android/bluetooth/BluetoothSocket.java new file mode 100644 index 00000000000..80470a11a52 --- /dev/null +++ b/java/src/IceBT/src/main/java/android/bluetooth/BluetoothSocket.java @@ -0,0 +1,40 @@ +// +// This is a placeholder for the Android API. It is not included in the IceBT JAR file. +// + +package android.bluetooth; + +public final class BluetoothSocket implements java.io.Closeable +{ + public void close() + throws java.io.IOException + { + } + + public void connect() + throws java.io.IOException + { + } + + public java.io.InputStream getInputStream() + throws java.io.IOException + { + return null; + } + + public java.io.OutputStream getOutputStream() + throws java.io.IOException + { + return null; + } + + public BluetoothDevice getRemoteDevice() + { + return null; + } + + public boolean isConnected() + { + return false; + } +} diff --git a/java/src/IceBT/src/main/java/com/zeroc/IceBT/AcceptorI.java b/java/src/IceBT/src/main/java/com/zeroc/IceBT/AcceptorI.java new file mode 100644 index 00000000000..d320816f4ed --- /dev/null +++ b/java/src/IceBT/src/main/java/com/zeroc/IceBT/AcceptorI.java @@ -0,0 +1,230 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +package com.zeroc.IceBT; + +import com.zeroc.IceInternal.Acceptor; +import com.zeroc.IceInternal.ReadyCallback; +import com.zeroc.IceInternal.SocketOperation; +import com.zeroc.IceInternal.Transceiver; +import com.zeroc.Ice.SocketException; + +import android.bluetooth.BluetoothSocket; +import android.bluetooth.BluetoothServerSocket; +import java.util.UUID; + +final class AcceptorI implements Acceptor +{ + @Override + public java.nio.channels.ServerSocketChannel fd() + { + return null; + } + + @Override + public void setReadyCallback(ReadyCallback callback) + { + _readyCallback = callback; + } + + @Override + public void close() + { + synchronized(this) + { + _closed = true; + } + + if(_socket != null) + { + try + { + _socket.close(); // Wakes up the thread blocked in accept(). + } + catch(Exception ex) + { + // Ignore. + } + } + if(_thread != null) + { + try + { + _thread.join(); + } + catch(Exception ex) + { + // Ignore. + } + } + } + + @Override + public com.zeroc.IceInternal.EndpointI listen() + { + try + { + // + // We always listen using the "secure" method. + // + _socket = _instance.bluetoothAdapter().listenUsingRfcommWithServiceRecord(_name, _uuid); + } + catch(java.io.IOException ex) + { + throw new SocketException(ex); + } + + // + // Use a helper thread to perform the blocking accept() calls. + // + _thread = new Thread() + { + public void run() + { + runAccept(); + } + }; + _thread.start(); + + return _endpoint; + } + + @Override + public synchronized Transceiver accept() + { + if(_exception != null) + { + throw new SocketException(_exception); + } + + // + // accept() should only be called when we have at least one socket ready. + // + assert(!_pending.isEmpty()); + + BluetoothSocket socket = _pending.pop(); + + // + // Update our status with the thread pool. + // + _readyCallback.ready(SocketOperation.Read, !_pending.isEmpty()); + + return new TransceiverI(_instance, socket, _uuid, _adapterName); + } + + @Override + public String protocol() + { + return _instance.protocol(); + } + + @Override + public String toString() + { + StringBuffer s = new StringBuffer("local address = "); + s.append(_instance.bluetoothAdapter().getAddress()); + return s.toString(); + } + + @Override + public String toDetailedString() + { + StringBuffer s = new StringBuffer(toString()); + if(!_name.isEmpty()) + { + s.append("\nservice name = '"); + s.append(_name); + s.append("'"); + } + if(_uuid != null) + { + s.append("\nservice uuid = "); + s.append(_uuid.toString()); + } + return s.toString(); + } + + AcceptorI(EndpointI endpoint, Instance instance, String adapterName, UUID uuid, String name) + { + _endpoint = endpoint; + _instance = instance; + _adapterName = adapterName; + _name = name; + _uuid = uuid; + + _pending = new java.util.Stack<BluetoothSocket>(); + _closed = false; + } + + private void runAccept() + { + try + { + while(true) + { + BluetoothSocket socket = _socket.accept(); + synchronized(this) + { + _pending.push(socket); + + // + // Notify the thread pool that we are ready to "read". The thread pool will invoke accept() + // and we can return a new transceiver. + // + _readyCallback.ready(SocketOperation.Read, true); + } + } + } + catch(Exception ex) + { + synchronized(this) + { + if(!_closed) + { + _exception = ex; + _readyCallback.ready(SocketOperation.Read, true); + } + } + } + + // + // Close any remaining incoming sockets that haven't been accepted yet. + // + java.util.Stack<BluetoothSocket> pending; + synchronized(this) + { + pending = _pending; + _pending = null; + } + + for(BluetoothSocket s : pending) + { + try + { + s.close(); + } + catch(Exception ex) + { + // Ignore. + } + } + } + + private EndpointI _endpoint; + private Instance _instance; + private String _adapterName; + private String _name; + private UUID _uuid; + private ReadyCallback _readyCallback; + private BluetoothServerSocket _socket; + private java.util.Stack<BluetoothSocket> _pending; + private Thread _thread; + private Exception _exception; + private boolean _closed; +} diff --git a/java/src/IceBT/src/main/java/com/zeroc/IceBT/ConnectorI.java b/java/src/IceBT/src/main/java/com/zeroc/IceBT/ConnectorI.java new file mode 100644 index 00000000000..8a3cef88d06 --- /dev/null +++ b/java/src/IceBT/src/main/java/com/zeroc/IceBT/ConnectorI.java @@ -0,0 +1,114 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +package com.zeroc.IceBT; + +import com.zeroc.IceInternal.Connector; +import com.zeroc.IceInternal.HashUtil; +import com.zeroc.IceInternal.Transceiver; +import com.zeroc.Ice.SocketException; + +import java.util.UUID; + +final class ConnectorI implements Connector +{ + @Override + public Transceiver connect() + { + return new TransceiverI(_instance, _addr, _uuid, _connectionId); + } + + @Override + public short type() + { + return _instance.type(); + } + + @Override + public String toString() + { + StringBuffer buf = new StringBuffer(); + if(!_addr.isEmpty()) + { + buf.append(_addr); + } + if(_uuid != null) + { + if(!_addr.isEmpty()) + { + buf.append(';'); + } + buf.append(_uuid.toString()); + } + return buf.toString(); + } + + @Override + public int hashCode() + { + return _hashCode; + } + + // + // Only for use by EndpointI. + // + ConnectorI(Instance instance, String addr, UUID uuid, int timeout, String connectionId) + { + _instance = instance; + _addr = addr; + _uuid = uuid; + _timeout = timeout; + _connectionId = connectionId; + + _hashCode = 5381; + _hashCode = HashUtil.hashAdd(_hashCode , _addr); + _hashCode = HashUtil.hashAdd(_hashCode , _uuid); + _hashCode = HashUtil.hashAdd(_hashCode , _timeout); + _hashCode = HashUtil.hashAdd(_hashCode , _connectionId); + } + + @Override + public boolean equals(java.lang.Object obj) + { + if(!(obj instanceof ConnectorI)) + { + return false; + } + + if(this == obj) + { + return true; + } + + ConnectorI p = (ConnectorI)obj; + if(!_uuid.equals(p._uuid)) + { + return false; + } + + if(_timeout != p._timeout) + { + return false; + } + + if(!_connectionId.equals(p._connectionId)) + { + return false; + } + + return _addr.equals(p._addr); + } + + private Instance _instance; + private String _addr; + private UUID _uuid; + private int _timeout; + private String _connectionId; + private int _hashCode; +} diff --git a/java/src/IceBT/src/main/java/com/zeroc/IceBT/EndpointFactoryI.java b/java/src/IceBT/src/main/java/com/zeroc/IceBT/EndpointFactoryI.java new file mode 100644 index 00000000000..66c2b391438 --- /dev/null +++ b/java/src/IceBT/src/main/java/com/zeroc/IceBT/EndpointFactoryI.java @@ -0,0 +1,62 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +package com.zeroc.IceBT; + +import com.zeroc.IceInternal.EndpointFactory; +import com.zeroc.IceInternal.ProtocolInstance; + +final class EndpointFactoryI implements EndpointFactory +{ + EndpointFactoryI(Instance instance) + { + _instance = instance; + } + + @Override + public short type() + { + return _instance.type(); + } + + @Override + public String protocol() + { + return _instance.protocol(); + } + + @Override + public com.zeroc.IceInternal.EndpointI create(java.util.ArrayList<String> args, boolean oaEndpoint) + { + EndpointI endpt = new EndpointI(_instance); + endpt.initWithOptions(args, oaEndpoint); + return endpt; + } + + @Override + public com.zeroc.IceInternal.EndpointI read(com.zeroc.Ice.InputStream s) + { + return new EndpointI(_instance, s); + } + + @Override + public void destroy() + { + _instance.destroy(); + _instance = null; + } + + @Override + public EndpointFactory clone(ProtocolInstance inst, EndpointFactory del) + { + return new EndpointFactoryI(new Instance(_instance.communicator(), inst.type(), inst.protocol())); + } + + private Instance _instance; +} diff --git a/java/src/IceBT/src/main/java/com/zeroc/IceBT/EndpointI.java b/java/src/IceBT/src/main/java/com/zeroc/IceBT/EndpointI.java new file mode 100644 index 00000000000..b57cc25368b --- /dev/null +++ b/java/src/IceBT/src/main/java/com/zeroc/IceBT/EndpointI.java @@ -0,0 +1,562 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +package com.zeroc.IceBT; + +import com.zeroc.IceInternal.Acceptor; +import com.zeroc.IceInternal.Connector; +import com.zeroc.IceInternal.EndpointI_connectors; +import com.zeroc.IceInternal.HashUtil; +import com.zeroc.IceInternal.Transceiver; +import com.zeroc.Ice.EndpointParseException; +import com.zeroc.Ice.EndpointSelectionType; +import com.zeroc.Ice.InputStream; +import com.zeroc.Ice.MarshalException; +import com.zeroc.Ice.OutputStream; + +import android.bluetooth.BluetoothAdapter; +import java.util.UUID; + +final class EndpointI extends com.zeroc.IceInternal.EndpointI +{ + public EndpointI(Instance instance, String addr, UUID uuid, String name, int channel, int timeout, + String connectionId, boolean compress) + { + _instance = instance; + _addr = addr; + _uuid = uuid; + _name = name; + _channel = channel; + _timeout = timeout; + _connectionId = connectionId; + _compress = compress; + hashInit(); + } + + public EndpointI(Instance instance) + { + _instance = instance; + _addr = ""; + _uuid = null; + _name = ""; + _channel = 0; + _timeout = instance.defaultTimeout(); + _connectionId = ""; + _compress = false; + } + + public EndpointI(Instance instance, InputStream s) + { + _instance = instance; + + // + // _name and _channel are not marshaled. + // + _name = ""; + _channel = 0; + + _addr = s.readString().toUpperCase(); + if(!BluetoothAdapter.checkBluetoothAddress(_addr)) + { + throw new MarshalException("invalid address `" + _addr + "' in endpoint"); + } + + try + { + _uuid = UUID.fromString(s.readString()); + } + catch(IllegalArgumentException ex) + { + throw new MarshalException("invalid UUID for Bluetooth endpoint", ex); + } + + _timeout = s.readInt(); + _compress = s.readBool(); + hashInit(); + } + + @Override + public void streamWriteImpl(OutputStream s) + { + // + // _name and _channel are not marshaled. + // + s.writeString(_addr); + s.writeString(_uuid.toString()); + s.writeInt(_timeout); + s.writeBool(_compress); + } + + @Override + public short type() + { + return _instance.type(); + } + + @Override + public String protocol() + { + return _instance.protocol(); + } + + @Override + public int timeout() + { + return _timeout; + } + + @Override + public com.zeroc.IceInternal.EndpointI timeout(int timeout) + { + if(timeout == _timeout) + { + return this; + } + else + { + return new EndpointI(_instance, _addr, _uuid, _name, _channel, timeout, _connectionId, _compress); + } + } + + @Override + public String connectionId() + { + return _connectionId; + } + + @Override + public com.zeroc.IceInternal.EndpointI connectionId(String connectionId) + { + if(connectionId.equals(_connectionId)) + { + return this; + } + else + { + return new EndpointI(_instance, _addr, _uuid, _name, _channel, _timeout, connectionId, _compress); + } + } + + @Override + public boolean compress() + { + return _compress; + } + + @Override + public com.zeroc.IceInternal.EndpointI compress(boolean compress) + { + if(compress == _compress) + { + return this; + } + else + { + return new EndpointI(_instance, _addr, _uuid, _name, _channel, _timeout, _connectionId, compress); + } + } + + @Override + public boolean datagram() + { + return false; + } + + @Override + public boolean secure() + { + return _instance.secure(); + } + + @Override + public Transceiver transceiver() + { + return null; + } + + @Override + public void connectors_async(EndpointSelectionType selType, EndpointI_connectors callback) + { + java.util.List<Connector> conns = new java.util.ArrayList<Connector>(); + conns.add(new ConnectorI(_instance, _addr, _uuid, _timeout, _connectionId)); + callback.connectors(conns); + } + + @Override + public Acceptor acceptor(String adapterName) + { + return new AcceptorI(this, _instance, adapterName, _uuid, _name); + } + + @Override + public java.util.List<com.zeroc.IceInternal.EndpointI> expand() + { + java.util.List<com.zeroc.IceInternal.EndpointI> endps = + new java.util.ArrayList<com.zeroc.IceInternal.EndpointI>(); + endps.add(this); + return endps; + } + + @Override + public boolean equivalent(com.zeroc.IceInternal.EndpointI endpoint) + { + if(!(endpoint instanceof EndpointI)) + { + return false; + } + EndpointI btEndpointI = (EndpointI)endpoint; + return btEndpointI.type() == type() && btEndpointI._addr.equals(_addr) && btEndpointI._uuid.equals(_uuid) && + btEndpointI._channel == _channel; + } + + @Override + public String options() + { + // + // WARNING: Certain features, such as proxy validation in Glacier2, + // depend on the format of proxy strings. Changes to toString() and + // methods called to generate parts of the reference string could break + // these features. Please review for all features that depend on the + // format of proxyToString() before changing this and related code. + // + String s = ""; + + if(_addr != null && _addr.length() > 0) + { + s += " -a "; + boolean addQuote = _addr.indexOf(':') != -1; + if(addQuote) + { + s += "\""; + } + s += _addr; + if(addQuote) + { + s += "\""; + } + } + + if(_uuid != null) + { + s += " -u "; + String uuidStr = _uuid.toString(); + boolean addQuote = uuidStr.indexOf(':') != -1; + if(addQuote) + { + s += "\""; + } + s += uuidStr; + if(addQuote) + { + s += "\""; + } + } + + if(_channel > 0) + { + s += " -c " + _channel; + } + + if(_timeout == -1) + { + s += " -t infinite"; + } + else + { + s += " -t " + _timeout; + } + + if(_compress) + { + s += " -z"; + } + + return s; + } + + public void initWithOptions(java.util.ArrayList<String> args, boolean oaEndpoint) + { + super.initWithOptions(args); + + if(_addr.length() == 0) + { + _addr = _instance.defaultHost(); + if(_addr == null) + { + _addr = ""; + } + } + + if(_addr.length() == 0 || _addr.equals("*")) + { + if(oaEndpoint) + { + // + // Ignore a missing address, we always use the default adapter anyway. + // + } + else + { + throw new EndpointParseException( + "a device address must be specified using the -a option or Ice.Default.Host"); + } + } + + if(_name.length() == 0) + { + _name = "Ice Service"; + } + + if(_uuid == null) + { + if(oaEndpoint) + { + // + // Generate a UUID for object adapters that don't specify one. + // + _uuid = UUID.randomUUID(); + } + else + { + throw new EndpointParseException("a UUID must be specified using the -u option"); + } + } + + hashInit(); + } + + @Override + public com.zeroc.Ice.EndpointInfo getInfo() + { + EndpointInfo info = new EndpointInfo() + { + @Override + public short type() + { + return EndpointI.this.type(); + } + + @Override + public boolean datagram() + { + return EndpointI.this.datagram(); + } + + @Override + public boolean secure() + { + return EndpointI.this.secure(); + } + }; + info.addr = _addr; + info.uuid = _uuid.toString(); + return info; + } + + @Override + public int compareTo(com.zeroc.IceInternal.EndpointI obj) // From java.lang.Comparable + { + if(!(obj instanceof EndpointI)) + { + return type() < obj.type() ? -1 : 1; + } + + EndpointI p = (EndpointI)obj; + if(this == p) + { + return 0; + } + + int v = _addr.compareTo(p._addr); + if(v != 0) + { + return v; + } + + v = _uuid.toString().compareTo(p._uuid.toString()); + if(v != 0) + { + return v; + } + + if(_channel < p._channel) + { + return -1; + } + else if(p._channel < _channel) + { + return 1; + } + + if(_timeout < p._timeout) + { + return -1; + } + else if(p._timeout < _timeout) + { + return 1; + } + + v = _connectionId.compareTo(p._connectionId); + if(v != 0) + { + return v; + } + + if(!_compress && p._compress) + { + return -1; + } + else if(!p._compress && _compress) + { + return 1; + } + + return 0; + } + + @Override + public int hashCode() + { + return _hashValue; + } + + @Override + protected boolean checkOption(String option, String argument, String endpoint) + { + if(super.checkOption(option, argument, endpoint)) + { + return true; + } + + if(option.equals("-a")) + { + if(argument == null) + { + throw new EndpointParseException("no argument provided for -a option in endpoint " + endpoint); + } + if(!argument.equals("*") && !BluetoothAdapter.checkBluetoothAddress(argument.toUpperCase())) + { + throw new EndpointParseException("invalid address provided for -a option in endpoint " + endpoint); + } + _addr = argument.toUpperCase(); // Android requires a hardware address to use upper case letters. + } + else if(option.equals("-u")) + { + if(argument == null) + { + throw new EndpointParseException("no argument provided for -u option in endpoint " + endpoint); + } + try + { + _uuid = UUID.fromString(argument); + } + catch(IllegalArgumentException ex) + { + throw new EndpointParseException("invalid UUID for Bluetooth endpoint", ex); + } + } + else if(option.equals("-c")) + { + if(argument == null) + { + throw new EndpointParseException("no argument provided for -c option in endpoint " + endpoint); + } + + try + { + _channel = Integer.parseInt(argument); + } + catch(NumberFormatException ex) + { + throw new EndpointParseException("invalid channel value `" + argument + "' in endpoint " + endpoint); + } + + if(_channel < 0 || _channel > 30) // RFCOMM channel limit is 30 + { + throw new EndpointParseException("channel value `" + argument + "' out of range in endpoint " + + endpoint); + } + } + else if(option.equals("-t")) + { + if(argument == null) + { + throw new EndpointParseException("no argument provided for -t option in endpoint " + endpoint); + } + + if(argument.equals("infinite")) + { + _timeout = -1; + } + else + { + try + { + _timeout = Integer.parseInt(argument); + if(_timeout < 1) + { + throw new EndpointParseException("invalid timeout value `" + argument + "' in endpoint " + + endpoint); + } + } + catch(NumberFormatException ex) + { + throw new EndpointParseException("invalid timeout value `" + argument + "' in endpoint " + + endpoint); + } + } + } + else if(option.equals("-z")) + { + if(argument != null) + { + throw new EndpointParseException("unexpected argument `" + argument + + "' provided for -z option in " + endpoint); + } + + _compress = true; + } + else if(option.equals("--name")) + { + if(argument == null) + { + throw new EndpointParseException("no argument provided for --name option in endpoint " + endpoint); + } + + _name = argument; + } + else + { + return false; + } + return true; + } + + private void hashInit() + { + int h = 5381; + h = HashUtil.hashAdd(h, _addr); + h = HashUtil.hashAdd(h, _uuid.toString()); + h = HashUtil.hashAdd(h, _timeout); + h = HashUtil.hashAdd(h, _connectionId); + h = HashUtil.hashAdd(h, _compress); + _hashValue = h; + } + + private Instance _instance; + private String _addr; + private UUID _uuid; + private String _name; + private int _channel; + private int _timeout; + private String _connectionId; + private boolean _compress; + private int _hashValue; +} diff --git a/java/src/IceBT/src/main/java/com/zeroc/IceBT/Instance.java b/java/src/IceBT/src/main/java/com/zeroc/IceBT/Instance.java new file mode 100644 index 00000000000..326424062de --- /dev/null +++ b/java/src/IceBT/src/main/java/com/zeroc/IceBT/Instance.java @@ -0,0 +1,57 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +package com.zeroc.IceBT; + +import com.zeroc.Ice.Communicator; +import com.zeroc.Ice.PluginInitializationException; + +import android.bluetooth.BluetoothAdapter; + +class Instance extends com.zeroc.IceInternal.ProtocolInstance +{ + Instance(Communicator communicator, short type, String protocol) + { + // + // We consider the transport to be "secure" because it uses the secure versions Android's Bluetooth API + // methods for establishing and accepting connections. The boolean argument below sets secure=true. + // + super(communicator, type, protocol, true); + + _communicator = communicator; + + _bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if(_bluetoothAdapter == null) + { + throw new PluginInitializationException("bluetooth adapter not available"); + } + else if(!_bluetoothAdapter.isEnabled()) + { + throw new PluginInitializationException("bluetooth is not enabled"); + } + } + + void destroy() + { + _communicator = null; + } + + Communicator communicator() + { + return _communicator; + } + + BluetoothAdapter bluetoothAdapter() + { + return _bluetoothAdapter; + } + + private Communicator _communicator; + private BluetoothAdapter _bluetoothAdapter; +} diff --git a/java/src/IceBT/src/main/java/com/zeroc/IceBT/PluginFactory.java b/java/src/IceBT/src/main/java/com/zeroc/IceBT/PluginFactory.java new file mode 100644 index 00000000000..0143e792a5c --- /dev/null +++ b/java/src/IceBT/src/main/java/com/zeroc/IceBT/PluginFactory.java @@ -0,0 +1,33 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +package com.zeroc.IceBT; + +/** + * Plug-in factories must implement this interface. + **/ +public class PluginFactory implements com.zeroc.Ice.PluginFactory +{ + /** + * Returns a new plug-in. + * + * @param communicator The communicator for the plug-in. + * @param name The name of the plug-in. + * @param args The arguments that are specified in the plug-in's configuration. + * + * @return The new plug-in. <code>null</code> can be returned to indicate + * that a general error occurred. Alternatively, <code>create</code> can throw + * PluginInitializationException to provide more detailed information. + **/ + @Override + public com.zeroc.Ice.Plugin create(com.zeroc.Ice.Communicator communicator, String name, String[] args) + { + return new PluginI(communicator); + } +} diff --git a/java/src/IceBT/src/main/java/com/zeroc/IceBT/PluginI.java b/java/src/IceBT/src/main/java/com/zeroc/IceBT/PluginI.java new file mode 100644 index 00000000000..2da1c7920a7 --- /dev/null +++ b/java/src/IceBT/src/main/java/com/zeroc/IceBT/PluginI.java @@ -0,0 +1,46 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +package com.zeroc.IceBT; + +class PluginI implements com.zeroc.Ice.Plugin +{ + public PluginI(com.zeroc.Ice.Communicator communicator) + { + final com.zeroc.IceInternal.ProtocolPluginFacade facade = + com.zeroc.IceInternal.Util.getProtocolPluginFacade(communicator); + + // + // Register the endpoint factory. We have to do this now, rather than + // in initialize, because the communicator may need to interpret + // proxies before the plug-in is fully initialized. + // + EndpointFactoryI factory = + new EndpointFactoryI(new Instance(communicator, com.zeroc.Ice.BTEndpointType.value, "bt")); + facade.addEndpointFactory(factory); + + com.zeroc.IceInternal.EndpointFactory sslFactory = + facade.getEndpointFactory(com.zeroc.Ice.SSLEndpointType.value); + if(sslFactory != null) + { + Instance instance = new Instance(communicator, com.zeroc.Ice.BTSEndpointType.value, "bts"); + facade.addEndpointFactory(sslFactory.clone(instance, new EndpointFactoryI(instance))); + } + } + + @Override + public void initialize() + { + } + + @Override + public void destroy() + { + } +} diff --git a/java/src/IceBT/src/main/java/com/zeroc/IceBT/TransceiverI.java b/java/src/IceBT/src/main/java/com/zeroc/IceBT/TransceiverI.java new file mode 100644 index 00000000000..94f56119695 --- /dev/null +++ b/java/src/IceBT/src/main/java/com/zeroc/IceBT/TransceiverI.java @@ -0,0 +1,700 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +package com.zeroc.IceBT; + +import com.zeroc.IceInternal.Buffer; +import com.zeroc.IceInternal.ReadyCallback; +import com.zeroc.IceInternal.SocketOperation; +import com.zeroc.IceInternal.Transceiver; +import com.zeroc.Ice.LocalException; +import com.zeroc.Ice.SocketException; + +import java.lang.Thread; +import java.nio.ByteBuffer; +import java.nio.channels.*; +import java.util.UUID; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSocket; + +final class TransceiverI implements Transceiver +{ + @Override + public SelectableChannel fd() + { + // + // Android doesn't provide non-blocking APIs for Bluetooth. + // + return null; + } + + @Override + public void setReadyCallback(ReadyCallback callback) + { + _readyCallback = callback; + } + + @Override + public synchronized int initialize(Buffer readBuffer, Buffer writeBuffer) + { + if(_exception != null) + { + throw _exception; + //throw (LocalException) _exception.fillInStackTrace(); + } + + if(_state == StateConnecting) + { + // + // Wait until the connect thread is finished. + // + return SocketOperation.Read; + } + else if(_state == StateConnected) + { + // + // Update our Read state to indicate whether we still have more data waiting to be read. + // + _readyCallback.ready(SocketOperation.Read, _readBuffer.b.position() > 0); + } + + return SocketOperation.None; + } + + @Override + public int closing(boolean initiator, LocalException ex) + { + // + // If we are initiating the connection closure, wait for the peer + // to close the connection. Otherwise, close immediately. + // + return initiator ? SocketOperation.Read : SocketOperation.None; + } + + @Override + public void close() + { + Thread connectThread = null, readThread = null, writeThread = null; + + synchronized(this) + { + // + // Close the socket first in order to interrupt the helper threads. + // + if(_socket != null) + { + try + { + _socket.close(); + } + catch(java.io.IOException ex) + { + // Ignore. + } + _socket = null; + } + + connectThread = _connectThread; + _connectThread = null; + readThread = _readThread; + _readThread = null; + writeThread = _writeThread; + _writeThread = null; + + _state = StateClosed; + + if(writeThread != null) + { + notifyAll(); // Wake up the read/write threads. + } + } + + if(connectThread != null) + { + try + { + connectThread.join(); + } + catch(InterruptedException ex) + { + // Ignore. + } + } + + if(readThread != null) + { + try + { + readThread.join(); + } + catch(InterruptedException ex) + { + // Ignore. + } + } + + if(writeThread != null) + { + try + { + writeThread.join(); + } + catch(InterruptedException ex) + { + // Ignore. + } + } + } + + @Override + public com.zeroc.IceInternal.EndpointI bind() + { + assert(false); + return null; + } + + @Override + public synchronized int write(Buffer buf) + { + if(_exception != null) + { + throw _exception; + //throw (LocalException) _exception.fillInStackTrace(); + } + + // + // Accept up to _sndSize bytes in our internal buffer. + // + final int capacity = _sndSize - _writeBuffer.b.position(); + if(capacity > 0) + { + final int num = Math.min(capacity, buf.b.remaining()); + _writeBuffer.expand(num); + final int lim = buf.b.limit(); // Save the current limit. + buf.b.limit(buf.b.position() + num); // Temporarily change the limit. + _writeBuffer.b.put(buf.b); // Copy to our internal buffer. + buf.b.limit(lim); // Restore the previous limit. + + notifyAll(); // We've added data to the internal buffer, so wake up the write thread. + } + + return buf.b.hasRemaining() ? SocketOperation.Write : SocketOperation.None; + } + + @Override + public synchronized int read(Buffer buf) + { + if(_exception != null) + { + throw _exception; + //throw (LocalException) _exception.fillInStackTrace(); + } + + // + // Copy the requested amount of data from our internal buffer to the given buffer. + // + _readBuffer.b.flip(); + if(_readBuffer.b.hasRemaining()) + { + int bytesAvailable = _readBuffer.b.remaining(); + int bytesNeeded = buf.b.remaining(); + if(bytesAvailable > bytesNeeded) + { + bytesAvailable = bytesNeeded; + } + if(buf.b.hasArray()) + { + // + // Copy directly into the destination buffer's backing array. + // + byte[] arr = buf.b.array(); + _readBuffer.b.get(arr, buf.b.arrayOffset() + buf.b.position(), bytesAvailable); + buf.b.position(buf.b.position() + bytesAvailable); + } + else if(_readBuffer.b.hasArray()) + { + // + // Copy directly from the source buffer's backing array. + // + byte[] arr = _readBuffer.b.array(); + buf.b.put(arr, _readBuffer.b.arrayOffset() + _readBuffer.b.position(), bytesAvailable); + _readBuffer.b.position(_readBuffer.b.position() + bytesAvailable); + } + else + { + // + // Copy using a temporary array. + // + byte[] arr = new byte[bytesAvailable]; + _readBuffer.b.get(arr); + buf.b.put(arr); + } + } + _readBuffer.b.compact(); + + // + // The read thread will temporarily stop reading if we exceed our configured limit. + // + if(_readBuffer.b.position() < _rcvSize) + { + notifyAll(); + } + + // + // Update our Read state to indicate whether we still have more data waiting to be read. + // + _readyCallback.ready(SocketOperation.Read, _readBuffer.b.position() > 0); + + return buf.b.hasRemaining() ? SocketOperation.Read : SocketOperation.None; + } + + @Override + public String protocol() + { + return _instance.protocol(); + } + + @Override + public String toString() + { + return _desc; + } + + @Override + public String toDetailedString() + { + return toString(); + } + + @Override + public com.zeroc.Ice.ConnectionInfo getInfo() + { + ConnectionInfo info = new ConnectionInfo(); + info.incoming = _adapterName != null; + info.adapterName = _adapterName != null ? _adapterName : ""; + info.connectionId = _connectionId; + info.rcvSize = _rcvSize; + info.sndSize = _sndSize; + info.localAddress = BluetoothAdapter.getDefaultAdapter().getAddress(); + //info.localChannel - not available, use default value of -1 + info.remoteAddress = _remoteAddr; + //info.remoteChannel - not available, use default value of -1 + info.uuid = _uuid.toString(); + return info; + } + + @Override + public synchronized void setBufferSize(int rcvSize, int sndSize) + { + _rcvSize = Math.max(1024, rcvSize); + _sndSize = Math.max(1024, sndSize); + } + + @Override + public void checkSendSize(Buffer buf) + { + } + + // + // Used by ConnectorI. + // + TransceiverI(Instance instance, String remoteAddr, UUID uuid, String connectionId) + { + _instance = instance; + _remoteAddr = remoteAddr; + _uuid = uuid; + _connectionId = connectionId; + _state = StateConnecting; + + init(); + + BluetoothAdapter adapter = _instance.bluetoothAdapter(); + assert(adapter != null); + + BluetoothDevice device = null; + try + { + device = adapter.getRemoteDevice(_remoteAddr); + } + catch(IllegalArgumentException ex) + { + // + // Illegal address - This should have been detected by the endpoint. + // + assert(false); + throw new SocketException(ex); + } + + try + { + // + // We always connect using the "secure" method. + // + _socket = device.createRfcommSocketToServiceRecord(_uuid); + } + catch(java.io.IOException ex) + { + throw new SocketException(ex); + } + + _connectThread = new Thread() + { + public void run() + { + String name = "IceBT.ConnectThread"; + if(_remoteAddr != null && !_remoteAddr.isEmpty()) + { + name += "-" + _remoteAddr; + } + if(_uuid != null) + { + name += "-" + _uuid.toString(); + } + setName(name); + + runConnectThread(); + } + }; + _connectThread.start(); + } + + // + // Used by AcceptorI. + // + TransceiverI(Instance instance, BluetoothSocket socket, UUID uuid, String adapterName) + { + _instance = instance; + _remoteAddr = socket.getRemoteDevice().getAddress(); + _uuid = uuid; + _connectionId = ""; + _adapterName = adapterName; + _socket = socket; + _state = StateConnected; + + init(); + + startReadWriteThreads(); + } + + private void init() + { + _desc = "local address = " + _instance.bluetoothAdapter().getAddress(); + if(_remoteAddr != null && !_remoteAddr.isEmpty()) + { + _desc += "\nremote address = " + _remoteAddr; + } + if(_uuid != null) + { + _desc += "\nservice uuid = " + _uuid.toString(); + } + + final int defaultBufSize = 128 * 1024; + _rcvSize = _instance.properties().getPropertyAsIntWithDefault("IceBT.RcvSize", defaultBufSize); + _sndSize = _instance.properties().getPropertyAsIntWithDefault("IceBT.SndSize", defaultBufSize); + + _readBuffer = new Buffer(false); + _writeBuffer = new Buffer(false); + } + + private synchronized void exception(LocalException ex) + { + if(_exception == null) + { + _exception = ex; + } + } + + private void runConnectThread() + { + // + // Always cancel discovery prior to a connect attempt. + // + _instance.bluetoothAdapter().cancelDiscovery(); + + try + { + // + // This can block for several seconds. + // + _socket.connect(); + } + catch(java.io.IOException ex) + { + exception(new com.zeroc.Ice.ConnectFailedException(ex)); + } + + synchronized(this) + { + _connectThread = null; + + if(_exception == null) + { + // + // Connect succeeded. + // + _state = StateConnected; + + startReadWriteThreads(); + } + } + + // + // This causes the Ice run time to invoke initialize() again. + // + _readyCallback.ready(SocketOperation.Read, true); + } + + private void startReadWriteThreads() + { + String s = ""; + if(_remoteAddr != null && !_remoteAddr.isEmpty()) + { + s += "-" + _remoteAddr; + } + if(_uuid != null) + { + s += "-" + _uuid.toString(); + } + final String suffix = s; + + _readThread = new Thread() + { + public void run() + { + setName("IceBT.ReadThread" + suffix); + + runReadThread(); + } + }; + _readThread.start(); + + _writeThread = new Thread() + { + public void run() + { + setName("IceBT.WriteThread" + suffix); + + runWriteThread(); + } + }; + _writeThread.start(); + } + + private void runReadThread() + { + java.io.InputStream in = null; + + try + { + byte[] buf = null; + + synchronized(this) + { + if(_socket == null) + { + return; + } + in = _socket.getInputStream(); + + buf = new byte[_rcvSize]; + } + + while(true) + { + ByteBuffer b = null; + + synchronized(this) + { + // + // If we've read too much data, wait until the application consumes some before we read again. + // + while(_state == StateConnected && _exception == null && _readBuffer.b.position() > _rcvSize) + { + try + { + wait(); + } + catch(InterruptedException ex) + { + break; + } + } + + if(_state != StateConnected || _exception != null) + { + break; + } + } + + int num = in.read(buf); + if(num > 0) + { + synchronized(this) + { + _readBuffer.expand(num); + _readBuffer.b.put(buf, 0, num); + _readyCallback.ready(SocketOperation.Read, true); + + if(buf.length != _rcvSize) + { + // + // Application must have called setBufferSize. + // + buf = new byte[_rcvSize]; + } + } + } + } + } + catch(java.io.IOException ex) + { + exception(new SocketException(ex)); + // + // Mark as ready for reading so that the Ice run time will invoke read() and we can report the exception. + // + _readyCallback.ready(SocketOperation.Read, true); + } + catch(LocalException ex) + { + exception(ex); + // + // Mark as ready for reading so that the Ice run time will invoke read() and we can report the exception. + // + _readyCallback.ready(SocketOperation.Read, true); + } + finally + { + if(in != null) + { + try + { + in.close(); + } + catch(java.io.IOException ex) + { + // Ignore. + } + } + } + } + + private void runWriteThread() + { + java.io.OutputStream out = null; + + try + { + synchronized(this) + { + if(_socket == null) + { + return; + } + out = _socket.getOutputStream(); + } + + boolean done = false; + while(!done) + { + ByteBuffer b = null; + + synchronized(this) + { + while(_state == StateConnected && _exception == null && _writeBuffer.b.position() == 0) + { + try + { + wait(); + } + catch(InterruptedException ex) + { + break; + } + } + + if(_state != StateConnected || _exception != null) + { + done = true; + } + + b = _writeBuffer.b; // Adopt the ByteBuffer. + _writeBuffer.clear(); + } + + assert(b != null && b.hasArray()); + b.flip(); + if(b.hasRemaining() && !done) + { + // + // write() blocks until all the data has been written. + // + out.write(b.array(), b.arrayOffset(), b.remaining()); + } + + // TBD: Recycle the buffer? + + synchronized(this) + { + // + // After the write is complete, indicate whether we can accept more data. + // + _readyCallback.ready(SocketOperation.Write, _writeBuffer.b.position() < _sndSize); + } + } + } + catch(java.io.IOException ex) + { + exception(new SocketException(ex)); + } + finally + { + if(out != null) + { + try + { + out.close(); + } + catch(java.io.IOException ex) + { + // Ignore. + } + } + } + } + + private Instance _instance; + private String _remoteAddr; + private UUID _uuid; + private String _connectionId; + private String _adapterName; + + private BluetoothSocket _socket; + + private static final int StateConnecting = 0; + private static final int StateConnected = 1; + private static final int StateClosed = 2; + private int _state; + + private Thread _connectThread; + private Thread _readThread; + private Thread _writeThread; + + private LocalException _exception; + + private int _rcvSize; + private int _sndSize; + + private Buffer _readBuffer; + private Buffer _writeBuffer; + + private String _desc; + + private ReadyCallback _readyCallback; +} |