/**
   * Add {@link DiscoverInfo} to our caps database.
   *
   * <p><b>Warning</b>: The specified <tt>DiscoverInfo</tt> is trusted to be valid with respect to
   * the specified <tt>Caps</tt> for performance reasons because the <tt>DiscoverInfo</tt> should
   * have already been validated in order to be used elsewhere anyway.
   *
   * @param caps the <tt>Caps<tt/> i.e. the node, the hash and the ver for which a
   *     <tt>DiscoverInfo</tt> is to be added to our caps database.
   * @param info {@link DiscoverInfo} for the specified <tt>Caps</tt>.
   */
  public static void addDiscoverInfoByCaps(Caps caps, DiscoverInfo info) {
    cleanupDiscoverInfo(info);
    /*
     * DiscoverInfo carries the node we're now associating it with a
     * specific node so we'd better keep them in sync.
     */
    info.setNode(caps.getNodeVer());

    synchronized (caps2discoverInfo) {
      DiscoverInfo oldInfo = caps2discoverInfo.put(caps, info);

      /*
       * If the specified info is a new association for the specified
       * node, remember it across application instances in order to not
       * query for it over the network.
       */
      if ((oldInfo == null) || !oldInfo.equals(info)) {
        String xml = info.getChildElementXML();

        if ((xml != null) && (xml.length() != 0)) {
          getConfigService().setProperty(getCapsPropertyName(caps), xml);
        }
      }
    }
  }
    /**
     * Determines whether a specific <tt>DiscoverInfo</tt> is valid according to this <tt>Caps</tt>
     * i.e. whether the <tt>discoverInfo</tt> has the node and the ver of this <tt>Caps</tt> and the
     * ver calculated from the <tt>discoverInfo</tt> using the hash (algorithm) of this
     * <tt>Caps</tt> is equal to the ver of this <tt>Caps</tt>.
     *
     * @param discoverInfo the <tt>DiscoverInfo</tt> to be validated by this <tt>Caps</tt>
     * @return <tt>true</tt> if the specified <tt>DiscoverInfo</tt> has the node and the ver of this
     *     <tt>Caps</tt> and the ver calculated from the <tt>discoverInfo</tt> using the hash
     *     (algorithm) of this <tt>Caps</tt> is equal to the ver of this <tt>Caps</tt>; otherwise,
     *     <tt>false</tt>
     */
    public boolean isValid(DiscoverInfo discoverInfo) {
      if (discoverInfo != null) {
        // The "node" attribute is not necessary in the query element.
        // For example, Swift does not send back the "node" attribute in
        // the Disco#info response. Thus, if the node of the IQ response
        // is null, then we set it to the request one.
        if (discoverInfo.getNode() == null) {
          discoverInfo.setNode(getNodeVer());
        }

        if (getNodeVer().equals(discoverInfo.getNode())
            && !hash.equals("")
            && ver.equals(capsToHash(hash, calculateEntityCapsString(discoverInfo)))) {
          return true;
        }
      }
      return false;
    }
  /**
   * Set our own caps version.
   *
   * @param discoverInfo the {@link DiscoverInfo} that we'd like to map to the <tt>capsVersion</tt>.
   * @param capsVersion the new caps version
   */
  public void setCurrentCapsVersion(DiscoverInfo discoverInfo, String capsVersion) {
    Caps caps = new Caps(getNode(), CapsPacketExtension.HASH_METHOD, capsVersion, null);

    /*
     * DiscoverInfo carries the node and the ver and we're now setting a new
     * ver so we should update the DiscoveryInfo.
     */
    discoverInfo.setNode(caps.getNodeVer());

    if (!caps.isValid(discoverInfo)) {
      throw new IllegalArgumentException(
          "The specified discoverInfo must be valid with respect"
              + " to the specified capsVersion");
    }

    currentCapsVersion = capsVersion;
    addDiscoverInfoByCaps(caps, discoverInfo);
    fireCapsVerChanged();
  }
  /**
   * Calculates the <tt>String</tt> for a specific <tt>DiscoverInfo</tt> which is to be hashed in
   * order to compute the ver string for that <tt>DiscoverInfo</tt>.
   *
   * @param discoverInfo the <tt>DiscoverInfo</tt> for which the <tt>String</tt> to be hashed in
   *     order to compute its ver string is to be calculated
   * @return the <tt>String</tt> for <tt>discoverInfo</tt> which is to be hashed in order to compute
   *     its ver string
   */
  private static String calculateEntityCapsString(DiscoverInfo discoverInfo) {
    StringBuilder bldr = new StringBuilder();

    // Add identities
    {
      Iterator<DiscoverInfo.Identity> identities = discoverInfo.getIdentities();
      SortedSet<DiscoverInfo.Identity> is =
          new TreeSet<DiscoverInfo.Identity>(
              new Comparator<DiscoverInfo.Identity>() {
                public int compare(DiscoverInfo.Identity i1, DiscoverInfo.Identity i2) {
                  int category = i1.getCategory().compareTo(i2.getCategory());

                  if (category != 0) return category;

                  int type = i1.getType().compareTo(i2.getType());

                  if (type != 0) return type;

                  /*
                   * TODO Sort by xml:lang.
                   *
                   * Since sort by xml:lang is currently missing,
                   * use the last supported sort criterion i.e.
                   * type.
                   */
                  return type;
                }
              });

      if (identities != null) while (identities.hasNext()) is.add(identities.next());

      for (DiscoverInfo.Identity i : is) {
        bldr.append(i.getCategory())
            .append('/')
            .append(i.getType())
            .append("//")
            .append(i.getName())
            .append('<');
      }
    }

    // Add features
    {
      Iterator<DiscoverInfo.Feature> features = getDiscoverInfoFeatures(discoverInfo);
      SortedSet<String> fs = new TreeSet<String>();

      if (features != null) while (features.hasNext()) fs.add(features.next().getVar());

      for (String f : fs) bldr.append(f).append('<');
    }

    DataForm extendedInfo = (DataForm) discoverInfo.getExtension("x", "jabber:x:data");

    if (extendedInfo != null) {
      synchronized (extendedInfo) {
        SortedSet<FormField> fs =
            new TreeSet<FormField>(
                new Comparator<FormField>() {
                  public int compare(FormField f1, FormField f2) {
                    return f1.getVariable().compareTo(f2.getVariable());
                  }
                });

        FormField formType = null;

        for (Iterator<FormField> fieldsIter = extendedInfo.getFields(); fieldsIter.hasNext(); ) {
          FormField f = fieldsIter.next();
          if (!f.getVariable().equals("FORM_TYPE")) fs.add(f);
          else formType = f;
        }

        // Add FORM_TYPE values
        if (formType != null) formFieldValuesToCaps(formType.getValues(), bldr);

        // Add the other values
        for (FormField f : fs) {
          bldr.append(f.getVariable()).append('<');
          formFieldValuesToCaps(f.getValues(), bldr);
        }
      }
    }

    return bldr.toString();
  }
 /**
  * Removes from, to and packet-id from <tt>info</tt>.
  *
  * @param info the {@link DiscoverInfo} that we'd like to cleanup.
  */
 private static void cleanupDiscoverInfo(DiscoverInfo info) {
   info.setFrom(null);
   info.setTo(null);
   info.setPacketID(null);
 }