/** Returns whether or not the local Node is in the given List */
 private boolean containsNodeID(Collection<Contact> nodes, KUID id) {
   for (Contact node : nodes) {
     if (id.equals(node.getNodeID())) {
       return true;
     }
   }
   return false;
 }
  @SuppressWarnings({"InfiniteLoopStatement"})
  public static void main(String[] args) throws Exception {
    ArcsVisualizer arcs = new ArcsVisualizer(null, KUID.createRandomID());

    JFrame frame = new JFrame();
    frame.getContentPane().add(arcs);
    frame.setBounds(20, 30, 640, 480);
    frame.setVisible(true);

    Random generator = new Random();

    EventType type = null;
    KUID nodeId = null;

    final int sleep = 100;

    while (true) {

      // Simulate an received or sent message
      nodeId = KUID.createRandomID();
      if (generator.nextBoolean()) {
        type = EventType.MESSAGE_RECEIVED;
      } else {
        type = EventType.MESSAGE_SENT;
      }

      OpCode opcode = OpCode.valueOf(1 + generator.nextInt(OpCode.values().length - 1));
      // OpCode opcode = OpCode.FIND_NODE_REQUEST;

      arcs.painter.handle(type, nodeId, null, opcode, true);

      // Sleep a bit...
      // Thread.sleep(sleep);
      Thread.sleep(generator.nextInt(sleep));

      // Send every now an then a response
      if (generator.nextBoolean()) {
        if (type.equals(EventType.MESSAGE_SENT)) {
          arcs.painter.handle(EventType.MESSAGE_RECEIVED, nodeId, null, opcode, false);
        } else {
          arcs.painter.handle(EventType.MESSAGE_SENT, nodeId, null, opcode, false);
        }
      }
    }
  }
  /**
   * Determines whether to remove, forward or to do nothing with the value that is associated with
   * the given valueId.
   */
  private Operation getOperation(Contact node, Contact existing, KUID valueId) {
    // To avoid redundant STORE forward, a node only transfers a value
    // if it is the closest to the key or if its ID is closer than any
    // other ID (except the new closest one of course)
    // TODO: maybe relax this a little bit: what if we're not the closest
    // and the closest is stale?

    int k = KademliaSettings.REPLICATION_PARAMETER.getValue();
    RouteTable routeTable = context.getRouteTable();
    List<Contact> nodes =
        org.limewire.collection.CollectionUtils.toList(
            routeTable.select(valueId, k, SelectMode.ALL));
    Contact closest = nodes.get(0);
    Contact furthest = nodes.get(nodes.size() - 1);

    if (LOG.isDebugEnabled()) {
      LOG.debug(
          MessageFormat.format(
              "node: {0}, existing: {1}, close nodes: {2}", node, existing, nodes));
    }

    //        StringBuilder sb = new StringBuilder();
    //        sb.append("ME: "+context.getLocalNode()+"\n");
    //        sb.append("Them: "+node).append("\n");
    //        sb.append("RT nearest: " + closest).append("\n");
    //        sb.append("RT furthest: " + furthest).append("\n");
    //        sb.append(CollectionUtils.toString(nodes)).append("\n");

    // We store forward if:
    // #1 We're the nearest Node of the k-closest Nodes to
    //    the given valueId
    //
    // #2 We're the second nearest of the k-closest Nodes to
    //    the given valueId AND the other Node is the nearest.
    //    In other words it changed its instance ID 'cause it
    //    was offline for a short period of time or whatsoever.
    //    (see also pre-condition(s) from where we're calling
    //    this method)
    //
    // The first condition applies if the Node is new
    // and we're the closest Node. The second condition
    // applies if the Node has changed it's instanceId.
    // That means we're the second closest and since
    // the other Node has changed its instanceId we must
    // re-send the values
    if (context.isLocalNode(closest)
        || (node.equals(closest) && nodes.size() > 1 && context.isLocalNode(nodes.get(1)))) {

      KUID nodeId = node.getNodeID();
      KUID furthestId = furthest.getNodeID();

      // #3 The other Node must be equal to the furthest Node
      //    or better
      if (nodeId.equals(furthestId) || nodeId.isNearerTo(valueId, furthestId)) {

        //                sb.append("CONDITION B (FORWARD)").append("\n");
        //                sb.append("Local (from): " + context.getLocalNode()).append("\n");
        //                sb.append("Remote (to): " + node).append("\n");
        //                sb.append(CollectionUtils.toString(nodes)).append("\n");
        //                System.out.println(sb.toString());

        if (LOG.isTraceEnabled()) {
          LOG.trace(
              "Node " + node + " is now close enough to a value and we are responsible for xfer");
        }

        return Operation.FORWARD;
      }

      // We remove a value if:
      // #1 The value is stored at k Nodes
      //    (i.e. the total number of Nodes in the DHT
      //     is equal or greater than k. If the DHT has
      //     less than k Nodes then there's no reason to
      //     remove a value)
      //
      // #2 This Node is the furthest of the k-closest Nodes
      //
      // #3 The new Node isn't in our RouteTable yet. That means
      //    adding it will push this Node out of the club of the
      //    k-closest Nodes and makes it the (k+1)-closest Node.
      //
      // #4 The new Node is nearer to the given valueId then
      //    the furthest away Node (we).
    } else if (nodes.size() >= k
        && context.isLocalNode(furthest)
        && (existing == null || existing.isDead())) {

      KUID nodeId = node.getNodeID();
      KUID furthestId = furthest.getNodeID();

      if (nodeId.isNearerTo(valueId, furthestId)) {
        //                sb.append("CONDITION C").append("\n");
        //                sb.append("ME:").append(context.getLocalNode()).append("\n");
        //                sb.append("VALUE:").append(valueId).append("\n");
        //                sb.append("NODE:").append(node).append("\n");
        //                sb.append(CollectionUtils.toString(nodes)).append("\n");
        //                System.out.println(sb.toString());

        return Operation.DELETE;
      }
    }

    return Operation.NOTHING;
  }
 /**
  * Creates and returns a local Contact.
  *
  * @param vendor our vendor ID
  * @param version the version
  * @param firewalled whether or not we're firewalled
  */
 public static Contact createLocalContact(Vendor vendor, Version version, boolean firewalled) {
   return createLocalContact(vendor, version, KUID.createRandomID(), 0, firewalled);
 }
 /** Reads a KUID from the InputStream. */
 public KUID readKUID() throws IOException {
   return KUID.createWithInputStream(this);
 }