alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/**
 * $RCSfile: Roster.java,v $
 * $Revision: 1.1 $
 * $Date: 2005/02/25 21:41:42 $
 *
 * Copyright 2003-2004 Jive Software.
 *
 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.smack;

import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.util.StringUtils;

import java.util.*;

/**
 * Represents a user's roster, which is the collection of users a person receives
 * presence updates for. Roster items are categorized into groups for easier management.<p>
 *
 * Others users may attempt to subscribe to this user using a subscription request. Three
 * modes are supported for handling these requests: <ul>
 *      <li> SUBSCRIPTION_ACCEPT_ALL -- accept all subscription requests.
 *      <li> SUBSCRIPTION_REJECT_ALL -- reject all subscription requests.
 *      <li> SUBSCRIPTION_MANUAL -- manually process all subscription requests. 
 *
 * All presence subscription requests are automatically approved to this client
 * are automatically approved. This logic will be updated in the future to allow for
 * pluggable behavior.
 *
 * @see XMPPConnection#getRoster()
 * @author Matt Tucker
 */
public class Roster {

    /**
     * Automatically accept all subscription requests. This is the default mode
     * and is suitable for simple client. More complex client will likely wish to
     * handle subscription requests manually.
     */
    public static final int SUBSCRIPTION_ACCEPT_ALL = 0;

    /**
     * Automatically reject all subscription requests.
     */
    public static final int SUBSCRIPTION_REJECT_ALL = 1;

    /**
     * Subscription requests are ignored, which means they must be manually
     * processed by registering a listener for presence packets and then looking
     * for any presence requests that have the type Presence.Type.SUBSCRIBE.
     */
    public static final int SUBSCRIPTION_MANUAL = 2;

    /**
     * The default subscription processing mode to use when a Roster is created. By default 
     * all subscription requests are automatically accepted. 
     */
    private static int defaultSubscriptionMode = SUBSCRIPTION_ACCEPT_ALL;

    private XMPPConnection connection;
    private Map groups;
    private List entries;
    private List unfiledEntries;
    private List rosterListeners;
    private Map presenceMap;
    // The roster is marked as initialized when at least a single roster packet
    // has been recieved and processed.
    boolean rosterInitialized = false;

    private int subscriptionMode = getDefaultSubscriptionMode();

    /**
     * Returns the default subscription processing mode to use when a new Roster is created. The 
     * subscription processing mode dictates what action Smack will take when subscription 
     * requests from other users are made. The default subscription mode 
     * is {@link #SUBSCRIPTION_ACCEPT_ALL}.
     * 
     * @return the default subscription mode to use for new Rosters
     */
    public static int getDefaultSubscriptionMode() {
        return defaultSubscriptionMode;
    }

    /**
     * Sets the default subscription processing mode to use when a new Roster is created. The 
     * subscription processing mode dictates what action Smack will take when subscription 
     * requests from other users are made. The default subscription mode 
     * is {@link #SUBSCRIPTION_ACCEPT_ALL}.
     *
     * @param subscriptionMode the default subscription mode to use for new Rosters.
     */
    public static void setDefaultSubscriptionMode(int subscriptionMode) {
        defaultSubscriptionMode = subscriptionMode;
    }

    /**
     * Creates a new roster.
     *
     * @param connection an XMPP connection.
     */
    Roster(final XMPPConnection connection) {
        this.connection = connection;
        groups = new Hashtable();
        unfiledEntries = new ArrayList();
        entries = new ArrayList();
        rosterListeners = new ArrayList();
        presenceMap = new HashMap();
        // Listen for any roster packets.
        PacketFilter rosterFilter = new PacketTypeFilter(RosterPacket.class);
        connection.addPacketListener(new RosterPacketListener(), rosterFilter);
        // Listen for any presence packets.
        PacketFilter presenceFilter = new PacketTypeFilter(Presence.class);
        connection.addPacketListener(new PresencePacketListener(), presenceFilter);
    }

    /**
     * Returns the subscription processing mode, which dictates what action
     * Smack will take when subscription requests from other users are made.
     * The default subscription mode is {@link #SUBSCRIPTION_ACCEPT_ALL}.<p>
     *
     * If using the manual mode, a PacketListener should be registered that
     * listens for Presence packets that have a type of
     * {@link org.jivesoftware.smack.packet.Presence.Type#SUBSCRIBE}.
     *
     * @return the subscription mode.
     */
    public int getSubscriptionMode() {
        return subscriptionMode;
    }

