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 org.jivesoftware.smack.packet.IQ; 021import org.jivesoftware.smack.packet.Stanza; 022import org.jivesoftware.smack.util.XmlStringBuilder; 023 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.List; 027import java.util.Locale; 028import java.util.Set; 029import java.util.concurrent.CopyOnWriteArraySet; 030 031/** 032 * Represents XMPP roster packets. 033 * 034 * @author Matt Tucker 035 */ 036public class RosterPacket extends IQ { 037 038 public static final String ELEMENT = QUERY_ELEMENT; 039 public static final String NAMESPACE = "jabber:iq:roster"; 040 041 private final List<Item> rosterItems = new ArrayList<Item>(); 042 private String rosterVersion; 043 044 public RosterPacket() { 045 super(ELEMENT, NAMESPACE); 046 } 047 048 /** 049 * Adds a roster item to the packet. 050 * 051 * @param item a roster item. 052 */ 053 public void addRosterItem(Item item) { 054 synchronized (rosterItems) { 055 rosterItems.add(item); 056 } 057 } 058 059 /** 060 * Returns the number of roster items in this roster packet. 061 * 062 * @return the number of roster items. 063 */ 064 public int getRosterItemCount() { 065 synchronized (rosterItems) { 066 return rosterItems.size(); 067 } 068 } 069 070 /** 071 * Returns a copied list of the roster items in the packet. 072 * 073 * @return a copied list of the roster items in the packet. 074 */ 075 public List<Item> getRosterItems() { 076 synchronized (rosterItems) { 077 return new ArrayList<Item>(rosterItems); 078 } 079 } 080 081 @Override 082 protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder buf) { 083 buf.optAttribute("ver", rosterVersion); 084 buf.rightAngleBracket(); 085 086 synchronized (rosterItems) { 087 for (Item entry : rosterItems) { 088 buf.append(entry.toXML()); 089 } 090 } 091 return buf; 092 } 093 094 public String getVersion() { 095 return rosterVersion; 096 } 097 098 public void setVersion(String version) { 099 rosterVersion = version; 100 } 101 102 /** 103 * A roster item, which consists of a JID, their name, the type of subscription, and 104 * the groups the roster item belongs to. 105 */ 106 public static class Item { 107 108 public static final String GROUP = "group"; 109 110 private String user; 111 private String name; 112 private ItemType itemType; 113 private ItemStatus itemStatus; 114 private final Set<String> groupNames; 115 116 /** 117 * Creates a new roster item. 118 * 119 * @param user the user. 120 * @param name the user's name. 121 */ 122 public Item(String user, String name) { 123 this.user = user.toLowerCase(Locale.US); 124 this.name = name; 125 itemType = null; 126 itemStatus = null; 127 groupNames = new CopyOnWriteArraySet<String>(); 128 } 129 130 /** 131 * Returns the user. 132 * 133 * @return the user. 134 */ 135 public String getUser() { 136 return user; 137 } 138 139 /** 140 * Returns the user's name. 141 * 142 * @return the user's name. 143 */ 144 public String getName() { 145 return name; 146 } 147 148 /** 149 * Sets the user's name. 150 * 151 * @param name the user's name. 152 */ 153 public void setName(String name) { 154 this.name = name; 155 } 156 157 /** 158 * Returns the roster item type. 159 * 160 * @return the roster item type. 161 */ 162 public ItemType getItemType() { 163 return itemType; 164 } 165 166 /** 167 * Sets the roster item type. 168 * 169 * @param itemType the roster item type. 170 */ 171 public void setItemType(ItemType itemType) { 172 this.itemType = itemType; 173 } 174 175 /** 176 * Returns the roster item status. 177 * 178 * @return the roster item status. 179 */ 180 public ItemStatus getItemStatus() { 181 return itemStatus; 182 } 183 184 /** 185 * Sets the roster item status. 186 * 187 * @param itemStatus the roster item status. 188 */ 189 public void setItemStatus(ItemStatus itemStatus) { 190 this.itemStatus = itemStatus; 191 } 192 193 /** 194 * Returns an unmodifiable set of the group names that the roster item 195 * belongs to. 196 * 197 * @return an unmodifiable set of the group names. 198 */ 199 public Set<String> getGroupNames() { 200 return Collections.unmodifiableSet(groupNames); 201 } 202 203 /** 204 * Adds a group name. 205 * 206 * @param groupName the group name. 207 */ 208 public void addGroupName(String groupName) { 209 groupNames.add(groupName); 210 } 211 212 /** 213 * Removes a group name. 214 * 215 * @param groupName the group name. 216 */ 217 public void removeGroupName(String groupName) { 218 groupNames.remove(groupName); 219 } 220 221 public XmlStringBuilder toXML() { 222 XmlStringBuilder xml = new XmlStringBuilder(); 223 xml.halfOpenElement(Stanza.ITEM).attribute("jid", user); 224 xml.optAttribute("name", name); 225 xml.optAttribute("subscription", itemType); 226 xml.optAttribute("ask", itemStatus); 227 xml.rightAngleBracket(); 228 229 for (String groupName : groupNames) { 230 xml.openElement(GROUP).escape(groupName).closeElement(GROUP); 231 } 232 xml.closeElement(Stanza.ITEM); 233 return xml; 234 } 235 236 @Override 237 public int hashCode() { 238 final int prime = 31; 239 int result = 1; 240 result = prime * result + ((groupNames == null) ? 0 : groupNames.hashCode()); 241 result = prime * result + ((itemStatus == null) ? 0 : itemStatus.hashCode()); 242 result = prime * result + ((itemType == null) ? 0 : itemType.hashCode()); 243 result = prime * result + ((name == null) ? 0 : name.hashCode()); 244 result = prime * result + ((user == null) ? 0 : user.hashCode()); 245 return result; 246 } 247 248 @Override 249 public boolean equals(Object obj) { 250 if (this == obj) 251 return true; 252 if (obj == null) 253 return false; 254 if (getClass() != obj.getClass()) 255 return false; 256 Item other = (Item) obj; 257 if (groupNames == null) { 258 if (other.groupNames != null) 259 return false; 260 } 261 else if (!groupNames.equals(other.groupNames)) 262 return false; 263 if (itemStatus != other.itemStatus) 264 return false; 265 if (itemType != other.itemType) 266 return false; 267 if (name == null) { 268 if (other.name != null) 269 return false; 270 } 271 else if (!name.equals(other.name)) 272 return false; 273 if (user == null) { 274 if (other.user != null) 275 return false; 276 } 277 else if (!user.equals(other.user)) 278 return false; 279 return true; 280 } 281 282 } 283 284 /** 285 * The subscription status of a roster item. An optional element that indicates 286 * the subscription status if a change request is pending. 287 */ 288 public static enum ItemStatus { 289 /** 290 * Request to subscribe 291 */ 292 subscribe, 293 294 /** 295 * Request to unsubscribe 296 */ 297 unsubscribe; 298 299 public static final ItemStatus SUBSCRIPTION_PENDING = subscribe; 300 public static final ItemStatus UNSUBSCRIPTION_PENDING = unsubscribe; 301 302 public static ItemStatus fromString(String s) { 303 if (s == null) { 304 return null; 305 } 306 try { 307 return ItemStatus.valueOf(s); 308 } 309 catch (IllegalArgumentException e) { 310 return null; 311 } 312 } 313 } 314 315 public static enum ItemType { 316 317 /** 318 * The user does not have a subscription to the contact's presence, and the contact does not 319 * have a subscription to the user's presence; this is the default value, so if the 320 * subscription attribute is not included then the state is to be understood as "none". 321 */ 322 none, 323 324 /** 325 * The user has a subscription to the contact's presence, but the contact does not have a 326 * subscription to the user's presence. 327 */ 328 to, 329 330 /** 331 * The contact has a subscription to the user's presence, but the user does not have a 332 * subscription to the contact's presence. 333 */ 334 from, 335 336 /** 337 * The user and the contact have subscriptions to each other's presence (also called a 338 * "mutual subscription"). 339 */ 340 both, 341 342 /** 343 * The user wishes to stop receiving presence updates from the subscriber. 344 */ 345 remove 346 } 347}