public abstract class AbstractConnectionMap<V extends Connection> implements ConnectionMap<V> {

  protected final Vector<ConnectionMapListener<V>> conn_listeners =
      new Vector<ConnectionMapListener<V>>();
  protected final Map<Address, V> conns = new HashMap<Address, V>();
  protected final Lock lock = new ReentrantLock();
  protected final ThreadFactory factory;
  protected final long reaperInterval;
  protected final Reaper reaper;
  protected final Log log = LogFactory.getLog(getClass());

  public AbstractConnectionMap(ThreadFactory factory) {
    this(factory, 0);
  }

  public AbstractConnectionMap(ThreadFactory factory, long reaperInterval) {
    super();
    this.factory = factory;
    this.reaperInterval = reaperInterval;
    if (reaperInterval > 0) {
      reaper = new Reaper();
    } else {
      reaper = null;
    }
  }

  public Lock getLock() {
    return lock;
  }

  public boolean hasOpenConnection(Address address) {
    lock.lock();
    try {
      V v = conns.get(address);
      return v != null && v.isOpen();
    } finally {
      lock.unlock();
    }
  }

  public boolean hasConnection(Address address) {
    lock.lock();
    try {
      return conns.containsKey(address);
    } finally {
      lock.unlock();
    }
  }

  public void addConnection(Address address, V conn) {
    lock.lock();
    try {
      V previous = conns.put(address, conn);
      Util.close(previous);
    } finally {
      lock.unlock();
    }
    notifyConnectionOpened(address, conn);
  }

  public void addConnectionMapListener(ConnectionMapListener<V> cml) {
    if (cml != null && !conn_listeners.contains(cml)) conn_listeners.addElement(cml);
  }

  public int getNumConnections() {
    lock.lock();
    try {
      return conns.size();
    } finally {
      lock.unlock();
    }
  }

  public String printConnections() {
    StringBuilder sb = new StringBuilder();

    lock.lock();
    try {
      for (Map.Entry<Address, V> entry : conns.entrySet()) {
        sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
      }
    } finally {
      lock.unlock();
    }

    return sb.toString();
  }

  public ThreadFactory getThreadFactory() {
    return factory;
  }

  public void removeConnection(Address address) {
    Connection conn = null;
    lock.lock();
    try {
      conn = conns.remove(address);
    } finally {
      lock.unlock();
    }
    Util.close(conn);
  }

  public void removeConnectionMapListener(ConnectionMapListener<V> cml) {
    if (cml != null) conn_listeners.removeElement(cml);
  }

  /**
   * Removes all connections from ConnectionTable which are not in current_mbrs
   *
   * @param current_mbrs
   */
  public void retainAll(Collection<Address> current_mbrs) {
    if (current_mbrs == null) return;

    Map<Address, V> copy = null;
    lock.lock();
    try {
      copy = new HashMap<Address, V>(conns);
      conns.keySet().retainAll(current_mbrs);
    } finally {
      lock.unlock();
    }
    copy.keySet().removeAll(current_mbrs);

    for (Iterator<Entry<Address, V>> i = copy.entrySet().iterator(); i.hasNext(); ) {
      Entry<Address, V> e = i.next();
      Util.close(e.getValue());
    }
    copy.clear();
  }

  public void start() throws Exception {
    if (reaper != null) {
      reaper.start();
    }
  }

  public void stop() {
    if (reaper != null) {
      reaper.stop();
    }
    lock.lock();
    try {
      for (Iterator<Entry<Address, V>> i = conns.entrySet().iterator(); i.hasNext(); ) {
        Entry<Address, V> e = i.next();
        Util.close(e.getValue());
      }
      clear();
    } finally {
      lock.unlock();
    }
    conn_listeners.clear();
  }

  protected void clear() {
    lock.lock();
    try {
      conns.clear();
    } finally {
      lock.unlock();
    }
  }

