package me.jesensky.dan.playertracker;

import me.jesensky.dan.playertracker.events.ConnectionEvent;
import me.jesensky.dan.playertracker.exceptions.InvalidArgumentException;
import me.jesensky.dan.playertracker.listeners.ConnectionListener;
import me.jesensky.dan.playertracker.net.Connection;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class ConnectionManager extends Thread {
    private HashMap<InetAddress, Connection> connections;
    private ServerSocket socket;
    private boolean accepting;
    private List<ConnectionListener> listeners;

    public ConnectionManager() {
        super();
        this.connections = new HashMap<>();
        this.listeners = new ArrayList<ConnectionListener>();
    }

    public HashMap<InetAddress, Connection> getConnections() {
        return this.connections;
    }

    public Connection getConnection(InetSocketAddress addr) {
        return this.connections.get(addr.getAddress());
    }

    public Connection getConnection(InetAddress addr) {
        return this.connections.get(addr);
    }

    /**
     * Registers an observer to be invoked upon the server's
     * {@code ConnectionManager} receiving a new connection.
     * This will not fire if for some internal reason
     * the connection refuses abruptly.<br />
     * <br />
     * Returns an id -- that of the listener which is being registered.
     * This id can be used to remove the listener from operation, if
     * so desired.
     *
     * @param listener The {@code ConnectionListener} implementation to
     *                 invoke upon event firing.
     * @return The ID of the listener.
     * @see #unregisterConnectionListener(int)
     */
    public int registerConnectionListener(ConnectionListener listener) {
        if (!this.listeners.contains(listener)) {
            this.listeners.add(listener);
        }
        return this.listeners.indexOf(listener);
    }

    /**
     * Unregisters an observer from the queue of invocation when the
     * server receives a connection. Removing a listener will not affect
     * the ids of other listeners in any way. Using an index of {@code -1}
     * or that which exceeds the upper boundary of the listener listing
     * will cause nothing to happen.
     *
     * @param id The id to remove.
     */
    public void unregisterConnectionListener(int id) {
        if (id < 0 || id >= this.listeners.size())
            return;
        this.listeners.set(id, null);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void run() {
        while (this.accepting) {
            try {
                Socket sock = this.socket.accept();
                Connection conn = new Connection(sock);
                this.connections.put(sock.getInetAddress(), conn);
                this.updateConnectionListeners(new ConnectionEvent(conn));
            } catch (IOException | InvalidArgumentException e) {
                //TODO log exception
            }
        }
        super.run();
    }

    private void updateConnectionListeners(ConnectionEvent evt) {
        for (ConnectionListener listener : this.listeners) {
            if (listener != null)
                listener.onConnectEvent(evt);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return "ConnectionManager@" + super.hashCode() + "[connections=" + this.connections.size() + "]";
    }
}
@@ -740,166 +740,1212 @@
     
-public ConnectionManager() %7B%0A        super();%0A        this.connections = new HashMap%3C%3E();%0A        this.listeners = new ArrayList%3CConnectionListener%3E();%0A    %7D%0A
+/**%0A     * Instantiates a new %7B@code ConnectionManager%7D.%3Cbr /%3E%0A     * %3Cbr /%3E%0A     * %7B@code ConnectionManager%7D is a subclass of %7B@link java.lang.Thread%7D,%0A     * and should be treated with care upon accessing any members.%3Cbr /%3E%0A     * %3Cbr /%3E%0A     * Given the purpose of the class, queries should be wary of%0A     * thread-safety with whatever actions they may perform.%0A     */%0A    public ConnectionManager() %7B%0A        super();%0A        this.connections = new HashMap%3C%3E();%0A        this.listeners = new ArrayList%3CConnectionListener%3E();%0A    %7D%0A%0A    /**%0A     * Gets all of the currently live connections to the %7B@code Server%7D%0A     * singleton.%3Cbr /%3E%0A     * %3Cbr /%3E%0A     * Currently, the result is stored in a %7B@code HashMap%7D, ordered by%0A     * %7B@code InetAddress%7Des, which could lead to issues if more than one%0A     * person connects via the same ethernet uplink.%3Cbr /%3E%0A     * %3Cbr /%3E%0A     * As such, this implementation is volatile and subject to change at%0A     * any time, and is likely to do so in the future (likely to%0A     * %7B@code List%7D).%0A     *%0A     * @return A %7B@code HashMap%7D of all %7B@code Connection%7Ds to the%0A     * %7B@code Server%7D singleton. Subject to change and should not%0A     * be relied upon.%0A     */
 %0A   
@@ -4213,24 +4213,504 @@
 n();%0A    %7D%0A%0A
+    /**%0A     * Calls %7B@link me.jesensky.dan.playertracker.listeners.ConnectionListener#onConnectEvent(me.jesensky.dan.playertracker.events.ConnectionEvent)%7D%0A     * for all %7B@code ConnectionListener%7Ds registered to%0A     * this %7B@code ConnectionManager%7D. This method requires a%0A     * %7B@code ConnectionEvent%7D instance to pass to each%0A     * listener thereof.%0A     *%0A     * @param evt The %7B@code ConnectionEvent%7D to pass to each%0A     *            %7B@code ConnectionListener%7D.%0A     */%0A
     private 

Define ConnectionManager documentation