    /**
     * Sets the subscription processing mode, which dictates what action
     * Smack will take when subscription requests from other users are made.
     * The default subscription mode is {@link #SUBSCRIPTION_ACCEPT_ALL}.<p>
     *
     * If using the manual mode, a PacketListener should be registered that
     * listens for Presence packets that have a type of
     * {@link org.jivesoftware.smack.packet.Presence.Type#SUBSCRIBE}.
     *
     * @param subscriptionMode the subscription mode.
     */
    public void setSubscriptionMode(int subscriptionMode) {
        if (subscriptionMode != SUBSCRIPTION_ACCEPT_ALL &&
                subscriptionMode != SUBSCRIPTION_REJECT_ALL &&
                subscriptionMode != SUBSCRIPTION_MANUAL)
        {
            throw new IllegalArgumentException("Invalid mode.");
        }
        this.subscriptionMode = subscriptionMode;
    }

    /**
     * Reloads the entire roster from the server. This is an asynchronous operation,
     * which means the method will return immediately, and the roster will be
     * reloaded at a later point when the server responds to the reload request.
     */
    public void reload() {
        connection.sendPacket(new RosterPacket());
    }

    /**
     * Adds a listener to this roster. The listener will be fired anytime one or more
     * changes to the roster are pushed from the server.
     *
     * @param rosterListener a roster listener.
     */
    public void addRosterListener(RosterListener rosterListener) {
        synchronized (rosterListeners) {
            if (!rosterListeners.contains(rosterListener)) {
                rosterListeners.add(rosterListener);
            }
        }
    }

    /**
     * Removes a listener from this roster. The listener will be fired anytime one or more
     * changes to the roster are pushed from the server.
     *
     * @param rosterListener a roster listener.
     */
    public void removeRosterListener(RosterListener rosterListener) {
        synchronized (rosterListeners) {
            rosterListeners.remove(rosterListener);
        }
    }

    /**
     * Creates a new group.<p>
     *
     * Note: you must add at least one entry to the group for the group to be kept
     * after a logout/login. This is due to the way that XMPP stores group information.
     *
     * @param name the name of the group.
     * @return a new group.
     */
    public RosterGroup createGroup(String name) {
        synchronized (groups) {
            if (groups.containsKey(name)) {
                throw new IllegalArgumentException("Group with name " + name + " alread exists.");
            }
            RosterGroup group = new RosterGroup(name, connection);
            groups.put(name, group);
            fireRosterChangedEvent();
            return group;
        }
    }

    /**
     * Creates a new roster entry and presence subscription. The server will asynchronously
     * update the roster with the subscription status.
     *
     * @param user the user. (e.g. johndoe@jabber.org)
     * @param name the nickname of the user.
     * @param groups the list of group names the entry will belong to, or <tt>null if the
     *      the roster entry won't belong to a group.
     */
    public void createEntry(String user, String name, String [] groups) throws XMPPException {
        // Create and send roster entry creation packet.
        RosterPacket rosterPacket = new RosterPacket();
        rosterPacket.setType(IQ.Type.SET);
        RosterPacket.Item item = new RosterPacket.Item(user, name);
        if (groups != null) {
            for (int i=0; i<groups.length; i++) {
                if (groups[i] != null) {
                    item.addGroupName(groups[i]);
                }
            }
        }
        rosterPacket.addRosterItem(item);
        // Wait up to a certain number of seconds for a reply from the server.
        PacketCollector collector = connection.createPacketCollector(
                new PacketIDFilter(rosterPacket.getPacketID()));
        connection.sendPacket(rosterPacket);
        IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
        if (response == null) {
            throw new XMPPException("No response from the server.");
        }
        // If the server replied with an error, throw an exception.
        else if (response.getType() == IQ.Type.ERROR) {
            throw new XMPPException(response.getError());
        }
        collector.cancel();

        // Create a presence subscription packet and send.
        Presence presencePacket = new Presence(Presence.Type.SUBSCRIBE);
        presencePacket.setTo(user);
        connection.sendPacket(presencePacket);
    }

