public void addVersionCheckClientListener(
      boolean triggerStartListener, VersionCheckClientListener l) {
    synchronized (listeners) {
      listeners.add(l);

      if (triggerStartListener && startCheckRan) {
        try {
          l.versionCheckStarted(REASON_UPDATE_CHECK_START);
        } catch (Exception e) {
          Debug.out(e);
        }
      }
    }
  }
  public PluginInterface getLocalPluginInterface(Class plugin_class, String id)
      throws PluginException {
    try {
      Plugin p = (Plugin) plugin_class.newInstance();

      // Discard plugin.id from the properties, we want the
      // plugin ID we create to take priority - not a value
      // from the original plugin ID properties file.
      Properties local_props = new Properties(props);
      local_props.remove("plugin.id");

      if (id.endsWith("_v")) {

        throw (new Exception("Verified plugins must be loaded from a jar"));
      }

      PluginInterfaceImpl pi =
          new PluginInterfaceImpl(
              p,
              initialiser,
              initialiser_key,
              class_loader,
              null,
              key + "." + id,
              local_props,
              pluginDir,
              getPluginID() + "." + id,
              plugin_version);

      initialiser.fireCreated(pi);

      p.initialize(pi);

      children.add(pi);

      return (pi);

    } catch (Throwable e) {

      if (e instanceof PluginException) {

        throw ((PluginException) e);
      }

      throw (new PluginException("Local initialisation fails", e));
    }
  }
  protected Object[] handleAnnounceAndScrape(
      String client_ip_address, PRUDPPacketRequest request, int request_type) throws Exception {
    if (!checkConnectionId(client_ip_address, request.getConnectionId())) {

      return (null);
    }

    List hashbytes = new ArrayList();
    HashWrapper peer_id = null;
    int port = 0;
    String event = null;

    long uploaded = 0;
    long downloaded = 0;
    long left = 0;
    int num_want = -1;

    String key = null;

    if (request_type == TRTrackerServerRequest.RT_ANNOUNCE) {

      if (PRUDPPacketTracker.VERSION == 1) {
        PRUDPPacketRequestAnnounce announce = (PRUDPPacketRequestAnnounce) request;

        hashbytes.add(announce.getHash());

        peer_id = new HashWrapper(announce.getPeerId());

        port = announce.getPort();

        int i_event = announce.getEvent();

        switch (i_event) {
          case PRUDPPacketRequestAnnounce.EV_STARTED:
            {
              event = "started";
              break;
            }
          case PRUDPPacketRequestAnnounce.EV_STOPPED:
            {
              event = "stopped";
              break;
            }
          case PRUDPPacketRequestAnnounce.EV_COMPLETED:
            {
              event = "completed";
              break;
            }
        }

        uploaded = announce.getUploaded();

        downloaded = announce.getDownloaded();

        left = announce.getLeft();

        num_want = announce.getNumWant();

        int i_ip = announce.getIPAddress();

        if (i_ip != 0) {

          client_ip_address = PRHelpers.intToAddress(i_ip);
        }
      } else {

        PRUDPPacketRequestAnnounce2 announce = (PRUDPPacketRequestAnnounce2) request;

        hashbytes.add(announce.getHash());

        peer_id = new HashWrapper(announce.getPeerId());

        port = announce.getPort();

        int i_event = announce.getEvent();

        switch (i_event) {
          case PRUDPPacketRequestAnnounce.EV_STARTED:
            {
              event = "started";
              break;
            }
          case PRUDPPacketRequestAnnounce.EV_STOPPED:
            {
              event = "stopped";
              break;
            }
          case PRUDPPacketRequestAnnounce.EV_COMPLETED:
            {
              event = "completed";
              break;
            }
        }

        uploaded = announce.getUploaded();

        downloaded = announce.getDownloaded();

        left = announce.getLeft();

        num_want = announce.getNumWant();

        int i_ip = announce.getIPAddress();

        if (i_ip != 0) {

          client_ip_address = PRHelpers.intToAddress(i_ip);
        }

        key = "" + announce.getKey();
      }
    } else {

      PRUDPPacketRequestScrape scrape = (PRUDPPacketRequestScrape) request;

      hashbytes.addAll(scrape.getHashes());
    }

    Map[] root_out = new Map[1];
    TRTrackerServerPeerImpl[] peer_out = new TRTrackerServerPeerImpl[1];

    TRTrackerServerTorrentImpl torrent =
        processTrackerRequest(
            server,
            "",
            root_out,
            peer_out,
            request_type,
            (byte[][]) hashbytes.toArray(new byte[0][0]),
            null,
            null,
            peer_id,
            false,
            TRTrackerServerTorrentImpl.COMPACT_MODE_NONE,
            key, // currently no "no_peer_id" / "compact" in the packet and anyway they aren't
                 // returned / key
            event,
            false,
            port,
            0,
            0,
            client_ip_address,
            client_ip_address,
            downloaded,
            uploaded,
            left,
            num_want,
            TRTrackerServerPeer.CRYPTO_NONE,
            (byte) 1,
            0,
            null);

    Map root = root_out[0];

    if (request_type == TRTrackerServerRequest.RT_ANNOUNCE) {

      if (PRUDPPacketTracker.VERSION == 1) {
        PRUDPPacketReplyAnnounce reply = new PRUDPPacketReplyAnnounce(request.getTransactionId());

        reply.setInterval(((Long) root.get("interval")).intValue());

        List peers = (List) root.get("peers");

        int[] addresses = new int[peers.size()];
        short[] ports = new short[addresses.length];

        for (int i = 0; i < addresses.length; i++) {

          Map peer = (Map) peers.get(i);

          addresses[i] = PRHelpers.addressToInt(new String((byte[]) peer.get("ip")));

          ports[i] = (short) ((Long) peer.get("port")).shortValue();
        }

        reply.setPeers(addresses, ports);

        return (new Object[] {reply, torrent});

      } else {

        PRUDPPacketReplyAnnounce2 reply = new PRUDPPacketReplyAnnounce2(request.getTransactionId());

        reply.setInterval(((Long) root.get("interval")).intValue());

        boolean local_scrape = client_ip_address.equals("127.0.0.1");

        Map scrape_details = torrent.exportScrapeToMap("", client_ip_address, !local_scrape);

        int seeders = ((Long) scrape_details.get("complete")).intValue();
        int leechers = ((Long) scrape_details.get("incomplete")).intValue();

        reply.setLeechersSeeders(leechers, seeders);

        List peers = (List) root.get("peers");

        int[] addresses = new int[peers.size()];
        short[] ports = new short[addresses.length];

        for (int i = 0; i < addresses.length; i++) {

          Map peer = (Map) peers.get(i);

          addresses[i] = PRHelpers.addressToInt(new String((byte[]) peer.get("ip")));

          ports[i] = (short) ((Long) peer.get("port")).shortValue();
        }

        reply.setPeers(addresses, ports);

        return (new Object[] {reply, torrent});
      }

    } else {

      if (PRUDPPacketTracker.VERSION == 1) {

        PRUDPPacketReplyScrape reply = new PRUDPPacketReplyScrape(request.getTransactionId());

        /*
        Long	interval = (Long)root.get("interval");

        if ( interval != null ){

        	reply.setInterval(interval.intValue());
        }
        */

        Map files = (Map) root.get("files");

        byte[][] hashes = new byte[files.size()][];
        int[] s_complete = new int[hashes.length];
        int[] s_downloaded = new int[hashes.length];
        int[] s_incomplete = new int[hashes.length];

        Iterator it = files.keySet().iterator();

        int pos = 0;

        while (it.hasNext()) {

          String hash_str = (String) it.next();

          hashes[pos] = hash_str.getBytes(Constants.BYTE_ENCODING);

          Map details = (Map) files.get(hash_str);

          s_complete[pos] = ((Long) details.get("complete")).intValue();
          s_incomplete[pos] = ((Long) details.get("incomplete")).intValue();
          s_downloaded[pos] = ((Long) details.get("downloaded")).intValue();

          pos++;
        }

        reply.setDetails(hashes, s_complete, s_downloaded, s_incomplete);

        return (new Object[] {reply, torrent});

      } else {

        PRUDPPacketReplyScrape2 reply = new PRUDPPacketReplyScrape2(request.getTransactionId());

        /*
        Long	interval = (Long)root.get("interval");

        if ( interval != null ){

        	reply.setInterval(interval.intValue());
        }
        */

        Map files = (Map) root.get("files");

        int[] s_complete = new int[files.size()];
        int[] s_downloaded = new int[s_complete.length];
        int[] s_incomplete = new int[s_complete.length];

        Iterator it = files.keySet().iterator();

        int pos = 0;

        while (it.hasNext()) {

          String hash_str = (String) it.next();

          Map details = (Map) files.get(hash_str);

          s_complete[pos] = ((Long) details.get("complete")).intValue();
          s_incomplete[pos] = ((Long) details.get("incomplete")).intValue();
          s_downloaded[pos] = ((Long) details.get("downloaded")).intValue();

          pos++;
        }

        reply.setDetails(s_complete, s_downloaded, s_incomplete);

        return (new Object[] {reply, torrent});
      }
    }
  }
  protected long allocateConnectionId(String client_address) {
    try {
      random_mon.enter();

      long id = random.nextLong();

      Long new_key = new Long(id);

      connectionData new_data = new connectionData(client_address, id);

      // check for timeouts

      if (new_data.getTime() - last_timeout_check > 500) {

        last_timeout_check = new_data.getTime();

        Iterator<Long> it = connection_id_map.keySet().iterator();

        while (it.hasNext()) {

          Long key = it.next();

          connectionData data = connection_id_map.get(key);

          if (new_data.getTime() - data.getTime() > CONNECTION_ID_LIFETIME) {

            // System.out.println( "TRTrackerServerProcessorUDP: connection id timeout" );

            it.remove();

            List<connectionData> cds = connection_ip_map.get(client_address);

            if (cds != null) {

              Iterator<connectionData> it2 = cds.iterator();

              while (it2.hasNext()) {

                if (it2.next().getID() == key) {

                  it2.remove();

                  break;
                }
              }

              if (cds.size() == 0) {

                connection_ip_map.remove(client_address);
              }
            }

          } else {
            // insertion order into map is time based - LinkedHashMap returns keys in same order

            break;
          }
        }
      }

      List<connectionData> cds = connection_ip_map.get(client_address);

      if (cds == null) {

        cds = new ArrayList<connectionData>();

        connection_ip_map.put(client_address, cds);
      }

      cds.add(new_data);

      if (cds.size() > 512) {

        connectionData dead = cds.remove(0);

        connection_id_map.remove(dead.getID());
      }

      connection_id_map.put(new_key, new_data);

      // System.out.println( "TRTrackerServerProcessorUDP: allocated:" + id + ", connection id map
      // size = " + connection_id_map.size());

      return (id);

    } finally {

      random_mon.exit();
    }
  }
  /**
   * Construct the default version check message.
   *
   * @return message to send
   */
  public static Map constructVersionCheckMessage(String reason) {

    // only send if anonymous-check flag is not set

    boolean send_info = COConfigurationManager.getBooleanParameter("Send Version Info");

    Map message = new HashMap();

    // always send
    message.put("appid", SystemProperties.getApplicationIdentifier());
    message.put("appname", SystemProperties.getApplicationName());
    message.put("version", Constants.AZUREUS_VERSION);

    String sub_ver = Constants.AZUREUS_SUBVER;

    if (sub_ver.length() > 0) {
      message.put("subver", sub_ver);
    }

    if (COConfigurationManager.getBooleanParameter("Beta Programme Enabled")) {

      message.put("beta_prog", "true");
    }

    message.put("ui", COConfigurationManager.getStringParameter("ui", "unknown"));
    message.put("os", Constants.OSName);
    message.put("os_version", System.getProperty("os.version"));
    message.put(
        "os_arch", System.getProperty("os.arch")); // see http://lopica.sourceforge.net/os.html

    boolean using_phe =
        COConfigurationManager.getBooleanParameter("network.transport.encrypted.require");
    message.put("using_phe", using_phe ? new Long(1) : new Long(0));

    // swt stuff
    try {
      Class c = Class.forName("org.eclipse.swt.SWT");

      String swt_platform =
          (String) c.getMethod("getPlatform", new Class[] {}).invoke(null, new Object[] {});
      message.put("swt_platform", swt_platform);

      Integer swt_version =
          (Integer) c.getMethod("getVersion", new Class[] {}).invoke(null, new Object[] {});
      message.put("swt_version", new Long(swt_version.longValue()));

      if (send_info) {
        c = Class.forName("org.gudy.azureus2.ui.swt.mainwindow.MainWindow");
        if (c != null) {
          c.getMethod("addToVersionCheckMessage", new Class[] {Map.class})
              .invoke(null, new Object[] {message});
        }
      }
    } catch (ClassNotFoundException e) {
      /* ignore */
    } catch (NoClassDefFoundError er) {
      /* ignore */
    } catch (InvocationTargetException err) {
      /* ignore */
    } catch (Throwable t) {
      t.printStackTrace();
    }

    int last_send_time = COConfigurationManager.getIntParameter("Send Version Info Last Time", -1);
    int current_send_time = (int) (SystemTime.getCurrentTime() / 1000);
    COConfigurationManager.setParameter("Send Version Info Last Time", current_send_time);

    String id = COConfigurationManager.getStringParameter("ID", null);

    if (id != null && send_info) {
      message.put("id", id);

      try {
        byte[] id2 = CryptoManagerFactory.getSingleton().getSecureID();

        message.put("id2", id2);

      } catch (Throwable e) {
      }

      if (last_send_time != -1 && last_send_time < current_send_time) {
        // time since last
        message.put("tsl", new Long(current_send_time - last_send_time));
      }

      message.put("reason", reason);

      String java_version = System.getProperty("java.version");
      if (java_version == null) {
        java_version = "unknown";
      }
      message.put("java", java_version);

      String java_vendor = System.getProperty("java.vm.vendor");
      if (java_vendor == null) {
        java_vendor = "unknown";
      }
      message.put("javavendor", java_vendor);

      long max_mem = Runtime.getRuntime().maxMemory() / (1024 * 1024);
      message.put("javamx", new Long(max_mem));

      String java_rt_name = System.getProperty("java.runtime.name");
      if (java_rt_name != null) {
        message.put("java_rt_name", java_rt_name);
      }

      String java_rt_version = System.getProperty("java.runtime.version");
      if (java_rt_version != null) {
        message.put("java_rt_version", java_rt_version);
      }

      OverallStats stats = StatsFactory.getStats();

      if (stats != null) {

        // long total_bytes_downloaded 	= stats.getDownloadedBytes();
        // long total_bytes_uploaded		= stats.getUploadedBytes();
        long total_uptime = stats.getTotalUpTime();

        // removed due to complaints about anonymous stats collection
        // message.put( "total_bytes_downloaded", new Long( total_bytes_downloaded ) );
        // message.put( "total_bytes_uploaded", new Long( total_bytes_uploaded ) );
        message.put("total_uptime", new Long(total_uptime));
        // message.put( "dlstats", stats.getDownloadStats());
      }

      try {
        NetworkAdminASN current_asn = NetworkAdmin.getSingleton().getCurrentASN();

        String as = current_asn.getAS();

        message.put("ip_as", current_asn.getAS());

        String asn = current_asn.getASName();

        if (asn.length() > 64) {

          asn = asn.substring(0, 64);
        }

        message.put("ip_asn", asn);

      } catch (Throwable e) {

        Debug.out(e);
      }

      // send locale, so we can determine which languages need attention
      message.put("locale", Locale.getDefault().toString());
      String originalLocale =
          System.getProperty("user.language") + "_" + System.getProperty("user.country");
      String variant = System.getProperty("user.variant");
      if (variant != null && variant.length() > 0) {
        originalLocale += "_" + variant;
      }
      message.put("orig_locale", originalLocale);

      // We may want to reply differently if the user is in Beginner mode vs Advanced
      message.put("user_mode", COConfigurationManager.getIntParameter("User Mode", -1));

      Set<String> features = UtilitiesImpl.getFeaturesInstalled();

      if (features.size() > 0) {

        String str = "";

        for (String f : features) {
          str += (str.length() == 0 ? "" : ",") + f;
        }

        message.put("vzfeatures", str);
      }

      try {
        if (AzureusCoreFactory.isCoreAvailable()) {

          // installed plugin IDs
          PluginInterface[] plugins =
              AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaces();

          List pids = new ArrayList();

          List vs_data = new ArrayList();

          for (int i = 0; i < plugins.length; i++) {

            PluginInterface plugin = plugins[i];

            String pid = plugin.getPluginID();

            String info = plugin.getPluginconfig().getPluginStringParameter("plugin.info");

            // filter out built-in and core ones
            if ((info != null && info.length() > 0)
                || (!pid.startsWith("<")
                    && !pid.startsWith("azbp")
                    && !pid.startsWith("azupdater")
                    && !pid.startsWith("azplatform")
                    && !pids.contains(pid))) {

              if (info != null && info.length() > 0) {

                if (info.length() < 256) {

                  pid += ":" + info;

                } else {

                  Debug.out("Plugin '" + pid + "' reported excessive info string '" + info + "'");
                }
              }

              pids.add(pid);
            }

            Map data =
                plugin.getPluginconfig().getPluginMapParameter("plugin.versionserver.data", null);

            if (data != null) {

              Map payload = new HashMap();

              byte[] data_bytes = BEncoder.encode(data);

              if (data_bytes.length > 16 * 1024) {

                Debug.out(
                    "Plugin '"
                        + pid
                        + "' reported excessive version server data (length="
                        + data_bytes.length
                        + ")");

                payload.put("error", "data too long: " + data_bytes.length);

              } else {

                payload.put("data", data_bytes);
              }

              payload.put("id", pid);
              payload.put("version", plugin.getPluginVersion());

              vs_data.add(payload);
            }
          }
          message.put("plugins", pids);

          if (vs_data.size() > 0) {

            message.put("plugin_data", vs_data);
          }
        }
      } catch (Throwable e) {

        Debug.out(e);
      }
    }

    return message;
  }