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