com.skype.connector.Connector Maven / Gradle / Ivy
The newest version!
/*******************************************************************************
* Copyright (c) 2006-2007 Koji Hisano - UBION Inc. Developer
* Copyright (c) 2006-2007 UBION Inc.
*
* Copyright (c) 2006-2007 Skype Technologies S.A.
*
* Skype4Java is licensed under either the Apache License, Version 2.0 or
* the Eclipse Public License v1.0.
* You may use it freely in commercial and non-commercial products.
* You may obtain a copy of the licenses at
*
* the Apache License - http://www.apache.org/licenses/LICENSE-2.0
* the Eclipse Public License - http://www.eclipse.org/legal/epl-v10.html
*
* If it is possible to cooperate with the publicity of Skype4Java, please add
* links to the Skype4Java web site
* in your web site or documents.
*
* Contributors:
* Koji Hisano - initial API and implementation
* Gabriel Takeuchi - retry commands instead of "ping-pong" to improve reliability
******************************************************************************/
package com.skype.connector;
import java.io.*;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Base class for all platform specific connectors. A connector connects the
* Skype Java API with a running Skype client.
*
* @author Koji Hisano
*/
public abstract class Connector {
/**
* Enumeration of the connector status.
*/
public enum Status {
/**
* PENDING_AUTHORIZATION - The connector is waiting for the user to
* accept this app to connect to the Skype client. ATTACHED - The
* connector is attached to the Skype client. REFUSED - The user denied
* the application to connect to the Skype client. NOT_AVAILABLE - The
* is no Skype client available to connect to. API_AVAILABLE - Redundant
* of ATTACHED. NOT_RUNNING - Connection can't be established.
*/
PENDING_AUTHORIZATION, ATTACHED, REFUSED, NOT_AVAILABLE, API_AVAILABLE, NOT_RUNNING;
}
/** Singleton instance of this class. */
private static Connector _instance;
/**
* Initializes a platform specific connection. This method will select a
* connector based on the os.name. Windows has two versions see
* useJNIConnector.
*
* @return an initialized connection.
*/
public static synchronized Connector getInstance() {
if (_instance == null) {
String connectorClassName = null;
String osName = System.getProperty("os.name");
if (osName.startsWith("Windows")) {
connectorClassName = "com.skype.connector.win32.Win32Connector";
} else if (osName.startsWith("Linux") || osName.startsWith("LINUX")) {
connectorClassName = "com.skype.connector.linux.LinuxConnector";
} else if (osName.startsWith("Mac OS X")) {
connectorClassName = "com.skype.connector.osx.OSXConnector";
}
if (connectorClassName == null) {
throw new IllegalStateException(
"This platform is not supported by Skype4Java.");
}
try {
@SuppressWarnings("unchecked")
Class connectorClass = (Class) Class
.forName(connectorClassName);
Method getInstance = connectorClass.getMethod("getInstance");
_instance = (Connector) getInstance.invoke(null);
} catch (Exception e) {
throw new IllegalStateException(
"The connector couldn't be initialized.", e);
}
}
return _instance;
}
/**
* Sets the instance of the connector for test cases.
*
* @param newInstance
* The new instance.
* @throws ConnectorException
* thrown when instance is not valid.
*/
protected static synchronized void setInstance(final Connector newInstance)
throws ConnectorException {
if (_instance != null) {
_instance.dispose();
}
_instance = newInstance;
}
/**
* The mutex object for the _debugListener field.
*/
private final Object _debugListenerMutex = new Object();
/**
* The connector listener for debug out.
*/
private ConnectorListener _debugListener;
/**
* The debug output stream. This stream is initialized by
* new PrintWriter(System.out, true).
*/
private volatile PrintWriter _debugOut = new PrintWriter(System.out, true);
/**
* The application name used to get the access grant of Skype API.
*/
private volatile String _applicationName = "Skype4Java";
/**
* The status of this connector.
*/
private volatile Status _status = Status.NOT_RUNNING;
/**
* The connect timeout in milliseconds.
*/
private volatile int _connectTimeout = 20000;
/**
* The command reply timeout in milliseconds.
*/
private volatile int _commandTimeout = 20000;
/**
* The mutex object for the _isInitialized field.
*/
private final Object _isInitializedMutex = new Object();
/**
* The flag to check if the connector is already initialized.
*/
private boolean _isInitialized;
/**
* The flag to check missed messages after start
*/
private boolean _readMissedMessages;
/** Asynchronous message sender */
private ExecutorService _asyncSender;
/** Synchronous message sender */
private ExecutorService _syncSender;
/** Collection of asynchronous event listeners for the connector. */
private final List _asyncListeners = new CopyOnWriteArrayList();
/** Collection of synchronous event listeners for the connector. */
private final List _syncListeners = new CopyOnWriteArrayList();
/** Command counter, can be used to identify message and reply pairs. */
private final AtomicInteger _commandCount = new AtomicInteger();
/** Command executor */
private ExecutorService _commandExecutor;
/** The properties of this connector **/
private final Map properties = new ConcurrentHashMap();
/**
* Because this object should be a singleton the constructor is protected.
*/
protected Connector() {
}
/**
* Try to get the absolute path to the skype client. Should be overridden
* for each platfrom specific connector. Not geranteed to work.
*
* @return The absolute path to the Skype client executable.
*/
public String getInstalledPath() {
return "skype";
}
/**
* Enable or disable debug printing for more information.
*
* @param on
* if true debug output will be written to System.out
* @throws ConnectorException
* thrown when connection to Skype Client has gone bad.
*/
public final void setDebug(final boolean on) throws ConnectorException {
synchronized (_debugListenerMutex) {
if (on) {
if (_debugListener == null) {
_debugListener = new AbstractConnectorListener() {
@Override
public void messageReceived(
final ConnectorMessageEvent event) {
getDebugOut().println("<- " + event.getMessage());
}
@Override
public void messageSent(
final ConnectorMessageEvent event) {
getDebugOut().println("-> " + event.getMessage());
}
};
addConnectorListener(_debugListener, true, true);
}
} else {
if (_debugListener != null) {
removeConnectorListener(_debugListener);
_debugListener = null;
}
}
}
}
/**
* Sets the debug output stream.
*
* @param newDebugOut
* the new debug output stream
* @throws NullPointerException
* if the specified new debug out is null
* @see #setDebugOut(PrintStream)
* @see #getDebugOut()
*/
public final void setDebugOut(final PrintWriter newDebugOut) {
ConnectorUtils.checkNotNull("debugOut", newDebugOut);
_debugOut = newDebugOut;
}
/**
* Sets the debug output stream.
*
* @param newDebugOut
* the new debug output stream
* @throws NullPointerException
* if the specified new debug out is null
* @see #setDebugOut(PrintWriter)
* @see #getDebugOut()
*/
public final void setDebugOut(final PrintStream newDebugOut) {
ConnectorUtils.checkNotNull("debugOut", newDebugOut);
setDebugOut(new PrintWriter(newDebugOut, true));
}
/**
* Gets the debug output stream.
*
* @return the current debug output stream
* @see #setDebugOut(PrintWriter)
* @see #setDebugOut(PrintStream)
*/
public final PrintWriter getDebugOut() {
return _debugOut;
}
/**
* Sets the application name used to get the access grant of Skype API. The
* specified name is what the User will see in the Skype API Allow/Deny
* dialog.
*
* @param newApplicationName
* the application name
* @throws NullPointerException
* if the specified application name is null
* @see #getApplicationName()
*/
public final void setApplicationName(final String newApplicationName) {
ConnectorUtils.checkNotNull("applicationName", newApplicationName);
_applicationName = newApplicationName;
}
/**
* Gets the application name used to get the access grant of Skype API.
*
* @return the application name
* @see #setApplicationName(String)
*/
public final String getApplicationName() {
return _applicationName;
}
/**
* Sets the status of this connector. After setting, an status changed event
* will be sent to the all listeners.
*
* @param newValue
* the new status
* @throws NullPointerException
* if the specified status is null
* @see #getStatus()
*/
protected final void setStatus(final Status newStatus) {
ConnectorUtils.checkNotNull("status", newStatus);
_status = newStatus;
fireStatusChanged(newStatus);
}
/**
* Sends a status change event to the all listeners.
*
* @param newStatus
* the new status
*/
private void fireStatusChanged(final Status newStatus) {
_syncSender.execute(new Runnable() {
public void run() {
// use listener array instead of list because of reverse
// iteration
fireStatusChanged(toConnectorListenerArray(_syncListeners),
newStatus);
}
});
_asyncSender.execute(new Runnable() {
public void run() {
// use listener array instead of list because of reverse
// iteration
fireStatusChanged(toConnectorListenerArray(_asyncListeners),
newStatus);
}
});
}
/**
* Converts the specified listener list to an listener array.
*
* @param listeners
* the listener list
* @return an listener array
*/
private ConnectorListener[] toConnectorListenerArray(
final List listeners) {
return listeners.toArray(new ConnectorListener[0]);
}
/**
* Sends a status change event to the specified listeners.
*
* @param listeners
* the event listeners
* @param newStatus
* the new status
*/
private void fireStatusChanged(final ConnectorListener[] listeners,
final Status newStatus) {
final ConnectorStatusEvent event = new ConnectorStatusEvent(this,
newStatus);
for (int i = listeners.length - 1; 0 <= i; i--) {
listeners[i].statusChanged(event);
}
}
/**
* Gets the status of this connector.
*
* @return status the status of this connector
* @see #setStatus(com.skype.connector.Connector.Status)
*/
public final Status getStatus() {
return _status;
}
/**
* Sets the connect timeout of this connector.
*
* @param newConnectTimeout
* the new connect timeout in milliseconds
* @throws IllegalArgumentException
* if the new connect timeout is not more than 0
* @see #getConnectTimeout()
*/
public final void setConnectTimeout(final int newConnectTimeout) {
if (newConnectTimeout < 0) {
throw new IllegalArgumentException(
"The connect timeout must be more than 0.");
}
_connectTimeout = newConnectTimeout;
}
/**
* Gets the connect timeout of this connector.
*
* @return the connect timeout in milliseconds
* @see #setConnectTimeout(int)
*/
public final int getConnectTimeout() {
return _connectTimeout;
}
/**
* Sets the command reply timeout of this connector.
*
* @param newCommandTimeout
* the new command reply timeout in milliseconds
* @throws IllegalArgumentException
* if the new command reply timeout is not more than 0
* @see #getCommandTimeout()
*/
public final void setCommandTimeout(final int newCommandTimeout) {
if (newCommandTimeout < 0) {
throw new IllegalArgumentException(
"The connect timeout must be more than 0.");
}
_commandTimeout = newCommandTimeout;
}
/**
* Gets the command reply timeout of this connector.
*
* @return the command reply timeout in milliseconds
* @see #setCommandTimeout(int)
*/
public final int getCommandTimeout() {
return _commandTimeout;
}
/**
* Tries to connect this connector to the Skype client.
*
* @return the status after trying to connect.
* @throws ConnectorException
* if trying to connect failed
* @throws NotAttachedException
* if the Skype client is not running
*/
public final Status connect() throws ConnectorException {
initialize();
Status status = connect(getConnectTimeout());
if (status == Status.ATTACHED) {
sendApplicationName(getApplicationName());
sendProtocol();
}
return status;
}
/**
* Initializes this connector.
*
* @throws ConnectorException
* if the initialization failed.
*/
protected final void initialize() throws ConnectorException {
synchronized (_isInitializedMutex) {
if (!_isInitialized) {
_asyncSender = Executors
.newCachedThreadPool(new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger();
public Thread newThread(Runnable r) {
Thread thread = new Thread(r,
"AsyncSkypeMessageSender-"
+ threadNumber
.getAndIncrement());
thread.setDaemon(true);
return thread;
}
});
_syncSender = Executors
.newSingleThreadExecutor(new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread thread = new Thread(r,
"SyncSkypeMessageSender");
thread.setDaemon(true);
return thread;
}
});
// newCachedThreadPool(
_commandExecutor = Executors.newCachedThreadPool(
new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger();
public Thread newThread(Runnable r) {
Thread thread = new Thread(r,"CommandExecutor-"+ threadNumber.getAndIncrement());
thread.setDaemon(true);
return thread;
}
});
initializeImpl();
_isInitialized = true;
}
}
}
/**
* Initializes the platform specific resources.
*
* @throws ConnectorException
* if the initialization failed.
*/
protected abstract void initializeImpl() throws ConnectorException;
/**
* Tries to connect this connector to the Skype client on the platform
* mechanism.
*
* @param timeout
* the connect timeout in milliseconds to use while connecting.
* @return the status after trying to connect
* @throws ConnectorException
* if the trying to connect failed.
*/
protected abstract Status connect(int timeout) throws ConnectorException;
/**
* Sends the application name to the Skype client. The default
* implementation does nothing.
*
* @param applicationName
* the application name
* @throws ConnectorException
* if sending the specified application name failed
*/
protected void sendApplicationName(String applicationName)
throws ConnectorException {
}
/**
* Sends the command to read MISSEDMESSAGES
*
* @throws ConnectorException
* if sending the protocol version failed
*/
public void getMissedMessages() throws ConnectorException {
String command = "SEARCH MISSEDMESSAGES";
String responseHeader = "CHATMESSAGES ";
String response = execute(command, responseHeader);
if (response.contains("ERROR")) {
return;
}
String data = response.substring(responseHeader.length());
if ("".equals(data)) {
return;
}
String[] ids = data.split(", ");
/* message ids are reversed here.
* actually after SEARCH messages appear in #fireMessageReceived
* but in NON-deterministic order, that's why we are forced to
* call it manually & use mutex inside to avoid duplication
* */
for (int i = ids.length - 1; i >= 0; --i) {
String id = ids[i];
fireMessageReceived("CHATMESSAGE " + id + " STATUS RECEIVED");
String seen_response = execute("SET CHATMESSAGE " + id + " SEEN", "");
}
}
/**
* Sends the Skype API protocol version to use. The default implementation
* uses the latest version of the Skype API.
*
* @throws ConnectorException
* if sending the protocol version failed
*/
protected void sendProtocol() throws ConnectorException {
execute("PROTOCOL 9999", new String[] { "PROTOCOL " }, false);
}
/**
* Disconnects from the Skype client and clean up the resources.
*
* @throws ConnectorException
* if cleaning up the resources failed
*/
public final void dispose() throws ConnectorException {
synchronized (_isInitializedMutex) {
if (!_isInitialized) {
return;
}
disposeImpl();
setStatus(Status.NOT_RUNNING);
_commandExecutor.shutdown();
_syncSender.shutdown();
_asyncSender.shutdown();
_syncListeners.clear();
_asyncListeners.clear();
synchronized (_debugListenerMutex) {
if (_debugListener != null) {
addConnectorListener(_debugListener, false, true);
}
}
_isInitialized = false;
}
}
/**
* Disconnects from the Skype client and clean up the resources of the
* platfrom.
*
* @throws ConnectorException
* if cleaning up the resources failed
*/
protected abstract void disposeImpl() throws ConnectorException;
/**
* Checks if the Skype client is running or not.
*
* @return true if the Skype client is runnunig; false otherwise
* @throws ConnectorException
* if checking the Skype client status failed
*/
public boolean isRunning() throws ConnectorException {
try {
assureAttached();
return true;
} catch (ConnectorException e) {
return false;
}
}
/**
* Executes the specified command and handles the response by the specified
* message processor.
*
* @param command
* the command to execute
* @param processor
* the message processor
* @throws NullPointerException
* if the specified command or processor is null
* @throws ConnectorException
* if executing the command failed
*/
@Deprecated
public final void execute(final String command,
final MessageProcessor processor) throws ConnectorException {
ConnectorUtils.checkNotNull("command", command);
ConnectorUtils.checkNotNull("processor", processor);
assureAttached();
final Object wait = new Object();
ConnectorListener listener = new AbstractConnectorListener() {
public void messageReceived(ConnectorMessageEvent event) {
processor.messageReceived(event.getMessage());
}
};
processor.init(wait, listener);
addConnectorListener(listener, false);
synchronized (wait) {
try {
fireMessageSent(command);
sendCommand(command);
long start = System.currentTimeMillis();
long commandResponseTime = getCommandTimeout();
wait.wait(commandResponseTime);
if (commandResponseTime <= System.currentTimeMillis() - start) {
setStatus(Status.NOT_RUNNING);
throw new NotAttachedException(Status.NOT_RUNNING);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ConnectorException("The '" + command
+ "' command was interrupted.", e);
} finally {
removeConnectorListener(listener);
}
}
}
/**
* Executes the specified command and gets the response. It is better to use
* {@link #executeWithId(String, String)} because it returns the accurate
* response.
*
* @param command
* the command to execute
* @return the response after execution
* @throws NullPointerException
* if the specified command is null
* @throws ConnectorException
* if executing the command failed
* @see #executeWithId(String, String)
*/
public final String execute(final String command) throws ConnectorException {
ConnectorUtils.checkNotNull("command", command);
return execute(command, command);
}
/**
* Executes the specified command and gets the response using a command ID.
*
* @param command
* the command to execute
* @param responseHeader
* the response header to get the accurate response
* @return the response after execution
* @throws NullPointerException
* if the specified command or responseHeader is null
* @throws ConnectorException
* if executing the command failed
*/
public final String executeWithId(final String command,
final String responseHeader) throws ConnectorException {
ConnectorUtils.checkNotNull("command", command);
ConnectorUtils.checkNotNull("responseHeader", responseHeader);
final String header = "#" + _commandCount.getAndIncrement() + " ";
final String response = execute(header + command, new String[] {
header + responseHeader, header + "ERROR " }, true);
return response.substring(header.length());
}
/**
* Executes the specified command and gets the future using a command ID.
*
* @param command
* the command to execute
* @param responseHeader
* the response header to get the accurate first response
* @param checker
* the notification checker to detect the end
* @return the future to wait for the end of the execution
* @throws NullPointerException
* if the specified command, responseHeader or checker is null
* @throws ConnectorException
* if executing the command failed
*/
@SuppressWarnings("rawtypes")
public final Future waitForEndWithId(final String command,
final String responseHeader, final NotificationChecker checker)
throws ConnectorException {
ConnectorUtils.checkNotNull("command", command);
ConnectorUtils.checkNotNull("responseHeader", responseHeader);
ConnectorUtils.checkNotNull("responseHeader", checker);
final String header = "#" + _commandCount.getAndIncrement() + " ";
final NotificationChecker wrappedChecker = new NotificationChecker() {
public boolean isTarget(String message) {
if (checker.isTarget(message)) {
return true;
}
return message.startsWith(header + "ERROR ");
}
};
final Future future = execute(header + command, wrappedChecker,
true, false);
return new Future() {
public boolean isDone() {
return future.isDone();
}
public boolean isCancelled() {
return future.isCancelled();
}
public String get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException,
TimeoutException {
return removeId(future.get(timeout, unit));
}
public String get() throws InterruptedException, ExecutionException {
return removeId(future.get());
}
private String removeId(String message) {
if (message.startsWith(header)) {
return message.substring(header.length());
}
return message;
}
public boolean cancel(boolean mayInterruptIfRunning) {
return future.cancel(mayInterruptIfRunning);
}
};
}
/**
* Executes the specified command and waits for the response without
* timeout.
*
* @param command
* the command to execute
* @param responseHeader
* the response header to get the accurate response
* @return the response after execution
* @throws NullPointerException
* if the specified command or responseHeader is null
* @throws ConnectorException
* if executing the command failed
*/
public final String executeWithoutTimeout(final String command,
final String responseHeader) throws ConnectorException {
ConnectorUtils.checkNotNull("command", command);
ConnectorUtils.checkNotNull("responseHeader", responseHeader);
return execute(command, new String[] { responseHeader, "ERROR " },
true, true);
}
/**
* Executes the specified command and gets the response.
*
* @param command
* the command to execute
* @param responseHeader
* the response header to get the accurate response
* @return the response after execution
* @throws NullPointerException
* if the specified command or responseHeader is null
* @throws ConnectorException
* if executing the command failed
*/
public final String execute(final String command,
final String responseHeader) throws ConnectorException {
ConnectorUtils.checkNotNull("command", command);
ConnectorUtils.checkNotNull("responseHeader", responseHeader);
return execute(command, new String[] { responseHeader, "ERROR " }, true);
}
/**
* Executes the specified command and gets the response.
*
* @param command
* the command to execute
* @param responseHeaders
* the response headers to get the accurate response
* @return the response after execution
* @throws NullPointerException
* if the specified command or responseHeader is null
* @throws ConnectorException
* if executing the command failed
*/
public final String execute(final String command,
final String[] responseHeaders) throws ConnectorException {
ConnectorUtils.checkNotNull("command", command);
ConnectorUtils.checkNotNull("responseHeaders", responseHeaders);
return execute(command, responseHeaders, true);
}
/**
* Executes the specified command and gets the response.
*
* @param command
* the command to execute
* @param responseHeaders
* the response headers to get the accurate response
* @param checkAttached
* if true check if this connector is attached
* @return the response after execution
* @throws NullPointerException
* if the specified command or responseHeader is null
* @throws ConnectorException
* if executing the command failed
*/
protected final String execute(final String command,
final String[] responseHeaders, final boolean checkAttached)
throws ConnectorException {
return execute(command, responseHeaders, checkAttached, false);
}
/**
* Executes the specified command and gets the response.
*
* @param command
* the command to execute
* @param responseHeaders
* the response headers to get the accurate response
* @param checkAttached
* if true check if this connector is attached
* @param withoutTimeout
* if true it will not be time out
* @return the response after execution
* @throws NullPointerException
* if the specified command or responseHeader is null
* @throws ConnectorException
* if executing the command failed
*/
private String execute(final String command,
final String[] responseHeaders, final boolean checkAttached,
boolean withoutTimeout) throws ConnectorException {
final NotificationChecker checker = new NotificationChecker() {
public boolean isTarget(String message) {
for (String responseHeader : responseHeaders) {
if (message.startsWith(responseHeader)) {
return true;
}
}
return false;
}
};
try {
return execute(command, checker, checkAttached, withoutTimeout)
.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ConnectorException("The '" + command
+ "' command was interrupted.", e);
} catch (ExecutionException e) {
if (e.getCause() instanceof NotAttachedException) {
NotAttachedException cause = (NotAttachedException) e
.getCause();
throw new NotAttachedException(cause.getStatus(), cause);
} else if (e.getCause() instanceof ConnectorException) {
ConnectorException cause = (ConnectorException) e.getCause();
throw new ConnectorException(cause.getMessage(), cause);
}
throw new ConnectorException("The '" + command
+ "' command execution failed.", e);
}
}
/**
* Executes the specified command and gets the future using a command ID.
*
* @param command
* the command to execute
* @param responseChecker
* the notification checker to detect the end
* @param checkAttached
* if true check if this connector is attached
* @return the future to wait for the end of the execution
* @throws NullPointerException
* if the specified command or responseChecker is null
* @throws ConnectorException
* if executing the command failed
*/
private Future execute(final String command,
final NotificationChecker responseChecker,
final boolean checkAttached, boolean withoutTimeout)
throws ConnectorException {
ConnectorUtils.checkNotNull("command", command);
ConnectorUtils.checkNotNull("responseChecker", responseChecker);
if (checkAttached) {
assureAttached();
}
return _commandExecutor.submit(new Callable() {
public String call() throws Exception {
final BlockingQueue responses = new LinkedBlockingQueue();
ConnectorListener listener = new AbstractConnectorListener() {
public void messageReceived(ConnectorMessageEvent event) {
String message = event.getMessage();
if (responseChecker.isTarget(message)
|| message.startsWith("PONG")) {
responses.add(message);
}
}
};
addConnectorListener(listener, false);
fireMessageSent(command);
sendCommand(command);
try {
boolean pinged = false;
while (true) {
// to cancel getting responses, you must call
// Future#cancel(true)
String response = responses.poll(getCommandTimeout(),
TimeUnit.MILLISECONDS);
if (response == null) {
if (pinged) {
setStatus(Status.NOT_RUNNING);
throw new NotAttachedException(
Status.NOT_RUNNING);
} else {
// retry the message again
fireMessageSent(command);
sendCommand(command);
pinged = true;
continue;
}
}
return response;
}
} finally {
removeConnectorListener(listener);
}
}
});
}
/**
* Fires a message sent event.
*
* @param message
* the message that triggered the event
*/
private void fireMessageSent(final String message) {
fireMessageEvent(message, false);
}
/**
* Sends the specified command to the Skype client on the platform dependent
* communication layer.
*
* @param command
* the command to be executed
*/
protected abstract void sendCommand(String command);
/**
* Assures the attached status.
*
* @throws ConnectorException
* if this connector is not attached or trying to connect
* failed.
*/
private void assureAttached() throws ConnectorException {
Status attachedStatus = getStatus();
if (attachedStatus != Status.ATTACHED) {
attachedStatus = connect();
if (attachedStatus != Status.ATTACHED) {
throw new NotAttachedException(attachedStatus);
}
}
}
/**
* Adds the specified listener to this connector.
*
* @param listener
* the listener to be added
* @throws NullPointerException
* if the specified listener is null
* @throws ConnectorException
* if trying to connect failed
* @see #removeConnectorListener(ConnectorListener)
*/
public final void addConnectorListener(final ConnectorListener listener)
throws ConnectorException {
addConnectorListener(listener, true);
}
/**
* Adds the specified listener to this connector.
*
* @param listener
* the listener to be added
* @param checkAttached
* if true checks if this connector is attached
* @throws NullPointerException
* if the specified listener is null
* @throws ConnectorException
* if trying to connect failed
* @see #removeConnectorListener(ConnectorListener)
*/
public final void addConnectorListener(final ConnectorListener listener,
final boolean checkAttached) throws ConnectorException {
addConnectorListener(listener, checkAttached, false);
}
/**
* Adds the specified listener to this connector.
*
* @param listener
* the listener to be added
* @param checkAttached
* if true checks if this connector is attached
* @param isSynchronous
* if true the listener will be handled synchronously
* @throws NullPointerException
* if the specified listener is null
* @throws ConnectorException
* if trying to connect failed
* @see #removeConnectorListener(ConnectorListener)
*/
public final void addConnectorListener(final ConnectorListener listener,
final boolean checkAttached, final boolean isSynchronous)
throws ConnectorException {
ConnectorUtils.checkNotNull("listener", listener);
if (isSynchronous) {
_syncListeners.add(listener);
} else {
_asyncListeners.add(listener);
}
if (checkAttached) {
assureAttached();
}
/* consume startup messages */
if (!_readMissedMessages && listener.getClass().getName().contains("ChatMessageConnectorListener")) {
getMissedMessages();
_readMissedMessages = true;
}
}
/**
* Removes the specified listener from this connector.
*
* @param listener
* the listener to be removed
* @throws NullPointerException
* if the specified listener is null
* @see #addConnectorListener(ConnectorListener)
*/
public final void removeConnectorListener(final ConnectorListener listener) {
ConnectorUtils.checkNotNull("listener", listener);
_syncListeners.remove(listener);
_asyncListeners.remove(listener);
}
/**
* Fires a message received event.
*
* @param message
* the message that triggered the event
*/
protected final void fireMessageReceived(final String message) {
fireMessageEvent(message, true);
}
/**
* Fires a message event.
*
* @param message
* the message that triggered the event
* @param isReceived
* the message is a received type or not
*/
private void fireMessageEvent(final String message, final boolean isReceived) {
ConnectorUtils.checkNotNull("message", message);
_syncSender.execute(new Runnable() {
public void run() {
fireMessageEvent(toConnectorListenerArray(_syncListeners),
message, isReceived);
}
});
_asyncSender.execute(new Runnable() {
public void run() {
fireMessageEvent(toConnectorListenerArray(_asyncListeners),
message, isReceived);
}
});
}
/**
* Fires a message event.
*
* @param listenerList
* the event listener list
* @param message
* the message that triggered the event
* @param isReceived
* the message is a received type or not
*/
private void fireMessageEvent(final ConnectorListener[] listeners,
final String message, final boolean isReceived) {
ConnectorMessageEvent event = new ConnectorMessageEvent(this, message);
for (int i = listeners.length - 1; 0 <= i; i--) {
if (isReceived) {
listeners[i].messageReceived(event);
} else {
listeners[i].messageSent(event);
}
}
}
/**
* Sets the specified property. If the specified value is null, the property
* is removed.
*
* @param name
* the property name
* @param value
* the property value
* @throws NullPointerException
* if the specified name is null
* @see #getStringProperty(String)
*/
public final void setStringProperty(final String name, final String value) {
ConnectorUtils.checkNotNull("name", name);
if (value != null) {
properties.put(name, value);
} else {
properties.remove(name);
}
}
/**
* Gets the specified property value.
*
* @param name
* the property name
* @return the property value
* @throws NullPointerException
* if the specified name is null
* @see #setStringProperty(String, String)
*/
public final String getStringProperty(final String name) {
ConnectorUtils.checkNotNull("name", name);
return properties.get(name);
}
}