1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-09-10 17:49:38 +02:00

Phase 1 of large refactoring. Removing dead code, bug fixes, updates to JDK 1.5.

git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@4511 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
Matt Tucker 2006-07-17 08:39:08 +00:00 committed by matt
parent 1a08715f67
commit bbbfe09c31
62 changed files with 291 additions and 3524 deletions

View file

@ -1,353 +0,0 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.filter.*;
import java.util.*;
/**
* A GroupChat is a conversation that takes place among many users in a virtual
* room. When joining a group chat, you specify a nickname, which is the identity
* that other chat room users see.
*
* @see XMPPConnection#createGroupChat(String)
* @author Matt Tucker
*/
public class GroupChat {
private XMPPConnection connection;
private String room;
private String nickname = null;
private boolean joined = false;
private List participants = new ArrayList();
private List connectionListeners = new ArrayList();
private PacketFilter presenceFilter;
private PacketFilter messageFilter;
private PacketCollector messageCollector;
/**
* Creates a new group chat with the specified connection and room name. Note: no
* information is sent to or received from the server until you attempt to
* {@link #join(String) join} the chat room. On some server implementations,
* the room will not be created until the first person joins it.<p>
*
* Most XMPP servers use a sub-domain for the chat service (eg chat.example.com
* for the XMPP server example.com). You must ensure that the room address you're
* trying to connect to includes the proper chat sub-domain.
*
* @param connection the XMPP connection.
* @param room the name of the room in the form "roomName@service", where
* "service" is the hostname at which the multi-user chat
* service is running.
*/
public GroupChat(XMPPConnection connection, String room) {
this.connection = connection;
this.room = room;
// Create a collector for all incoming messages.
messageFilter = new AndFilter(new FromContainsFilter(room),
new PacketTypeFilter(Message.class));
messageFilter = new AndFilter(messageFilter, new PacketFilter() {
public boolean accept(Packet packet) {
Message msg = (Message)packet;
return msg.getType() == Message.Type.GROUP_CHAT;
}
});
messageCollector = connection.createPacketCollector(messageFilter);
// Create a listener for all presence updates.
presenceFilter = new AndFilter(new FromContainsFilter(room),
new PacketTypeFilter(Presence.class));
connection.addPacketListener(new PacketListener() {
public void processPacket(Packet packet) {
Presence presence = (Presence)packet;
String from = presence.getFrom();
if (presence.getType() == Presence.Type.AVAILABLE) {
synchronized (participants) {
if (!participants.contains(from)) {
participants.add(from);
}
}
}
else if (presence.getType() == Presence.Type.UNAVAILABLE) {
synchronized (participants) {
participants.remove(from);
}
}
}
}, presenceFilter);
}
/**
* Returns the name of the room this GroupChat object represents.
*
* @return the groupchat room name.
*/
public String getRoom() {
return room;
}
/**
* Joins the chat room using the specified nickname. If already joined
* using another nickname, this method will first leave the room and then
* re-join using the new nickname. The default timeout of 5 seconds for a reply
* from the group chat server that the join succeeded will be used.
*
* @param nickname the nickname to use.
* @throws XMPPException if an error occurs joining the room. In particular, a
* 409 error can occur if someone is already in the group chat with the same
* nickname.
*/
public synchronized void join(String nickname) throws XMPPException {
join(nickname, SmackConfiguration.getPacketReplyTimeout());
}
/**
* Joins the chat room using the specified nickname. If already joined as
* another nickname, will leave as that name first before joining under the new
* name.
*
* @param nickname the nickname to use.
* @param timeout the number of milleseconds to wait for a reply from the
* group chat that joining the room succeeded.
* @throws XMPPException if an error occurs joining the room. In particular, a
* 409 error can occur if someone is already in the group chat with the same
* nickname.
*/
public synchronized void join(String nickname, long timeout) throws XMPPException {
if (nickname == null || nickname.equals("")) {
throw new IllegalArgumentException("Nickname must not be null or blank.");
}
// If we've already joined the room, leave it before joining under a new
// nickname.
if (joined) {
leave();
}
// We join a room by sending a presence packet where the "to"
// field is in the form "roomName@service/nickname"
Presence joinPresence = new Presence(Presence.Type.AVAILABLE);
joinPresence.setTo(room + "/" + nickname);
// Wait for a presence packet back from the server.
PacketFilter responseFilter = new AndFilter(
new FromContainsFilter(room + "/" + nickname),
new PacketTypeFilter(Presence.class));
PacketCollector response = connection.createPacketCollector(responseFilter);
// Send join packet.
connection.sendPacket(joinPresence);
// Wait up to a certain number of seconds for a reply.
Presence presence = (Presence)response.nextResult(timeout);
response.cancel();
if (presence == null) {
throw new XMPPException("No response from server.");
}
else if (presence.getError() != null) {
throw new XMPPException(presence.getError());
}
this.nickname = nickname;
joined = true;
}
/**
* Returns true if currently in the group chat (after calling the {@link
* #join(String)} method.
*
* @return true if currently in the group chat room.
*/
public boolean isJoined() {
return joined;
}
/**
* Leave the chat room.
*/
public synchronized void leave() {
// If not joined already, do nothing.
if (!joined) {
return;
}
// We leave a room by sending a presence packet where the "to"
// field is in the form "roomName@service/nickname"
Presence leavePresence = new Presence(Presence.Type.UNAVAILABLE);
leavePresence.setTo(room + "/" + nickname);
connection.sendPacket(leavePresence);
// Reset participant information.
participants = new ArrayList();
nickname = null;
joined = false;
}
/**
* Returns the nickname that was used to join the room, or <tt>null</tt> if not
* currently joined.
*
* @return the nickname currently being used.
*/
public String getNickname() {
return nickname;
}
/**
* Returns the number of participants in the group chat.<p>
*
* Note: this value will only be accurate after joining the group chat, and
* may fluctuate over time. If you query this value directly after joining the
* group chat it may not be accurate, as it takes a certain amount of time for
* the server to send all presence packets to this client.
*
* @return the number of participants in the group chat.
*/
public int getParticipantCount() {
synchronized (participants) {
return participants.size();
}
}
/**
* Returns an Iterator (of Strings) for the list of fully qualified participants
* in the group chat. For example, "conference@chat.jivesoftware.com/SomeUser".
* Typically, a client would only display the nickname of the participant. To
* get the nickname from the fully qualified name, use the
* {@link org.jivesoftware.smack.util.StringUtils#parseResource(String)} method.
* Note: this value will only be accurate after joining the group chat, and may
* fluctuate over time.
*
* @return an Iterator for the participants in the group chat.
*/
public Iterator getParticipants() {
synchronized (participants) {
return Collections.unmodifiableList(new ArrayList(participants)).iterator();
}
}
/**
* Adds a packet listener that will be notified of any new Presence packets
* sent to the group chat. Using a listener is a suitable way to know when the list
* of participants should be re-loaded due to any changes.
*
* @param listener a packet listener that will be notified of any presence packets
* sent to the group chat.
*/
public void addParticipantListener(PacketListener listener) {
connection.addPacketListener(listener, presenceFilter);
connectionListeners.add(listener);
}
/**
* Sends a message to the chat room.
*
* @param text the text of the message to send.
* @throws XMPPException if sending the message fails.
*/
public void sendMessage(String text) throws XMPPException {
Message message = new Message(room, Message.Type.GROUP_CHAT);
message.setBody(text);
connection.sendPacket(message);
}
/**
* Creates a new Message to send to the chat room.
*
* @return a new Message addressed to the chat room.
*/
public Message createMessage() {
return new Message(room, Message.Type.GROUP_CHAT);
}
/**
* Sends a Message to the chat room.
*
* @param message the message.
* @throws XMPPException if sending the message fails.
*/
public void sendMessage(Message message) throws XMPPException {
connection.sendPacket(message);
}
/**
* Polls for and returns the next message, or <tt>null</tt> if there isn't
* a message immediately available. This method provides significantly different
* functionalty than the {@link #nextMessage()} method since it's non-blocking.
* In other words, the method call will always return immediately, whereas the
* nextMessage method will return only when a message is available (or after
* a specific timeout).
*
* @return the next message if one is immediately available and
* <tt>null</tt> otherwise.
*/
public Message pollMessage() {
return (Message)messageCollector.pollResult();
}
/**
* Returns the next available message in the chat. The method call will block
* (not return) until a message is available.
*
* @return the next message.
*/
public Message nextMessage() {
return (Message)messageCollector.nextResult();
}
/**
* Returns the next available message in the chat. The method call will block
* (not return) until a packet is available or the <tt>timeout</tt> has elapased.
* If the timeout elapses without a result, <tt>null</tt> will be returned.
*
* @param timeout the maximum amount of time to wait for the next message.
* @return the next message, or <tt>null</tt> if the timeout elapses without a
* message becoming available.
*/
public Message nextMessage(long timeout) {
return (Message)messageCollector.nextResult(timeout);
}
/**
* Adds a packet listener that will be notified of any new messages in the
* group chat. Only "group chat" messages addressed to this group chat will
* be delivered to the listener. If you wish to listen for other packets
* that may be associated with this group chat, you should register a
* PacketListener directly with the XMPPConnection with the appropriate
* PacketListener.
*
* @param listener a packet listener.
*/
public void addMessageListener(PacketListener listener) {
connection.addPacketListener(listener, messageFilter);
connectionListeners.add(listener);
}
public void finalize() throws Throwable {
super.finalize();
try {
if (messageCollector != null) {
messageCollector.cancel();
}
// Remove all the PacketListeners added to the connection by this GroupChat
for (Iterator it=connectionListeners.iterator(); it.hasNext();) {
connection.removePacketListener((PacketListener) it.next());
}
}
catch (Exception e) {}
}
}

