/**
   * Create a copy of TokenMetadata with tokenToEndpointMap reflecting situation after all current
   * leave operations have finished.
   *
   * @return new token metadata
   */
  public TokenMetadata cloneAfterAllLeft() {
    lock.readLock().lock();
    try {
      TokenMetadata allLeftMetadata = cloneOnlyTokenMap();

      for (InetAddress endpoint : leavingEndpoints) allLeftMetadata.removeEndpoint(endpoint);

      return allLeftMetadata;
    } finally {
      lock.readLock().unlock();
    }
  }
  /**
   * Create a copy of TokenMetadata with tokenToEndpointMap reflecting situation after all current
   * leave, move, and relocate operations have finished.
   *
   * @return new token metadata
   */
  public TokenMetadata cloneAfterAllSettled() {
    lock.readLock().lock();

    try {
      TokenMetadata metadata = cloneOnlyTokenMap();

      for (InetAddress endpoint : leavingEndpoints) metadata.removeEndpoint(endpoint);

      for (Pair<Token, InetAddress> pair : movingEndpoints)
        metadata.updateNormalToken(pair.left, pair.right);

      for (Map.Entry<Token, InetAddress> relocating : relocatingTokens.entrySet())
        metadata.updateNormalToken(relocating.getKey(), relocating.getValue());

      return metadata;
    } finally {
      lock.readLock().unlock();
    }
  }