protected void removeRequest(PeerNATTraversal request, int outcome) {
    synchronized (initiators) {
      LinkedList requests = (LinkedList) initiators.get(request.getInitiator());

      if (requests != null) {

        requests.remove(request);
      }

      pending_requests.remove(request);

      if (active_requests.remove(request)) {

        usage_average.addValue(request.getTimeUsed());

        if (outcome == OUTCOME_SUCCESS) {

          success_count++;

        } else {

          InetSocketAddress target = request.getTarget();

          negative_result_bloom.add(target.toString().getBytes());

          if (outcome == OUTCOME_FAILED_NO_REND) {

            failed_no_rendezvous++;
          }
        }
      }
    }
  }
public class PeerNATTraverser implements NATTraversalHandler {
  private static final LogIDs LOGID = LogIDs.PEER;

  private static final int OUTCOME_SUCCESS = 0;
  private static final int OUTCOME_FAILED_NO_REND = 1;
  private static final int OUTCOME_FAILED_OTHER = 2;

  private static PeerNATTraverser singleton;

  public static void initialise(AzureusCore core) {
    singleton = new PeerNATTraverser(core);
  }

  public static PeerNATTraverser getSingleton() {
    return (singleton);
  }

  private static int MAX_ACTIVE_REQUESTS;

  static {
    COConfigurationManager.addAndFireParameterListener(
        "peer.nat.traversal.request.conc.max",
        new ParameterListener() {
          public void parameterChanged(String name) {
            MAX_ACTIVE_REQUESTS = COConfigurationManager.getIntParameter(name);
          }
        });
  }

  private static final int TIMER_PERIOD = 10 * 1000;
  private static final int USAGE_PERIOD = TIMER_PERIOD;
  private static final int USAGE_DURATION_SECS = 60;
  private static final int MAX_USAGE_PER_MIN = MAX_ACTIVE_REQUESTS * 5 * 1000;

  private static final int STATS_TICK_COUNT = 120 * 1000 / TIMER_PERIOD;

  private NATTraverser nat_traverser;

  private Map initiators = new HashMap();
  private LinkedList pending_requests = new LinkedList();
  private List active_requests = new ArrayList();

  private Average usage_average = Average.getInstance(USAGE_PERIOD, USAGE_DURATION_SECS);

  private int attempted_count = 0;
  private int success_count = 0;
  private int failed_no_rendezvous = 0;
  private int failed_negative_bloom = 0;

  private BloomFilter negative_result_bloom = BloomFilterFactory.createAddOnly(BLOOM_SIZE);

  private static final int BLOOM_SIZE = MAX_ACTIVE_REQUESTS * 1024;
  private static final int BLOOM_REBUILD_PERIOD = 5 * 60 * 1000;
  private static final int BLOOM_REBUILD_TICKS = BLOOM_REBUILD_PERIOD / TIMER_PERIOD;

  private PeerNATTraverser(AzureusCore core) {
    nat_traverser = core.getNATTraverser();

    nat_traverser.registerHandler(this);

    SimpleTimer.addPeriodicEvent(
        "PeerNAT:stats",
        TIMER_PERIOD,
        new TimerEventPerformer() {
          private int ticks;

          public void perform(TimerEvent event) {
            ticks++;

            List to_run = null;

            synchronized (initiators) {
              if (ticks % BLOOM_REBUILD_TICKS == 0) {

                int size = negative_result_bloom.getEntryCount();

                if (Logger.isEnabled()) {

                  if (size > 0) {

                    Logger.log(
                        new LogEvent(LOGID, "PeerNATTraverser: negative bloom size = " + size));
                  }
                }

                negative_result_bloom = BloomFilterFactory.createAddOnly(BLOOM_SIZE);
              }

              if (ticks % STATS_TICK_COUNT == 0) {

                String msg =
                    "NAT traversal stats: active="
                        + active_requests.size()
                        + ",pending="
                        + pending_requests.size()
                        + ",attempted="
                        + attempted_count
                        + ",no rendezvous="
                        + failed_no_rendezvous
                        + ",negative bloom="
                        + failed_negative_bloom
                        + ",successful="
                        + success_count;

                // System.out.println( msg );

                if (Logger.isEnabled()) {
                  Logger.log(new LogEvent(LOGID, msg));
                }
              }

              int used = 0;

              for (int i = 0; i < active_requests.size(); i++) {

                used += ((PeerNATTraversal) active_requests.get(i)).getTimeUsed();
              }

              usage_average.addValue(used);

              int usage = (int) usage_average.getAverage();

              if (usage > MAX_USAGE_PER_MIN) {

                return;
              }

              // System.out.println( "usage = " + usage );

              while (true) {

                if (pending_requests.size() == 0 || active_requests.size() >= MAX_ACTIVE_REQUESTS) {

                  break;
                }

                // TODO: prioritisation based on initiator connections etc?

                PeerNATTraversal traversal = (PeerNATTraversal) pending_requests.removeFirst();

                active_requests.add(traversal);

                if (to_run == null) {

                  to_run = new ArrayList();
                }

                to_run.add(traversal);

                attempted_count++;
              }
            }

            if (to_run != null) {

              for (int i = 0; i < to_run.size(); i++) {

                PeerNATTraversal traversal = (PeerNATTraversal) to_run.get(i);

                boolean bad = false;

                synchronized (initiators) {
                  if (negative_result_bloom.contains(traversal.getTarget().toString().getBytes())) {

                    bad = true;

                    failed_negative_bloom++;
                  }
                }

                if (bad) {

                  removeRequest(traversal, OUTCOME_FAILED_OTHER);

                  traversal.getAdapter().failed();
                } else {

                  traversal.run();
                }
              }
            }
          }
        });
  }

