Home | History | Annotate | Download | only in smack
      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 
     21 package org.jivesoftware.smack;
     22 
     23 import org.jivesoftware.smack.filter.PacketIDFilter;
     24 import org.jivesoftware.smack.packet.IQ;
     25 import org.jivesoftware.smack.packet.RosterPacket;
     26 import org.jivesoftware.smack.util.StringUtils;
     27 
     28 import java.util.ArrayList;
     29 import java.util.Collection;
     30 import java.util.Collections;
     31 import java.util.List;
     32 
     33 /**
     34  * A group of roster entries.
     35  *
     36  * @see Roster#getGroup(String)
     37  * @author Matt Tucker
     38  */
     39 public 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 (at) 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 }