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; 019 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.Iterator; 023import java.util.List; 024 025import org.jivesoftware.smack.Manager; 026import org.jivesoftware.smack.SmackException.NoResponseException; 027import org.jivesoftware.smack.SmackException.NotConnectedException; 028import org.jivesoftware.smack.XMPPConnection; 029import org.jivesoftware.smack.XMPPException.XMPPErrorException; 030import org.jivesoftware.smack.packet.IQ; 031import org.jivesoftware.smack.packet.Presence; 032import org.jivesoftware.smack.packet.Presence.Type; 033import org.jivesoftware.smack.roster.packet.RosterPacket; 034 035import org.jxmpp.jid.BareJid; 036 037 038/** 039 * Each user in your roster is represented by a roster entry, which contains the user's 040 * JID and a name or nickname you assign. 041 * 042 * @author Matt Tucker 043 * @author Florian Schmaus 044 */ 045public final class RosterEntry extends Manager { 046 047 private RosterPacket.Item item; 048 final private Roster roster; 049 050 /** 051 * Creates a new roster entry. 052 * 053 * @param item the Roster Stanza's Item entry. 054 * @param roster The Roster managing this entry. 055 * @param connection a connection to the XMPP server. 056 */ 057 RosterEntry(RosterPacket.Item item, Roster roster, XMPPConnection connection) { 058 super(connection); 059 this.item = item; 060 this.roster = roster; 061 } 062 063 /** 064 * Returns the JID of the user associated with this entry. 065 * 066 * @return the user associated with this entry. 067 * @deprecated use {@link #getJid()} instead. 068 */ 069 @Deprecated 070 public String getUser() { 071 return getJid().toString(); 072 } 073 074 /** 075 * Returns the JID associated with this entry. 076 * 077 * @return the user associated with this entry. 078 */ 079 public BareJid getJid() { 080 return item.getJid(); 081 } 082 083 /** 084 * Returns the name associated with this entry. 085 * 086 * @return the name. 087 */ 088 public String getName() { 089 return item.getName(); 090 } 091 092 /** 093 * Sets the name associated with this entry. 094 * 095 * @param name the name. 096 * @throws NotConnectedException 097 * @throws XMPPErrorException 098 * @throws NoResponseException 099 * @throws InterruptedException 100 */ 101 public synchronized void setName(String name) throws NotConnectedException, NoResponseException, XMPPErrorException, InterruptedException { 102 // Do nothing if the name hasn't changed. 103 if (name != null && name.equals(getName())) { 104 return; 105 } 106 107 RosterPacket packet = new RosterPacket(); 108 packet.setType(IQ.Type.set); 109 110 // Create a new roster item with the current RosterEntry and the *new* name. Note that we can't set the name of 111 // RosterEntry right away, as otherwise the updated event wont get fired, because equalsDeep would return true. 112 packet.addRosterItem(toRosterItem(this, name)); 113 connection().createStanzaCollectorAndSend(packet).nextResultOrThrow(); 114 115 // We have received a result response to the IQ set, the name was successfully changed 116 item.setName(name); 117 } 118 119 /** 120 * Updates the state of the entry with the new values. 121 * 122 * @param name the nickname for the entry. 123 * @param type the subscription type. 124 * @param subscriptionPending TODO 125 */ 126 void updateItem(RosterPacket.Item item) { 127 assert (item != null); 128 this.item = item; 129 } 130 131 /** 132 * Returns the pre-approval state of this entry. 133 * 134 * @return the pre-approval state. 135 */ 136 public boolean isApproved() { 137 return item.isApproved(); 138 } 139 140 /** 141 * Returns an copied list of the roster groups that this entry belongs to. 142 * 143 * @return an iterator for the groups this entry belongs to. 144 */ 145 public List<RosterGroup> getGroups() { 146 List<RosterGroup> results = new ArrayList<RosterGroup>(); 147 // Loop through all roster groups and find the ones that contain this 148 // entry. This algorithm should be fine 149 for (RosterGroup group : roster.getGroups()) { 150 if (group.contains(this)) { 151 results.add(group); 152 } 153 } 154 return results; 155 } 156 157 /** 158 * Returns the roster subscription type of the entry. When the type is 159 * RosterPacket.ItemType.none or RosterPacket.ItemType.from, 160 * refer to {@link RosterEntry getStatus()} to see if a subscription request 161 * is pending. 162 * 163 * @return the type. 164 */ 165 public RosterPacket.ItemType getType() { 166 return item.getItemType(); 167 } 168 169 /** 170 * Returns the roster subscription request status of the entry. If 171 * {@code true}, then the contact did not answer the subscription request 172 * yet. 173 * 174 * @return the status. 175 * @since 4.2 176 */ 177 public boolean isSubscriptionPending() { 178 return item.isSubscriptionPending(); 179 } 180 181 /** 182 * Check if the contact is subscribed to "my" presence. This allows the contact to see the presence information. 183 * 184 * @return true if the contact has a presence subscription. 185 * @since 4.2 186 */ 187 public boolean canSeeMyPresence() { 188 switch (getType()) { 189 case from: 190 case both: 191 return true; 192 default: 193 return false; 194 } 195 } 196 197 /** 198 * Check if we are subscribed to the contact's presence. If <code>true</code> then the contact has allowed us to 199 * receive presence information. 200 * 201 * @return true if we are subscribed to the contact's presence. 202 * @since 4.2 203 */ 204 public boolean canSeeHisPresence() { 205 switch (getType()) { 206 case to: 207 case both: 208 return true; 209 default: 210 return false; 211 } 212 } 213 214 /** 215 * Cancel the presence subscription the XMPP entity representing this roster entry has with us. 216 * 217 * @throws NotConnectedException 218 * @throws InterruptedException 219 * @since 4.2 220 */ 221 public void cancelSubscription() throws NotConnectedException, InterruptedException { 222 Presence unsubscribed = new Presence(item.getJid(), Type.unsubscribed); 223 connection().sendStanza(unsubscribed); 224 } 225 226 @Override 227 public String toString() { 228 StringBuilder buf = new StringBuilder(); 229 if (getName() != null) { 230 buf.append(getName()).append(": "); 231 } 232 buf.append(getJid()); 233 Collection<RosterGroup> groups = getGroups(); 234 if (!groups.isEmpty()) { 235 buf.append(" ["); 236 Iterator<RosterGroup> iter = groups.iterator(); 237 RosterGroup group = iter.next(); 238 buf.append(group.getName()); 239 while (iter.hasNext()) { 240 buf.append(", "); 241 group = iter.next(); 242 buf.append(group.getName()); 243 } 244 buf.append(']'); 245 } 246 return buf.toString(); 247 } 248 249 @Override 250 public int hashCode() { 251 return getJid().hashCode(); 252 } 253 254 @Override 255 public boolean equals(Object object) { 256 if (this == object) { 257 return true; 258 } 259 if (object != null && object instanceof RosterEntry) { 260 return getJid().equals(((RosterEntry) object).getJid()); 261 } 262 else { 263 return false; 264 } 265 } 266 267 /** 268 * Indicates whether some other object is "equal to" this by comparing all members. 269 * <p> 270 * The {@link #equals(Object)} method returns <code>true</code> if the user JIDs are equal. 271 * 272 * @param obj the reference object with which to compare. 273 * @return <code>true</code> if this object is the same as the obj argument; <code>false</code> 274 * otherwise. 275 */ 276 public boolean equalsDeep(Object obj) { 277 if (this == obj) 278 return true; 279 if (obj == null) 280 return false; 281 if (getClass() != obj.getClass()) 282 return false; 283 RosterEntry other = (RosterEntry) obj; 284 return other.item.equals(this.item); 285 } 286 287 /** 288 * Convert the RosterEntry to a Roster stanza <item/> element. 289 * 290 * @param entry the roster entry. 291 * @return the roster item. 292 */ 293 static RosterPacket.Item toRosterItem(RosterEntry entry) { 294 return toRosterItem(entry, entry.getName(), false); 295 } 296 297 /** 298 * Convert the RosterEntry to a Roster stanza <item/> element. 299 * 300 * @param entry the roster entry 301 * @param name the name of the roster item. 302 * @return the roster item. 303 */ 304 static RosterPacket.Item toRosterItem(RosterEntry entry, String name) { 305 return toRosterItem(entry, name, false); 306 } 307 308 static RosterPacket.Item toRosterItem(RosterEntry entry, boolean includeAskAttribute) { 309 return toRosterItem(entry, entry.getName(), includeAskAttribute); 310 } 311 312 /** 313 * Convert a roster entry with the given name to a roster item. As per RFC 6121 ยง 2.1.2.2., clients MUST NOT include 314 * the 'ask' attribute, thus set {@code includeAskAttribute} to {@code false}. 315 * 316 * @param entry the roster entry. 317 * @param name the name of the roster item. 318 * @param includeAskAttribute whether or not to include the 'ask' attribute. 319 * @return the roster item. 320 */ 321 private static RosterPacket.Item toRosterItem(RosterEntry entry, String name, boolean includeAskAttribute) { 322 RosterPacket.Item item = new RosterPacket.Item(entry.getJid(), name); 323 item.setItemType(entry.getType()); 324 if (includeAskAttribute) { 325 item.setSubscriptionPending(entry.isSubscriptionPending()); 326 } 327 item.setApproved(entry.isApproved()); 328 // Set the correct group names for the item. 329 for (RosterGroup group : entry.getGroups()) { 330 item.addGroupName(group.getName()); 331 } 332 return item; 333 } 334 335}