/**
  * Returns a link for the remote address if already cached; otherwise, returns null.
  *
  * @param remoteAddr the remote address
  * @return a link if already cached; otherwise, null
  */
 public <T> Link<T> get(final SocketAddress remoteAddr) {
   final LinkReference linkRef = this.addrToLinkRefMap.get(remoteAddr);
   return linkRef != null ? (Link<T>) linkRef.getLink() : null;
 }
  /**
   * Returns a link for the remote address if cached; otherwise opens, caches and returns. When it
   * opens a link for the remote address, only one attempt for the address is made at a given time
   *
   * @param remoteAddr the remote socket address
   * @param encoder the encoder
   * @param listener the link listener
   * @return a link associated with the address
   */
  @Override
  public <T> Link<T> open(
      final SocketAddress remoteAddr,
      final Encoder<? super T> encoder,
      final LinkListener<? super T> listener)
      throws IOException {

    Link<T> link = null;

    for (int i = 0; i <= this.numberOfTries; ++i) {
      LinkReference linkRef = this.addrToLinkRefMap.get(remoteAddr);

      if (linkRef != null) {
        link = (Link<T>) linkRef.getLink();
        if (LOG.isLoggable(Level.FINE)) {
          LOG.log(Level.FINE, "Link {0} for {1} found", new Object[] {link, remoteAddr});
        }
        if (link != null) {
          return link;
        }
      }

      if (i == this.numberOfTries) {
        // Connection failure
        throw new ConnectException("Connection to " + remoteAddr + " refused");
      }

      LOG.log(
          Level.FINE,
          "No cached link for {0} thread {1}",
          new Object[] {remoteAddr, Thread.currentThread()});

      // no linkRef
      final LinkReference newLinkRef = new LinkReference();
      final LinkReference prior = this.addrToLinkRefMap.putIfAbsent(remoteAddr, newLinkRef);
      final AtomicInteger flag =
          prior != null ? prior.getConnectInProgress() : newLinkRef.getConnectInProgress();

      synchronized (flag) {
        if (!flag.compareAndSet(0, 1)) {
          while (flag.get() == 1) {
            try {
              flag.wait();
            } catch (final InterruptedException ex) {
              LOG.log(Level.WARNING, "Wait interrupted", ex);
            }
          }
        }
      }

      linkRef = this.addrToLinkRefMap.get(remoteAddr);
      link = (Link<T>) linkRef.getLink();

      if (link != null) {
        return link;
      }

      ChannelFuture connectFuture = null;
      try {
        connectFuture = this.clientBootstrap.connect(remoteAddr);
        connectFuture.syncUninterruptibly();

        link = new NettyLink<>(connectFuture.channel(), encoder, listener);
        linkRef.setLink(link);

        synchronized (flag) {
          flag.compareAndSet(1, 2);
          flag.notifyAll();
        }
        break;
      } catch (final Exception e) {
        if (e.getClass().getSimpleName().compareTo("ConnectException") == 0) {
          LOG.log(
              Level.WARNING,
              "Connection refused. Retry {0} of {1}",
              new Object[] {i + 1, this.numberOfTries});
          synchronized (flag) {
            flag.compareAndSet(1, 0);
            flag.notifyAll();
          }

          if (i < this.numberOfTries) {
            try {
              Thread.sleep(retryTimeout);
            } catch (final InterruptedException interrupt) {
              LOG.log(
                  Level.WARNING, "Thread {0} interrupted while sleeping", Thread.currentThread());
            }
          }
        } else {
          throw e;
        }
      }
    }

    return link;
  }