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