package net.sourceforge.transfile.operations; import java.util.ArrayList; import java.util.Collection; import net.sourceforge.transfile.operations.messages.DisconnectMessage; import net.sourceforge.transfile.operations.messages.Message; /** * TODO doc * * @author codistmonk (creation 2010-06-05) * */ public abstract class AbstractConnection implements Connection { private final Collection<Listener> listeners; private String localPeer; private String remotePeer; private Exception connectionError; private State state; private long lastMessageTime; private long receivedMessageCount; private long sentMessageCount; private final Object synchronizer; public AbstractConnection() { this(DEFAULT_LOCAL_PEER, DEFAULT_REMOTE_PEER); } /** * * @param localPeer * <br>Should not be null * <br>Shared parameter * @param remotePeer * <br>Should not be null * <br>Shared parameter */ public AbstractConnection(final String localPeer, final String remotePeer) { this.listeners = new ArrayList<Listener>(); this.localPeer = localPeer; this.remotePeer = remotePeer; this.state = State.DISCONNECTED; this.synchronizer = new Object(); } /** * * @return * <br>A non-null value * <br>A new value */ public final synchronized Listener[] getListeners() { return this.listeners.toArray(new Listener[this.listeners.size()]); } @Override public final synchronized void addConnectionListener(final Listener listener) { this.listeners.add(listener); } @Override public final synchronized void removeConnectionListener(final Listener listener) { this.listeners.remove(listener); } @Override public final String getLocalPeer() { return this.localPeer; } /** * * @param localPeer * <br>Should not be null * <br>Shared parameter */ @Override public final void setLocalPeer(final String localPeer) { if (this.getState() != State.DISCONNECTED) { throw new IllegalStateException(this.getState().toString()); } if (!this.getLocalPeer().equals(localPeer)) { this.localPeer = localPeer; this.new LocalPeerChangedEvent().fire(); } } @Override public final String getRemotePeer() { return this.remotePeer; } /** * * @param remotePeer * <br>Should not be null * <br>Shared parameter */ @Override public final void setRemotePeer(final String remotePeer) { if (this.getState() != State.DISCONNECTED) { throw new IllegalStateException(this.getState().toString()); } if (!this.getRemotePeer().equals(remotePeer)) { this.remotePeer = remotePeer; this.new RemotePeerChangedEvent().fire(); } } @Override public final Exception getConnectionError() { return this.connectionError; } /** * * @return * <br>Range: {@code [0L .. Long.MAX_VALUE]} */ public final long getLastMessageTime() { synchronized (this.synchronizer) { return this.lastMessageTime; } } @Override public final synchronized State getState() { return this.state; } /** * * @param state * <br>Should not be null * <br>Shared parameter */ public final void setState(State state) { if (this.getState() != state) { synchronized (this) { this.state = state; } this.new StateChangedEvent().fire(); } } @Override public final void sendMessage(final Message message) { if (this.getState() != State.CONNECTED) { return; } synchronized (this.synchronizer) { this.setLastMessageTime(); ++this.sentMessageCount; } this.doSendMessage(message); } /** * * @return * <br>Range: {@code [0L .. Long.MAX_VALUE]} */ public final long getReceivedMessageCount() { synchronized (this.synchronizer) { return this.receivedMessageCount; } } /** * * @return * <br>Range: {@code [0L .. Long.MAX_VALUE]} */ public final long getSentMessageCount() { synchronized (this.synchronizer) { return this.sentMessageCount; } } /** * TODO doc * * @param message * <br>Should not be null */ protected abstract void doSendMessage(final Message message); /** * TODO doc * * @param connectionError * <br>Can be null * <br>Shared parameter */ protected final void setConnectionError(final Exception connectionError) { this.connectionError = connectionError; } /** * TODO doc * * @param message * <br>Should not be null * <br>Maybe shared parameter */ protected final void dispatchMessage(final Message message) { synchronized (this.synchronizer) { this.setLastMessageTime(); ++this.receivedMessageCount; } if (message instanceof DisconnectMessage) { this.setState(State.DISCONNECTED); } this.new MessageReceivedEvent(message).fire(); } protected final void setLastMessageTime() { this.lastMessageTime = System.currentTimeMillis(); } /** * * TODO doc * * @author codistmonk (creation 2010-06-19) * */ protected abstract class AbstractEvent { public final void fire() { for (final Listener listener : AbstractConnection.this.getListeners()) { this.notifyListener(listener); } } /** * TODO doc * * @param listener * <br>Not null * <br>Shared * <br>Input-output */ protected abstract void notifyListener(Listener listener); } /** * * TODO doc * * @author codistmonk (creation 2010-06-19) * */ private class LocalPeerChangedEvent extends AbstractEvent { /** * Package-private default constructor to suppress visibility warnings. */ LocalPeerChangedEvent() { // Do nothing } @Override protected final void notifyListener(final Listener listener) { listener.localPeerChanged(); } } /** * * TODO doc * * @author codistmonk (creation 2010-06-19) * */ private class RemotePeerChangedEvent extends AbstractEvent { /** * Package-private default constructor to suppress visibility warnings. */ RemotePeerChangedEvent() { // Do nothing } @Override protected final void notifyListener(final Listener listener) { listener.remotePeerChanged(); } } /** * * TODO doc * * @author codistmonk (creation 2010-06-19) * */ private class StateChangedEvent extends AbstractEvent { /** * Package-private default constructor to suppress visibility warnings. */ StateChangedEvent() { // Do nothing } @Override protected final void notifyListener(final Listener listener) { listener.stateChanged(); } } /** * * TODO doc * * @author codistmonk (creation 2010-06-19) * */ private class MessageReceivedEvent extends AbstractEvent { private final Message message; /** * * @param message * <br>Not null * <br>Shared */ MessageReceivedEvent(final Message message) { this.message = message; } @Override protected final void notifyListener(final Listener listener) { listener.messageReceived(this.message); } } public static final String DEFAULT_LOCAL_PEER = getPeer("transfile", "0.0.0.0", "12345"); public static final String DEFAULT_REMOTE_PEER = getPeer("transfile", "0.0.0.0", "54321"); /** * Converts a string {@code "protocol://host:port"} into an array { {@code "protocol"}, {@code "host"}, {@code "port"} }, * and a string {@code "host:port"} into an array { {@code ""}, {@code "host"}, {@code "port"} }. * * @param peer * <br>Should not be null * @return * <br>A new value * <br>A non-null value */ public static final String[] getProtocolHostPort(final String peer) { final String[] result = ((peer.contains("://") ? "" : ":") + peer).split(":"); if (result[1].startsWith("//")) { result[1] = result[1].substring("//".length()); } return result; } /** * TODO doc * * @param peer * <br>Should not be null * @return * <br>Range: any integer */ public static final int getPort(final String peer) { return Integer.parseInt(getProtocolHostPort(peer)[2]); } /** * Converts an array { {@code "protocol"}, {@code "host"}, {@code "port"} } into a string "protocol://host:port", * and an array { {@code "host"}, {@code "port"} } into a string {@code "host:port"}. * * @param protocolHostPort * <br>Should not be null * @return * <br>A new value * <br>A non-null value */ public static final String getPeer(final String... protocolHostPort) { final StringBuilder result = new StringBuilder(protocolHostPort[0]); result.append(protocolHostPort.length == 3 ? "://" : ":"); result.append(protocolHostPort[1]); if (protocolHostPort.length == 3) { result.append(":"); result.append(protocolHostPort[2]); } return result.toString(); } }