Initial roster support.

git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@1835 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
Matt Tucker 2003-02-10 05:01:01 +00:00 committed by mtucker
parent e08d37ac2e
commit b2127ce982
7 changed files with 687 additions and 20 deletions

View File

@ -72,6 +72,9 @@ import org.jivesoftware.smack.util.StringUtils;
*/
class PacketReader {
/**
* Namespace used to store packet properties.
*/
private static final String PROPERTIES_NAMESPACE =
"http://www.jivesoftware.com/xmlns/xmpp/properties";
@ -107,8 +110,21 @@ class PacketReader {
listenerThread.setDaemon(true);
try {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance(
System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
final String defaultProviderName = "org.xmlpull.mxp1.MXParserFactory";
XmlPullParserFactory factory = null;
try {
// Attempt to load a factory implementation using a system property
// and a classloader context.
factory = XmlPullParserFactory.newInstance(
System.getProperty(XmlPullParserFactory.PROPERTY_NAME),
Thread.currentThread().getContextClassLoader().getClass());
}
catch (Exception e) {
if (factory == null) {
// Loading failed. Therefore, use the hardcoded default.
factory = XmlPullParserFactory.newInstance(defaultProviderName, null);
}
}
factory.setNamespaceAware(true);
parser = factory.newPullParser();
parser.setInput(connection.reader);
@ -323,6 +339,9 @@ class PacketReader {
if (namespace.equals("jabber:iq:auth")) {
iqPacket = parseAuthentication(parser);
}
else if (namespace.equals("jabber:iq:roster")) {
iqPacket = parseRoster(parser);
}
}
else if (parser.getName().equals("error")) {
error = parseError(parser);
@ -387,6 +406,37 @@ class PacketReader {
return authentication;
}
private static RosterPacket parseRoster(XmlPullParser parser) throws Exception {
RosterPacket roster = new RosterPacket();
boolean done = false;
RosterPacket.Item item = null;
while (!done) {
int eventType = parser.next();
if (eventType == parser.START_TAG) {
if (parser.getName().equals("item")) {
String jid = parser.getAttributeValue("", "jid");
String name = parser.getAttributeValue("", "name");
String subscription = parser.getAttributeValue("", "subscription");
item = new RosterPacket.Item(jid, name);
item.setItemType(RosterPacket.ItemType.fromString(subscription));
}
if (parser.getName().equals("group")) {
String groupName = parser.nextText();
item.addGroupName(groupName);
}
}
else if (eventType == parser.END_TAG) {
if (parser.getName().equals("item")) {
roster.addRosterItem(item);
}
if (parser.getName().equals("query")) {
done = true;
}
}
}
return roster;
}
private static Error parseError(XmlPullParser parser) throws Exception {
String errorCode = null;
for (int i=0; i<parser.getAttributeCount(); i++) {
@ -542,7 +592,6 @@ class PacketReader {
Map properties = new HashMap();
while (true) {
int eventType = parser.next();
System.out.println("Start: " + parser.getName());
if (eventType == parser.START_TAG) {
String name = parser.nextText();
parser.next();
@ -615,10 +664,6 @@ class PacketReader {
return false;
}
public void processPacket(Packet packet) {
packetCollector.processPacket(packet);
}
public boolean notifyListener() {
Packet packet = packetCollector.pollResult();
if (packet != null) {

View File

@ -52,21 +52,134 @@
package org.jivesoftware.smack;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.filter.*;
import java.util.*;
/**
* NOTE: this class is not yet implemented.
* Roster.
*
* @see XMPPConnection#getRoster()
* @author Matt Tucker
*/
public class Roster {
private Map presenceMap = new HashMap();
private XMPPConnection connection;
private Map groups;
protected Roster(XMPPConnection connection) {
Roster(final XMPPConnection connection) {
this.connection = connection;
groups = new HashMap();
// Listen for any roster packets.
PacketFilter filter = new PacketTypeFilter(RosterPacket.class);
PacketListener rosterListener = new RosterListener();
connection.addPacketListener(rosterListener, filter);
}
/**
* Reloads the entire roster from the server. This is an asynchronous operation,
* which means the method will return immediately, and the roster will be
* reloaded at a later point when the server responds to the reload request.
*/
public void reload() {
connection.sendPacket(new RosterPacket());
}
/**
* Creates a new group.
*
* @param name the name of the group.
* @return a new group.
*/
public RosterGroup createGroup(String name) {
synchronized (groups) {
if (groups.containsKey(name)) {
throw new IllegalArgumentException("Group with name " + name + " alread exists.");
}
RosterGroup group = new RosterGroup(name, connection);
groups.put(name, group);
return group;
}
}
/**
* Returns the roster group with the specified name, or <tt>null</tt> if the
* group doesn't exist.
*
* @param name the name of the group.
* @return the roster group with the specified name.
*/
public RosterGroup getGroup(String name) {
synchronized (groups) {
return (RosterGroup)groups.get(name);
}
}
/**
* Returns an iterator the for 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();
}
}
/**
* Listens for all roster packets and processes them.
*/
private class RosterListener implements PacketListener {
public void processPacket(Packet packet) {
RosterPacket rosterPacket = (RosterPacket)packet;
for (Iterator i=rosterPacket.getRosterItems(); i.hasNext(); ) {
RosterPacket.Item item = (RosterPacket.Item)i.next();
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
connection);
// 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 newGroupNames = new ArrayList();
for (Iterator k = item.getGroupNames(); k.hasNext(); ) {
String groupName = (String)k.next();
// Add the group name to the list.
newGroupNames.add(groupName);
// Add the entry to the group.
RosterGroup group = getGroup(groupName);
if (group == null) {
group = createGroup(groupName);
groups.put(groupName, group);
}
// Add the entry.
group.addEntryLocal(entry);
}
// We have the list of old and new group names. We now need to
// remove the entry from the all the groups it may no longer belong
// to. We do this by subracting the new group set from the old.
for (int m=0; m<newGroupNames.size(); m++) {
currentGroupNames.remove(newGroupNames.get(m));
}
// Loop through any groups that remain and remove the entries.
for (int n=0; n<currentGroupNames.size(); n++) {
String groupName = (String)currentGroupNames.get(n);
RosterGroup group = getGroup(groupName);
group.removeEntryLocal(entry);
if (group.getEntryCount() == 0) {
synchronized (groups) {
groups.remove(groupName);
}
}
}
}
}
}
}

View File

@ -0,0 +1,93 @@
package org.jivesoftware.smack;
import org.jivesoftware.smack.packet.RosterPacket;
import org.jivesoftware.smack.packet.IQ;
import java.util.*;
/**
* Each user in your roster is represented by a roster entry, which contains the user's
* JID and a name or nickname you assign.
*
* @author Matt Tucker
*/
public class RosterEntry {
private String user;
private String name;
private XMPPConnection connection;
RosterEntry(String user, String name, XMPPConnection connection) {
this.user = user;
this.name = name;
this.connection = connection;
}
/**
* Returns the JID of the user associated with this entry.
*
* @return the user associated with this entry.
*/
public String getUser() {
return user;
}
/**
* Returns the name associated with this entry.
*
* @return the name.
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
RosterPacket packet = new RosterPacket();
packet.setType(IQ.Type.SET);
packet.addRosterItem(toRosterItem(this));
connection.sendPacket(packet);
}
/**
* Returns an iterator for all the roster groups that this entry belongs to.
*
* @return an iterator for the groups this entry belongs to.
*/
public Iterator getGroups() {
Roster roster = connection.getRoster();
List results = new ArrayList();
// Loop through all roster groups and find the ones that contain this
// entry. This algorithm should be fine
for (Iterator i=roster.getGroups(); i.hasNext(); ) {
RosterGroup group = (RosterGroup)i.next();
if (group.contains(this)) {
results.add(group);
}
}
return results.iterator();
}
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object != null && object instanceof RosterEntry) {
return user.equals(((RosterEntry)object).getUser());
}
else {
return false;
}
}
static RosterPacket.Item toRosterItem(RosterEntry entry) {
RosterPacket.Item item = new RosterPacket.Item(entry.getUser(), entry.getName());
item.setItemType(RosterPacket.ItemType.BOTH);
// Set the correct group names for the item.
for (Iterator j=entry.getGroups(); j.hasNext(); ) {
RosterGroup group = (RosterGroup)j.next();
item.addGroupName(group.getName());
}
return item;
}
}

View File

@ -0,0 +1,167 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2002-2003 Jive Software. All rights reserved.
* ====================================================================
* The Jive Software License (based on Apache Software License, Version 1.1)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by
* Jive Software (http://www.jivesoftware.com)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Smack" and "Jive Software" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please
* contact webmaster@jivesoftware.com.
*
* 5. Products derived from this software may not be called "Smack",
* nor may "Smack" appear in their name, without prior written
* permission of Jive Software.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*/
package org.jivesoftware.smack;
import org.jivesoftware.smack.packet.RosterPacket;
import org.jivesoftware.smack.packet.IQ;
import java.util.*;
/**
*
* @see Roster#getGroup(String)
* @author Matt Tucker
*/
public class RosterGroup {
private String name;
private XMPPConnection connection;
List entries;
/**
* Creates a new roster group instance.
*
* @param name the name of the group.
* @param connection the connection the group belongs to.
*/
RosterGroup(String name, XMPPConnection connection) {
this.name = name;
this.connection = connection;
entries = new ArrayList();
}
/**
* Returns the name of the group.
*
* @return the name of the group.
*/
public String getName() {
return name;
}
/**
* Sets the name of the group.
*
* @param name the name of the group.
*/
public void setName(String name) {
this.name = name;
synchronized (entries) {
for (int i=0; i<entries.size(); i++) {
RosterPacket packet = new RosterPacket();
packet.setType(IQ.Type.SET);
RosterEntry entry = (RosterEntry)entries.get(i);
packet.addRosterItem(RosterEntry.toRosterItem(entry));
connection.sendPacket(packet);
}
}
}
public int getEntryCount() {
synchronized (entries) {
return entries.size();
}
}
public Iterator getEntries() {
return Collections.unmodifiableList(entries).iterator();
}
public boolean contains(RosterEntry entry) {
return entries.contains(entry);
}
public void addEntry(RosterEntry entry) {
// Only add the entry if it isn't already in the list.
synchronized (entries) {
if (!entries.contains(entry)) {
entries.add(entry);
RosterPacket packet = new RosterPacket();
packet.setType(IQ.Type.SET);
packet.addRosterItem(RosterEntry.toRosterItem(entry));
connection.sendPacket(packet);
}
}
}
public void removeEntry(RosterEntry entry) {
// Only remove the entry if it's in the entry list.
synchronized (entries) {
if (entries.contains(entry)) {
entries.remove(entry);
RosterPacket packet = new RosterPacket();
packet.setType(IQ.Type.SET);
packet.addRosterItem(RosterEntry.toRosterItem(entry));
connection.sendPacket(packet);
}
}
}
void addEntryLocal(RosterEntry entry) {
// Only add the entry if it isn't already in the list.
synchronized (entries) {
entries.remove(entry);
entries.add(entry);
}
}
void removeEntryLocal(RosterEntry entry) {
// Only remove the entry if it's in the entry list.
synchronized (entries) {
if (entries.contains(entry)) {
entries.remove(entry);
}
}
}
}

View File

@ -109,6 +109,8 @@ public class XMPPConnection {
private PacketWriter packetWriter;
private PacketReader packetReader;
private Roster roster;
Writer writer;
Reader reader;
@ -274,6 +276,14 @@ public class XMPPConnection {
collector.cancel();
// Set presence to online.
packetWriter.sendPacket(new Presence(Presence.Type.AVAILABLE));
// Finally, create the roster.
this.roster = new Roster(this);
roster.reload();
}
public Roster getRoster() {
return roster;
}
/**
@ -389,8 +399,8 @@ public class XMPPConnection {
*/
void init() throws XMPPException {
try {
reader = new InputStreamReader(socket.getInputStream(), "UTF-8");
writer = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
}
catch (IOException ioe) {
throw new XMPPException("Error establishing connection with server.", ioe);
@ -496,11 +506,20 @@ public class XMPPConnection {
int count = myReader.read(cbuf, off, len);
if (count > 0) {
String str = new String(cbuf, off, count);
receivedText1.append(str);
receivedText2.append(str);
if (str.endsWith(">")) {
int index = str.lastIndexOf(">");
if (index != -1) {
receivedText1.append(str.substring(0, index+1));
receivedText2.append(str.substring(0, index+1));
receivedText1.append(NEWLINE);
receivedText2.append(NEWLINE);
if (str.length() > index) {
receivedText1.append(str.substring(index+1));
receivedText2.append(str.substring(index+1));
}
}
else {
receivedText1.append(str);
receivedText2.append(str);
}
}
return count;

View File

@ -52,8 +52,6 @@
package org.jivesoftware.smack.packet;
import org.jivesoftware.smack.*;
/**
* Represents XMPP presence packets. Every presence packet has a type, which is one of
* the following values:
@ -84,7 +82,7 @@ import org.jivesoftware.smack.*;
* the clients current presence status. Second, they are used to subscribe and
* unsubscribe users from the roster.
*
* @see Roster
* @see RosterPacket
* @author Matt Tucker
*/
public class Presence extends Packet {

View File

@ -0,0 +1,232 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright (C) 2002-2003 Jive Software. All rights reserved.
* ====================================================================
* The Jive Software License (based on Apache Software License, Version 1.1)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by
* Jive Software (http://www.jivesoftware.com)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Smack" and "Jive Software" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please
* contact webmaster@jivesoftware.com.
*
* 5. Products derived from this software may not be called "Smack",
* nor may "Smack" appear in their name, without prior written
* permission of Jive Software.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*/
package org.jivesoftware.smack.packet;
import java.util.*;
/**
* Represents XMPP roster packets.
*
* @author Matt Tucker
*/
public class RosterPacket extends IQ {
private List rosterItems = new ArrayList();
public void addRosterItem(Item entry) {
synchronized (rosterItems) {
rosterItems.add(entry);
}
}
public Iterator getRosterItems() {
synchronized (rosterItems) {
List entries = Collections.unmodifiableList(new ArrayList(rosterItems));
return entries.iterator();
}
}
public String getQueryXML() {
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);
buf.append(entry.toXML());
}
}
buf.append("</query>");
return buf.toString();
}
public static class Item {
private String user;
private String name;
private ItemType itemType;
private List groupNames;
public Item(String user, String name) {
this.user = user;
this.name = name;
itemType = null;
groupNames = new ArrayList();
}
public String getUser() {
return user;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ItemType getItemType() {
return itemType;
}
public void setItemType(ItemType itemType) {
this.itemType = itemType;
}
public Iterator getGroupNames() {
synchronized (groupNames) {
return Collections.unmodifiableList(groupNames).iterator();
}
}
public void addGroupName(String groupName) {
synchronized (groupNames) {
if (!groupNames.contains(groupName)) {
groupNames.add(groupName);
}
}
}
public void removeGroupName(String groupName) {
synchronized (groupNames) {
groupNames.remove(groupName);
}
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<item jid=\"").append(user).append("\"");
if (name != null) {
buf.append(" name=\"").append(name).append("\"");
}
if (itemType != null) {
buf.append(" subscription=\"").append(itemType).append("\"");
}
buf.append(">");
synchronized (groupNames) {
for (int i=0; i<groupNames.size(); i++) {
String groupName = (String)groupNames.get(i);
buf.append("<group>").append(groupName).append("</group>");
}
}
buf.append("</item>");
return buf.toString();
}
}
public static class ItemStatus {
public static final ItemStatus SUBSCRIBED = new ItemStatus("subscribed");
public static final ItemStatus SUBSCRIPTION_PENDING = new ItemStatus("subscribe");
public static final ItemStatus UNSUBCRIPTION_PENDING = new ItemStatus("unsubscribe");
public static ItemStatus fromString(String value) {
if ("subscribed".equals(value)) {
return SUBSCRIBED;
}
else if ("subscribe".equals(value)) {
return SUBSCRIPTION_PENDING;
}
else {
return SUBSCRIBED;
}
}
private String value;
private ItemStatus (String value) {
this.value = value;
}
public String toString() {
return value;
}
}
public static class ItemType {
public static final ItemType NONE = new ItemType("none");
public static final ItemType TO = new ItemType("to");
public static final ItemType FROM = new ItemType("from");
public static final ItemType BOTH = new ItemType("both");
public static ItemType fromString(String value) {
if ("none".equals(value)) {
return NONE;
}
else if ("to".equals(value)) {
return TO;
}
else if ("from".equals(value)) {
return FROM;
}
else if ("both".equals(value)) {
return BOTH;
}
else {
return null;
}
}
private String value;
public ItemType (String value) {
this.value = value;
}
public String toString() {
return value;
}
}
}