public String toString() {
    StringBuilder sb = new StringBuilder();
    lock.readLock().lock();
    try {
      Set<InetAddress> eps = tokenToEndpointMap.inverse().keySet();

      if (!eps.isEmpty()) {
        sb.append("Normal Tokens:");
        sb.append(System.getProperty("line.separator"));
        for (InetAddress ep : eps) {
          sb.append(ep);
          sb.append(":");
          sb.append(tokenToEndpointMap.inverse().get(ep));
          sb.append(System.getProperty("line.separator"));
        }
      }

      if (!bootstrapTokens.isEmpty()) {
        sb.append("Bootstrapping Tokens:");
        sb.append(System.getProperty("line.separator"));
        for (Map.Entry<Token, InetAddress> entry : bootstrapTokens.entrySet()) {
          sb.append(entry.getValue()).append(":").append(entry.getKey());
          sb.append(System.getProperty("line.separator"));
        }
      }

      if (!leavingEndpoints.isEmpty()) {
        sb.append("Leaving Endpoints:");
        sb.append(System.getProperty("line.separator"));
        for (InetAddress ep : leavingEndpoints) {
          sb.append(ep);
          sb.append(System.getProperty("line.separator"));
        }
      }

      if (!pendingRanges.isEmpty()) {
        sb.append("Pending Ranges:");
        sb.append(System.getProperty("line.separator"));
        sb.append(printPendingRanges());
      }
    } finally {
      lock.readLock().unlock();
    }

    return sb.toString();
  }
  public boolean isMember(InetAddress endpoint) {
    assert endpoint != null;

    lock.readLock().lock();
    try {
      return tokenToEndpointMap.inverse().containsKey(endpoint);
    } finally {
      lock.readLock().unlock();
    }
  }
  public Collection<Token> getTokens(InetAddress endpoint) {
    assert endpoint != null;
    assert isMember(endpoint); // don't want to return nulls

    lock.readLock().lock();
    try {
      return new ArrayList<Token>(tokenToEndpointMap.inverse().get(endpoint));
    } finally {
      lock.readLock().unlock();
    }
  }