protected synchronized void punchHole() {
    if (!this.doHolePunching) return;

    if (this.lastSendDest == null) return;

    long curTime = System.currentTimeMillis();
    long interval = this.config.getPunchingInterval();

    if (curTime < this.lastSendTime + interval) return;

    // ask self exterior address to an other node
    Message reqMsg =
        UDPMessagingMessageFactory.getPunchHoleReqMessage(
            IDAddressPair.getIDAddressPair(null, this.selfAddr));

    synchronized (this.punchingLock) {
      this.punchReplyReceived = false;

      try {
        this.sender.send(this.lastSendDest, reqMsg);
      } catch (IOException e) {
        logger.log(
            Level.WARNING,
            "Could not send an PUNCH_HOLE_REQ message for UDP hole punching: "
                + this.lastSendDest
                + " on "
                + this.selfAddr,
            e);
        return;
      }

      // wait for a reply
      if (!this.punchReplyReceived) {
        try {
          this.punchingLock.wait(this.config.getPunchingRepTimeout());
        } catch (InterruptedException e) {
          // do nothing
        }
      }

      this.endFirstPunching(this.punchReplyReceived);

      if (!this.punchReplyReceived) {
        logger.log(
            Level.WARNING,
            "Could not receive an PUNCH_HOLE_REP message for UDP hole punching: "
                + this.lastSendDest
                + " on "
                + this.selfAddr);
      }
    } // synchronized (this.punchingLock)
  }
    public void run() {
      Thread.currentThread().setName("UDPMessageHandler: " + this.srcAddr);

      // handling an incoming message
      int tag = msg.getTag();
      Message ret = null;

      if (tag == Tag.PUNCH_HOLE_REQ.getNumber()) { // for UDP hole punching
        MessagingAddress src = provider.getMessagingAddress(this.srcAddr);

        ret =
            UDPMessagingMessageFactory.getPunchHoleRepMessage(
                IDAddressPair.getIDAddressPair(null, selfAddr), (InetMessagingAddress) src);
      } else if (tag == Tag.PUNCH_HOLE_REP.getNumber()) { // for UDP hole punching
        // notify
        synchronized (punchingLock) {
          punchReplyReceived = true;
          punchingLock.notifyAll();
        }

        if (doHolePunching) {
          Serializable[] contents = msg.getContents();
          InetMessagingAddress selfExteriorAddress = (InetMessagingAddress) contents[0];

          logger.log(
              Level.INFO, "UDP hole punching: self exterior address is " + selfExteriorAddress);

          if (selfExteriorAddress.equals(selfAddr)) {
            // UDP hole punching is not required
            logger.log(Level.INFO, "UDP hole punching was *not* required.");
          } else {
            // set self address
            UDPMessageReceiver.this.selfAddr = selfExteriorAddress;

            synchronized (UDPMessageReceiver.this) {
              if (holePunchingDaemon == null) {
                logger.log(Level.INFO, "UDP hole punching is required.");

                // start punching daemon
                Runnable r = new UDPHolePunchingDaemon();
                holePunchingDaemon = new Thread(r);
                holePunchingDaemon.setName("UDPHolePunchingDaemon");
                holePunchingDaemon.setDaemon(true);
                holePunchingDaemon.start();
              }
            }
          }
        }
      } else {
        // process the received message
        ret = processMessage(msg);
      }

      // return a Message (from the last handler)
      if (ret != null) {
        logger.log(Level.INFO, "Return a message.");

        MessagingAddress src = (msg.getSource() != null ? msg.getSource().getAddress() : null);
        try {
          ByteBuffer buf = sender.send(sock, this.srcAddr, src, ret, true);

          // notify statistics collector
          if (src != null) {
            msgReporter.notifyStatCollectorOfMessageSent(src, ret, buf.remaining());
          }
        } catch (IOException e) {
          logger.log(Level.WARNING, "Could not return a message.");

          // notify statistics collector
          if (src != null) {
            msgReporter.notifyStatCollectorOfDeletedNode(ret.getSource(), src, ret.getTag());
          }
        }
      } else {
        logger.log(Level.INFO, "Return no message.");
      }

      if (tag != Tag.PUNCH_HOLE_REQ.getNumber()
          && tag != Tag.PUNCH_HOLE_REP.getNumber()) { // not for UDP hole punching
        // post-process
        postProcessMessage(msg);
      }

      handlerThreads.remove(Thread.currentThread());

      Thread.currentThread().setName(MessagingFactory.DEFAULT_POOLED_THREAD_NAME);
    }