001/** 002 * 003 * Copyright 2003-2007 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.jivesoftware.smack.roster; 019 020import java.util.ArrayList; 021import java.util.LinkedHashSet; 022import java.util.List; 023import java.util.Set; 024 025import org.jivesoftware.smack.Manager; 026import org.jivesoftware.smack.XMPPConnection; 027import org.jivesoftware.smack.SmackException.NoResponseException; 028import org.jivesoftware.smack.SmackException.NotConnectedException; 029import org.jivesoftware.smack.XMPPException.XMPPErrorException; 030import org.jivesoftware.smack.packet.IQ; 031import org.jivesoftware.smack.roster.packet.RosterPacket; 032import org.jxmpp.jid.Jid; 033 034/** 035 * A group of roster entries. 036 * 037 * @see Roster#getGroup(String) 038 * @author Matt Tucker 039 */ 040public class RosterGroup extends Manager { 041 042 private final String name; 043 private final Set<RosterEntry> entries; 044 045 /** 046 * Creates a new roster group instance. 047 * 048 * @param name the name of the group. 049 * @param connection the connection the group belongs to. 050 */ 051 RosterGroup(String name, XMPPConnection connection) { 052 super(connection); 053 this.name = name; 054 entries = new LinkedHashSet<RosterEntry>(); 055 } 056 057 /** 058 * Returns the name of the group. 059 * 060 * @return the name of the group. 061 */ 062 public String getName() { 063 return name; 064 } 065 066 /** 067 * Sets the name of the group. Changing the group's name is like moving all the group entries 068 * of the group to a new group specified by the new name. Since this group won't have entries 069 * it will be removed from the roster. This means that all the references to this object will 070 * be invalid and will need to be updated to the new group specified by the new name. 071 * 072 * @param name the name of the group. 073 * @throws NotConnectedException 074 * @throws XMPPErrorException 075 * @throws NoResponseException 076 * @throws InterruptedException 077 */ 078 public void setName(String name) throws NotConnectedException, NoResponseException, XMPPErrorException, InterruptedException { 079 synchronized (entries) { 080 for (RosterEntry entry : entries) { 081 RosterPacket packet = new RosterPacket(); 082 packet.setType(IQ.Type.set); 083 RosterPacket.Item item = RosterEntry.toRosterItem(entry); 084 item.removeGroupName(this.name); 085 item.addGroupName(name); 086 packet.addRosterItem(item); 087 connection().createStanzaCollectorAndSend(packet).nextResultOrThrow(); 088 } 089 } 090 } 091 092 /** 093 * Returns the number of entries in the group. 094 * 095 * @return the number of entries in the group. 096 */ 097 public int getEntryCount() { 098 synchronized (entries) { 099 return entries.size(); 100 } 101 } 102 103 /** 104 * Returns an copied list of all entries in the group. 105 * 106 * @return all entries in the group. 107 */ 108 public List<RosterEntry> getEntries() { 109 synchronized (entries) { 110 return new ArrayList<RosterEntry>(entries); 111 } 112 } 113 114 /** 115 * Returns the roster entry associated with the given XMPP address or 116 * <tt>null</tt> if the user is not an entry in the group. 117 * 118 * @param user the XMPP address of the user (eg "jsmith@example.com"). 119 * @return the roster entry or <tt>null</tt> if it does not exist in the group. 120 */ 121 public RosterEntry getEntry(Jid user) { 122 if (user == null) { 123 return null; 124 } 125 // Roster entries never include a resource so remove the resource 126 // if it's a part of the XMPP address. 127 user = user.asBareJid(); 128 synchronized (entries) { 129 for (RosterEntry entry : entries) { 130 if (entry.getJid().equals(user)) { 131 return entry; 132 } 133 } 134 } 135 return null; 136 } 137 138 /** 139 * Returns true if the specified entry is part of this group. 140 * 141 * @param entry a roster entry. 142 * @return true if the entry is part of this group. 143 */ 144 public boolean contains(RosterEntry entry) { 145 synchronized (entries) { 146 return entries.contains(entry); 147 } 148 } 149 150 /** 151 * Returns true if the specified XMPP address is an entry in this group. 152 * 153 * @param user the XMPP address of the user. 154 * @return true if the XMPP address is an entry in this group. 155 */ 156 public boolean contains(Jid user) { 157 return getEntry(user) != null; 158 } 159 160 /** 161 * Adds a roster entry to this group. If the entry was unfiled then it will be removed from 162 * the unfiled list and will be added to this group. 163 * Note that this is a synchronous call -- Smack must wait for the server 164 * to receive the updated roster. 165 * 166 * @param entry a roster entry. 167 * @throws XMPPErrorException if an error occured while trying to add the entry to the group. 168 * @throws NoResponseException if there was no response from the server. 169 * @throws NotConnectedException 170 * @throws InterruptedException 171 */ 172 public void addEntry(RosterEntry entry) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 173 // Only add the entry if it isn't already in the list. 174 synchronized (entries) { 175 if (!entries.contains(entry)) { 176 RosterPacket packet = new RosterPacket(); 177 packet.setType(IQ.Type.set); 178 RosterPacket.Item item = RosterEntry.toRosterItem(entry); 179 item.addGroupName(getName()); 180 packet.addRosterItem(item); 181 // Wait up to a certain number of seconds for a reply from the server. 182 connection().createStanzaCollectorAndSend(packet).nextResultOrThrow(); 183 } 184 } 185 } 186 187 /** 188 * Removes a roster entry from this group. If the entry does not belong to any other group 189 * then it will be considered as unfiled, therefore it will be added to the list of unfiled 190 * entries. 191 * Note that this is a synchronous call -- Smack must wait for the server 192 * to receive the updated roster. 193 * 194 * @param entry a roster entry. 195 * @throws XMPPErrorException if an error occurred while trying to remove the entry from the group. 196 * @throws NoResponseException if there was no response from the server. 197 * @throws NotConnectedException 198 * @throws InterruptedException 199 */ 200 public void removeEntry(RosterEntry entry) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 201 // Only remove the entry if it's in the entry list. 202 // Remove the entry locally, if we wait for RosterPacketListenerprocess>>Packet(Packet) 203 // to take place the entry will exist in the group until a packet is received from the 204 // server. 205 synchronized (entries) { 206 if (entries.contains(entry)) { 207 RosterPacket packet = new RosterPacket(); 208 packet.setType(IQ.Type.set); 209 RosterPacket.Item item = RosterEntry.toRosterItem(entry); 210 item.removeGroupName(this.getName()); 211 packet.addRosterItem(item); 212 // Wait up to a certain number of seconds for a reply from the server. 213 connection().createStanzaCollectorAndSend(packet).nextResultOrThrow(); 214 } 215 } 216 } 217 218 void addEntryLocal(RosterEntry entry) { 219 // Update the entry if it is already in the list 220 synchronized (entries) { 221 entries.remove(entry); 222 entries.add(entry); 223 } 224 } 225 226 void removeEntryLocal(RosterEntry entry) { 227 // Only remove the entry if it's in the entry list. 228 synchronized (entries) { 229 if (entries.contains(entry)) { 230 entries.remove(entry); 231 } 232 } 233 } 234}