KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jivesoftware > smack > Roster


1 /**
2  * $RCSfile$
3  * $Revision: 2506 $
4  * $Date: 2005-06-28 14:48:04 -0300 (Tue, 28 Jun 2005) $
5  *
6  * Copyright 2003-2004 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
21 package org.jivesoftware.smack;
22
23 import org.jivesoftware.smack.packet.*;
24 import org.jivesoftware.smack.filter.*;
25 import org.jivesoftware.smack.util.StringUtils;
26
27 import java.util.*;
28
29 /**
30  * Represents a user's roster, which is the collection of users a person receives
31  * presence updates for. Roster items are categorized into groups for easier management.<p>
32  *
33  * Others users may attempt to subscribe to this user using a subscription request. Three
34  * modes are supported for handling these requests: <ul>
35  * <li> SUBSCRIPTION_ACCEPT_ALL -- accept all subscription requests.
36  * <li> SUBSCRIPTION_REJECT_ALL -- reject all subscription requests.
37  * <li> SUBSCRIPTION_MANUAL -- manually process all subscription requests. </ul>
38  *
39  * @see XMPPConnection#getRoster()
40  * @author Matt Tucker
41  */

42 public class Roster {
43
44     /**
45      * Automatically accept all subscription requests. This is the default mode
46      * and is suitable for simple client. More complex client will likely wish to
47      * handle subscription requests manually.
48      */

49     public static final int SUBSCRIPTION_ACCEPT_ALL = 0;
50
51     /**
52      * Automatically reject all subscription requests.
53      */

54     public static final int SUBSCRIPTION_REJECT_ALL = 1;
55
56     /**
57      * Subscription requests are ignored, which means they must be manually
58      * processed by registering a listener for presence packets and then looking
59      * for any presence requests that have the type Presence.Type.SUBSCRIBE.
60      */

61     public static final int SUBSCRIPTION_MANUAL = 2;
62
63     /**
64      * The default subscription processing mode to use when a Roster is created. By default
65      * all subscription requests are automatically accepted.
66      */

67     private static int defaultSubscriptionMode = SUBSCRIPTION_ACCEPT_ALL;
68
69     private XMPPConnection connection;
70     private Map groups;
71     private List entries;
72     private List unfiledEntries;
73     private List rosterListeners;
74     private Map presenceMap;
75     // The roster is marked as initialized when at least a single roster packet
76
// has been recieved and processed.
77
boolean rosterInitialized = false;
78
79     private int subscriptionMode = getDefaultSubscriptionMode();
80
81     /**
82      * Returns the default subscription processing mode to use when a new Roster is created. The
83      * subscription processing mode dictates what action Smack will take when subscription
84      * requests from other users are made. The default subscription mode
85      * is {@link #SUBSCRIPTION_ACCEPT_ALL}.
86      *
87      * @return the default subscription mode to use for new Rosters
88      */

89     public static int getDefaultSubscriptionMode() {
90         return defaultSubscriptionMode;
91     }
92
93     /**
94      * Sets the default subscription processing mode to use when a new Roster is created. The
95      * subscription processing mode dictates what action Smack will take when subscription
96      * requests from other users are made. The default subscription mode
97      * is {@link #SUBSCRIPTION_ACCEPT_ALL}.
98      *
99      * @param subscriptionMode the default subscription mode to use for new Rosters.
100      */

101     public static void setDefaultSubscriptionMode(int subscriptionMode) {
102         defaultSubscriptionMode = subscriptionMode;
103     }
104
105     /**
106      * Creates a new roster.
107      *
108      * @param connection an XMPP connection.
109      */

110     Roster(final XMPPConnection connection) {
111         this.connection = connection;
112         groups = new Hashtable();
113         unfiledEntries = new ArrayList();
114         entries = new ArrayList();
115         rosterListeners = new ArrayList();
116         presenceMap = new HashMap();
117         // Listen for any roster packets.
118
PacketFilter rosterFilter = new PacketTypeFilter(RosterPacket.class);
119         connection.addPacketListener(new RosterPacketListener(), rosterFilter);
120         // Listen for any presence packets.
121
PacketFilter presenceFilter = new PacketTypeFilter(Presence.class);
122         connection.addPacketListener(new PresencePacketListener(), presenceFilter);
123     }
124
125     /**
126      * Returns the subscription processing mode, which dictates what action
127      * Smack will take when subscription requests from other users are made.
128      * The default subscription mode is {@link #SUBSCRIPTION_ACCEPT_ALL}.<p>
129      *
130      * If using the manual mode, a PacketListener should be registered that
131      * listens for Presence packets that have a type of
132      * {@link org.jivesoftware.smack.packet.Presence.Type#SUBSCRIBE}.
133      *
134      * @return the subscription mode.
135      */

136     public int getSubscriptionMode() {
137         return subscriptionMode;
138     }
139
140     /**
141      * Sets the subscription processing mode, which dictates what action
142      * Smack will take when subscription requests from other users are made.
143      * The default subscription mode is {@link #SUBSCRIPTION_ACCEPT_ALL}.<p>
144      *
145      * If using the manual mode, a PacketListener should be registered that
146      * listens for Presence packets that have a type of
147      * {@link org.jivesoftware.smack.packet.Presence.Type#SUBSCRIBE}.
148      *
149      * @param subscriptionMode the subscription mode.
150      */

151     public void setSubscriptionMode(int subscriptionMode) {
152         if (subscriptionMode != SUBSCRIPTION_ACCEPT_ALL &&
153                 subscriptionMode != SUBSCRIPTION_REJECT_ALL &&
154                 subscriptionMode != SUBSCRIPTION_MANUAL)
155         {
156             throw new IllegalArgumentException JavaDoc("Invalid mode.");
157         }
158         this.subscriptionMode = subscriptionMode;
159     }
160
161     /**
162      * Reloads the entire roster from the server. This is an asynchronous operation,
163      * which means the method will return immediately, and the roster will be
164      * reloaded at a later point when the server responds to the reload request.
165      */

166     public void reload() {
167         connection.sendPacket(new RosterPacket());
168     }
169
170     /**
171      * Adds a listener to this roster. The listener will be fired anytime one or more
172      * changes to the roster are pushed from the server.
173      *
174      * @param rosterListener a roster listener.
175      */

176     public void addRosterListener(RosterListener rosterListener) {
177         synchronized (rosterListeners) {
178             if (!rosterListeners.contains(rosterListener)) {
179                 rosterListeners.add(rosterListener);
180             }
181         }
182     }
183
184     /**
185      * Removes a listener from this roster. The listener will be fired anytime one or more
186      * changes to the roster are pushed from the server.
187      *
188      * @param rosterListener a roster listener.
189      */

190     public void removeRosterListener(RosterListener rosterListener) {
191         synchronized (rosterListeners) {
192             rosterListeners.remove(rosterListener);
193         }
194     }
195
196     /**
197      * Creates a new group.<p>
198      *
199      * Note: you must add at least one entry to the group for the group to be kept
200      * after a logout/login. This is due to the way that XMPP stores group information.
201      *
202      * @param name the name of the group.
203      * @return a new group.
204      */

205     public RosterGroup createGroup(String JavaDoc name) {
206         synchronized (groups) {
207             if (groups.containsKey(name)) {
208                 throw new IllegalArgumentException JavaDoc("Group with name " + name + " alread exists.");
209             }
210             RosterGroup group = new RosterGroup(name, connection);
211             groups.put(name, group);
212             return group;
213         }
214     }
215
216     /**
217      * Creates a new roster entry and presence subscription. The server will asynchronously
218      * update the roster with the subscription status.
219      *
220      * @param user the user. (e.g. johndoe@jabber.org)
221      * @param name the nickname of the user.
222      * @param groups the list of group names the entry will belong to, or <tt>null</tt> if the
223      * the roster entry won't belong to a group.
224      */

225     public void createEntry(String JavaDoc user, String JavaDoc name, String JavaDoc [] groups) throws XMPPException {
226         // Create and send roster entry creation packet.
227
RosterPacket rosterPacket = new RosterPacket();
228         rosterPacket.setType(IQ.Type.SET);
229         RosterPacket.Item item = new RosterPacket.Item(user, name);
230         if (groups != null) {
231             for (int i=0; i<groups.length; i++) {
232                 if (groups[i] != null) {
233                     item.addGroupName(groups[i]);
234                 }
235             }
236         }
237         rosterPacket.addRosterItem(item);
238         // Wait up to a certain number of seconds for a reply from the server.
239
PacketCollector collector = connection.createPacketCollector(
240                 new PacketIDFilter(rosterPacket.getPacketID()));
241         connection.sendPacket(rosterPacket);
242         IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
243         collector.cancel();
244         if (response == null) {
245             throw new XMPPException("No response from the server.");
246         }
247         // If the server replied with an error, throw an exception.
248
else if (response.getType() == IQ.Type.ERROR) {
249             throw new XMPPException(response.getError());
250         }
251
252         // Create a presence subscription packet and send.
253
Presence presencePacket = new Presence(Presence.Type.SUBSCRIBE);
254         presencePacket.setTo(user);
255         connection.sendPacket(presencePacket);
256     }
257
258     /**
259      * Removes a roster entry from the roster. The roster entry will also be removed from the
260      * unfiled entries or from any roster group where it could belong and will no longer be part
261      * of the roster. Note that this is an asynchronous call -- Smack must wait for the server
262      * to send an updated subscription status.
263      *
264      * @param entry a roster entry.
265      */

266     public void removeEntry(RosterEntry entry) throws XMPPException {
267         // Only remove the entry if it's in the entry list.
268
// The actual removal logic takes place in RosterPacketListenerprocess>>Packet(Packet)
269
synchronized (entries) {
270             if (!entries.contains(entry)) {
271                 return;
272             }
273         }
274         RosterPacket packet = new RosterPacket();
275         packet.setType(IQ.Type.SET);
276         RosterPacket.Item item = RosterEntry.toRosterItem(entry);
277         // Set the item type as REMOVE so that the server will delete the entry
278
item.setItemType(RosterPacket.ItemType.REMOVE);
279         packet.addRosterItem(item);
280         PacketCollector collector = connection.createPacketCollector(
281         new PacketIDFilter(packet.getPacketID()));
282         connection.sendPacket(packet);
283         IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
284         collector.cancel();
285         if (response == null) {
286             throw new XMPPException("No response from the server.");
287         }
288         // If the server replied with an error, throw an exception.
289
else if (response.getType() == IQ.Type.ERROR) {
290             throw new XMPPException(response.getError());
291         }
292         else {
293             
294         }
295     }
296
297     /**
298      * Returns a count of the entries in the roster.
299      *
300      * @return the number of entries in the roster.
301      */

302     public int getEntryCount() {
303         HashMap entryMap = new HashMap();
304         // Loop through all roster groups.
305
for (Iterator groups = getGroups(); groups.hasNext(); ) {
306             RosterGroup rosterGroup = (RosterGroup) groups.next();
307             for (Iterator entries = rosterGroup.getEntries(); entries.hasNext(); ) {
308                 entryMap.put(entries.next(), "");
309             }
310         }
311         synchronized (unfiledEntries) {
312             return entryMap.size() + unfiledEntries.size();
313         }
314     }
315
316     /**
317      * Returns all entries in the roster, including entries that don't belong to
318      * any groups.
319      *
320      * @return all entries in the roster.
321      */

322     public Iterator getEntries() {
323         ArrayList allEntries = new ArrayList();
324         // Loop through all roster groups and add their entries to the answer
325
for (Iterator groups = getGroups(); groups.hasNext(); ) {
326             RosterGroup rosterGroup = (RosterGroup) groups.next();
327             for (Iterator entries = rosterGroup.getEntries(); entries.hasNext(); ) {
328                 RosterEntry entry = (RosterEntry)entries.next();
329                 if (!allEntries.contains(entry)) {
330                     allEntries.add(entry);
331                 }
332             }
333         }
334         // Add the roster unfiled entries to the answer
335
synchronized (unfiledEntries) {
336             allEntries.addAll(unfiledEntries);
337         }
338         return allEntries.iterator();
339     }
340
341     /**
342      * Returns a count of the unfiled entries in the roster. An unfiled entry is
343      * an entry that doesn't belong to any groups.
344      *
345      * @return the number of unfiled entries in the roster.
346      */

347     public int getUnfiledEntryCount() {
348         synchronized (unfiledEntries) {
349             return unfiledEntries.size();
350         }
351     }
352
353     /**
354      * Returns an Iterator for the unfiled roster entries. An unfiled entry is
355      * an entry that doesn't belong to any groups.
356      *
357      * @return an iterator the unfiled roster entries.
358      */

359     public Iterator getUnfiledEntries() {
360         synchronized (unfiledEntries) {
361             return Collections.unmodifiableList(new ArrayList(unfiledEntries)).iterator();
362         }
363     }
364
365     /**
366      * Returns the roster entry associated with the given XMPP address or
367      * <tt>null</tt> if the user is not an entry in the roster.
368      *
369      * @param user the XMPP address of the user (eg "jsmith@example.com"). The address could be
370      * in any valid format (e.g. "domain/resource", "user@domain" or "user@domain/resource").
371      * @return the roster entry or <tt>null</tt> if it does not exist.
372      */

373     public RosterEntry getEntry(String JavaDoc user) {
374         if (user == null) {
375             return null;
376         }
377         synchronized (entries) {
378             for (Iterator i=entries.iterator(); i.hasNext(); ) {
379                 RosterEntry entry = (RosterEntry)i.next();
380                 if (entry.getUser().toLowerCase().equals(user.toLowerCase())) {
381                     return entry;
382                 }
383             }
384         }
385         return null;
386     }
387
388     /**
389      * Returns true if the specified XMPP address is an entry in the roster.
390      *
391      * @param user the XMPP address of the user (eg "jsmith@example.com"). The address could be
392      * in any valid format (e.g. "domain/resource", "user@domain" or "user@domain/resource").
393      * @return true if the XMPP address is an entry in the roster.
394      */

395     public boolean contains(String JavaDoc user) {
396         if (user == null) {
397             return false;
398         }
399         synchronized (entries) {
400             for (Iterator i=entries.iterator(); i.hasNext(); ) {
401                 RosterEntry entry = (RosterEntry)i.next();
402                 if (entry.getUser().toLowerCase().equals(user.toLowerCase())) {
403                     return true;
404                 }
405             }
406         }
407         return false;
408     }
409
410     /**
411      * Returns the roster group with the specified name, or <tt>null</tt> if the
412      * group doesn't exist.
413      *
414      * @param name the name of the group.
415      * @return the roster group with the specified name.
416      */

417     public RosterGroup getGroup(String JavaDoc name) {
418         synchronized (groups) {
419             return (RosterGroup)groups.get(name);
420         }
421     }
422
423     /**
424      * Returns the number of the groups in the roster.
425      *
426      * @return the number of groups in the roster.
427      */

428     public int getGroupCount() {
429         synchronized (groups) {
430             return groups.size();
431         }
432     }
433
434     /**
435      * Returns an iterator the for all the roster groups.
436      *
437      * @return an iterator for all roster groups.
438      */

439     public Iterator getGroups() {
440         synchronized (groups) {
441             List groupsList = Collections.unmodifiableList(new ArrayList(groups.values()));
442             return groupsList.iterator();
443         }
444     }
445
446     /**
447      * Returns the presence info for a particular user, or <tt>null</tt> if the user
448      * is unavailable (offline) or if no presence information is available, such as
449      * when you are not subscribed to the user's presence updates.<p>
450      *
451      * If the user has several presences (one for each resource) then answer the presence
452      * with the highest priority.
453      *
454      * @param user a fully qualified xmpp ID. The address could be in any valid format (e.g.
455      * "domain/resource", "user@domain" or "user@domain/resource").
456      * @return the user's current presence, or <tt>null</tt> if the user is unavailable
457      * or if no presence information is available..
458      */

459     public Presence getPresence(String JavaDoc user) {
460         String JavaDoc key = getPresenceMapKey(user);
461         Map userPresences = (Map) presenceMap.get(key);
462         if (userPresences == null) {
463             return null;
464         }
465         else {
466             // Find the resource with the highest priority
467
// Might be changed to use the resource with the highest availability instead.
468
Iterator it = userPresences.keySet().iterator();
469             Presence p;
470             Presence presence = null;
471
472             while (it.hasNext()) {
473                 p = (Presence) userPresences.get(it.next());
474                 if (presence == null) {
475                     presence = p;
476                 }
477                 else {
478                     if (p.getPriority() > presence.getPriority()) {
479                         presence = p;
480                     }
481                 }
482             }
483             return presence;
484         }
485     }
486
487     /**
488      * Returns the presence info for a particular user's resource, or <tt>null</tt> if the user
489      * is unavailable (offline) or if no presence information is available, such as
490      * when you are not subscribed to the user's presence updates.
491      *
492      * @param userResource a fully qualified xmpp ID including a resource.
493      * @return the user's current presence, or <tt>null</tt> if the user is unavailable
494      * or if no presence information is available.
495      */

496     public Presence getPresenceResource(String JavaDoc userResource) {
497         String JavaDoc key = getPresenceMapKey(userResource);
498         String JavaDoc resource = StringUtils.parseResource(userResource);
499         Map userPresences = (Map)presenceMap.get(key);
500         if (userPresences == null) {
501             return null;
502         }
503         else {
504             return (Presence) userPresences.get(resource);
505         }
506     }
507
508     /**
509      * Returns an iterator (of Presence objects) for all the user's current presences
510      * or <tt>null</tt> if the user is unavailable (offline) or if no presence information
511      * is available, such as when you are not subscribed to the user's presence updates.
512      *
513      * @param user a fully qualified xmpp ID, e.g. jdoe@example.com
514      * @return an iterator (of Presence objects) for all the user's current presences,
515      * or <tt>null</tt> if the user is unavailable or if no presence information
516      * is available.
517      */

518     public Iterator getPresences(String JavaDoc user) {
519         String JavaDoc key = getPresenceMapKey(user);
520         Map userPresences = (Map)presenceMap.get(key);
521         if (userPresences == null) {
522             return null;
523         }
524         else {
525             synchronized (userPresences) {
526                 return new HashMap(userPresences).values().iterator();
527             }
528         }
529     }
530
531     /**
532      * Returns the key to use in the presenceMap for a fully qualified xmpp ID. The roster
533      * can contain any valid address format such us "domain/resource", "user@domain" or
534      * "user@domain/resource". If the roster contains an entry associated with the fully qualified
535      * xmpp ID then use the fully qualified xmpp ID as the key in presenceMap, otherwise use the
536      * bare address. Note: When the key in presenceMap is a fully qualified xmpp ID, the
537      * userPresences is useless since it will always contain one entry for the user.
538      *
539      * @param user the fully qualified xmpp ID, e.g. jdoe@example.com/Work.
540      * @return the key to use in the presenceMap for the fully qualified xmpp ID.
541      */

542     private String JavaDoc getPresenceMapKey(String JavaDoc user) {
543         String JavaDoc key = user;
544         if (!contains(user)) {
545             key = StringUtils.parseBareAddress(user);
546         }
547         return key;
548     }
549
550     /**
551      * Fires roster changed event to roster listeners.
552      */

553     private void fireRosterChangedEvent() {
554         RosterListener [] listeners = null;
555         synchronized (rosterListeners) {
556             listeners = new RosterListener[rosterListeners.size()];
557             rosterListeners.toArray(listeners);
558         }
559         for (int i=0; i<listeners.length; i++) {
560             listeners[i].rosterModified();
561         }
562     }
563
564     /**
565      * Fires roster presence changed event to roster listeners.
566      */

567     private void fireRosterPresenceEvent(String JavaDoc user) {
568         RosterListener [] listeners = null;
569         synchronized (rosterListeners) {
570             listeners = new RosterListener[rosterListeners.size()];
571             rosterListeners.toArray(listeners);
572         }
573         for (int i=0; i<listeners.length; i++) {
574             listeners[i].presenceChanged(user);
575         }
576     }
577
578     /**
579      * Listens for all presence packets and processes them.
580      */

581     private class PresencePacketListener implements PacketListener {
582         public void processPacket(Packet packet) {
583             Presence presence = (Presence)packet;
584             String JavaDoc from = presence.getFrom();
585             String JavaDoc key = getPresenceMapKey(from);
586
587             // If an "available" packet, add it to the presence map. Each presence map will hold
588
// for a particular user a map with the presence packets saved for each resource.
589
if (presence.getType() == Presence.Type.AVAILABLE) {
590                 Map userPresences;
591                 // Get the user presence map
592
if (presenceMap.get(key) == null) {
593                     userPresences = new HashMap();
594                     presenceMap.put(key, userPresences);
595                 }
596                 else {
597                     userPresences = (Map)presenceMap.get(key);
598                 }
599                 // Add the new presence, using the resources as a key.
600
synchronized (userPresences) {
601                     userPresences.put(StringUtils.parseResource(from), presence);
602                 }
603                 // If the user is in the roster, fire an event.
604
synchronized (entries) {
605                     for (Iterator i = entries.iterator(); i.hasNext();) {
606                         RosterEntry entry = (RosterEntry) i.next();
607                         if (entry.getUser().toLowerCase().equals(key.toLowerCase())) {
608                             fireRosterPresenceEvent(from);
609                         }
610                     }
611                 }
612             }
613             // If an "unavailable" packet, remove any entries in the presence map.
614
else if (presence.getType() == Presence.Type.UNAVAILABLE) {
615                 if (presenceMap.get(key) != null) {
616                     Map userPresences = (Map) presenceMap.get(key);
617                     synchronized (userPresences) {
618                         userPresences.remove(StringUtils.parseResource(from));
619                     }
620                     if (userPresences.isEmpty()) {
621                         presenceMap.remove(key);
622                     }
623                 }
624                 // If the user is in the roster, fire an event.
625
synchronized (entries) {
626                     for (Iterator i=entries.iterator(); i.hasNext(); ) {
627                         RosterEntry entry = (RosterEntry)i.next();
628                         if (entry.getUser().toLowerCase().equals(key.toLowerCase())) {
629                             fireRosterPresenceEvent(from);
630                         }
631                     }
632                 }
633             }
634             else if (presence.getType() == Presence.Type.SUBSCRIBE) {
635                 if (subscriptionMode == SUBSCRIPTION_ACCEPT_ALL) {
636                     // Accept all subscription requests.
637
Presence response = new Presence(Presence.Type.SUBSCRIBED);
638                     response.setTo(presence.getFrom());
639                     connection.sendPacket(response);
640                 }
641                 else if (subscriptionMode == SUBSCRIPTION_REJECT_ALL) {
642                     // Reject all subscription requests.
643
Presence response = new Presence(Presence.Type.UNSUBSCRIBED);
644                     response.setTo(presence.getFrom());
645                     connection.sendPacket(response);
646                 }
647                 // Otherwise, in manual mode so ignore.
648
}
649         }
650     }
651
652     /**
653      * Listens for all roster packets and processes them.
654      */

655     private class RosterPacketListener implements PacketListener {
656
657         public void processPacket(Packet packet) {
658             RosterPacket rosterPacket = (RosterPacket)packet;
659             for (Iterator i=rosterPacket.getRosterItems(); i.hasNext(); ) {
660                 RosterPacket.Item item = (RosterPacket.Item)i.next();
661                 RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
662                         item.getItemType(), connection);
663
664                 // If the packet is of the type REMOVE then remove the entry
665
if (RosterPacket.ItemType.REMOVE.equals(item.getItemType())) {
666                     // Remove the entry from the entry list.
667
if (entries.contains(entry)) {
668                         entries.remove(entry);
669                     }
670                     // Remove the entry from the unfiled entry list.
671
synchronized (unfiledEntries) {
672                         if (unfiledEntries.contains(entry)) {
673                             unfiledEntries.remove(entry);
674                         }
675                     }
676                     // Removing the user from the roster, so remove any presence information
677
// about them.
678
String JavaDoc key = StringUtils.parseName(item.getUser()) + "@" +
679                             StringUtils.parseServer(item.getUser());
680                     presenceMap.remove(key);
681                 }
682                 else {
683                     // Make sure the entry is in the entry list.
684
if (!entries.contains(entry)) {
685                         entries.add(entry);
686                     }
687                     else {
688                         // If the entry was in then list then update its state with the new values
689
RosterEntry existingEntry =
690                             (RosterEntry) entries.get(entries.indexOf(entry));
691                         existingEntry.updateState(entry.getName(), entry.getType());
692                     }
693                     // If the roster entry belongs to any groups, remove it from the
694
// list of unfiled entries.
695
if (item.getGroupNames().hasNext()) {
696                         synchronized (unfiledEntries) {
697                             unfiledEntries.remove(entry);
698                         }
699                     }
700                     // Otherwise add it to the list of unfiled entries.
701
else {
702                         synchronized (unfiledEntries) {
703                             if (!unfiledEntries.contains(entry)) {
704                                 unfiledEntries.add(entry);
705                             }
706                         }
707                     }
708                 }
709
710                 // Find the list of groups that the user currently belongs to.
711
List currentGroupNames = new ArrayList();
712                 for (Iterator j = entry.getGroups(); j.hasNext(); ) {
713                     RosterGroup group = (RosterGroup)j.next();
714                     currentGroupNames.add(group.getName());
715                 }
716
717                 // If the packet is not of the type REMOVE then add the entry to the groups
718
if (!RosterPacket.ItemType.REMOVE.equals(item.getItemType())) {
719                     // Create the new list of groups the user belongs to.
720
List newGroupNames = new ArrayList();
721                     for (Iterator k = item.getGroupNames(); k.hasNext(); ) {
722                         String JavaDoc groupName = (String JavaDoc)k.next();
723                         // Add the group name to the list.
724
newGroupNames.add(groupName);
725
726                         // Add the entry to the group.
727
RosterGroup group = getGroup(groupName);
728                         if (group == null) {
729                             group = createGroup(groupName);
730                             groups.put(groupName, group);
731                         }
732                         // Add the entry.
733
group.addEntryLocal(entry);
734                     }
735
736                     // We have the list of old and new group names. We now need to
737
// remove the entry from the all the groups it may no longer belong
738
// to. We do this by subracting the new group set from the old.
739
for (int m=0; m<newGroupNames.size(); m++) {
740                         currentGroupNames.remove(newGroupNames.get(m));
741                     }
742                 }
743
744                 // Loop through any groups that remain and remove the entries.
745
// This is neccessary for the case of remote entry removals.
746
for (int n=0; n<currentGroupNames.size(); n++) {
747                     String JavaDoc groupName = (String JavaDoc)currentGroupNames.get(n);
748                     RosterGroup group = getGroup(groupName);
749                     group.removeEntryLocal(entry);
750                     if (group.getEntryCount() == 0) {
751                         synchronized (groups) {
752                             groups.remove(groupName);
753                         }
754                     }
755                 }
756                 // Remove all the groups with no entries. We have to do this because
757
// RosterGroup.removeEntry removes the entry immediately (locally) and the
758
// group could remain empty.
759
// TODO Check the performance/logic for rosters with large number of groups
760
for (Iterator it = getGroups(); it.hasNext();) {
761                     RosterGroup group = (RosterGroup)it.next();
762                     if (group.getEntryCount() == 0) {
763                         synchronized (groups) {
764                             groups.remove(group.getName());
765                         }
766                     }
767                 }
768             }
769
770             // Mark the roster as initialized.
771
synchronized (Roster.this) {
772                 rosterInitialized = true;
773                 Roster.this.notifyAll();
774             }
775
776             // Fire event for roster listeners.
777
fireRosterChangedEvent();
778         }
779     }
780 }
Popular Tags