View file

@ -135,17 +135,34 @@ public class PacketCollector {
public synchronized Packet nextResult(long timeout) {
// Wait up to the specified amount of time for a result.
if (resultQueue.isEmpty()) {
long waitTime = timeout;
long start = System.currentTimeMillis();
try {
wait(timeout);
// Keep waiting until the specified amount of time has elapsed, or
// a packet is available to return.
while (resultQueue.isEmpty()) {
if (waitTime <= 0) {
break;
}
wait(waitTime);
long now = System.currentTimeMillis();
waitTime -= (now - start);
start = now;
}
}
catch (InterruptedException ie) {
// Ignore.
}
// Still haven't found a result, so return null.
if (resultQueue.isEmpty()) {
return null;
}
// Return the packet that was found.
else {
return (Packet)resultQueue.removeLast();
}
}
// If still no result, return null.
if (resultQueue.isEmpty()) {
return null;
}
// There's already a packet waiting, so return it.
else {
return (Packet)resultQueue.removeLast();
}

View file

@ -25,6 +25,7 @@ import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.util.StringUtils;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Represents a user's roster, which is the collection of users a person receives
@ -68,10 +69,10 @@ public class Roster {
private static int defaultSubscriptionMode = SUBSCRIPTION_ACCEPT_ALL;
private XMPPConnection connection;
private Map groups;
private Map<String,RosterGroup> groups;
private List entries;
private List unfiledEntries;
private List rosterListeners;
private List<RosterListener> rosterListeners;
private Map presenceMap;
// The roster is marked as initialized when at least a single roster packet
// has been recieved and processed.
@ -110,10 +111,10 @@ public class Roster {
*/
Roster(final XMPPConnection connection) {
this.connection = connection;
groups = new Hashtable();
groups = new ConcurrentHashMap<String,RosterGroup>();
unfiledEntries = new ArrayList();
entries = new ArrayList();
rosterListeners = new ArrayList();
rosterListeners = new ArrayList<RosterListener>();
presenceMap = new HashMap();
// Listen for any roster packets.
PacketFilter rosterFilter = new PacketTypeFilter(RosterPacket.class);
@ -130,7 +131,7 @@ public class Roster {
*
* If using the manual mode, a PacketListener should be registered that
* listens for Presence packets that have a type of
* {@link org.jivesoftware.smack.packet.Presence.Type#SUBSCRIBE}.
* {@link org.jivesoftware.smack.packet.Presence.Type#subscribe}.
*
* @return the subscription mode.
*/
@ -145,7 +146,7 @@ public class Roster {
*
* If using the manual mode, a PacketListener should be registered that
* listens for Presence packets that have a type of
* {@link org.jivesoftware.smack.packet.Presence.Type#SUBSCRIBE}.
* {@link org.jivesoftware.smack.packet.Presence.Type#subscribe}.
*
* @param subscriptionMode the subscription mode.
*/
@ -251,7 +252,7 @@ public class Roster {
}
// Create a presence subscription packet and send.
Presence presencePacket = new Presence(Presence.Type.SUBSCRIBE);
Presence presencePacket = new Presence(Presence.Type.subscribe);
presencePacket.setTo(user);
connection.sendPacket(presencePacket);
}
@ -291,7 +292,7 @@ public class Roster {
throw new XMPPException(response.getError());
}
else {
}
}
@ -301,42 +302,26 @@ public class Roster {
* @return the number of entries in the roster.
*/
public int getEntryCount() {
HashMap entryMap = new HashMap();
// Loop through all roster groups.
for (Iterator groups = getGroups(); groups.hasNext(); ) {
RosterGroup rosterGroup = (RosterGroup) groups.next();
for (Iterator entries = rosterGroup.getEntries(); entries.hasNext(); ) {
entryMap.put(entries.next(), "");
}
}
synchronized (unfiledEntries) {
return entryMap.size() + unfiledEntries.size();
}
return getEntries().size();
}
/**
* Returns all entries in the roster, including entries that don't belong to
* any groups.
* Returns an unmodifiable collection of all entries in the roster, including entries
* that don't belong to any groups.
*
* @return all entries in the roster.
*/
public Iterator getEntries() {
ArrayList allEntries = new ArrayList();
public Collection<RosterEntry> getEntries() {
Set<RosterEntry> allEntries = new HashSet<RosterEntry>();
// Loop through all roster groups and add their entries to the answer
for (Iterator groups = getGroups(); groups.hasNext(); ) {
RosterGroup rosterGroup = (RosterGroup) groups.next();
for (Iterator entries = rosterGroup.getEntries(); entries.hasNext(); ) {
RosterEntry entry = (RosterEntry)entries.next();
if (!allEntries.contains(entry)) {
allEntries.add(entry);
}
}
for (RosterGroup rosterGroup: getGroups()) {
allEntries.addAll(rosterGroup.getEntries());
}
// Add the roster unfiled entries to the answer
synchronized (unfiledEntries) {
allEntries.addAll(unfiledEntries);
}
return allEntries.iterator();
return Collections.unmodifiableCollection(allEntries);
}
/**
@ -406,9 +391,7 @@ public class Roster {
* @return the roster group with the specified name.
*/
public RosterGroup getGroup(String name) {
synchronized (groups) {
return (RosterGroup)groups.get(name);
}
return groups.get(name);
}
/**
@ -417,21 +400,16 @@ public class Roster {
* @return the number of groups in the roster.
*/
public int getGroupCount() {
synchronized (groups) {
return groups.size();
}
return groups.size();
}
/**
* Returns an iterator the for all the roster groups.
* Returns an unmodiable collections of all the roster groups.
*
* @return an iterator for all roster groups.
*/
public Iterator getGroups() {
synchronized (groups) {
List groupsList = Collections.unmodifiableList(new ArrayList(groups.values()));
return groupsList.iterator();
}
public Collection<RosterGroup> getGroups() {
return Collections.unmodifiableCollection(groups.values());
}
/**
@ -483,7 +461,7 @@ public class Roster {
}
}
/**
/**
* Returns the presence info for a particular user's resource, or <tt>null</tt> if the user
* is unavailable (offline) or if no presence information is available, such as
* when you are not subscribed to the user's presence updates.
@ -603,7 +581,7 @@ public class Roster {
// If an "available" packet, add it to the presence map. Each presence map will hold
// for a particular user a map with the presence packets saved for each resource.
if (presence.getType() == Presence.Type.AVAILABLE) {
if (presence.getType() == Presence.Type.available) {
Map userPresences;
// Get the user presence map
if (presenceMap.get(key) == null) {
@ -628,7 +606,7 @@ public class Roster {
}
}
// If an "unavailable" packet, remove any entries in the presence map.
else if (presence.getType() == Presence.Type.UNAVAILABLE) {
else if (presence.getType() == Presence.Type.unavailable) {
if (presenceMap.get(key) != null) {
Map userPresences = (Map) presenceMap.get(key);
synchronized (userPresences) {
@ -648,27 +626,27 @@ public class Roster {
}
}
}
else if (presence.getType() == Presence.Type.SUBSCRIBE) {
else if (presence.getType() == Presence.Type.subscribe) {
if (subscriptionMode == SUBSCRIPTION_ACCEPT_ALL) {
// Accept all subscription requests.
Presence response = new Presence(Presence.Type.SUBSCRIBED);
Presence response = new Presence(Presence.Type.subscribed);
response.setTo(presence.getFrom());
connection.sendPacket(response);
}
else if (subscriptionMode == SUBSCRIPTION_REJECT_ALL) {
// Reject all subscription requests.
Presence response = new Presence(Presence.Type.UNSUBSCRIBED);
Presence response = new Presence(Presence.Type.unsubscribed);
response.setTo(presence.getFrom());
connection.sendPacket(response);
}
// Otherwise, in manual mode so ignore.
}
else if (presence.getType() == Presence.Type.UNSUBSCRIBE) {
else if (presence.getType() == Presence.Type.unsubscribe) {
if (subscriptionMode != SUBSCRIPTION_MANUAL) {
// Acknowledge and accept unsubscription notification so that the
// server will stop sending notifications saying that the contact
// has unsubscribed to our presence.
Presence response = new Presence(Presence.Type.UNSUBSCRIBED);
Presence response = new Presence(Presence.Type.unsubscribed);
response.setTo(presence.getFrom());
connection.sendPacket(response);
}
@ -690,8 +668,7 @@ public class Roster {
Collection deletedEntries = new ArrayList();
RosterPacket rosterPacket = (RosterPacket)packet;
for (Iterator i=rosterPacket.getRosterItems(); i.hasNext(); ) {
RosterPacket.Item item = (RosterPacket.Item)i.next();
for (RosterPacket.Item item : rosterPacket.getRosterItems()) {
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
item.getItemType(), item.getItemStatus(), connection);
@ -733,7 +710,7 @@ public class Roster {
}
// If the roster entry belongs to any groups, remove it from the
// list of unfiled entries.
if (item.getGroupNames().hasNext()) {
if (!item.getGroupNames().isEmpty()) {
synchronized (unfiledEntries) {
unfiledEntries.remove(entry);
}
@ -749,18 +726,16 @@ public class Roster {
}
// Find the list of groups that the user currently belongs to.
List currentGroupNames = new ArrayList();
for (Iterator j = entry.getGroups(); j.hasNext(); ) {
RosterGroup group = (RosterGroup)j.next();
currentGroupNames.add(group.getName());
List<String> currentGroupNames = new ArrayList<String>();
for (RosterGroup rosterGroup : entry.getGroups()) {
currentGroupNames.add(rosterGroup.getName());
}
// If the packet is not of the type REMOVE then add the entry to the groups
if (!RosterPacket.ItemType.REMOVE.equals(item.getItemType())) {
// Create the new list of groups the user belongs to.
List newGroupNames = new ArrayList();
for (Iterator k = item.getGroupNames(); k.hasNext(); ) {
String groupName = (String)k.next();
List<String> newGroupNames = new ArrayList<String>();
for (String groupName : item.getGroupNames()) {
// Add the group name to the list.
newGroupNames.add(groupName);
@ -798,12 +773,9 @@ public class Roster {
// RosterGroup.removeEntry removes the entry immediately (locally) and the
// group could remain empty.
// TODO Check the performance/logic for rosters with large number of groups
for (Iterator it = getGroups(); it.hasNext();) {
RosterGroup group = (RosterGroup)it.next();
for (RosterGroup group : getGroups()) {
if (group.getEntryCount() == 0) {
synchronized (groups) {
groups.remove(group.getName());
}
groups.remove(group.getName());
}
}
}

View file

@ -106,21 +106,20 @@ public class RosterEntry {
}
/**
* Returns an iterator for all the roster groups that this entry belongs to.
* Returns an unmodifiable collection of the roster groups that this entry belongs to.
*
* @return an iterator for the groups this entry belongs to.
*/
public Iterator getGroups() {
List results = new ArrayList();
public Collection<RosterGroup> getGroups() {
List<RosterGroup> results = new ArrayList<RosterGroup>();
// Loop through all roster groups and find the ones that contain this
// entry. This algorithm should be fine
for (Iterator i=connection.roster.getGroups(); i.hasNext(); ) {
RosterGroup group = (RosterGroup)i.next();
for (RosterGroup group: connection.roster.getGroups()) {
if (group.contains(this)) {
results.add(group);
}
}
return results.iterator();
return Collections.unmodifiableCollection(results);
}
/**
@ -152,14 +151,15 @@ public class RosterEntry {
buf.append(name).append(": ");
}
buf.append(user);
Iterator groups = getGroups();
if (groups.hasNext()) {
Collection<RosterGroup> groups = getGroups();
if (!groups.isEmpty()) {
buf.append(" [");
RosterGroup group = (RosterGroup)groups.next();
Iterator<RosterGroup> iter = groups.iterator();
RosterGroup group = iter.next();
buf.append(group.getName());
while (groups.hasNext()) {
while (iter.hasNext()) {
buf.append(", ");
group = (RosterGroup)groups.next();
group = iter.next();
buf.append(group.getName());
}
buf.append("]");
@ -184,8 +184,7 @@ public class RosterEntry {
item.setItemType(entry.getType());
item.setItemStatus(entry.getStatus());
// Set the correct group names for the item.
for (Iterator j=entry.getGroups(); j.hasNext(); ) {
RosterGroup group = (RosterGroup)j.next();
for (RosterGroup group : entry.getGroups()) {
item.addGroupName(group.getName());
}
return item;

View file

@ -37,7 +37,7 @@ public class RosterGroup {
private String name;
private XMPPConnection connection;
private List entries;
private List<RosterEntry> entries;
/**
* Creates a new roster group instance.
@ -48,7 +48,7 @@ public class RosterGroup {
RosterGroup(String name, XMPPConnection connection) {
this.name = name;
this.connection = connection;
entries = new ArrayList();
entries = new ArrayList<RosterEntry>();
}
/**
@ -95,13 +95,13 @@ public class RosterGroup {
}
/**
* Returns an iterator for the entries in the group.
* Returns an unmodifiable collection of all entries in the group.
*
* @return an iterator for the entries in the group.
* @return all entries in the group.
*/
public Iterator getEntries() {
public Collection<RosterEntry> getEntries() {
synchronized (entries) {
return Collections.unmodifiableList(new ArrayList(entries)).iterator();
return Collections.unmodifiableList(new ArrayList<RosterEntry>(entries));
}
}

View file

@ -454,7 +454,7 @@ public class XMPPConnection {
// Set presence to online.
if (sendPresence) {
packetWriter.sendPacket(new Presence(Presence.Type.AVAILABLE));
packetWriter.sendPacket(new Presence(Presence.Type.available));
}
// Indicate that we're now authenticated.
@ -512,7 +512,7 @@ public class XMPPConnection {
roster = null;
// Set presence to online.
packetWriter.sendPacket(new Presence(Presence.Type.AVAILABLE));
packetWriter.sendPacket(new Presence(Presence.Type.available));
// Indicate that we're now authenticated.
authenticated = true;
@ -595,24 +595,6 @@ public class XMPPConnection {
return new Chat(this, participant);
}
/**
* Creates a new group chat connected to the specified room. The room name
* should be full address, such as <tt>room@chat.example.com</tt>.
* <p>
* Most XMPP servers use a sub-domain for the chat service (eg chat.example.com
* for the XMPP server example.com). You must ensure that the room address you're
* trying to connect to includes the proper chat sub-domain.
*
* @param room the fully qualifed name of the room.
* @return a new GroupChat object.
*/
public GroupChat createGroupChat(String room) {
if (!isConnected()) {
throw new IllegalStateException("Not connected to server.");
}
return new GroupChat(this, room);
}
/**
* Returns true if currently connected to the XMPP server.
*
@ -656,7 +638,7 @@ public class XMPPConnection {
*/
public void close() {
// Set presence to offline.
packetWriter.sendPacket(new Presence(Presence.Type.UNAVAILABLE));
packetWriter.sendPacket(new Presence(Presence.Type.unavailable));
packetReader.shutdown();
packetWriter.shutdown();
// Wait 150 ms for processes to clean-up, then shutdown.

View file

@ -26,16 +26,16 @@ import org.jivesoftware.smack.util.StringUtils;
* Represents XMPP presence packets. Every presence packet has a type, which is one of
* the following values:
* <ul>
* <li><tt>Presence.Type.AVAILABLE</tt> -- (Default) indicates the user is available to
* <li>{@link Presence.Type#available available} -- (Default) indicates the user is available to
* receive messages.
* <li><tt>Presence.Type.UNAVAILABLE</tt> -- the user is unavailable to receive messages.
* <li><tt>Presence.Type.SUBSCRIBE</tt> -- request subscription to recipient's presence.
* <li><tt>Presence.Type.SUBSCRIBED</tt> -- grant subscription to sender's presence.
* <li><tt>Presence.Type.UNSUBSCRIBE</tt> -- request removal of subscription to sender's
* presence.
* <li><tt>Presence.Type.UNSUBSCRIBED</tt> -- grant removal of subscription to sender's
* presence.
* <li><tt>Presence.Type.ERROR</tt> -- the presence packet contains an error message.
* <li>{@link Presence.Type#unavailable unavailable} -- the user is unavailable to receive messages.
* <li>{@link Presence.Type#subscribe subscribe} -- request subscription to recipient's presence.
* <li>{@link Presence.Type#subscribed subscribed} -- grant subscription to sender's presence.
* <li>{@link Presence.Type#unsubscribe unsubscribe} -- request removal of subscription to
* sender's presence.
* <li>{@link Presence.Type#unsubscribed unsubscribed} -- grant removal of subscription to
* sender's presence.
* <li>{@link Presence.Type#error error} -- the presence packet contains an error message.
* </ul><p>
*
* A number of attributes are optional:
@ -44,8 +44,9 @@ import org.jivesoftware.smack.util.StringUtils;
* <li>Priority -- non-negative numerical priority of a sender's resource. The
* highest resource priority is the default recipient of packets not addressed
* to a particular resource.
* <li>Mode -- one of five presence modes: available (the default), chat, away,
* xa (extended away, and dnd (do not disturb).
* <li>Mode -- one of five presence modes: {@link Mode#available available} (the default),
* {@link Mode#chat chat}, {@link Mode#away away}, {@link Mode#xa xa} (extended away), and
* {@link Mode#dnd dnd} (do not disturb).
* </ul><p>
*
* Presence packets are used for two purposes. First, to notify the server of our
@ -57,10 +58,10 @@ import org.jivesoftware.smack.util.StringUtils;
*/
public class Presence extends Packet {
private Type type = Type.AVAILABLE;
private Type type = Type.available;
private String status = null;
private int priority = -1;
private Mode mode = Mode.AVAILABLE;
private Mode mode = Mode.available;
/**
* Creates a new presence update. Status, priority, and mode are left un-set.
@ -179,7 +180,7 @@ public class Presence extends Packet {
if (getFrom() != null) {
buf.append(" from=\"").append(StringUtils.escapeForXML(getFrom())).append("\"");
}
if (type != Type.AVAILABLE) {
if (type != Type.available) {
buf.append(" type=\"").append(type).append("\"");
}
buf.append(">");
@ -189,7 +190,7 @@ public class Presence extends Packet {
if (priority != -1) {
buf.append("<priority>").append(priority).append("</priority>");
}
if (mode != null && mode != Mode.AVAILABLE) {
if (mode != null && mode != Mode.available) {
buf.append("<show>").append(mode).append("</show>");
}
@ -219,105 +220,78 @@ public class Presence extends Packet {
}
/**
* A typsafe enum class to represent the presecence type.
* A enum to represent the presecence type. Not that presence type is often confused
* with presence mode. Generally, if a user is signed into a server, they have a presence
* type of {@link #available available}, even if the mode is {@link Mode#away away},
* {@link Mode#dnd dnd}, etc. The presence type is only {@link #unavailable unavailable} when
* the user is signing out of the server.
*/
public static class Type {
public enum Type {
public static final Type AVAILABLE = new Type("available");
public static final Type UNAVAILABLE = new Type("unavailable");
public static final Type SUBSCRIBE = new Type("subscribe");
public static final Type SUBSCRIBED = new Type("subscribed");
public static final Type UNSUBSCRIBE = new Type("unsubscribe");
public static final Type UNSUBSCRIBED = new Type("unsubscribed");
public static final Type ERROR = new Type("error");
private String value;
private Type(String value) {
this.value = value;
}
public String toString() {
return value;
}
/**
* The user is available to receive messages (default).
*/
available,
/**
* Returns the type constant associated with the String value.
* The user is unavailable to receive messages.
*/
public static Type fromString(String value) {
if (value == null) {
return AVAILABLE;
}
value = value.toLowerCase();
if ("unavailable".equals(value)) {
return UNAVAILABLE;
}
else if ("subscribe".equals(value)) {
return SUBSCRIBE;
}
else if ("subscribed".equals(value)) {
return SUBSCRIBED;
}
else if ("unsubscribe".equals(value)) {
return UNSUBSCRIBE;
}
else if ("unsubscribed".equals(value)) {
return UNSUBSCRIBED;
}
else if ("error".equals(value)) {
return ERROR;
}
// Default to available.
else {
return AVAILABLE;
}
}
unavailable,
/**
* Request subscription to recipient's presence.
*/
subscribe,
/**
* Grant subscription to sender's presence.
*/
subscribed,
/**
* Request removal of subscription to sender's presence.
*/
unsubscribe,
/**
* Grant removal of subscription to sender's presence.
*/
unsubscribed,
/**
* The presence packet contains an error message.
*/
error
}
/**
* A typsafe enum class to represent the presence mode.
* An enum to represent the presence mode.
*/
public static class Mode {
public static final Mode AVAILABLE = new Mode("available");
public static final Mode CHAT = new Mode("chat");
public static final Mode AWAY = new Mode("away");
public static final Mode EXTENDED_AWAY = new Mode("xa");
public static final Mode DO_NOT_DISTURB = new Mode("dnd");
private String value;
private Mode(String value) {
this.value = value;
}
public String toString() {
return value;
}
public enum Mode {
/**
* Returns the mode constant associated with the String value.
* Available (the default).
*/
public static Mode fromString(String value) {
if (value == null) {
return AVAILABLE;
}
value = value.toLowerCase();
if (value.equals("chat")) {
return CHAT;
}
else if (value.equals("away")) {
return AWAY;
}
else if (value.equals("xa")) {
return EXTENDED_AWAY;
}
else if (value.equals("dnd")) {
return DO_NOT_DISTURB;
}
else {
return AVAILABLE;
}
}
available,
/**
* Free to chat.
*/
chat,
/**
* Away.
*/
away,
/**
* Away for an extended period of time.
*/
xa,
/**
* Do not disturb.
*/
dnd
}
}
}

View file

@ -23,6 +23,7 @@ package org.jivesoftware.smack.packet;
import org.jivesoftware.smack.util.StringUtils;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Represents XMPP roster packets.
@ -31,7 +32,7 @@ import java.util.*;
*/
public class RosterPacket extends IQ {
private final List rosterItems = new ArrayList();
private final List<Item> rosterItems = new ArrayList<Item>();
/**
* Adds a roster item to the packet.
@ -56,14 +57,13 @@ public class RosterPacket extends IQ {
}
/**
* Returns an Iterator for the roster items in the packet.
* Returns an unmodifiable collection for the roster items in the packet.
*
* @return and Iterator for the roster items in the packet.
* @return an unmodifiable collection for the roster items in the packet.
*/
public Iterator getRosterItems() {
public Collection<Item> getRosterItems() {
synchronized (rosterItems) {
List entries = Collections.unmodifiableList(new ArrayList(rosterItems));
return entries.iterator();
return Collections.unmodifiableList(new ArrayList<Item>(rosterItems));
}
}
@ -71,8 +71,7 @@ public class RosterPacket extends IQ {
StringBuffer buf = new StringBuffer();
buf.append("<query xmlns=\"jabber:iq:roster\">");
synchronized (rosterItems) {
for (int i=0; i<rosterItems.size(); i++) {
Item entry = (Item)rosterItems.get(i);
for (Item entry : rosterItems) {
buf.append(entry.toXML());
}
}
@ -90,7 +89,7 @@ public class RosterPacket extends IQ {
private String name;
private ItemType itemType;
private ItemStatus itemStatus;
private final List groupNames;
private final Set<String> groupNames;
/**
* Creates a new roster item.
@ -103,7 +102,7 @@ public class RosterPacket extends IQ {
this.name = name;
itemType = null;
itemStatus = null;
groupNames = new ArrayList();
groupNames = new CopyOnWriteArraySet<String>();
}
/**
@ -170,15 +169,13 @@ public class RosterPacket extends IQ {
}
/**
* Returns an Iterator for the group names (as Strings) that the roster item
* Returns an unmodifiable set of the group names that the roster item
* belongs to.
*
* @return an Iterator for the group names.
* @return an unmodifiable set of the group names.
*/
public Iterator getGroupNames() {
synchronized (groupNames) {
return Collections.unmodifiableList(groupNames).iterator();
}
public Set<String> getGroupNames() {
return Collections.unmodifiableSet(groupNames);
}
/**
@ -187,11 +184,7 @@ public class RosterPacket extends IQ {
* @param groupName the group name.
*/
public void addGroupName(String groupName) {
synchronized (groupNames) {
if (!groupNames.contains(groupName)) {
groupNames.add(groupName);
}
}
groupNames.add(groupName);
}
/**
@ -200,9 +193,7 @@ public class RosterPacket extends IQ {
* @param groupName the group name.
*/
public void removeGroupName(String groupName) {
synchronized (groupNames) {
groupNames.remove(groupName);
}
groupNames.remove(groupName);
}
public String toXML() {
@ -218,11 +209,8 @@ public class RosterPacket extends IQ {
buf.append(" ask=\"").append(itemStatus).append("\"");
}
buf.append(">");
synchronized (groupNames) {
for (int i=0; i<groupNames.size(); i++) {
String groupName = (String)groupNames.get(i);
buf.append("<group>").append(StringUtils.escapeForXML(groupName)).append("</group>");
}
for (String groupName : groupNames) {
buf.append("<group>").append(StringUtils.escapeForXML(groupName)).append("</group>");
}
buf.append("</item>");
return buf.toString();

View file

@ -131,7 +131,16 @@ public class PacketParserUtils {
* @throws Exception if an exception occurs while parsing the packet.
*/
public static Presence parsePresence(XmlPullParser parser) throws Exception {
Presence.Type type = Presence.Type.fromString(parser.getAttributeValue("", "type"));
Presence.Type type = Presence.Type.available;
String typeString = parser.getAttributeValue("", "type");
if (typeString != null && !typeString.equals("")) {
try {
type = Presence.Type.valueOf(typeString);
}
catch (IllegalArgumentException iae) {
System.err.println("Found invalid presence type " + typeString);
}
}
Presence presence = new Presence(type);
presence.setTo(parser.getAttributeValue("", "to"));
@ -154,14 +163,22 @@ public class PacketParserUtils {
int priority = Integer.parseInt(parser.nextText());
presence.setPriority(priority);
}
catch (NumberFormatException nfe) { }
catch (NumberFormatException nfe) {
// Ignore.
}
catch (IllegalArgumentException iae) {
// Presence priority is out of range so assume priority to be zero
presence.setPriority(0);
}
}
else if (elementName.equals("show")) {
presence.setMode(Presence.Mode.fromString(parser.nextText()));
String modeText = parser.nextText();
try {
presence.setMode(Presence.Mode.valueOf(modeText));
}
catch (IllegalArgumentException iae) {
System.err.println("Found invalid presence mode " + modeText);
}
}
else if (elementName.equals("error")) {
presence.setError(parseError(parser));

View file

@ -129,8 +129,9 @@ public class RosterExchangeManager {
Message msg = new Message(targetUserID);
// Create a RosterExchange Package and add it to the message
RosterExchange rosterExchange = new RosterExchange();
for (Iterator it = rosterGroup.getEntries(); it.hasNext();)
rosterExchange.addRosterEntry((RosterEntry) it.next());
for (RosterEntry entry : rosterGroup.getEntries()) {
rosterExchange.addRosterEntry(entry);
}
msg.addExtension(rosterExchange);
// Send the message that contains the roster

View file

@ -47,7 +47,7 @@ public class DeafOccupantInterceptor implements PacketInterceptor {
public void interceptPacket(Packet packet) {
Presence presence = (Presence) packet;
// Check if user is joining a room
if (Presence.Type.AVAILABLE == presence.getType() &&
if (Presence.Type.available == presence.getType() &&
presence.getExtension("x", "http://jabber.org/protocol/muc") != null) {
// Add extension that indicates that user wants to be a deaf occupant
packet.addExtension(new DeafExtension());

View file

@ -291,7 +291,7 @@ public class MultiUserChat {
}
// We create a room by sending a presence packet to room@service/nick
// and signal support for MUC. The owner will be automatically logged into the room.
Presence joinPresence = new Presence(Presence.Type.AVAILABLE);
Presence joinPresence = new Presence(Presence.Type.available);
joinPresence.setTo(room + "/" + nickname);
// Indicate the the client supports MUC
joinPresence.addExtension(new MUCInitialPresence());
@ -422,7 +422,7 @@ public class MultiUserChat {
}
// We join a room by sending a presence packet where the "to"
// field is in the form "roomName@service/nickname"
Presence joinPresence = new Presence(Presence.Type.AVAILABLE);
Presence joinPresence = new Presence(Presence.Type.available);
joinPresence.setTo(room + "/" + nickname);
// Indicate the the client supports MUC
@ -484,7 +484,7 @@ public class MultiUserChat {
}
// We leave a room by sending a presence packet where the "to"
// field is in the form "roomName@service/nickname"
Presence leavePresence = new Presence(Presence.Type.UNAVAILABLE);
Presence leavePresence = new Presence(Presence.Type.unavailable);
leavePresence.setTo(room + "/" + nickname);
// Invoke presence interceptors so that extra information can be dynamically added
for (Iterator it = presenceInterceptors.iterator(); it.hasNext();) {
@ -944,7 +944,7 @@ public class MultiUserChat {
// We change the nickname by sending a presence packet where the "to"
// field is in the form "roomName@service/nickname"
// We don't have to signal the MUC support again
Presence joinPresence = new Presence(Presence.Type.AVAILABLE);
Presence joinPresence = new Presence(Presence.Type.available);
joinPresence.setTo(room + "/" + nickname);
// Invoke presence interceptors so that extra information can be dynamically added
for (Iterator it = presenceInterceptors.iterator(); it.hasNext();) {
@ -995,7 +995,7 @@ public class MultiUserChat {
}
// We change the availability status by sending a presence packet to the room with the
// new presence status and mode
Presence joinPresence = new Presence(Presence.Type.AVAILABLE);
Presence joinPresence = new Presence(Presence.Type.available);
joinPresence.setStatus(status);
joinPresence.setMode(mode);
joinPresence.setTo(room + "/" + nickname);
@ -2112,7 +2112,7 @@ public class MultiUserChat {
String from = presence.getFrom();
String myRoomJID = room + "/" + nickname;
boolean isUserStatusModification = presence.getFrom().equals(myRoomJID);
if (presence.getType() == Presence.Type.AVAILABLE) {
if (presence.getType() == Presence.Type.available) {
Presence oldPresence;
synchronized (occupantsMap) {
oldPresence = (Presence)occupantsMap.get(from);
@ -2145,7 +2145,7 @@ public class MultiUserChat {
}
}
}
else if (presence.getType() == Presence.Type.UNAVAILABLE) {
else if (presence.getType() == Presence.Type.unavailable) {
synchronized (occupantsMap) {
occupantsMap.remove(from);
}

View file

@ -64,8 +64,8 @@ public class RosterExchange implements PacketExtension {
*/
public RosterExchange(Roster roster) {
// Add all the roster entries to the new RosterExchange
for (Iterator rosterEntries = roster.getEntries(); rosterEntries.hasNext();) {
this.addRosterEntry((RosterEntry) rosterEntries.next());
for (RosterEntry rosterEntry : roster.getEntries()) {
this.addRosterEntry(rosterEntry);
}
}
@ -76,15 +76,16 @@ public class RosterExchange implements PacketExtension {
*/
public void addRosterEntry(RosterEntry rosterEntry) {
// Obtain a String[] from the roster entry groups name
ArrayList groupNamesList = new ArrayList();
List<String> groupNamesList = new ArrayList<String>();
String[] groupNames;
for (Iterator groups = rosterEntry.getGroups(); groups.hasNext();) {
groupNamesList.add(((RosterGroup) groups.next()).getName());
for (RosterGroup group : rosterEntry.getGroups()) {
groupNamesList.add(group.getName());
}
groupNames = (String[]) groupNamesList.toArray(new String[groupNamesList.size()]);
groupNames = groupNamesList.toArray(new String[groupNamesList.size()]);
// Create a new Entry based on the rosterEntry and add it to the packet
RemoteRosterEntry remoteRosterEntry = new RemoteRosterEntry(rosterEntry.getUser(), rosterEntry.getName(), groupNames);
RemoteRosterEntry remoteRosterEntry = new RemoteRosterEntry(rosterEntry.getUser(),
rosterEntry.getName(), groupNames);
addRosterEntry(remoteRosterEntry);
}