blob: e768f6d76859eaf10de59e19776391c926fe4a5b [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001/**
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}