    /**
     * Removes a roster entry from the roster. The roster entry will also be removed from the 
     * unfiled entries or from any roster group where it could belong and will no longer be part
     * of the roster.
     *
     * @param entry a roster entry.
     */
    public void removeEntry(RosterEntry entry) {
        // Only remove the entry if it's in the entry list.
        // The actual removal logic takes place in RosterPacketListenerprocess>>Packet(Packet)
        synchronized (entries) {
            if (entries.contains(entry)) {
                RosterPacket packet = new RosterPacket();
                packet.setType(IQ.Type.SET);
                RosterPacket.Item item = RosterEntry.toRosterItem(entry);
                // Set the item type as REMOVE so that the server will delete the entry
                item.setItemType(RosterPacket.ItemType.REMOVE);
                packet.addRosterItem(item);
                connection.sendPacket(packet);
            }
        }
    }

    /**
     * Returns a count of the entries in the roster.
     *
     * @return the number of entries in the roster.
     */
    public int getEntryCount() {
        HashMap entryMap = new HashMap();
        // Loop through all roster groups.
        for (Iterator groups = getGroups(); groups.hasNext(); ) {
            RosterGroup rosterGroup = (RosterGroup) groups.next();
            for (Iterator entries = rosterGroup.getEntries(); entries.hasNext(); ) {
                entryMap.put(entries.next(), "");
            }
        }
        synchronized (unfiledEntries) {
            return entryMap.size() + unfiledEntries.size();
        }
    }

    /**
     * Returns all entries in the roster, including entries that don't belong to
     * any groups.
     *
     * @return all entries in the roster.
     */
    public Iterator getEntries() {
        ArrayList allEntries = new ArrayList();
        // Loop through all roster groups and add their entries to the answer
        for (Iterator groups = getGroups(); groups.hasNext(); ) {
            RosterGroup rosterGroup = (RosterGroup) groups.next();
            for (Iterator entries = rosterGroup.getEntries(); entries.hasNext(); ) {
                RosterEntry entry = (RosterEntry)entries.next();
                if (!allEntries.contains(entry)) {
                    allEntries.add(entry);
                }
            }
        }
        // Add the roster unfiled entries to the answer
        synchronized (unfiledEntries) {
            allEntries.addAll(unfiledEntries);
        }
        return allEntries.iterator();
    }

    /**
     * Returns a count of the unfiled entries in the roster. An unfiled entry is
     * an entry that doesn't belong to any groups.
     *
     * @return the number of unfiled entries in the roster.
     */
    public int getUnfiledEntryCount() {
        synchronized (unfiledEntries) {
            return unfiledEntries.size();
        }
    }

    /**
     * Returns an Iterator for the unfiled roster entries. An unfiled entry is
     * an entry that doesn't belong to any groups.
     *
     * @return an iterator the unfiled roster entries.
     */
    public Iterator getUnfiledEntries() {
        synchronized (unfiledEntries) {
            return Collections.unmodifiableList(new ArrayList(unfiledEntries)).iterator();
        }
    }

    /**
     * Returns the roster entry associated with the given XMPP address or
     * <tt>null if the user is not an entry in the roster.
     *
     * @param user the XMPP address of the user (eg "jsmith@example.com"). The address could be
     * in any valid format (e.g. "domain/resource", "user@domain" or "user@domain/resource").
     * @return the roster entry or <tt>null if it does not exist.
     */
    public RosterEntry getEntry(String user) {
        if (user == null) {
            return null;
        }
        synchronized (entries) {
            for (Iterator i=entries.iterator(); i.hasNext(); ) {
                RosterEntry entry = (RosterEntry)i.next();
                if (entry.getUser().toLowerCase().equals(user.toLowerCase())) {
                    return entry;
                }
            }
        }
        return null;
    }