  protected void notifyConnectionClosed(Address address) {
    for (ConnectionMapListener<V> l : conn_listeners) {
      l.connectionClosed(address);
    }
  }

  protected void notifyConnectionOpened(Address address, V conn) {

    for (ConnectionMapListener<V> l : conn_listeners) {
      l.connectionOpened(address, conn);
    }
  }

  class Reaper implements Runnable {

    private Thread thread;

    public synchronized void start() {
      if (thread == null || !thread.isAlive()) {
        thread = factory.newThread(new Reaper(), "Reaper");
        thread.start();
      }
    }

    public synchronized void stop() {
      if (thread != null && thread.isAlive()) {
        thread.interrupt();
        try {
          thread.join(Global.THREAD_SHUTDOWN_WAIT_TIME);
        } catch (InterruptedException ignored) {
        }
      }
      thread = null;
    }

    public void run() {
      while (!Thread.currentThread().isInterrupted()) {
        lock.lock();
        try {
          for (Iterator<Entry<Address, V>> it = conns.entrySet().iterator(); it.hasNext(); ) {
            Entry<Address, V> entry = it.next();
            V c = entry.getValue();
            if (c.isExpired(System.currentTimeMillis())) {
              log.info("Connection " + c.toString() + " reaped");
              Util.close(c);
              it.remove();
            }
          }
        } finally {
          lock.unlock();
        }
        Util.sleep(reaperInterval);
      }
    }
  }

  public interface ConnectionMapListener<V> {

    public void connectionClosed(Address address);

    public void connectionOpened(Address address, V conn);
  }
}
/**
 * Demoes the NotificationBus (without caching). Start a number of members and type in messages. All
 * members will receive the messages. View changes will also be displayed (e.g. member joined,
 * left).
 *
 * @author Bela Ban
 */
public class NotificationBusDemo implements NotificationBus.Consumer {
  NotificationBus bus = null;
  BufferedReader in = null;
  String line;
  final long timeout = 0;
  final Vector cache = null;
  Log log = LogFactory.getLog(getClass());

  public void start(String bus_name, String props) {
    try {

      bus = new NotificationBus(bus_name, props);
      bus.setConsumer(this);
      bus.start();
      // System.out.println("Getting the cache from coordinator:");
      // cache=(Vector)bus.getCacheFromCoordinator(3000, 3);
      // if(cache == null) cache=new Vector();
      // System.out.println("cache is " + cache);

      in = new BufferedReader(new InputStreamReader(System.in));
      while (true) {
        try {
          System.out.print("> ");
          System.out.flush();
          line = in.readLine();
          if (line.startsWith("quit") || line.startsWith("exit")) {
            bus.stop();
            bus = null;
            break;
          }
          bus.sendNotification(line);
        } catch (Exception e) {
          log.error(e.toString());
        }
      }
    } catch (Exception ex) {
      log.error(ex.toString());
    } finally {
      if (bus != null) bus.stop();
    }
  }

  public void handleNotification(Serializable n) {
    System.out.println("** Received notification: " + n);
    // if(cache != null)
    //  cache.addElement(n);
    // System.out.println("cache is " + cache);
  }

  public Serializable getCache() {
    // return cache;
    return null;
  }

  public void memberJoined(Address mbr) {
    System.out.println("** Member joined: " + mbr);
  }

  public void memberLeft(Address mbr) {
    System.out.println("** Member left: " + mbr);
  }

  public static void main(String[] args) {
    String name = "BusDemo";
    String props = "udp.xml";

    for (int i = 0; i < args.length; i++) {
      if ("-bus_name".equals(args[i])) {
        name = args[++i];
        continue;
      }
      if ("-props".equals(args[i])) {
        props = args[++i];
        continue;
      }
      System.out.println(
          "NotificationBusDemo [-help] [-bus_name <name>] " + "[-props <properties>]");
      return;
    }
    System.out.println("Starting NotificationBus with name " + name);
    new NotificationBusDemo().start(name, props);
  }
}