  public int getType() {
    return (NATTraverser.TRAVERSE_REASON_PEER_DATA);
  }

  public String getName() {
    return ("Peer Traversal");
  }

  public void register(PeerNATInitiator initiator) {
    synchronized (initiators) {
      if (initiators.put(initiator, new LinkedList()) != null) {

        Debug.out("initiator already present");
      }
    }
  }

  public void unregister(PeerNATInitiator initiator) {
    List to_cancel;

    synchronized (initiators) {
      LinkedList requests = (LinkedList) initiators.remove(initiator);

      if (requests == null) {

        Debug.out("initiator not present");

        return;

      } else {

        to_cancel = requests;
      }
    }

    Iterator it = to_cancel.iterator();

    while (it.hasNext()) {

      PeerNATTraversal traversal = (PeerNATTraversal) it.next();

      traversal.cancel();
    }
  }

  public void create(
      PeerNATInitiator initiator, InetSocketAddress target, PeerNATTraversalAdapter adapter) {
    boolean bad = false;

    synchronized (initiators) {
      if (negative_result_bloom.contains(target.toString().getBytes())) {

        bad = true;

        failed_negative_bloom++;

      } else {

        LinkedList requests = (LinkedList) initiators.get(initiator);

        if (requests == null) {

          // we get here when download stopped at same time
          // Debug.out( "initiator not found" );

          bad = true;

        } else {

          PeerNATTraversal traversal = new PeerNATTraversal(initiator, target, adapter);

          requests.addLast(traversal);

          pending_requests.addLast(traversal);

          if (Logger.isEnabled()) {
            Logger.log(
                new LogEvent(
                    LOGID,
                    "created NAT traversal for " + initiator.getDisplayName() + "/" + target));
          }
        }
      }
    }

    if (bad) {

      adapter.failed();
    }
  }

  public List getTraversals(PeerNATInitiator initiator) {
    List result = new ArrayList();

    synchronized (initiators) {
      LinkedList requests = (LinkedList) initiators.get(initiator);

      if (requests != null) {

        Iterator it = requests.iterator();

        while (it.hasNext()) {

          PeerNATTraversal x = (PeerNATTraversal) it.next();

          result.add(x.getTarget());
        }
      }
    }

    return (result);
  }

  protected void removeRequest(PeerNATTraversal request, int outcome) {
    synchronized (initiators) {
      LinkedList requests = (LinkedList) initiators.get(request.getInitiator());

      if (requests != null) {

        requests.remove(request);
      }

      pending_requests.remove(request);

      if (active_requests.remove(request)) {

        usage_average.addValue(request.getTimeUsed());

        if (outcome == OUTCOME_SUCCESS) {

          success_count++;

        } else {

          InetSocketAddress target = request.getTarget();

          negative_result_bloom.add(target.toString().getBytes());

          if (outcome == OUTCOME_FAILED_NO_REND) {

            failed_no_rendezvous++;
          }
        }
      }
    }
  }

  public Map process(InetSocketAddress originator, Map data) {
    // System.out.println( "PeerNAT: received traversal from " + originator );

    return (null);
  }

  protected class PeerNATTraversal implements NATTraversalObserver {
    private PeerNATInitiator initiator;
    private InetSocketAddress target;
    private PeerNATTraversalAdapter adapter;

    private NATTraversal traversal;
    private boolean cancelled;

    private long time;

    protected PeerNATTraversal(
        PeerNATInitiator _initiator, InetSocketAddress _target, PeerNATTraversalAdapter _adapter) {
      initiator = _initiator;
      target = _target;
      adapter = _adapter;
    }

    protected PeerNATInitiator getInitiator() {
      return (initiator);
    }

    protected InetSocketAddress getTarget() {
      return (target);
    }

    protected PeerNATTraversalAdapter getAdapter() {
      return (adapter);
    }

    protected long getTimeUsed() {
      long now = SystemTime.getCurrentTime();

      long elapsed = now - time;

      time = now;

      if (elapsed < 0) {

        elapsed = 0;

      } else {
        // sanity check

        elapsed = Math.min(elapsed, TIMER_PERIOD);
      }

      return (elapsed);
    }

    protected void run() {
      synchronized (this) {
        if (!cancelled) {

          time = SystemTime.getCurrentTime();

          traversal =
              nat_traverser.attemptTraversal(PeerNATTraverser.this, target, null, false, this);
        }
      }
    }

    public void succeeded(InetSocketAddress rendezvous, InetSocketAddress target, Map reply) {
      removeRequest(this, OUTCOME_SUCCESS);

      if (Logger.isEnabled()) {
        Logger.log(
            new LogEvent(
                LOGID,
                "NAT traversal for " + initiator.getDisplayName() + "/" + target + " succeeded"));
      }

      adapter.success(target);
    }

    public void failed(int reason) {
      removeRequest(
          this,
          reason == NATTraversalObserver.FT_NO_RENDEZVOUS
              ? OUTCOME_FAILED_NO_REND
              : OUTCOME_FAILED_OTHER);

      adapter.failed();
    }

    public void failed(Throwable cause) {
      removeRequest(this, OUTCOME_FAILED_OTHER);

      adapter.failed();
    }

    public void disabled() {
      removeRequest(this, OUTCOME_FAILED_OTHER);

      adapter.failed();
    }

    protected void cancel() {
      NATTraversal active_traversal;

      synchronized (this) {
        cancelled = true;

        active_traversal = traversal;
      }

      if (active_traversal == null) {

        removeRequest(this, OUTCOME_FAILED_OTHER);

      } else {

        active_traversal.cancel();
      }

      adapter.failed();
    }
  }
}