| /** |
| * $RCSfile$ |
| * $Revision$ |
| * $Date$ |
| * |
| * Copyright 2003-2007 Jive Software. |
| * |
| * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.jivesoftware.smackx.packet; |
| |
| import org.jivesoftware.smack.packet.IQ; |
| import org.jivesoftware.smack.util.StringUtils; |
| |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| |
| /** |
| * A DiscoverInfo IQ packet, which is used by XMPP clients to request and receive information |
| * to/from other XMPP entities.<p> |
| * |
| * The received information may contain one or more identities of the requested XMPP entity, and |
| * a list of supported features by the requested XMPP entity. |
| * |
| * @author Gaston Dombiak |
| */ |
| public class DiscoverInfo extends IQ { |
| |
| public static final String NAMESPACE = "http://jabber.org/protocol/disco#info"; |
| |
| private final List<Feature> features = new CopyOnWriteArrayList<Feature>(); |
| private final List<Identity> identities = new CopyOnWriteArrayList<Identity>(); |
| private String node; |
| |
| public DiscoverInfo() { |
| super(); |
| } |
| |
| /** |
| * Copy constructor |
| * |
| * @param d |
| */ |
| public DiscoverInfo(DiscoverInfo d) { |
| super(d); |
| |
| // Set node |
| setNode(d.getNode()); |
| |
| // Copy features |
| synchronized (d.features) { |
| for (Feature f : d.features) { |
| addFeature(f); |
| } |
| } |
| |
| // Copy identities |
| synchronized (d.identities) { |
| for (Identity i : d.identities) { |
| addIdentity(i); |
| } |
| } |
| } |
| |
| /** |
| * Adds a new feature to the discovered information. |
| * |
| * @param feature the discovered feature |
| */ |
| public void addFeature(String feature) { |
| addFeature(new Feature(feature)); |
| } |
| |
| /** |
| * Adds a collection of features to the packet. Does noting if featuresToAdd is null. |
| * |
| * @param featuresToAdd |
| */ |
| public void addFeatures(Collection<String> featuresToAdd) { |
| if (featuresToAdd == null) return; |
| for (String feature : featuresToAdd) { |
| addFeature(feature); |
| } |
| } |
| |
| private void addFeature(Feature feature) { |
| synchronized (features) { |
| features.add(feature); |
| } |
| } |
| |
| /** |
| * Returns the discovered features of an XMPP entity. |
| * |
| * @return an Iterator on the discovered features of an XMPP entity |
| */ |
| public Iterator<Feature> getFeatures() { |
| synchronized (features) { |
| return Collections.unmodifiableList(features).iterator(); |
| } |
| } |
| |
| /** |
| * Adds a new identity of the requested entity to the discovered information. |
| * |
| * @param identity the discovered entity's identity |
| */ |
| public void addIdentity(Identity identity) { |
| synchronized (identities) { |
| identities.add(identity); |
| } |
| } |
| |
| /** |
| * Adds identities to the DiscoverInfo stanza |
| * |
| * @param identitiesToAdd |
| */ |
| public void addIdentities(Collection<Identity> identitiesToAdd) { |
| if (identitiesToAdd == null) return; |
| synchronized (identities) { |
| identities.addAll(identitiesToAdd); |
| } |
| } |
| |
| /** |
| * Returns the discovered identities of an XMPP entity. |
| * |
| * @return an Iterator on the discoveted identities |
| */ |
| public Iterator<Identity> getIdentities() { |
| synchronized (identities) { |
| return Collections.unmodifiableList(identities).iterator(); |
| } |
| } |
| |
| /** |
| * Returns the node attribute that supplements the 'jid' attribute. A node is merely |
| * something that is associated with a JID and for which the JID can provide information.<p> |
| * |
| * Node attributes SHOULD be used only when trying to provide or query information which |
| * is not directly addressable. |
| * |
| * @return the node attribute that supplements the 'jid' attribute |
| */ |
| public String getNode() { |
| return node; |
| } |
| |
| /** |
| * Sets the node attribute that supplements the 'jid' attribute. A node is merely |
| * something that is associated with a JID and for which the JID can provide information.<p> |
| * |
| * Node attributes SHOULD be used only when trying to provide or query information which |
| * is not directly addressable. |
| * |
| * @param node the node attribute that supplements the 'jid' attribute |
| */ |
| public void setNode(String node) { |
| this.node = node; |
| } |
| |
| /** |
| * Returns true if the specified feature is part of the discovered information. |
| * |
| * @param feature the feature to check |
| * @return true if the requestes feature has been discovered |
| */ |
| public boolean containsFeature(String feature) { |
| for (Iterator<Feature> it = getFeatures(); it.hasNext();) { |
| if (feature.equals(it.next().getVar())) |
| return true; |
| } |
| return false; |
| } |
| |
| public String getChildElementXML() { |
| StringBuilder buf = new StringBuilder(); |
| buf.append("<query xmlns=\"" + NAMESPACE + "\""); |
| if (getNode() != null) { |
| buf.append(" node=\""); |
| buf.append(StringUtils.escapeForXML(getNode())); |
| buf.append("\""); |
| } |
| buf.append(">"); |
| synchronized (identities) { |
| for (Identity identity : identities) { |
| buf.append(identity.toXML()); |
| } |
| } |
| synchronized (features) { |
| for (Feature feature : features) { |
| buf.append(feature.toXML()); |
| } |
| } |
| // Add packet extensions, if any are defined. |
| buf.append(getExtensionsXML()); |
| buf.append("</query>"); |
| return buf.toString(); |
| } |
| |
| /** |
| * Test if a DiscoverInfo response contains duplicate identities. |
| * |
| * @return true if duplicate identities where found, otherwise false |
| */ |
| public boolean containsDuplicateIdentities() { |
| List<Identity> checkedIdentities = new LinkedList<Identity>(); |
| for (Identity i : identities) { |
| for (Identity i2 : checkedIdentities) { |
| if (i.equals(i2)) |
| return true; |
| } |
| checkedIdentities.add(i); |
| } |
| return false; |
| } |
| |
| /** |
| * Test if a DiscoverInfo response contains duplicate features. |
| * |
| * @return true if duplicate identities where found, otherwise false |
| */ |
| public boolean containsDuplicateFeatures() { |
| List<Feature> checkedFeatures = new LinkedList<Feature>(); |
| for (Feature f : features) { |
| for (Feature f2 : checkedFeatures) { |
| if (f.equals(f2)) |
| return true; |
| } |
| checkedFeatures.add(f); |
| } |
| return false; |
| } |
| |
| /** |
| * Represents the identity of a given XMPP entity. An entity may have many identities but all |
| * the identities SHOULD have the same name.<p> |
| * |
| * Refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a> |
| * in order to get the official registry of values for the <i>category</i> and <i>type</i> |
| * attributes. |
| * |
| */ |
| public static class Identity implements Comparable<Identity> { |
| |
| private String category; |
| private String name; |
| private String type; |
| private String lang; // 'xml:lang; |
| |
| /** |
| * Creates a new identity for an XMPP entity. |
| * |
| * @param category the entity's category. |
| * @param name the entity's name. |
| * @deprecated As per the spec, the type field is mandatory and the 3 argument constructor should be used instead. |
| */ |
| public Identity(String category, String name) { |
| this.category = category; |
| this.name = name; |
| } |
| |
| /** |
| * Creates a new identity for an XMPP entity. |
| * 'category' and 'type' are required by |
| * <a href="http://xmpp.org/extensions/xep-0030.html#schemas">XEP-30 XML Schemas</a> |
| * |
| * @param category the entity's category (required as per XEP-30). |
| * @param name the entity's name. |
| * @param type the entity's type (required as per XEP-30). |
| */ |
| public Identity(String category, String name, String type) { |
| if ((category == null) || (type == null)) |
| throw new IllegalArgumentException("category and type cannot be null"); |
| |
| this.category = category; |
| this.name = name; |
| this.type = type; |
| } |
| |
| /** |
| * Returns the entity's category. To get the official registry of values for the |
| * 'category' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a> |
| * |
| * @return the entity's category. |
| */ |
| public String getCategory() { |
| return category; |
| } |
| |
| /** |
| * Returns the identity's name. |
| * |
| * @return the identity's name. |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * Returns the entity's type. To get the official registry of values for the |
| * 'type' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a> |
| * |
| * @return the entity's type. |
| */ |
| public String getType() { |
| return type; |
| } |
| |
| /** |
| * Sets the entity's type. To get the official registry of values for the |
| * 'type' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a> |
| * |
| * @param type the identity's type. |
| * @deprecated As per the spec, this field is mandatory and the 3 argument constructor should be used instead. |
| */ |
| public void setType(String type) { |
| this.type = type; |
| } |
| |
| /** |
| * Sets the natural language (xml:lang) for this identity (optional) |
| * |
| * @param lang the xml:lang of this Identity |
| */ |
| public void setLanguage(String lang) { |
| this.lang = lang; |
| } |
| |
| /** |
| * Returns the identities natural language if one is set |
| * |
| * @return the value of xml:lang of this Identity |
| */ |
| public String getLanguage() { |
| return lang; |
| } |
| |
| public String toXML() { |
| StringBuilder buf = new StringBuilder(); |
| buf.append("<identity"); |
| // Check if this packet has 'lang' set and maybe append it to the resulting string |
| if (lang != null) |
| buf.append(" xml:lang=\"").append(StringUtils.escapeForXML(lang)).append("\""); |
| // Category must always be set |
| buf.append(" category=\"").append(StringUtils.escapeForXML(category)).append("\""); |
| // Name must always be set |
| buf.append(" name=\"").append(StringUtils.escapeForXML(name)).append("\""); |
| // Check if this packet has 'type' set and maybe append it to the resulting string |
| if (type != null) { |
| buf.append(" type=\"").append(StringUtils.escapeForXML(type)).append("\""); |
| } |
| buf.append("/>"); |
| return buf.toString(); |
| } |
| |
| /** |
| * Check equality for Identity for category, type, lang and name |
| * in that order as defined by |
| * <a href="http://xmpp.org/extensions/xep-0115.html#ver-proc">XEP-0015 5.4 Processing Method (Step 3.3)</a> |
| * |
| */ |
| public boolean equals(Object obj) { |
| if (obj == null) |
| return false; |
| if (obj == this) |
| return true; |
| if (obj.getClass() != getClass()) |
| return false; |
| |
| DiscoverInfo.Identity other = (DiscoverInfo.Identity) obj; |
| if (!this.category.equals(other.category)) |
| return false; |
| |
| String otherLang = other.lang == null ? "" : other.lang; |
| String thisLang = lang == null ? "" : lang; |
| if (!otherLang.equals(thisLang)) |
| return false; |
| |
| // This safeguard can be removed once the deprecated constructor is removed. |
| String otherType = other.type == null ? "" : other.type; |
| String thisType = type == null ? "" : type; |
| if (!otherType.equals(thisType)) |
| return false; |
| |
| String otherName = other.name == null ? "" : other.name; |
| String thisName = name == null ? "" : other.name; |
| if (!thisName.equals(otherName)) |
| return false; |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = 1; |
| result = 37 * result + category.hashCode(); |
| result = 37 * result + (lang == null ? 0 : lang.hashCode()); |
| result = 37 * result + (type == null ? 0 : type.hashCode()); |
| result = 37 * result + (name == null ? 0 : name.hashCode()); |
| return result; |
| } |
| |
| /** |
| * Compares this identity with another one. The comparison order is: |
| * Category, Type, Lang. If all three are identical the other Identity is considered equal. |
| * Name is not used for comparision, as defined by XEP-0115 |
| * |
| * @param obj |
| * @return |
| */ |
| public int compareTo(DiscoverInfo.Identity other) { |
| String otherLang = other.lang == null ? "" : other.lang; |
| String thisLang = lang == null ? "" : lang; |
| |
| // This can be removed once the deprecated constructor is removed. |
| String otherType = other.type == null ? "" : other.type; |
| String thisType = type == null ? "" : type; |
| |
| if (category.equals(other.category)) { |
| if (thisType.equals(otherType)) { |
| if (thisLang.equals(otherLang)) { |
| // Don't compare on name, XEP-30 says that name SHOULD |
| // be equals for all identities of an entity |
| return 0; |
| } else { |
| return thisLang.compareTo(otherLang); |
| } |
| } else { |
| return thisType.compareTo(otherType); |
| } |
| } else { |
| return category.compareTo(other.category); |
| } |
| } |
| } |
| |
| /** |
| * Represents the features offered by the item. This information helps requestors determine |
| * what actions are possible with regard to this item (registration, search, join, etc.) |
| * as well as specific feature types of interest, if any (e.g., for the purpose of feature |
| * negotiation). |
| */ |
| public static class Feature { |
| |
| private String variable; |
| |
| /** |
| * Creates a new feature offered by an XMPP entity or item. |
| * |
| * @param variable the feature's variable. |
| */ |
| public Feature(String variable) { |
| if (variable == null) |
| throw new IllegalArgumentException("variable cannot be null"); |
| this.variable = variable; |
| } |
| |
| /** |
| * Returns the feature's variable. |
| * |
| * @return the feature's variable. |
| */ |
| public String getVar() { |
| return variable; |
| } |
| |
| public String toXML() { |
| StringBuilder buf = new StringBuilder(); |
| buf.append("<feature var=\"").append(StringUtils.escapeForXML(variable)).append("\"/>"); |
| return buf.toString(); |
| } |
| |
| public boolean equals(Object obj) { |
| if (obj == null) |
| return false; |
| if (obj == this) |
| return true; |
| if (obj.getClass() != getClass()) |
| return false; |
| |
| DiscoverInfo.Feature other = (DiscoverInfo.Feature) obj; |
| return variable.equals(other.variable); |
| } |
| |
| @Override |
| public int hashCode() { |
| return 37 * variable.hashCode(); |
| } |
| } |
| } |