1/** 2 * $RCSfile$ 3 * $Revision$ 4 * $Date$ 5 * 6 * Copyright 2003-2007 Jive Software. 7 * 8 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21package org.jivesoftware.smack; 22 23import org.jivesoftware.smack.filter.PacketIDFilter; 24import org.jivesoftware.smack.packet.IQ; 25import org.jivesoftware.smack.packet.RosterPacket; 26import org.jivesoftware.smack.util.StringUtils; 27 28import java.util.ArrayList; 29import java.util.Collection; 30import java.util.Collections; 31import java.util.List; 32 33/** 34 * A group of roster entries. 35 * 36 * @see Roster#getGroup(String) 37 * @author Matt Tucker 38 */ 39public class RosterGroup { 40 41 private String name; 42 private Connection connection; 43 private final List<RosterEntry> entries; 44 45 /** 46 * Creates a new roster group instance. 47 * 48 * @param name the name of the group. 49 * @param connection the connection the group belongs to. 50 */ 51 RosterGroup(String name, Connection connection) { 52 this.name = name; 53 this.connection = connection; 54 entries = new ArrayList<RosterEntry>(); 55 } 56 57 /** 58 * Returns the name of the group. 59 * 60 * @return the name of the group. 61 */ 62 public String getName() { 63 return name; 64 } 65 66 /** 67 * Sets the name of the group. Changing the group's name is like moving all the group entries 68 * of the group to a new group specified by the new name. Since this group won't have entries 69 * it will be removed from the roster. This means that all the references to this object will 70 * be invalid and will need to be updated to the new group specified by the new name. 71 * 72 * @param name the name of the group. 73 */ 74 public void setName(String name) { 75 synchronized (entries) { 76 for (RosterEntry entry : entries) { 77 RosterPacket packet = new RosterPacket(); 78 packet.setType(IQ.Type.SET); 79 RosterPacket.Item item = RosterEntry.toRosterItem(entry); 80 item.removeGroupName(this.name); 81 item.addGroupName(name); 82 packet.addRosterItem(item); 83 connection.sendPacket(packet); 84 } 85 } 86 } 87 88 /** 89 * Returns the number of entries in the group. 90 * 91 * @return the number of entries in the group. 92 */ 93 public int getEntryCount() { 94 synchronized (entries) { 95 return entries.size(); 96 } 97 } 98 99 /** 100 * Returns an unmodifiable collection of all entries in the group. 101 * 102 * @return all entries in the group. 103 */ 104 public Collection<RosterEntry> getEntries() { 105 synchronized (entries) { 106 return Collections.unmodifiableList(new ArrayList<RosterEntry>(entries)); 107 } 108 } 109 110 /** 111 * Returns the roster entry associated with the given XMPP address or 112 * <tt>null</tt> if the user is not an entry in the group. 113 * 114 * @param user the XMPP address of the user (eg "jsmith@example.com"). 115 * @return the roster entry or <tt>null</tt> if it does not exist in the group. 116 */ 117 public RosterEntry getEntry(String user) { 118 if (user == null) { 119 return null; 120 } 121 // Roster entries never include a resource so remove the resource 122 // if it's a part of the XMPP address. 123 user = StringUtils.parseBareAddress(user); 124 String userLowerCase = user.toLowerCase(); 125 synchronized (entries) { 126 for (RosterEntry entry : entries) { 127 if (entry.getUser().equals(userLowerCase)) { 128 return entry; 129 } 130 } 131 } 132 return null; 133 } 134 135 /** 136 * Returns true if the specified entry is part of this group. 137 * 138 * @param entry a roster entry. 139 * @return true if the entry is part of this group. 140 */ 141 public boolean contains(RosterEntry entry) { 142 synchronized (entries) { 143 return entries.contains(entry); 144 } 145 } 146 147 /** 148 * Returns true if the specified XMPP address is an entry in this group. 149 * 150 * @param user the XMPP address of the user. 151 * @return true if the XMPP address is an entry in this group. 152 */ 153 public boolean contains(String user) { 154 return getEntry(user) != null; 155 } 156 157 /** 158 * Adds a roster entry to this group. If the entry was unfiled then it will be removed from 159 * the unfiled list and will be added to this group. 160 * Note that this is an asynchronous call -- Smack must wait for the server 161 * to receive the updated roster. 162 * 163 * @param entry a roster entry. 164 * @throws XMPPException if an error occured while trying to add the entry to the group. 165 */ 166 public void addEntry(RosterEntry entry) throws XMPPException { 167 PacketCollector collector = null; 168 // Only add the entry if it isn't already in the list. 169 synchronized (entries) { 170 if (!entries.contains(entry)) { 171 RosterPacket packet = new RosterPacket(); 172 packet.setType(IQ.Type.SET); 173 RosterPacket.Item item = RosterEntry.toRosterItem(entry); 174 item.addGroupName(getName()); 175 packet.addRosterItem(item); 176 // Wait up to a certain number of seconds for a reply from the server. 177 collector = connection 178 .createPacketCollector(new PacketIDFilter(packet.getPacketID())); 179 connection.sendPacket(packet); 180 } 181 } 182 if (collector != null) { 183 IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); 184 collector.cancel(); 185 if (response == null) { 186 throw new XMPPException("No response from the server."); 187 } 188 // If the server replied with an error, throw an exception. 189 else if (response.getType() == IQ.Type.ERROR) { 190 throw new XMPPException(response.getError()); 191 } 192 } 193 } 194 195 /** 196 * Removes a roster entry from this group. If the entry does not belong to any other group 197 * then it will be considered as unfiled, therefore it will be added to the list of unfiled 198 * entries. 199 * Note that this is an asynchronous call -- Smack must wait for the server 200 * to receive the updated roster. 201 * 202 * @param entry a roster entry. 203 * @throws XMPPException if an error occured while trying to remove the entry from the group. 204 */ 205 public void removeEntry(RosterEntry entry) throws XMPPException { 206 PacketCollector collector = null; 207 // Only remove the entry if it's in the entry list. 208 // Remove the entry locally, if we wait for RosterPacketListenerprocess>>Packet(Packet) 209 // to take place the entry will exist in the group until a packet is received from the 210 // server. 211 synchronized (entries) { 212 if (entries.contains(entry)) { 213 RosterPacket packet = new RosterPacket(); 214 packet.setType(IQ.Type.SET); 215 RosterPacket.Item item = RosterEntry.toRosterItem(entry); 216 item.removeGroupName(this.getName()); 217 packet.addRosterItem(item); 218 // Wait up to a certain number of seconds for a reply from the server. 219 collector = connection 220 .createPacketCollector(new PacketIDFilter(packet.getPacketID())); 221 connection.sendPacket(packet); 222 } 223 } 224 if (collector != null) { 225 IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); 226 collector.cancel(); 227 if (response == null) { 228 throw new XMPPException("No response from the server."); 229 } 230 // If the server replied with an error, throw an exception. 231 else if (response.getType() == IQ.Type.ERROR) { 232 throw new XMPPException(response.getError()); 233 } 234 } 235 } 236 237 public void addEntryLocal(RosterEntry entry) { 238 // Only add the entry if it isn't already in the list. 239 synchronized (entries) { 240 entries.remove(entry); 241 entries.add(entry); 242 } 243 } 244 245 void removeEntryLocal(RosterEntry entry) { 246 // Only remove the entry if it's in the entry list. 247 synchronized (entries) { 248 if (entries.contains(entry)) { 249 entries.remove(entry); 250 } 251 } 252 } 253}