/**
   * Checks if the given {@linkplain JID} supports the requested feature. The JID may be non
   * resource qualified in which case all presences belonging to that JID are checked.
   *
   * <p>This method does <b>not</b> perform any I/O operation and will return immediately.
   *
   * <p><b>Please note the return value: <code>if(isFeatureSupported(foo, bar)) ... </code> is
   * likely to produce a {@link NullPointerException NPE}.</b>
   *
   * @param jid {@link JID} to query support for
   * @param namespace the namespace of the feature
   * @return <code>true</code> if the given feature is supported, <code>false</code> if it is not
   *     supported or <b><code>null</code> </b> if no information is available
   * @see #queryFeatureSupport(JID, String, boolean)
   */
  public Boolean isFeatureSupported(final JID jid, final String namespace) {
    checkJID(jid);

    Boolean supported = null;

    final List<JID> jidsToQuery = new ArrayList<JID>();

    if (jid.isBareJID()) jidsToQuery.addAll(rosterTracker.getAvailablePresences(jid));
    else jidsToQuery.add(jid);

    for (JID rqJID : jidsToQuery) {

      DiscoverInfoWrapper info = cache.get(rqJID.toString());
      if (info == null) continue;

      DiscoverInfo disco = info.item;

      if (disco == null) continue;

      supported = disco.containsFeature(namespace);

      if (supported) break;
    }

    return supported;
  }
  /**
   * Perform a service discovery and check if the given feature is among the features supported by
   * the given recipient. All registered listeners will be notified about the result.
   *
   * @param jid A RQ-JID (user@host/resource) of the user to query support for or a non RQ-JID to
   *     query all presences for this JID.
   * @blocking This method blocks until the ServiceDiscovery returns.
   * @reentrant This method can be called concurrently.
   * @caching If results are available in the cache, they are used instead of querying the server.
   */
  private Boolean queryFeatureSupport(JID jid, String namespace) {

    Boolean supported = null;

    checkJID(jid);

    DiscoverInfoWrapper wrapper;

    final List<JID> jidsToQuery = new ArrayList<JID>();

    if (jid.isBareJID()) jidsToQuery.addAll(rosterTracker.getAvailablePresences(jid));
    else jidsToQuery.add(jid);

    for (JID rqJID : jidsToQuery) {

      // add dummy
      synchronized (cache) {
        wrapper = cache.get(rqJID.toString());
        if (wrapper == null) {
          wrapper = new DiscoverInfoWrapper();
          cache.put(rqJID.toString(), wrapper);
        }
      }

      DiscoverInfo disco = null;

      // wait if there is already a discovery for the JID in progress
      synchronized (wrapper) {
        if (wrapper.isAvailable()) disco = wrapper.item;
        else {
          disco = wrapper.item = performServiceDiscovery(rqJID);
          if (disco != null) LOG.debug("Inserted DiscoveryInfo into Cache for: " + rqJID);
        }
      }

      // Null means that the discovery failed
      if (disco == null) {
        // and so we do not know if the feature is supported
        // notifyFeatureSupportUpdated(jid, namespace, false);
        continue;
      }

      notifyFeatureSupportUpdated(rqJID, namespace, disco.containsFeature(namespace));

      /*
       * loop through all presence regardless if we already know that the
       * feature is supported to notify the listener for every current
       * presence
       */
      if (supported != null) supported |= disco.containsFeature(namespace);
      else supported = disco.containsFeature(namespace);
    }

    return supported;
  }
 private void clearCache(Collection<String> addresses) {
   for (String pjid : addresses) {
     /*
      * TODO We should remove all presences kept for the given
      * addresses
      */
     for (Presence presence : rosterTracker.getPresences(new JID(pjid))) {
       clearCache(presence);
     }
   }
 }
  /**
   * Returns the RQ-JID of given plain JID supporting the feature of the given name-space if
   * available, otherwise null.
   *
   * <p>If not in the cache then a blocking cache update is performed.
   *
   * @param jid The JID of the user to find a supporting presence for. The JID can be resource
   *     qualified in which case the resource is stripped, before performing the look-up.
   * @blocking This method blocks until the ServiceDiscovery returns.
   * @reentrant This method can be called concurrently.
   * @caching If results are available in the cache, they are used instead of querying the server.
   */
  public JID getSupportingPresence(final JID jid, final String namespace) {
    checkJID(jid);

    for (Presence presence : rosterTracker.getPresences(jid.getBareJID())) {
      if (!presence.isAvailable()) continue;

      String rjid = presence.getFrom();
      if (rjid == null) {
        LOG.error("presence.getFrom() is null");
        continue;
      }

      JID jidToCheck = new JID(rjid);
      Boolean supported = queryFeatureSupport(jidToCheck, namespace);

      if (supported != null && supported) return jidToCheck;
    }

    return null;
  }
 @Override
 public void dispose() {
   rosterTracker.removeRosterListener(rosterListener);
   threadPoolExecutor.shutdownNow();
 }