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