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.Manager; 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 extends Manager { 042 043 private final String name; 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 super(connection); 054 this.name = name; 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 * @throws XMPPErrorException 076 * @throws NoResponseException 077 */ 078 public void setName(String name) throws NotConnectedException, NoResponseException, XMPPErrorException { 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().createPacketCollectorAndSend(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(String 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 = XmppStringUtils.parseBareJid(user); 128 String userLowerCase = user.toLowerCase(Locale.US); 129 synchronized (entries) { 130 for (RosterEntry entry : entries) { 131 if (entry.getUser().equals(userLowerCase)) { 132 return entry; 133 } 134 } 135 } 136 return null; 137 } 138 139 /** 140 * Returns true if the specified entry is part of this group. 141 * 142 * @param entry a roster entry. 143 * @return true if the entry is part of this group. 144 */ 145 public boolean contains(RosterEntry entry) { 146 synchronized (entries) { 147 return entries.contains(entry); 148 } 149 } 150 151 /** 152 * Returns true if the specified XMPP address is an entry in this group. 153 * 154 * @param user the XMPP address of the user. 155 * @return true if the XMPP address is an entry in this group. 156 */ 157 public boolean contains(String user) { 158 return getEntry(user) != null; 159 } 160 161 /** 162 * Adds a roster entry to this group. If the entry was unfiled then it will be removed from 163 * the unfiled list and will be added to this group. 164 * Note that this is a synchronous call -- Smack must wait for the server 165 * to receive the updated roster. 166 * 167 * @param entry a roster entry. 168 * @throws XMPPErrorException if an error occured while trying to add the entry to the group. 169 * @throws NoResponseException if there was no response from the server. 170 * @throws NotConnectedException 171 */ 172 public void addEntry(RosterEntry entry) throws NoResponseException, XMPPErrorException, NotConnectedException { 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().createPacketCollectorAndSend(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 */ 199 public void removeEntry(RosterEntry entry) throws NoResponseException, XMPPErrorException, NotConnectedException { 200 // Only remove the entry if it's in the entry list. 201 // Remove the entry locally, if we wait for RosterPacketListenerprocess>>Packet(Packet) 202 // to take place the entry will exist in the group until a packet is received from the 203 // server. 204 synchronized (entries) { 205 if (entries.contains(entry)) { 206 RosterPacket packet = new RosterPacket(); 207 packet.setType(IQ.Type.set); 208 RosterPacket.Item item = RosterEntry.toRosterItem(entry); 209 item.removeGroupName(this.getName()); 210 packet.addRosterItem(item); 211 // Wait up to a certain number of seconds for a reply from the server. 212 connection().createPacketCollectorAndSend(packet).nextResultOrThrow(); 213 } 214 } 215 } 216 217 void addEntryLocal(RosterEntry entry) { 218 // Update the entry if it is already in the list 219 synchronized (entries) { 220 entries.remove(entry); 221 entries.add(entry); 222 } 223 } 224 225 void removeEntryLocal(RosterEntry entry) { 226 // Only remove the entry if it's in the entry list. 227 synchronized (entries) { 228 if (entries.contains(entry)) { 229 entries.remove(entry); 230 } 231 } 232 } 233}