void isWritable(SelectionKey k) {
    EventableChannel ec = (EventableChannel) k.attachment();
    long b = ec.getBinding();

    if (ec.isWatchOnly()) {
      if (ec.isNotifyWritable()) eventCallback(b, EM_CONNECTION_NOTIFY_WRITABLE, null);
    } else {
      try {
        if (!ec.writeOutboundData()) UnboundConnections.add(b);
      } catch (IOException e) {
        UnboundConnections.add(b);
      }
    }
  }
  void close() {
    try {
      if (mySelector != null) mySelector.close();
    } catch (IOException e) {
    }
    mySelector = null;

    // run down open connections and sockets.
    Iterator<ServerSocketChannel> i = Acceptors.values().iterator();
    while (i.hasNext()) {
      try {
        i.next().close();
      } catch (IOException e) {
      }
    }

    // 29Sep09: We create an ArrayList of the existing connections, then iterate over
    // that to call unbind on them. This is because an unbind can trigger a reconnect,
    // which will add to the Connections HashMap, causing a ConcurrentModificationException.
    // XXX: The correct behavior here would be to latch the various reactor methods to return
    // immediately if the reactor is shutting down.
    ArrayList<EventableChannel> conns = new ArrayList<EventableChannel>();
    Iterator<EventableChannel> i2 = Connections.values().iterator();
    while (i2.hasNext()) {
      EventableChannel ec = i2.next();
      if (ec != null) {
        conns.add(ec);
      }
    }
    Connections.clear();

    ListIterator<EventableChannel> i3 = conns.listIterator(0);
    while (i3.hasNext()) {
      EventableChannel ec = i3.next();
      eventCallback(ec.getBinding(), EM_CONNECTION_UNBOUND, null);
      ec.close();

      EventableSocketChannel sc = (EventableSocketChannel) ec;
      if (sc != null && sc.isAttached()) DetachedConnections.add(sc);
    }

    ListIterator<EventableSocketChannel> i4 = DetachedConnections.listIterator(0);
    while (i4.hasNext()) {
      EventableSocketChannel ec = i4.next();
      ec.cleanup();
    }
    DetachedConnections.clear();
  }
  void removeUnboundConnections() {
    ListIterator<Long> iter = UnboundConnections.listIterator(0);
    while (iter.hasNext()) {
      long b = iter.next();

      EventableChannel ec = Connections.remove(b);
      if (ec != null) {
        eventCallback(b, EM_CONNECTION_UNBOUND, null);
        ec.close();

        EventableSocketChannel sc = (EventableSocketChannel) ec;
        if (sc != null && sc.isAttached()) DetachedConnections.add(sc);
      }
    }
    UnboundConnections.clear();
  }
  void isReadable(SelectionKey k) {
    EventableChannel ec = (EventableChannel) k.attachment();
    long b = ec.getBinding();

    if (ec.isWatchOnly()) {
      if (ec.isNotifyReadable()) eventCallback(b, EM_CONNECTION_NOTIFY_READABLE, null);
    } else {
      myReadBuffer.clear();

      try {
        ec.readInboundData(myReadBuffer);
        myReadBuffer.flip();
        if (myReadBuffer.limit() > 0) {
          if (ProxyConnections != null) {
            EventableChannel target = ProxyConnections.get(b);
            if (target != null) {
              ByteBuffer myWriteBuffer = ByteBuffer.allocate(myReadBuffer.limit());
              myWriteBuffer.put(myReadBuffer);
              myWriteBuffer.flip();
              target.scheduleOutboundData(myWriteBuffer);
            } else {
              eventCallback(b, EM_CONNECTION_READ, myReadBuffer);
            }
          } else {
            eventCallback(b, EM_CONNECTION_READ, myReadBuffer);
          }
        }
      } catch (IOException e) {
        UnboundConnections.add(b);
      }
    }
  }
  void isReadable(SelectionKey k) {
    EventableChannel ec = (EventableChannel) k.attachment();
    long b = ec.getBinding();

    if (ec.isWatchOnly()) {
      if (ec.isNotifyReadable()) eventCallback(b, EM_CONNECTION_NOTIFY_READABLE, null);
    } else {
      myReadBuffer.clear();

      try {
        ec.readInboundData(myReadBuffer);
        myReadBuffer.flip();
        if (myReadBuffer.limit() > 0) eventCallback(b, EM_CONNECTION_READ, myReadBuffer);
      } catch (IOException e) {
        UnboundConnections.add(b);
      }
    }
  }
  void removeUnboundConnections() {
    if (UnboundConnections.size() == 0) {
      return;
    }
    ArrayList<Long> currentUnboundConnections = UnboundConnections;
    // fix concurrent modification exception
    UnboundConnections = new ArrayList<Long>();
    for (long b : currentUnboundConnections) {
      EventableChannel ec = Connections.remove(b);
      if (ec != null) {
        if (ProxyConnections != null) {
          ProxyConnections.remove(b);
        }
        eventCallback(b, EM_CONNECTION_UNBOUND, null);
        ec.close();

        EventableSocketChannel sc = (EventableSocketChannel) ec;
        if (sc != null && sc.isAttached()) DetachedConnections.add(sc);
      }
    }
  }
  void addNewConnections() {
    ListIterator<EventableSocketChannel> iter = DetachedConnections.listIterator(0);
    while (iter.hasNext()) {
      EventableSocketChannel ec = iter.next();
      ec.cleanup();
    }
    DetachedConnections.clear();

    ListIterator<Long> iter2 = NewConnections.listIterator(0);
    while (iter2.hasNext()) {
      long b = iter2.next();

      EventableChannel ec = Connections.get(b);
      if (ec != null) {
        try {
          ec.register();
        } catch (ClosedChannelException e) {
          UnboundConnections.add(ec.getBinding());
        }
      }
    }
    NewConnections.clear();
  }
 public void closeConnection(long sig, boolean afterWriting) {
   EventableChannel ec = Connections.get(sig);
   if (ec != null) if (ec.scheduleClose(afterWriting)) UnboundConnections.add(sig);
 }
 public void sendData(long sig, ByteBuffer bb) throws IOException {
   EventableChannel channel = Connections.get(sig);
   if (channel != null) {
     channel.scheduleOutboundData(bb);
   }
 }