001/** 002 * 003 * Copyright 2003-2007 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.disco.packet; 018 019import org.jivesoftware.smack.packet.IQ; 020import org.jivesoftware.smack.util.XmlStringBuilder; 021 022import java.util.Collection; 023import java.util.Collections; 024import java.util.LinkedList; 025import java.util.List; 026 027/** 028 * A DiscoverInfo IQ packet, which is used by XMPP clients to request and receive information 029 * to/from other XMPP entities.<p> 030 * 031 * The received information may contain one or more identities of the requested XMPP entity, and 032 * a list of supported features by the requested XMPP entity. 033 * 034 * @author Gaston Dombiak 035 */ 036public class DiscoverInfo extends IQ implements Cloneable { 037 038 public static final String NAMESPACE = "http://jabber.org/protocol/disco#info"; 039 040 private final List<Feature> features = new LinkedList<Feature>(); 041 private final List<Identity> identities = new LinkedList<Identity>(); 042 private String node; 043 044 public DiscoverInfo() { 045 super(); 046 } 047 048 /** 049 * Copy constructor 050 * 051 * @param d 052 */ 053 public DiscoverInfo(DiscoverInfo d) { 054 super(d); 055 056 // Set node 057 setNode(d.getNode()); 058 059 // Copy features 060 for (Feature f : d.features) { 061 addFeature(f.clone()); 062 } 063 064 // Copy identities 065 for (Identity i : d.identities) { 066 addIdentity(i.clone()); 067 } 068 } 069 070 /** 071 * Adds a new feature to the discovered information. 072 * 073 * @param feature the discovered feature 074 */ 075 public void addFeature(String feature) { 076 addFeature(new Feature(feature)); 077 } 078 079 /** 080 * Adds a collection of features to the packet. Does noting if featuresToAdd is null. 081 * 082 * @param featuresToAdd 083 */ 084 public void addFeatures(Collection<String> featuresToAdd) { 085 if (featuresToAdd == null) return; 086 for (String feature : featuresToAdd) { 087 addFeature(feature); 088 } 089 } 090 091 private void addFeature(Feature feature) { 092 features.add(feature); 093 } 094 095 /** 096 * Returns the discovered features of an XMPP entity. 097 * 098 * @return an unmodifiable list of the discovered features of an XMPP entity 099 */ 100 public List<Feature> getFeatures() { 101 return Collections.unmodifiableList(features); 102 } 103 104 /** 105 * Adds a new identity of the requested entity to the discovered information. 106 * 107 * @param identity the discovered entity's identity 108 */ 109 public void addIdentity(Identity identity) { 110 identities.add(identity); 111 } 112 113 /** 114 * Adds identities to the DiscoverInfo stanza 115 * 116 * @param identitiesToAdd 117 */ 118 public void addIdentities(Collection<Identity> identitiesToAdd) { 119 if (identitiesToAdd == null) return; 120 identities.addAll(identitiesToAdd); 121 } 122 123 /** 124 * Returns the discovered identities of an XMPP entity. 125 * 126 * @return an unmodifiable list of the discovered identities 127 */ 128 public List<Identity> getIdentities() { 129 return Collections.unmodifiableList(identities); 130 } 131 132 /** 133 * Returns the node attribute that supplements the 'jid' attribute. A node is merely 134 * something that is associated with a JID and for which the JID can provide information.<p> 135 * 136 * Node attributes SHOULD be used only when trying to provide or query information which 137 * is not directly addressable. 138 * 139 * @return the node attribute that supplements the 'jid' attribute 140 */ 141 public String getNode() { 142 return node; 143 } 144 145 /** 146 * Sets the node attribute that supplements the 'jid' attribute. A node is merely 147 * something that is associated with a JID and for which the JID can provide information.<p> 148 * 149 * Node attributes SHOULD be used only when trying to provide or query information which 150 * is not directly addressable. 151 * 152 * @param node the node attribute that supplements the 'jid' attribute 153 */ 154 public void setNode(String node) { 155 this.node = node; 156 } 157 158 /** 159 * Returns true if the specified feature is part of the discovered information. 160 * 161 * @param feature the feature to check 162 * @return true if the requestes feature has been discovered 163 */ 164 public boolean containsFeature(String feature) { 165 for (Feature f : getFeatures()) { 166 if (feature.equals(f.getVar())) 167 return true; 168 } 169 return false; 170 } 171 172 @Override 173 public CharSequence getChildElementXML() { 174 XmlStringBuilder xml = new XmlStringBuilder(); 175 xml.halfOpenElement("query"); 176 xml.xmlnsAttribute(NAMESPACE); 177 xml.optAttribute("node", getNode()); 178 xml.rightAngelBracket(); 179 for (Identity identity : identities) { 180 xml.append(identity.toXML()); 181 } 182 for (Feature feature : features) { 183 xml.append(feature.toXML()); 184 } 185 // Add packet extensions, if any are defined. 186 xml.append(getExtensionsXML()); 187 xml.closeElement("query"); 188 return xml; 189 } 190 191 /** 192 * Test if a DiscoverInfo response contains duplicate identities. 193 * 194 * @return true if duplicate identities where found, otherwise false 195 */ 196 public boolean containsDuplicateIdentities() { 197 List<Identity> checkedIdentities = new LinkedList<Identity>(); 198 for (Identity i : identities) { 199 for (Identity i2 : checkedIdentities) { 200 if (i.equals(i2)) 201 return true; 202 } 203 checkedIdentities.add(i); 204 } 205 return false; 206 } 207 208 /** 209 * Test if a DiscoverInfo response contains duplicate features. 210 * 211 * @return true if duplicate identities where found, otherwise false 212 */ 213 public boolean containsDuplicateFeatures() { 214 List<Feature> checkedFeatures = new LinkedList<Feature>(); 215 for (Feature f : features) { 216 for (Feature f2 : checkedFeatures) { 217 if (f.equals(f2)) 218 return true; 219 } 220 checkedFeatures.add(f); 221 } 222 return false; 223 } 224 225 @Override 226 public DiscoverInfo clone() { 227 return new DiscoverInfo(this); 228 } 229 230 /** 231 * Represents the identity of a given XMPP entity. An entity may have many identities but all 232 * the identities SHOULD have the same name.<p> 233 * 234 * Refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a> 235 * in order to get the official registry of values for the <i>category</i> and <i>type</i> 236 * attributes. 237 * 238 */ 239 public static class Identity implements Comparable<Identity>, Cloneable { 240 241 private final String category; 242 private String name; 243 private final String type; 244 private String lang; // 'xml:lang; 245 246 public Identity(Identity identity) { 247 this(identity.category, identity.name, identity.type); 248 lang = identity.lang; 249 } 250 251 /** 252 * Creates a new identity for an XMPP entity. 253 * 'category' and 'type' are required by 254 * <a href="http://xmpp.org/extensions/xep-0030.html#schemas">XEP-30 XML Schemas</a> 255 * 256 * @param category the entity's category (required as per XEP-30). 257 * @param name the entity's name. 258 * @param type the entity's type (required as per XEP-30). 259 */ 260 public Identity(String category, String name, String type) { 261 if ((category == null) || (type == null)) 262 throw new IllegalArgumentException("category and type cannot be null"); 263 264 this.category = category; 265 this.name = name; 266 this.type = type; 267 } 268 269 /** 270 * Returns the entity's category. To get the official registry of values for the 271 * 'category' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a> 272 * 273 * @return the entity's category. 274 */ 275 public String getCategory() { 276 return category; 277 } 278 279 /** 280 * Returns the identity's name. 281 * 282 * @return the identity's name. 283 */ 284 public String getName() { 285 return name; 286 } 287 288 /** 289 * Set the identity's name. 290 * 291 * @param name 292 */ 293 public void setName(String name) { 294 this.name = name; 295 } 296 297 /** 298 * Returns the entity's type. To get the official registry of values for the 299 * 'type' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a> 300 * 301 * @return the entity's type. 302 */ 303 public String getType() { 304 return type; 305 } 306 307 /** 308 * Sets the natural language (xml:lang) for this identity (optional) 309 * 310 * @param lang the xml:lang of this Identity 311 */ 312 public void setLanguage(String lang) { 313 this.lang = lang; 314 } 315 316 /** 317 * Returns the identities natural language if one is set 318 * 319 * @return the value of xml:lang of this Identity 320 */ 321 public String getLanguage() { 322 return lang; 323 } 324 325 public XmlStringBuilder toXML() { 326 XmlStringBuilder xml = new XmlStringBuilder(); 327 xml.halfOpenElement("identity"); 328 xml.xmllangAttribute(lang); 329 xml.attribute("category", category); 330 xml.optAttribute("name", name); 331 xml.optAttribute("type", type); 332 xml.closeEmptyElement(); 333 return xml; 334 } 335 336 /** 337 * Check equality for Identity for category, type, lang and name 338 * in that order as defined by 339 * <a href="http://xmpp.org/extensions/xep-0115.html#ver-proc">XEP-0015 5.4 Processing Method (Step 3.3)</a> 340 * 341 */ 342 public boolean equals(Object obj) { 343 if (obj == null) 344 return false; 345 if (obj == this) 346 return true; 347 if (obj.getClass() != getClass()) 348 return false; 349 350 DiscoverInfo.Identity other = (DiscoverInfo.Identity) obj; 351 if (!this.category.equals(other.category)) 352 return false; 353 354 String otherLang = other.lang == null ? "" : other.lang; 355 String thisLang = lang == null ? "" : lang; 356 if (!otherLang.equals(thisLang)) 357 return false; 358 359 // This safeguard can be removed once the deprecated constructor is removed. 360 String otherType = other.type == null ? "" : other.type; 361 String thisType = type == null ? "" : type; 362 if (!otherType.equals(thisType)) 363 return false; 364 365 String otherName = other.name == null ? "" : other.name; 366 String thisName = name == null ? "" : other.name; 367 if (!thisName.equals(otherName)) 368 return false; 369 370 return true; 371 } 372 373 @Override 374 public int hashCode() { 375 int result = 1; 376 result = 37 * result + category.hashCode(); 377 result = 37 * result + (lang == null ? 0 : lang.hashCode()); 378 result = 37 * result + (type == null ? 0 : type.hashCode()); 379 result = 37 * result + (name == null ? 0 : name.hashCode()); 380 return result; 381 } 382 383 /** 384 * Compares this identity with another one. The comparison order is: Category, Type, Lang. 385 * If all three are identical the other Identity is considered equal. Name is not used for 386 * comparison, as defined by XEP-0115 387 * 388 * @param other 389 * @return a negative integer, zero, or a positive integer as this object is less than, 390 * equal to, or greater than the specified object. 391 */ 392 public int compareTo(DiscoverInfo.Identity other) { 393 String otherLang = other.lang == null ? "" : other.lang; 394 String thisLang = lang == null ? "" : lang; 395 396 // This can be removed once the deprecated constructor is removed. 397 String otherType = other.type == null ? "" : other.type; 398 String thisType = type == null ? "" : type; 399 400 if (category.equals(other.category)) { 401 if (thisType.equals(otherType)) { 402 if (thisLang.equals(otherLang)) { 403 // Don't compare on name, XEP-30 says that name SHOULD 404 // be equals for all identities of an entity 405 return 0; 406 } else { 407 return thisLang.compareTo(otherLang); 408 } 409 } else { 410 return thisType.compareTo(otherType); 411 } 412 } else { 413 return category.compareTo(other.category); 414 } 415 } 416 417 @Override 418 public Identity clone() { 419 return new Identity(this); 420 } 421 } 422 423 /** 424 * Represents the features offered by the item. This information helps requestors determine 425 * what actions are possible with regard to this item (registration, search, join, etc.) 426 * as well as specific feature types of interest, if any (e.g., for the purpose of feature 427 * negotiation). 428 */ 429 public static class Feature implements Cloneable { 430 431 private final String variable; 432 433 public Feature(Feature feature) { 434 this.variable = feature.variable; 435 } 436 437 /** 438 * Creates a new feature offered by an XMPP entity or item. 439 * 440 * @param variable the feature's variable. 441 */ 442 public Feature(String variable) { 443 if (variable == null) 444 throw new IllegalArgumentException("variable cannot be null"); 445 this.variable = variable; 446 } 447 448 /** 449 * Returns the feature's variable. 450 * 451 * @return the feature's variable. 452 */ 453 public String getVar() { 454 return variable; 455 } 456 457 public XmlStringBuilder toXML() { 458 XmlStringBuilder xml = new XmlStringBuilder(); 459 xml.halfOpenElement("feature"); 460 xml.attribute("var", variable); 461 xml.closeEmptyElement(); 462 return xml; 463 } 464 465 public boolean equals(Object obj) { 466 if (obj == null) 467 return false; 468 if (obj == this) 469 return true; 470 if (obj.getClass() != getClass()) 471 return false; 472 473 DiscoverInfo.Feature other = (DiscoverInfo.Feature) obj; 474 return variable.equals(other.variable); 475 } 476 477 @Override 478 public int hashCode() { 479 return 37 * variable.hashCode(); 480 } 481 482 @Override 483 public Feature clone() { 484 return new Feature(this); 485 } 486 } 487}