/*
* socket.io-java-client SocketIO.java
*
* Copyright (c) 2012, Enno Boland
* socket.io-java-client is a implementation of the socket.io protocol in Java.
*
* See LICENSE file for more information
*/
package io.socket;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Properties;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import org.json.JSONObject;
/**
* The Class SocketIO.
*/
public class SocketIO {
/** callback of this Socket. */
private IOCallback callback;
/** connection of this Socket. */
private IOConnection connection;
/** namespace. */
private String namespace;
/** Used for setting header during handshaking. */
private Properties headers = new Properties();
private URL url;
/**
* Instantiates a new socket.io connection. The object connects after
* calling {@link #connect(URL, IOCallback)} or
* {@link #connect(String, IOCallback)}
*/
public SocketIO() {
}
/**
* Instantiates a new socket.io connection. The object connects after
* calling {@link #connect(IOCallback)}
*
* @param url
* the url
* @throws MalformedURLException
* the malformed url exception
*/
public SocketIO(final String url) throws MalformedURLException {
if (url == null)
throw new RuntimeException("url may not be null.");
setAndConnect(new URL(url), null);
}
/**
* Instantiates a new socket.io connection and sets the request headers used
* while connecting the first time for authorizing. The object connects
* after calling {@link #connect(IOCallback)}
*
* @param url
* the url
* @param headers
* the {@link Properties headers} used while handshaking
* @throws MalformedURLException
* the malformed url exception
*/
public SocketIO(final String url, Properties headers)
throws MalformedURLException {
if (url == null)
throw new RuntimeException("url may not be null.");
if (headers != null)
this.headers = headers;
setAndConnect(new URL(url), null);
}
/**
* Instantiates a new socket.io object and connects to the given url. Do not
* call any of the connect() methods afterwards.
*
* @param url
* the url
* @param callback
* the callback
* @throws MalformedURLException
* the malformed url exception
*/
public SocketIO(final String url, final IOCallback callback)
throws MalformedURLException {
connect(url, callback);
}
/**
* Instantiates a new socket.io object and connects to the given url. Do not
* call any of the connect() methods afterwards.
*
* @param url
* the url
* @param callback
* the callback
*/
public SocketIO(final URL url, final IOCallback callback) {
if (setAndConnect(url, callback) == false)
throw new RuntimeException("url and callback may not be null.");
}
/**
* Instantiates a new socket.io connection. The object connects after
* calling {@link #connect(IOCallback)}
*
* @param url
* the url
*/
public SocketIO(final URL url) {
setAndConnect(url, null);
}
/**
* Set the socket factory used for SSL connections.
* @param socketFactory
*/
public static void setDefaultSSLSocketFactory(SSLContext sslContext) {
IOConnection.setSslContext(sslContext);
}
/**
* connects to supplied host using callback. Do only use this method if you
* instantiate {@link SocketIO} using {@link #SocketIO()}.
*
* @param url
* the url
* @param callback
* the callback
*/
public void connect(final String url, final IOCallback callback)
throws MalformedURLException {
if (setAndConnect(new URL(url), callback) == false) {
if (url == null || callback == null)
throw new RuntimeException("url and callback may not be null.");
else
throw new RuntimeException(
"connect(String, IOCallback) can only be invoked after SocketIO()");
}
}
/**
* connects to supplied host using callback. Do only use this method if you
* instantiate {@link SocketIO} using {@link #SocketIO()}.
*
* @param url
* the url
* @param callback
* the callback
*/
public void connect(URL url, IOCallback callback) {
if (setAndConnect(url, callback) == false) {
if (url == null || callback == null)
throw new RuntimeException("url and callback may not be null.");
else
throw new RuntimeException(
"connect(URL, IOCallback) can only be invoked after SocketIO()");
}
}
/**
* connects to an already set host. Do only use this method if you
* instantiate {@link SocketIO} using {@link #SocketIO(String)} or
* {@link #SocketIO(URL)}.
*
* @param callback
* the callback
*/
public void connect(IOCallback callback) {
if (setAndConnect(null, callback) == false) {
if (callback == null)
throw new RuntimeException("callback may not be null.");
else if (this.url == null)
throw new RuntimeException(
"connect(IOCallback) can only be invoked after SocketIO(String) or SocketIO(URL)");
}
}
/**
* Sets url and callback and initiates connecting if both are present
*
* @param url
* the url
* @param callback
* the callback
* @return true if connecting has been initiated, false if not
*/
private boolean setAndConnect(URL url, IOCallback callback) {
if(this.connection != null)
throw new RuntimeException("You can connect your SocketIO instance only once. Use a fresh instance instead.");
if ((this.url != null && url != null)
|| (this.callback != null && callback != null))
return false;
if (url != null) {
this.url = url;
}
if (callback != null) {
this.callback = callback;
}
if (this.callback != null && this.url != null) {
final String origin = this.url.getProtocol() + "://"
+ this.url.getAuthority();
this.namespace = this.url.getPath();
if (this.namespace.equals("/")) {
this.namespace = "";
}
this.connection = IOConnection.register(origin, this);
return true;
}
return false;
}
/**
* Emits an event to the Socket.IO server. If the connection is not
* established, the call will be buffered and sent as soon as it is
* possible.
*
* @param event
* the event name
* @param args
* arguments. can be any argument {@link org.json.JSONArray#put(Object)} can take.
*/
public void emit(final String event, final Object... args) {
this.connection.emit(this, event, null, args);
}
/**
* Emits an event to the Socket.IO server. If the connection is not
* established, the call will be buffered and sent as soon as it is
* possible.
*
* @param event
* the event name
* @param ack
* an acknowledge implementation
* @param args
* arguments. can be any argument {@link org.json.JSONArray#put(Object)} can take.
*/
public void emit(final String event, IOAcknowledge ack,
final Object... args) {
this.connection.emit(this, event, ack, args);
}
/**
* Gets the callback. Internally used.
*
* @return the callback
*/
public IOCallback getCallback() {
return this.callback;
}
/**
* Gets the namespace. Internally used.
*
* @return the namespace
*/
public String getNamespace() {
return this.namespace;
}
/**
* Send JSON data to the Socket.io server.
*
* @param json
* the JSON object
*/
public void send(final JSONObject json) {
this.connection.send(this, null, json);
}
/**
* Send JSON data to the Socket.io server.
*
* @param ack
* an acknowledge implementation
* @param json
* the JSON object
*/
public void send(IOAcknowledge ack, final JSONObject json) {
this.connection.send(this, ack, json);
}
/**
* Send String data to the Socket.io server.
*
* @param message
* the message String
*/
public void send(final String message) {
this.connection.send(this, null, message);
}
/**
* Send JSON data to the Socket.io server.
*
* @param ack
* an acknowledge implementation
* @param message
* the message String
*/
public void send(IOAcknowledge ack, final String message) {
this.connection.send(this, ack, message);
}
/**
* Disconnect the socket.
*/
public void disconnect() {
this.connection.unregister(this);
}
/**
* Triggers the transport to reconnect.
*
* This had become useful on some android devices which do not shut down
* tcp-connections when switching from HSDPA to Wifi
*/
public void reconnect() {
this.connection.reconnect();
}
/**
* Returns, if a connection is established at the moment
*
* @return true if a connection is established, false if the transport is
* not connected or currently connecting
*/
public boolean isConnected() {
return this.connection != null && this.connection.isConnected();
}
/**
* Returns the name of the used transport
*
* @return the name of the currently used transport
*/
public String getTransport() {
IOTransport transport = this.connection.getTransport();
return transport != null ? transport.getName() : null;
}
/**
* Returns the headers used while handshaking. These Properties are not
* necessarily the ones set by {@link #addHeader(String, String)} or
* {@link #SocketIO(String, Properties)} but the ones used for the
* handshake.
*
* @return the headers used while handshaking
*/
public Properties getHeaders() {
return headers;
}
/**
* Sets the headers used while handshaking. Internally used. Use
* {@link #SocketIO(String, Properties)} or
* {@link #addHeader(String, String)} instead.
*
* @param headers
* the headers used while handshaking
*/
void setHeaders(Properties headers) {
this.headers = headers;
}
/**
* Adds an header to the {@link #headers}
* @return SocketIO.this for daisy chaining.
*/
public SocketIO addHeader(String key, String value) {
if (this.connection != null)
throw new RuntimeException(
"You may only set headers before connecting.\n"
+ " Try to use new SocketIO().addHeader(key, value).connect(host, callback) "
+ "instead of SocketIO(host, callback).addHeader(key, value)");
this.headers.setProperty(key, value);
return this;
}
/**
* Returns the header value
*
* @return the header value or {@code null} if not present
*/
public String getHeader(String key) {
if (this.headers.contains(key))
return this.headers.getProperty(key);
return null;
}
}