DiscoverInfo.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.disco.packet;

  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.HashSet;
  21. import java.util.LinkedList;
  22. import java.util.List;
  23. import java.util.Set;

  24. import org.jivesoftware.smack.XMPPConnection;
  25. import org.jivesoftware.smack.packet.IQ;
  26. import org.jivesoftware.smack.packet.IqData;
  27. import org.jivesoftware.smack.util.EqualsUtil;
  28. import org.jivesoftware.smack.util.HashCode;
  29. import org.jivesoftware.smack.util.StringUtils;
  30. import org.jivesoftware.smack.util.XmlStringBuilder;

  31. import org.jxmpp.util.XmppStringUtils;

  32. /**
  33.  * A DiscoverInfo IQ packet, which is used by XMPP clients to request and receive information
  34.  * to/from other XMPP entities.<p>
  35.  *
  36.  * The received information may contain one or more identities of the requested XMPP entity, and
  37.  * a list of supported features by the requested XMPP entity.
  38.  *
  39.  * @author Gaston Dombiak
  40.  */
  41. public class DiscoverInfo extends IQ implements DiscoverInfoView {

  42.     public static final String ELEMENT = QUERY_ELEMENT;
  43.     public static final String NAMESPACE = "http://jabber.org/protocol/disco#info";

  44.     private final List<Feature> features = new ArrayList<>();
  45.     private final Set<Feature> featuresSet = new HashSet<>();
  46.     private final List<Identity> identities = new ArrayList<>();
  47.     private final Set<String> identitiesSet = new HashSet<>();
  48.     private String node;
  49.     private boolean containsDuplicateFeatures;

  50.     DiscoverInfo(DiscoverInfoBuilder builder, boolean validate) {
  51.         super(builder, ELEMENT, NAMESPACE);

  52.         features.addAll(builder.getFeatures());
  53.         identities.addAll(builder.getIdentities());
  54.         node = builder.getNode();


  55.         for (Feature feature : features) {
  56.             boolean featureIsNew = featuresSet.add(feature);
  57.             if (!featureIsNew) {
  58.                 containsDuplicateFeatures = true;
  59.             }
  60.         }

  61.         for (Identity identity : identities) {
  62.             identitiesSet.add(identity.getKey());
  63.         }

  64.         if (!validate) {
  65.             return;
  66.         }

  67.         if (containsDuplicateFeatures) {
  68.             throw new IllegalArgumentException("The disco#info request contains duplicate features.");
  69.         }
  70.     }

  71.     /**
  72.      * Copy constructor.
  73.      *
  74.      * @param d TODO javadoc me please
  75.      */
  76.     public DiscoverInfo(DiscoverInfo d) {
  77.         super(d);

  78.         // Set node
  79.         node = d.getNode();

  80.         // Copy features
  81.         features.addAll(d.features);
  82.         featuresSet.addAll(d.featuresSet);

  83.         // Copy identities
  84.         identities.addAll(d.identities);
  85.         identitiesSet.addAll(d.identitiesSet);
  86.     }

  87.     @Override
  88.     public List<Feature> getFeatures() {
  89.         return Collections.unmodifiableList(features);
  90.     }

  91.     @Override
  92.     public List<Identity> getIdentities() {
  93.         return Collections.unmodifiableList(identities);
  94.     }

  95.     /**
  96.      * Returns true if this DiscoverInfo contains at least one Identity of the given category and type.
  97.      *
  98.      * @param category the category to look for.
  99.      * @param type the type to look for.
  100.      * @return true if this DiscoverInfo contains a Identity of the given category and type.
  101.      */
  102.     public boolean hasIdentity(String category, String type) {
  103.         String key = XmppStringUtils.generateKey(category, type);
  104.         return identitiesSet.contains(key);
  105.     }

  106.     /**
  107.      * Returns all Identities of the given category and type of this DiscoverInfo.
  108.      *
  109.      * @param category category the category to look for.
  110.      * @param type type the type to look for.
  111.      * @return a list of Identities with the given category and type.
  112.      */
  113.     public List<Identity> getIdentities(String category, String type) {
  114.         List<Identity> res = new ArrayList<>(identities.size());
  115.         for (Identity identity : identities) {
  116.             if (identity.getCategory().equals(category) && identity.getType().equals(type)) {
  117.                 res.add(identity);
  118.             }
  119.         }
  120.         return res;
  121.     }

  122.     @Override
  123.     public String getNode() {
  124.         return node;
  125.     }

  126.     /**
  127.      * Returns true if the specified feature is part of the discovered information.
  128.      *
  129.      * @param feature the feature to check
  130.      * @return true if the requests feature has been discovered
  131.      */
  132.     public boolean containsFeature(CharSequence feature) {
  133.         return features.contains(new Feature(feature));
  134.     }

  135.     public static boolean nullSafeContainsFeature(DiscoverInfo discoverInfo, CharSequence feature) {
  136.         if (discoverInfo == null) {
  137.             return false;
  138.         }

  139.         return discoverInfo.containsFeature(feature);
  140.     }

  141.     @Override
  142.     protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
  143.         xml.optAttribute("node", getNode());
  144.         xml.rightAngleBracket();
  145.         for (Identity identity : identities) {
  146.             xml.append(identity.toXML());
  147.         }
  148.         for (Feature feature : features) {
  149.             xml.append(feature.toXML());
  150.         }

  151.         return xml;
  152.     }

  153.     /**
  154.      * Test if a DiscoverInfo response contains duplicate identities.
  155.      *
  156.      * @return true if duplicate identities where found, otherwise false
  157.      */
  158.     public boolean containsDuplicateIdentities() {
  159.         List<Identity> checkedIdentities = new LinkedList<>();
  160.         for (Identity i : identities) {
  161.             for (Identity i2 : checkedIdentities) {
  162.                 if (i.equals(i2))
  163.                     return true;
  164.             }
  165.             checkedIdentities.add(i);
  166.         }
  167.         return false;
  168.     }

  169.     /**
  170.      * Test if a DiscoverInfo response contains duplicate features.
  171.      *
  172.      * @return true if duplicate identities where found, otherwise false
  173.      */
  174.     public boolean containsDuplicateFeatures() {
  175.         return containsDuplicateFeatures;
  176.     }

  177.     public DiscoverInfoBuilder asBuilder(String stanzaId) {
  178.         return new DiscoverInfoBuilder(this, stanzaId);
  179.     }

  180.     public static DiscoverInfoBuilder builder(XMPPConnection connection) {
  181.         return new DiscoverInfoBuilder(connection);
  182.     }

  183.     public static DiscoverInfoBuilder builder(IqData iqData) {
  184.         return new DiscoverInfoBuilder(iqData);
  185.     }

  186.     public static DiscoverInfoBuilder builder(String stanzaId) {
  187.         return new DiscoverInfoBuilder(stanzaId);
  188.     }

  189.     /**
  190.      * Represents the identity of a given XMPP entity. An entity may have many identities but all
  191.      * the identities SHOULD have the same name.<p>
  192.      *
  193.      * Refer to <a href="https://xmpp.org/registrar/disco-categories.html">XMPP Registry for Service Discovery Identities</a>
  194.      * in order to get the official registry of values for the <i>category</i> and <i>type</i>
  195.      * attributes.
  196.      *
  197.      */
  198.     public static final class Identity implements Comparable<Identity> {

  199.         private final String category;
  200.         private final String type;
  201.         private final String key;
  202.         private final String name;
  203.         private final String lang; // 'xml:lang;

  204.         /**
  205.          * Creates a new identity for an XMPP entity.
  206.          *
  207.          * @param category the entity's category (required as per XEP-30).
  208.          * @param type the entity's type (required as per XEP-30).
  209.          */
  210.         public Identity(String category, String type) {
  211.             this(category, type, null, null);
  212.         }

  213.         /**
  214.          * Creates a new identity for an XMPP entity.
  215.          * 'category' and 'type' are required by
  216.          * <a href="http://xmpp.org/extensions/xep-0030.html#schemas">XEP-30 XML Schemas</a>
  217.          *
  218.          * @param category the entity's category (required as per XEP-30).
  219.          * @param name the entity's name.
  220.          * @param type the entity's type (required as per XEP-30).
  221.          */
  222.         public Identity(String category, String name, String type) {
  223.             this(category, type, name, null);
  224.         }

  225.         /**
  226.          * Creates a new identity for an XMPP entity.
  227.          * 'category' and 'type' are required by
  228.          * <a href="http://xmpp.org/extensions/xep-0030.html#schemas">XEP-30 XML Schemas</a>
  229.          *
  230.          * @param category the entity's category (required as per XEP-30).
  231.          * @param type the entity's type (required as per XEP-30).
  232.          * @param name the entity's name.
  233.          * @param lang the entity's lang.
  234.          */
  235.         public Identity(String category, String type, String name, String lang) {
  236.             this.category = StringUtils.requireNotNullNorEmpty(category, "category cannot be null");
  237.             this.type = StringUtils.requireNotNullNorEmpty(type, "type cannot be null");
  238.             this.key = XmppStringUtils.generateKey(category, type);
  239.             this.name = name;
  240.             this.lang = lang;
  241.         }

  242.         /**
  243.          * Returns the entity's category. To get the official registry of values for the
  244.          * 'category' attribute refer to <a href="https://xmpp.org/registrar/disco-categories.html">XMPP Registry for Service Discovery Identities</a>.
  245.          *
  246.          * @return the entity's category.
  247.          */
  248.         public String getCategory() {
  249.             return category;
  250.         }

  251.         /**
  252.          * Returns the identity's name.
  253.          *
  254.          * @return the identity's name.
  255.          */
  256.         public String getName() {
  257.             return name;
  258.         }

  259.         /**
  260.          * Returns the entity's type. To get the official registry of values for the
  261.          * 'type' attribute refer to <a href="https://xmpp.org/registrar/disco-categories.html">XMPP Registry for Service Discovery Identities</a>.
  262.          *
  263.          * @return the entity's type.
  264.          */
  265.         public String getType() {
  266.             return type;
  267.         }

  268.         /**
  269.          * Returns the identities natural language if one is set.
  270.          *
  271.          * @return the value of xml:lang of this Identity
  272.          */
  273.         public String getLanguage() {
  274.             return lang;
  275.         }

  276.         private String getKey() {
  277.             return key;
  278.         }

  279.         /**
  280.          * Returns true if this identity is of the given category and type.
  281.          *
  282.          * @param category the category.
  283.          * @param type the type.
  284.          * @return true if this identity is of the given category and type.
  285.          */
  286.         public boolean isOfCategoryAndType(String category, String type) {
  287.             return this.category.equals(category) && this.type.equals(type);
  288.         }

  289.         public XmlStringBuilder toXML() {
  290.             XmlStringBuilder xml = new XmlStringBuilder();
  291.             xml.halfOpenElement("identity");
  292.             xml.xmllangAttribute(lang);
  293.             xml.attribute("category", category);
  294.             xml.optAttribute("name", name);
  295.             xml.optAttribute("type", type);
  296.             xml.closeEmptyElement();
  297.             return xml;
  298.         }

  299.         /**
  300.          * Check equality for Identity  for category, type, lang and name
  301.          * in that order as defined by
  302.          * <a href="http://xmpp.org/extensions/xep-0115.html#ver-proc">XEP-0015 5.4 Processing Method (Step 3.3)</a>.
  303.          *
  304.          */
  305.         @Override
  306.         public boolean equals(Object obj) {
  307.             return EqualsUtil.equals(this, obj, (e, o) -> {
  308.                 e.append(key, o.key)
  309.                  .append(lang, o.lang)
  310.                  .append(name, o.name);
  311.             });
  312.         }

  313.         private final HashCode.Cache hashCodeCache = new HashCode.Cache();

  314.         @Override
  315.         public int hashCode() {
  316.             return hashCodeCache.getHashCode(c ->
  317.                 c.append(key)
  318.                  .append(lang)
  319.                  .append(name)
  320.             );
  321.         }

  322.         /**
  323.          * Compares this identity with another one. The comparison order is: Category, Type, Lang.
  324.          * If all three are identical the other Identity is considered equal. Name is not used for
  325.          * comparison, as defined by XEP-0115
  326.          *
  327.          * @param other TODO javadoc me please
  328.          * @return a negative integer, zero, or a positive integer as this object is less than,
  329.          *         equal to, or greater than the specified object.
  330.          */
  331.         @Override
  332.         public int compareTo(DiscoverInfo.Identity other) {
  333.             String otherLang = other.lang == null ? "" : other.lang;
  334.             String thisLang = lang == null ? "" : lang;

  335.             // This can be removed once the deprecated constructor is removed.
  336.             String otherType = other.type == null ? "" : other.type;
  337.             String thisType = type == null ? "" : type;

  338.             if (category.equals(other.category)) {
  339.                 if (thisType.equals(otherType)) {
  340.                     if (thisLang.equals(otherLang)) {
  341.                         // Don't compare on name, XEP-30 says that name SHOULD
  342.                         // be equals for all identities of an entity
  343.                         return 0;
  344.                     } else {
  345.                         return thisLang.compareTo(otherLang);
  346.                     }
  347.                 } else {
  348.                     return thisType.compareTo(otherType);
  349.                 }
  350.             } else {
  351.                 return category.compareTo(other.category);
  352.             }
  353.         }

  354.         @Override
  355.         public String toString() {
  356.             return toXML().toString();
  357.         }
  358.     }

  359.     /**
  360.      * Represents the features offered by the item. This information helps the requester to determine
  361.      * what actions are possible with regard to this item (registration, search, join, etc.)
  362.      * as well as specific feature types of interest, if any (e.g., for the purpose of feature
  363.      * negotiation).
  364.      */
  365.     public static final class Feature {

  366.         private final String variable;

  367.         public Feature(Feature feature) {
  368.             this.variable = feature.variable;
  369.         }

  370.         public Feature(CharSequence variable) {
  371.             this(variable.toString());
  372.         }

  373.         /**
  374.          * Creates a new feature offered by an XMPP entity or item.
  375.          *
  376.          * @param variable the feature's variable.
  377.          */
  378.         public Feature(String variable) {
  379.             this.variable = StringUtils.requireNotNullNorEmpty(variable, "variable cannot be null");
  380.         }

  381.         /**
  382.          * Returns the feature's variable.
  383.          *
  384.          * @return the feature's variable.
  385.          */
  386.         public String getVar() {
  387.             return variable;
  388.         }

  389.         public XmlStringBuilder toXML() {
  390.             XmlStringBuilder xml = new XmlStringBuilder();
  391.             xml.halfOpenElement("feature");
  392.             xml.attribute("var", variable);
  393.             xml.closeEmptyElement();
  394.             return xml;
  395.         }

  396.         @Override
  397.         public boolean equals(Object obj) {
  398.             return EqualsUtil.equals(this, obj, (e, o) -> {
  399.                 e.append(variable, o.variable);
  400.             });
  401.         }

  402.         @Override
  403.         public int hashCode() {
  404.             return variable.hashCode();
  405.         }

  406.         @Override
  407.         public String toString() {
  408.             return toXML().toString();
  409.         }
  410.     }
  411. }