    /**
     * Returns true if the specified XMPP address is an entry in the roster.
     *
     * @param user the XMPP address of the user (eg "jsmith@example.com"). The address could be
     * in any valid format (e.g. "domain/resource", "user@domain" or "user@domain/resource").
     * @return true if the XMPP address is an entry in the roster.
     */
    public boolean contains(String user) {
        if (user == null) {
            return false;
        }
        synchronized (entries) {
            for (Iterator i=entries.iterator(); i.hasNext(); ) {
                RosterEntry entry = (RosterEntry)i.next();
                if (entry.getUser().toLowerCase().equals(user.toLowerCase())) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Returns the roster group with the specified name, or <tt>null if the
     * group doesn't exist.
     *
     * @param name the name of the group.
     * @return the roster group with the specified name.
     */
    public RosterGroup getGroup(String name) {
        synchronized (groups) {
            return (RosterGroup)groups.get(name);
        }
    }

    /**
     * Returns the number of the groups in the roster.
     *
     * @return the number of groups in the roster.
     */
    public int getGroupCount() {
        synchronized (groups) {
            return groups.size();
        }
    }

    /**
     * Returns an iterator the for all the roster groups.
     *
     * @return an iterator for all roster groups.
     */
    public Iterator getGroups() {
        synchronized (groups) {
            List groupsList = Collections.unmodifiableList(new ArrayList(groups.values()));
            return groupsList.iterator();
        }
    }

    /**
     * Returns the presence info for a particular user, or <tt>null if the user
     * is unavailable (offline) or if no presence information is available, such as
     * when you are not subscribed to the user's presence updates.<p>
     * 
     * If the user has several presences (one for each resource) then answer the presence
     * with the highest priority.
     *
     * @param user a fully qualified xmpp ID. The address could be in any valid format (e.g.
     * "domain/resource", "user@domain" or "user@domain/resource").
     * @return the user's current presence, or <tt>null if the user is unavailable
     *      or if no presence information is available..
     */
    public Presence getPresence(String user) {
        String key = getPresenceMapKey(user);
        Map userPresences = (Map) presenceMap.get(key);
        if (userPresences == null) {
            return null;
        }
        else {
            // Find the resource with the highest priority
            // Might be changed to use the resource with the highest availability instead.
            Iterator it = userPresences.keySet().iterator();
            Presence p;
            Presence presence = null;

            while (it.hasNext()) {
                p = (Presence) userPresences.get(it.next());
                if (presence == null) {
                    presence = p;
                }
                else {
                    if (p.getPriority() > presence.getPriority()) {
                        presence = p;
                    }
                }
            }
            return presence;
        }
    }

    /**                                                                                                    
     * Returns the presence info for a particular user's resource, or <tt>null if the user
     * is unavailable (offline) or if no presence information is available, such as
     * when you are not subscribed to the user's presence updates.
     *
     * @param userResource a fully qualified xmpp ID including a resource.
     * @return the user's current presence, or <tt>null if the user is unavailable 
     * or if no presence information is available.
     */
    public Presence getPresenceResource(String userResource) {
        String key = getPresenceMapKey(userResource);
        String resource = StringUtils.parseResource(userResource);
        Map userPresences = (Map)presenceMap.get(key);
        if (userPresences == null) {
            return null;
        }
        else {
            return (Presence) userPresences.get(resource);
        }
    }

    /**
     * Returns an iterator (of Presence objects) for all the user's current presences
     * or <tt>null if the user is unavailable (offline) or if no presence information
     * is available, such as when you are not subscribed to the user's presence updates.
     *
     * @param user a fully qualified xmpp ID, e.g. jdoe@example.com
     * @return an iterator (of Presence objects) for all the user's current presences,
     *      or <tt>null if the user is unavailable or if no presence information
     *      is available.
     */
    public Iterator getPresences(String user) {
        String key = getPresenceMapKey(user);
        Map userPresences = (Map)presenceMap.get(key);
        if (userPresences == null) {
            return null;
        }
        else {
            synchronized (userPresences) {
                return new HashMap(userPresences).values().iterator();
            }
        }
    }

    /**
     * Returns the key to use in the presenceMap for a fully qualified xmpp ID. The roster
     * can contain any valid address format such us "domain/resource", "user@domain" or
     * "user@domain/resource". If the roster contains an entry associated with the fully qualified
     * xmpp ID then use the fully qualified xmpp ID as the key in presenceMap, otherwise use the
     * bare address. Note: When the key in presenceMap is a fully qualified xmpp ID, the
     * userPresences is useless since it will always contain one entry for the user.
     *
     * @param user the fully qualified xmpp ID, e.g. jdoe@example.com/Work.
     * @return the key to use in the presenceMap for the fully qualified xmpp ID.
     */
    private String getPresenceMapKey(String user) {
        String key = user;
        if (!contains(user)) {
            key = StringUtils.parseBareAddress(user);
        }
        return key;
    }

    /**
     * Fires roster changed event to roster listeners.
     */
    private void fireRosterChangedEvent() {
        RosterListener [] listeners = null;
        synchronized (rosterListeners) {
            listeners = new RosterListener[rosterListeners.size()];
            rosterListeners.toArray(listeners);
        }
        for (int i=0; i<listeners.length; i++) {
            listeners[i].rosterModified();
        }
    }

    /**
     * Fires roster presence changed event to roster listeners.
     */
    private void fireRosterPresenceEvent(String user) {
        RosterListener [] listeners = null;
        synchronized (rosterListeners) {
            listeners = new RosterListener[rosterListeners.size()];
            rosterListeners.toArray(listeners);
        }
        for (int i=0; i<listeners.length; i++) {
            listeners[i].presenceChanged(user);
        }
    }

    /**
     * Listens for all presence packets and processes them.
     */
    private class PresencePacketListener implements PacketListener {
        public void processPacket(Packet packet) {
            Presence presence = (Presence)packet;
            String from = presence.getFrom();
            String key = getPresenceMapKey(from);

            // If an "available" packet, add it to the presence map. Each presence map will hold
            // for a particular user a map with the presence packets saved for each resource.
            if (presence.getType() == Presence.Type.AVAILABLE) {
                Map userPresences;
                // Get the user presence map
                if (presenceMap.get(key) == null) {
                    userPresences = new HashMap();
                    presenceMap.put(key, userPresences);
                }
                else {
                    userPresences = (Map)presenceMap.get(key);
                }
                // Add the new presence, using the resources as a key.
                synchronized (userPresences) {
                    userPresences.put(StringUtils.parseResource(from), presence);
                }
                // If the user is in the roster, fire an event.
                synchronized (entries) {
                    for (Iterator i = entries.iterator(); i.hasNext();) {
                        RosterEntry entry = (RosterEntry) i.next();
                        if (entry.getUser().toLowerCase().equals(key.toLowerCase())) {
                            fireRosterPresenceEvent(from);
                        }
                    }
                }
            }
            // If an "unavailable" packet, remove any entries in the presence map.
            else if (presence.getType() == Presence.Type.UNAVAILABLE) {
                if (presenceMap.get(key) != null) {
                    Map userPresences = (Map) presenceMap.get(key);
                    synchronized (userPresences) {
                        userPresences.remove(StringUtils.parseResource(from));
                    }
                    if (userPresences.isEmpty()) {
                        presenceMap.remove(key);
                    }
                }
                // If the user is in the roster, fire an event.
                synchronized (entries) {
                    for (Iterator i=entries.iterator(); i.hasNext(); ) {
                        RosterEntry entry = (RosterEntry)i.next();
                        if (entry.getUser().toLowerCase().equals(key.toLowerCase())) {
                            fireRosterPresenceEvent(from);
                        }
                    }
                }
            }
            else if (presence.getType() == Presence.Type.SUBSCRIBE) {
                if (subscriptionMode == SUBSCRIPTION_ACCEPT_ALL) {
                    // Accept all subscription requests.
                    Presence response = new Presence(Presence.Type.SUBSCRIBED);
                    response.setTo(presence.getFrom());
                    connection.sendPacket(response);
                }
                else if (subscriptionMode == SUBSCRIPTION_REJECT_ALL) {
                    // Reject all subscription requests.
                    Presence response = new Presence(Presence.Type.UNSUBSCRIBED);
                    response.setTo(presence.getFrom());
                    connection.sendPacket(response);
                }
                // Otherwise, in manual mode so ignore.
            } else if(presence.getType() == Presence.Type.SUBSCRIBED) {
                Presence response = new Presence(Presence.Type.AVAILABLE);
                response.setTo(presence.getFrom());
                connection.sendPacket(response);
            }
        }
    }

    /**
     * Listens for all roster packets and processes them.
     */
    private class RosterPacketListener implements PacketListener {

        public void processPacket(Packet packet) {
            RosterPacket rosterPacket = (RosterPacket)packet;
            for (Iterator i=rosterPacket.getRosterItems(); i.hasNext(); ) {
                RosterPacket.Item item = (RosterPacket.Item)i.next();
                RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
                        item.getItemType(), connection);

                // If the packet is of the type REMOVE then remove the entry
                if (RosterPacket.ItemType.REMOVE.equals(item.getItemType())) {
                    // Remove the entry from the entry list.
                    if (entries.contains(entry)) {
                        entries.remove(entry);
                    }
                    // Remove the entry from the unfiled entry list.
                    synchronized (unfiledEntries) {
                        if (unfiledEntries.contains(entry)) {
                            unfiledEntries.remove(entry);
                        }
                    }
                    // Removing the user from the roster, so remove any presence information
                    // about them.
                    String key = StringUtils.parseName(item.getUser()) + "@" +
                            StringUtils.parseServer(item.getUser());
                    presenceMap.remove(key);
                }
                else {
                    // Make sure the entry is in the entry list.
                    if (!entries.contains(entry)) {
                        entries.add(entry);
                    }
                    else {
                        // If the entry was in then list then update its state with the new values
                        RosterEntry existingEntry =
                            (RosterEntry) entries.get(entries.indexOf(entry));
                        existingEntry.updateState(entry.getName(), entry.getType());
                    }
                    // If the roster entry belongs to any groups, remove it from the
                    // list of unfiled entries.
                    if (item.getGroupNames().hasNext()) {
                        synchronized (unfiledEntries) {
                            unfiledEntries.remove(entry);
                        }
                    }
                    // Otherwise add it to the list of unfiled entries.
                    else {
                        synchronized (unfiledEntries) {
                            if (!unfiledEntries.contains(entry)) {
                                unfiledEntries.add(entry);
                            }
                        }
                    }
                }

                // Find the list of groups that the user currently belongs to.
                List currentGroupNames = new ArrayList();
                for (Iterator j = entry.getGroups(); j.hasNext();  ) {
                    RosterGroup group = (RosterGroup)j.next();
                    currentGroupNames.add(group.getName());
                }

                // If the packet is not of the type REMOVE then add the entry to the groups
                if (!RosterPacket.ItemType.REMOVE.equals(item.getItemType())) {
                    // Create the new list of groups the user belongs to.
                    List newGroupNames = new ArrayList();
                    for (Iterator k = item.getGroupNames(); k.hasNext();  ) {
                        String groupName = (String)k.next();
                        // Add the group name to the list.
                        newGroupNames.add(groupName);

                        // Add the entry to the group.
                        RosterGroup group = getGroup(groupName);
                        if (group == null) {
                            group = createGroup(groupName);
                            groups.put(groupName, group);
                        }
                        // Add the entry.
                        group.addEntryLocal(entry);
                    }

                    // We have the list of old and new group names. We now need to
                    // remove the entry from the all the groups it may no longer belong
                    // to. We do this by subracting the new group set from the old.
                    for (int m=0; m<newGroupNames.size(); m++) {
                        currentGroupNames.remove(newGroupNames.get(m));
                    }
                }

                // Loop through any groups that remain and remove the entries.
                // This is neccessary for the case of remote entry removals.
                for (int n=0; n<currentGroupNames.size(); n++) {
                    String groupName = (String)currentGroupNames.get(n);
                    RosterGroup group = getGroup(groupName);
                    group.removeEntryLocal(entry);
                    if (group.getEntryCount() == 0) {
                        synchronized (groups) {
                            groups.remove(groupName);
                        }
                    }
                }
                // Remove all the groups with no entries. We have to do this because 
                // RosterGroup.removeEntry removes the entry immediately (locally) and the 
                // group could remain empty. 
                // TODO Check the performance/logic for rosters with large number of groups 
                for (Iterator it = getGroups(); it.hasNext();) {
                    RosterGroup group = (RosterGroup)it.next();
                    if (group.getEntryCount() == 0) {
                        synchronized (groups) {
                            groups.remove(group.getName());
                        }                            
                    }
                }
            }

            // Mark the roster as initialized.
            rosterInitialized = true;

            // Fire event for roster listeners.
            fireRosterChangedEvent();
        }
    }

    public XMPPConnection getConnection() {
    	return connection;
    }
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.