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.Locale; 024import java.util.Set; 025 026import org.jivesoftware.smack.PacketCollector; 027import org.jivesoftware.smack.XMPPConnection; 028import org.jivesoftware.smack.SmackException.NoResponseException; 029import org.jivesoftware.smack.SmackException.NotConnectedException; 030import org.jivesoftware.smack.XMPPException.XMPPErrorException; 031import org.jivesoftware.smack.packet.IQ; 032import org.jivesoftware.smack.roster.packet.RosterPacket; 033import org.jxmpp.util.XmppStringUtils; 034 035/** 036 * A group of roster entries. 037 * 038 * @see Roster#getGroup(String) 039 * @author Matt Tucker 040 */ 041public class RosterGroup { 042 043 private final String name; 044 private final XMPPConnection connection; 045 private final Set<RosterEntry> entries; 046 047 /** 048 * Creates a new roster group instance. 049 * 050 * @param name the name of the group. 051 * @param connection the connection the group belongs to. 052 */ 053 RosterGroup(String name, XMPPConnection connection) { 054 this.name = name; 055 this.connection = connection; 056 entries = new LinkedHashSet<RosterEntry>(); 057 } 058 059 /** 060 * Returns the name of the group. 061 * 062 * @return the name of the group. 063 */ 064 public String getName() { 065 return name; 066 } 067 068 /** 069 * Sets the name of the group. Changing the group's name is like moving all the group entries 070 * of the group to a new group specified by the new name. Since this group won't have entries 071 * it will be removed from the roster. This means that all the references to this object will 072 * be invalid and will need to be updated to the new group specified by the new name. 073 * 074 * @param name the name of the group. 075 * @throws NotConnectedException 076 * @throws XMPPErrorException 077 * @throws NoResponseException 078 */ 079 public void setName(String name) throws NotConnectedException, NoResponseException, XMPPErrorException { 080 synchronized (entries) { 081 for (RosterEntry entry : entries) { 082 RosterPacket packet = new RosterPacket(); 083 packet.setType(IQ.Type.set); 084 RosterPacket.Item item = RosterEntry.toRosterItem(entry); 085 item.removeGroupName(this.name); 086 item.addGroupName(name); 087 packet.addRosterItem(item); 088 connection.createPacketCollectorAndSend(packet).nextResultOrThrow(); 089 } 090 } 091 } 092 093 /** 094 * Returns the number of entries in the group. 095 * 096 * @return the number of entries in the group. 097 */ 098 public int getEntryCount() { 099 synchronized (entries) { 100 return entries.size(); 101 } 102 } 103 104 /** 105 * Returns an copied list of all entries in the group. 106 * 107 * @return all entries in the group. 108 */ 109 public List<RosterEntry> getEntries() { 110 synchronized (entries) { 111 return new ArrayList<RosterEntry>(entries); 112 } 113 } 114 115 /** 116 * Returns the roster entry associated with the given XMPP address or 117 * <tt>null</tt> if the user is not an entry in the group. 118 * 119 * @param user the XMPP address of the user (eg "jsmith@example.com"). 120 * @return the roster entry or <tt>null</tt> if it does not exist in the group. 121 */ 122 public RosterEntry getEntry(String user) { 123 if (user == null) { 124 return null; 125 } 126 // Roster entries never include a resource so remove the resource 127 // if it's a part of the XMPP address. 128 user = XmppStringUtils.parseBareJid(user); 129 String userLowerCase = user.toLowerCase(Locale.US); 130 synchronized (entries) { 131 for (RosterEntry entry : entries) { 132 if (entry.getUser().equals(userLowerCase)) { 133 return entry; 134 } 135 } 136 } 137 return null; 138 } 139 140 /** 141 * Returns true if the specified entry is part of this group. 142 * 143 * @param entry a roster entry. 144 * @return true if the entry is part of this group. 145 */ 146 public boolean contains(RosterEntry entry) { 147 synchronized (entries) { 148 return entries.contains(entry); 149 } 150 } 151 152 /** 153 * Returns true if the specified XMPP address is an entry in this group. 154 * 155 * @param user the XMPP address of the user. 156 * @return true if the XMPP address is an entry in this group. 157 */ 158 public boolean contains(String user) { 159 return getEntry(user) != null; 160 } 161 162 /** 163 * Adds a roster entry to this group. If the entry was unfiled then it will be removed from 164 * the unfiled list and will be added to this group. 165 * Note that this is a synchronous call -- Smack must wait for the server 166 * to receive the updated roster. 167 * 168 * @param entry a roster entry. 169 * @throws XMPPErrorException if an error occured while trying to add the entry to the group. 170 * @throws NoResponseException if there was no response from the server. 171 * @throws NotConnectedException 172 */ 173 public void addEntry(RosterEntry entry) throws NoResponseException, XMPPErrorException, NotConnectedException { 174 PacketCollector collector = null; 175 // Only add the entry if it isn't already in the list. 176 synchronized (entries) { 177 if (!entries.contains(entry)) { 178 RosterPacket packet = new RosterPacket(); 179 packet.setType(IQ.Type.set); 180 RosterPacket.Item item = RosterEntry.toRosterItem(entry); 181 item.addGroupName(getName()); 182 packet.addRosterItem(item); 183 // Wait up to a certain number of seconds for a reply from the server. 184 collector = connection.createPacketCollectorAndSend(packet); 185 } 186 } 187 if (collector != null) { 188 collector.nextResultOrThrow(); 189 } 190 } 191 192 /** 193 * Removes a roster entry from this group. If the entry does not belong to any other group 194 * then it will be considered as unfiled, therefore it will be added to the list of unfiled 195 * entries. 196 * Note that this is a synchronous call -- Smack must wait for the server 197 * to receive the updated roster. 198 * 199 * @param entry a roster entry. 200 * @throws XMPPErrorException if an error occurred while trying to remove the entry from the group. 201 * @throws NoResponseException if there was no response from the server. 202 * @throws NotConnectedException 203 */ 204 public void removeEntry(RosterEntry entry) throws NoResponseException, XMPPErrorException, NotConnectedException { 205 PacketCollector collector = null; 206 // Only remove the entry if it's in the entry list. 207 // Remove the entry locally, if we wait for RosterPacketListenerprocess>>Packet(Packet) 208 // to take place the entry will exist in the group until a packet is received from the 209 // server. 210 synchronized (entries) { 211 if (entries.contains(entry)) { 212 RosterPacket packet = new RosterPacket(); 213 packet.setType(IQ.Type.set); 214 RosterPacket.Item item = RosterEntry.toRosterItem(entry); 215 item.removeGroupName(this.getName()); 216 packet.addRosterItem(item); 217 // Wait up to a certain number of seconds for a reply from the server. 218 collector = connection.createPacketCollectorAndSend(packet); 219 } 220 } 221 if (collector != null) { 222 collector.nextResultOrThrow(); 223 } 224 } 225 226 void addEntryLocal(RosterEntry entry) { 227 // Update the entry if it is already in the list 228 synchronized (entries) { 229 entries.remove(entry); 230 entries.add(entry); 231 } 232 } 233 234 void removeEntryLocal(RosterEntry entry) { 235 // Only remove the entry if it's in the entry list. 236 synchronized (entries) { 237 if (entries.contains(entry)) { 238 entries.remove(entry); 239 } 240 } 241 } 242}