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 */ 017 018package org.jivesoftware.smack.roster.packet; 019 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.List; 023import java.util.Locale; 024import java.util.Set; 025import java.util.concurrent.CopyOnWriteArraySet; 026 027import javax.xml.namespace.QName; 028 029import org.jivesoftware.smack.packet.ExtensionElement; 030import org.jivesoftware.smack.packet.IQ; 031import org.jivesoftware.smack.packet.Stanza; 032import org.jivesoftware.smack.util.EqualsUtil; 033import org.jivesoftware.smack.util.HashCode; 034import org.jivesoftware.smack.util.Objects; 035import org.jivesoftware.smack.util.StringUtils; 036import org.jivesoftware.smack.util.XmlStringBuilder; 037 038import org.jxmpp.jid.BareJid; 039 040/** 041 * Represents XMPP roster packets. 042 * 043 * @author Matt Tucker 044 * @author Florian Schmaus 045 */ 046public final class RosterPacket extends IQ { 047 048 public static final String ELEMENT = QUERY_ELEMENT; 049 public static final String NAMESPACE = "jabber:iq:roster"; 050 051 private final List<Item> rosterItems = new ArrayList<>(); 052 private String rosterVersion; 053 054 public RosterPacket() { 055 super(ELEMENT, NAMESPACE); 056 } 057 058 /** 059 * Adds a roster item to the packet. 060 * 061 * @param item a roster item. 062 */ 063 public void addRosterItem(Item item) { 064 synchronized (rosterItems) { 065 rosterItems.add(item); 066 } 067 } 068 069 /** 070 * Returns the number of roster items in this roster packet. 071 * 072 * @return the number of roster items. 073 */ 074 public int getRosterItemCount() { 075 synchronized (rosterItems) { 076 return rosterItems.size(); 077 } 078 } 079 080 /** 081 * Returns a copied list of the roster items in the packet. 082 * 083 * @return a copied list of the roster items in the packet. 084 */ 085 public List<Item> getRosterItems() { 086 synchronized (rosterItems) { 087 return new ArrayList<>(rosterItems); 088 } 089 } 090 091 @Override 092 protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder buf) { 093 buf.optAttribute("ver", rosterVersion); 094 buf.rightAngleBracket(); 095 096 synchronized (rosterItems) { 097 for (Item entry : rosterItems) { 098 buf.append(entry.toXML()); 099 } 100 } 101 return buf; 102 } 103 104 public String getVersion() { 105 return rosterVersion; 106 } 107 108 public void setVersion(String version) { 109 rosterVersion = version; 110 } 111 112 /** 113 * A roster item, which consists of a JID, their name, the type of subscription, and 114 * the groups the roster item belongs to. 115 */ 116 // TODO Make this class immutable. 117 public static final class Item implements ExtensionElement { 118 119 /** 120 * The constant value "{@value}". 121 */ 122 public static final String ELEMENT = Stanza.ITEM; 123 124 public static final QName QNAME = new QName(NAMESPACE, ELEMENT); 125 126 public static final String GROUP = "group"; 127 128 private final BareJid jid; 129 130 /** 131 * TODO describe me. With link to the RFC. Is ask= attribute. 132 */ 133 private boolean subscriptionPending; 134 135 // TODO Make immutable. 136 private String name; 137 private ItemType itemType = ItemType.none; 138 private boolean approved; 139 private final Set<String> groupNames; 140 141 /** 142 * Creates a new roster item. 143 * 144 * @param jid TODO javadoc me please 145 * @param name TODO javadoc me please 146 */ 147 public Item(BareJid jid, String name) { 148 this(jid, name, false); 149 } 150 151 /** 152 * Creates a new roster item. 153 * 154 * @param jid the jid. 155 * @param name the user's name. 156 * @param subscriptionPending TODO javadoc me please 157 */ 158 public Item(BareJid jid, String name, boolean subscriptionPending) { 159 this.jid = Objects.requireNonNull(jid); 160 this.name = name; 161 this.subscriptionPending = subscriptionPending; 162 groupNames = new CopyOnWriteArraySet<>(); 163 } 164 165 @Override 166 public String getElementName() { 167 return QNAME.getLocalPart(); 168 } 169 170 @Override 171 public String getNamespace() { 172 return QNAME.getNamespaceURI(); 173 } 174 175 /** 176 * Returns the user. 177 * 178 * @return the user. 179 * @deprecated use {@link #getJid()} instead. 180 */ 181 @Deprecated 182 public String getUser() { 183 return jid.toString(); 184 } 185 186 /** 187 * Returns the JID of this item. 188 * 189 * @return the JID. 190 */ 191 public BareJid getJid() { 192 return jid; 193 } 194 195 /** 196 * Returns the user's name. 197 * 198 * @return the user's name. 199 */ 200 public String getName() { 201 return name; 202 } 203 204 /** 205 * Sets the user's name. 206 * 207 * @param name the user's name. 208 */ 209 public void setName(String name) { 210 this.name = name; 211 } 212 213 /** 214 * Returns the roster item type. 215 * 216 * @return the roster item type. 217 */ 218 public ItemType getItemType() { 219 return itemType; 220 } 221 222 /** 223 * Sets the roster item type. 224 * 225 * @param itemType the roster item type. 226 */ 227 public void setItemType(ItemType itemType) { 228 this.itemType = Objects.requireNonNull(itemType, "itemType must not be null"); 229 } 230 231 public void setSubscriptionPending(boolean subscriptionPending) { 232 this.subscriptionPending = subscriptionPending; 233 } 234 235 public boolean isSubscriptionPending() { 236 return subscriptionPending; 237 } 238 239 /** 240 * Returns the roster item pre-approval state. 241 * 242 * @return the pre-approval state. 243 */ 244 public boolean isApproved() { 245 return approved; 246 } 247 248 /** 249 * Sets the roster item pre-approval state. 250 * 251 * @param approved the pre-approval flag. 252 */ 253 public void setApproved(boolean approved) { 254 this.approved = approved; 255 } 256 257 /** 258 * Returns an unmodifiable set of the group names that the roster item 259 * belongs to. 260 * 261 * @return an unmodifiable set of the group names. 262 */ 263 public Set<String> getGroupNames() { 264 return Collections.unmodifiableSet(groupNames); 265 } 266 267 /** 268 * Adds a group name. 269 * 270 * @param groupName the group name. 271 */ 272 public void addGroupName(String groupName) { 273 groupNames.add(groupName); 274 } 275 276 /** 277 * Removes a group name. 278 * 279 * @param groupName the group name. 280 */ 281 public void removeGroupName(String groupName) { 282 groupNames.remove(groupName); 283 } 284 285 @Override 286 public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { 287 XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); 288 xml.attribute("jid", jid); 289 xml.optAttribute("name", name); 290 xml.optAttribute("subscription", itemType); 291 if (subscriptionPending) { 292 xml.append(" ask='subscribe'"); 293 } 294 xml.optBooleanAttribute("approved", approved); 295 xml.rightAngleBracket(); 296 297 for (String groupName : groupNames) { 298 xml.openElement(GROUP).escape(groupName).closeElement(GROUP); 299 } 300 xml.closeElement(this); 301 return xml; 302 } 303 304 @Override 305 public int hashCode() { 306 return HashCode.builder() 307 .append(groupNames) 308 .append(subscriptionPending) 309 .append(itemType) 310 .append(name) 311 .append(jid) 312 .append(approved) 313 .build(); 314 } 315 316 @Override 317 public boolean equals(Object obj) { 318 return EqualsUtil.equals(this, obj, (e, o) -> 319 e.append(groupNames, o.groupNames) 320 .append(subscriptionPending, o.subscriptionPending) 321 .append(itemType, o.itemType) 322 .append(name, o.name) 323 .append(jid, o.jid) 324 .append(approved, o.approved) 325 ); 326 } 327 328 } 329 330 public enum ItemType { 331 332 /** 333 * The user does not have a subscription to the contact's presence, and the contact does not 334 * have a subscription to the user's presence; this is the default value, so if the 335 * subscription attribute is not included then the state is to be understood as "none". 336 */ 337 none('⊥'), 338 339 /** 340 * The user has a subscription to the contact's presence, but the contact does not have a 341 * subscription to the user's presence. 342 */ 343 to('←'), 344 345 /** 346 * The contact has a subscription to the user's presence, but the user does not have a 347 * subscription to the contact's presence. 348 */ 349 from('→'), 350 351 /** 352 * The user and the contact have subscriptions to each other's presence (also called a 353 * "mutual subscription"). 354 */ 355 both('↔'), 356 357 /** 358 * The user wishes to stop receiving presence updates from the subscriber. 359 */ 360 remove('⚡'), 361 ; 362 363 364 private static final char ME = '●'; 365 366 private final String symbol; 367 368 ItemType(char secondSymbolChar) { 369 StringBuilder sb = new StringBuilder(2); 370 sb.append(ME).append(secondSymbolChar); 371 symbol = sb.toString(); 372 } 373 374 public static ItemType fromString(String string) { 375 if (StringUtils.isNullOrEmpty(string)) { 376 return none; 377 } 378 return ItemType.valueOf(string.toLowerCase(Locale.US)); 379 } 380 381 /** 382 * Get a String containing symbols representing the item type. The first symbol in the 383 * string is a big dot, representing the local entity. The second symbol represents the 384 * established subscription relation and is typically an arrow. The head(s) of the arrow 385 * point in the direction presence messages are sent. For example, if there is only a head 386 * pointing to the big dot, then the local user will receive presence information from the 387 * remote entity. 388 * 389 * @return the symbolic representation of this item type. 390 */ 391 public String asSymbol() { 392 return symbol; 393 } 394 } 395}