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.packet;
019
020import org.jivesoftware.smack.packet.IQ;
021import org.jivesoftware.smack.packet.Stanza;
022import org.jivesoftware.smack.util.XmlStringBuilder;
023
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.List;
027import java.util.Locale;
028import java.util.Set;
029import java.util.concurrent.CopyOnWriteArraySet;
030
031/**
032 * Represents XMPP roster packets.
033 *
034 * @author Matt Tucker
035 */
036public class RosterPacket extends IQ {
037
038    public static final String ELEMENT = QUERY_ELEMENT;
039    public static final String NAMESPACE = "jabber:iq:roster";
040
041    private final List<Item> rosterItems = new ArrayList<Item>();
042    private String rosterVersion;
043
044    public RosterPacket() {
045        super(ELEMENT, NAMESPACE);
046    }
047
048    /**
049     * Adds a roster item to the packet.
050     *
051     * @param item a roster item.
052     */
053    public void addRosterItem(Item item) {
054        synchronized (rosterItems) {
055            rosterItems.add(item);
056        }
057    }
058
059    /**
060     * Returns the number of roster items in this roster packet.
061     *
062     * @return the number of roster items.
063     */
064    public int getRosterItemCount() {
065        synchronized (rosterItems) {
066            return rosterItems.size();
067        }
068    }
069
070    /**
071     * Returns a copied list of the roster items in the packet.
072     *
073     * @return a copied list of the roster items in the packet.
074     */
075    public List<Item> getRosterItems() {
076        synchronized (rosterItems) {
077            return new ArrayList<Item>(rosterItems);
078        }
079    }
080
081    @Override
082    protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder buf) {
083        buf.optAttribute("ver", rosterVersion);
084        buf.rightAngleBracket();
085
086        synchronized (rosterItems) {
087            for (Item entry : rosterItems) {
088                buf.append(entry.toXML());
089            }
090        }
091        return buf;
092    }
093
094    public String getVersion() {
095        return rosterVersion;
096    }
097
098    public void setVersion(String version) {
099        rosterVersion = version;
100    }
101
102    /**
103     * A roster item, which consists of a JID, their name, the type of subscription, and
104     * the groups the roster item belongs to.
105     */
106    public static class Item {
107
108        public static final String GROUP = "group";
109
110        private String user;
111        private String name;
112        private ItemType itemType;
113        private ItemStatus itemStatus;
114        private final Set<String> groupNames;
115
116        /**
117         * Creates a new roster item.
118         *
119         * @param user the user.
120         * @param name the user's name.
121         */
122        public Item(String user, String name) {
123            this.user = user.toLowerCase(Locale.US);
124            this.name = name;
125            itemType = null;
126            itemStatus = null;
127            groupNames = new CopyOnWriteArraySet<String>();
128        }
129
130        /**
131         * Returns the user.
132         *
133         * @return the user.
134         */
135        public String getUser() {
136            return user;
137        }
138
139        /**
140         * Returns the user's name.
141         *
142         * @return the user's name.
143         */
144        public String getName() {
145            return name;
146        }
147
148        /**
149         * Sets the user's name.
150         *
151         * @param name the user's name.
152         */
153        public void setName(String name) {
154            this.name = name;
155        }
156
157        /**
158         * Returns the roster item type.
159         *
160         * @return the roster item type.
161         */
162        public ItemType getItemType() {
163            return itemType;
164        }
165
166        /**
167         * Sets the roster item type.
168         *
169         * @param itemType the roster item type.
170         */
171        public void setItemType(ItemType itemType) {
172            this.itemType = itemType;
173        }
174
175        /**
176         * Returns the roster item status.
177         *
178         * @return the roster item status.
179         */
180        public ItemStatus getItemStatus() {
181            return itemStatus;
182        }
183
184        /**
185         * Sets the roster item status.
186         *
187         * @param itemStatus the roster item status.
188         */
189        public void setItemStatus(ItemStatus itemStatus) {
190            this.itemStatus = itemStatus;
191        }
192
193        /**
194         * Returns an unmodifiable set of the group names that the roster item
195         * belongs to.
196         *
197         * @return an unmodifiable set of the group names.
198         */
199        public Set<String> getGroupNames() {
200            return Collections.unmodifiableSet(groupNames);
201        }
202
203        /**
204         * Adds a group name.
205         *
206         * @param groupName the group name.
207         */
208        public void addGroupName(String groupName) {
209            groupNames.add(groupName);
210        }
211
212        /**
213         * Removes a group name.
214         *
215         * @param groupName the group name.
216         */
217        public void removeGroupName(String groupName) {
218            groupNames.remove(groupName);
219        }
220
221        public XmlStringBuilder toXML() {
222            XmlStringBuilder xml = new XmlStringBuilder();
223            xml.halfOpenElement(Stanza.ITEM).attribute("jid", user);
224            xml.optAttribute("name", name);
225            xml.optAttribute("subscription", itemType);
226            xml.optAttribute("ask", itemStatus);
227            xml.rightAngleBracket();
228
229            for (String groupName : groupNames) {
230                xml.openElement(GROUP).escape(groupName).closeElement(GROUP);
231            }
232            xml.closeElement(Stanza.ITEM);
233            return xml;
234        }
235
236        @Override
237        public int hashCode() {
238            final int prime = 31;
239            int result = 1;
240            result = prime * result + ((groupNames == null) ? 0 : groupNames.hashCode());
241            result = prime * result + ((itemStatus == null) ? 0 : itemStatus.hashCode());
242            result = prime * result + ((itemType == null) ? 0 : itemType.hashCode());
243            result = prime * result + ((name == null) ? 0 : name.hashCode());
244            result = prime * result + ((user == null) ? 0 : user.hashCode());
245            return result;
246        }
247
248        @Override
249        public boolean equals(Object obj) {
250            if (this == obj)
251                return true;
252            if (obj == null)
253                return false;
254            if (getClass() != obj.getClass())
255                return false;
256            Item other = (Item) obj;
257            if (groupNames == null) {
258                if (other.groupNames != null)
259                    return false;
260            }
261            else if (!groupNames.equals(other.groupNames))
262                return false;
263            if (itemStatus != other.itemStatus)
264                return false;
265            if (itemType != other.itemType)
266                return false;
267            if (name == null) {
268                if (other.name != null)
269                    return false;
270            }
271            else if (!name.equals(other.name))
272                return false;
273            if (user == null) {
274                if (other.user != null)
275                    return false;
276            }
277            else if (!user.equals(other.user))
278                return false;
279            return true;
280        }
281
282    }
283
284    /**
285     * The subscription status of a roster item. An optional element that indicates
286     * the subscription status if a change request is pending.
287     */
288    public static enum ItemStatus {
289        /**
290         * Request to subscribe
291         */
292        subscribe,
293
294        /**
295         * Request to unsubscribe
296         */
297        unsubscribe;
298
299        public static final ItemStatus SUBSCRIPTION_PENDING = subscribe;
300        public static final ItemStatus UNSUBSCRIPTION_PENDING = unsubscribe;
301
302        public static ItemStatus fromString(String s) {
303            if (s == null) {
304                return null;
305            }
306            try {
307                return ItemStatus.valueOf(s);
308            }
309            catch (IllegalArgumentException e) {
310                return null;
311            }
312        }
313    }
314
315    public static enum ItemType {
316
317        /**
318         * The user does not have a subscription to the contact's presence, and the contact does not
319         * have a subscription to the user's presence; this is the default value, so if the
320         * subscription attribute is not included then the state is to be understood as "none".
321         */
322        none,
323
324        /**
325         * The user has a subscription to the contact's presence, but the contact does not have a
326         * subscription to the user's presence.
327         */
328        to,
329
330        /**
331         * The contact has a subscription to the user's presence, but the user does not have a
332         * subscription to the contact's presence.
333         */
334        from,
335
336        /**
337         * The user and the contact have subscriptions to each other's presence (also called a
338         * "mutual subscription").
339         */
340        both,
341
342        /**
343         * The user wishes to stop receiving presence updates from the subscriber.
344         */
345        remove
346    }
347}