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 private final 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 this entries item. 121 * 122 * @param item new item 123 */ 124 void updateItem(RosterPacket.Item item) { 125 assert (item != null); 126 this.item = item; 127 } 128 129 /** 130 * Returns the pre-approval state of this entry. 131 * 132 * @return the pre-approval state. 133 */ 134 public boolean isApproved() { 135 return item.isApproved(); 136 } 137 138 /** 139 * Returns an copied list of the roster groups that this entry belongs to. 140 * 141 * @return an iterator for the groups this entry belongs to. 142 */ 143 public List<RosterGroup> getGroups() { 144 List<RosterGroup> results = new ArrayList<>(); 145 // Loop through all roster groups and find the ones that contain this 146 // entry. This algorithm should be fine 147 for (RosterGroup group : roster.getGroups()) { 148 if (group.contains(this)) { 149 results.add(group); 150 } 151 } 152 return results; 153 } 154 155 /** 156 * Returns the roster subscription type of the entry. When the type is 157 * RosterPacket.ItemType.none or RosterPacket.ItemType.from, 158 * refer to {@link RosterEntry getStatus()} to see if a subscription request 159 * is pending. 160 * 161 * @return the type. 162 */ 163 public RosterPacket.ItemType getType() { 164 return item.getItemType(); 165 } 166 167 /** 168 * Returns the roster subscription request status of the entry. If 169 * {@code true}, then the contact did not answer the subscription request 170 * yet. 171 * 172 * @return the status. 173 * @since 4.2 174 */ 175 public boolean isSubscriptionPending() { 176 return item.isSubscriptionPending(); 177 } 178 179 /** 180 * Check if the contact is subscribed to "my" presence. This allows the contact to see the presence information. 181 * 182 * @return true if the contact has a presence subscription. 183 * @since 4.2 184 */ 185 public boolean canSeeMyPresence() { 186 switch (getType()) { 187 case from: 188 case both: 189 return true; 190 default: 191 return false; 192 } 193 } 194 195 /** 196 * Check if we are subscribed to the contact's presence. If <code>true</code> then the contact has allowed us to 197 * receive presence information. 198 * 199 * @return true if we are subscribed to the contact's presence. 200 * @since 4.2 201 */ 202 public boolean canSeeHisPresence() { 203 switch (getType()) { 204 case to: 205 case both: 206 return true; 207 default: 208 return false; 209 } 210 } 211 212 /** 213 * Cancel the presence subscription the XMPP entity representing this roster entry has with us. 214 * 215 * @throws NotConnectedException 216 * @throws InterruptedException 217 * @since 4.2 218 */ 219 public void cancelSubscription() throws NotConnectedException, InterruptedException { 220 Presence unsubscribed = new Presence(item.getJid(), Type.unsubscribed); 221 connection().sendStanza(unsubscribed); 222 } 223 224 @Override 225 public String toString() { 226 StringBuilder buf = new StringBuilder(); 227 if (getName() != null) { 228 buf.append(getName()).append(": "); 229 } 230 buf.append(getJid()); 231 Collection<RosterGroup> groups = getGroups(); 232 if (!groups.isEmpty()) { 233 buf.append(" ["); 234 Iterator<RosterGroup> iter = groups.iterator(); 235 RosterGroup group = iter.next(); 236 buf.append(group.getName()); 237 while (iter.hasNext()) { 238 buf.append(", "); 239 group = iter.next(); 240 buf.append(group.getName()); 241 } 242 buf.append(']'); 243 } 244 return buf.toString(); 245 } 246 247 @Override 248 public int hashCode() { 249 return getJid().hashCode(); 250 } 251 252 @Override 253 public boolean equals(Object object) { 254 if (this == object) { 255 return true; 256 } 257 if (object != null && object instanceof RosterEntry) { 258 return getJid().equals(((RosterEntry) object).getJid()); 259 } 260 else { 261 return false; 262 } 263 } 264 265 /** 266 * Indicates whether some other object is "equal to" this by comparing all members. 267 * <p> 268 * The {@link #equals(Object)} method returns <code>true</code> if the user JIDs are equal. 269 * 270 * @param obj the reference object with which to compare. 271 * @return <code>true</code> if this object is the same as the obj argument; <code>false</code> 272 * otherwise. 273 */ 274 public boolean equalsDeep(Object obj) { 275 if (this == obj) 276 return true; 277 if (obj == null) 278 return false; 279 if (getClass() != obj.getClass()) 280 return false; 281 RosterEntry other = (RosterEntry) obj; 282 return other.item.equals(this.item); 283 } 284 285 /** 286 * Convert the RosterEntry to a Roster stanza <item/> element. 287 * 288 * @param entry the roster entry. 289 * @return the roster item. 290 */ 291 static RosterPacket.Item toRosterItem(RosterEntry entry) { 292 return toRosterItem(entry, entry.getName(), false); 293 } 294 295 /** 296 * Convert the RosterEntry to a Roster stanza <item/> element. 297 * 298 * @param entry the roster entry 299 * @param name the name of the roster item. 300 * @return the roster item. 301 */ 302 static RosterPacket.Item toRosterItem(RosterEntry entry, String name) { 303 return toRosterItem(entry, name, false); 304 } 305 306 static RosterPacket.Item toRosterItem(RosterEntry entry, boolean includeAskAttribute) { 307 return toRosterItem(entry, entry.getName(), includeAskAttribute); 308 } 309 310 /** 311 * Convert a roster entry with the given name to a roster item. As per RFC 6121 ยง 2.1.2.2., clients MUST NOT include 312 * the 'ask' attribute, thus set {@code includeAskAttribute} to {@code false}. 313 * 314 * @param entry the roster entry. 315 * @param name the name of the roster item. 316 * @param includeAskAttribute whether or not to include the 'ask' attribute. 317 * @return the roster item. 318 */ 319 private static RosterPacket.Item toRosterItem(RosterEntry entry, String name, boolean includeAskAttribute) { 320 RosterPacket.Item item = new RosterPacket.Item(entry.getJid(), name); 321 item.setItemType(entry.getType()); 322 if (includeAskAttribute) { 323 item.setSubscriptionPending(entry.isSubscriptionPending()); 324 } 325 item.setApproved(entry.isApproved()); 326 // Set the correct group names for the item. 327 for (RosterGroup group : entry.getGroups()) { 328 item.addGroupName(group.getName()); 329 } 330 return item; 331 } 332 333}