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.Collection;
022import java.util.Iterator;
023import java.util.List;
024
025import org.jivesoftware.smack.Manager;
026import org.jivesoftware.smack.SmackException.NoResponseException;
027import org.jivesoftware.smack.XMPPConnection;
028import org.jivesoftware.smack.SmackException.NotConnectedException;
029import org.jivesoftware.smack.XMPPException.XMPPErrorException;
030import org.jivesoftware.smack.packet.IQ;
031import org.jivesoftware.smack.roster.packet.RosterPacket;
032
033
034/**
035 * Each user in your roster is represented by a roster entry, which contains the user's
036 * JID and a name or nickname you assign.
037 *
038 * @author Matt Tucker
039 */
040public final class RosterEntry extends Manager {
041
042    /**
043     * The JID of the entity/user.
044     */
045    private final String user;
046
047    private String name;
048    private RosterPacket.ItemType type;
049    private RosterPacket.ItemStatus status;
050    final private Roster roster;
051
052    /**
053     * Creates a new roster entry.
054     *
055     * @param user the user.
056     * @param name the nickname for the entry.
057     * @param type the subscription type.
058     * @param status the subscription status (related to subscriptions pending to be approbed).
059     * @param connection a connection to the XMPP server.
060     */
061    RosterEntry(String user, String name, RosterPacket.ItemType type,
062                RosterPacket.ItemStatus status, Roster roster, XMPPConnection connection) {
063        super(connection);
064        this.user = user;
065        this.name = name;
066        this.type = type;
067        this.status = status;
068        this.roster = roster;
069    }
070
071    /**
072     * Returns the JID of the user associated with this entry.
073     *
074     * @return the user associated with this entry.
075     */
076    public String getUser() {
077        return user;
078    }
079
080    /**
081     * Returns the name associated with this entry.
082     *
083     * @return the name.
084     */
085    public String getName() {
086        return name;
087    }
088
089    /**
090     * Sets the name associated with this entry.
091     *
092     * @param name the name.
093     * @throws NotConnectedException 
094     * @throws XMPPErrorException 
095     * @throws NoResponseException 
096     */
097    public synchronized void setName(String name) throws NotConnectedException, NoResponseException, XMPPErrorException {
098        // Do nothing if the name hasn't changed.
099        if (name != null && name.equals(this.name)) {
100            return;
101        }
102
103        RosterPacket packet = new RosterPacket();
104        packet.setType(IQ.Type.set);
105
106        // Create a new roster item with the current RosterEntry and the *new* name. Note that we can't set the name of
107        // RosterEntry right away, as otherwise the updated event wont get fired, because equalsDeep would return true.
108        packet.addRosterItem(toRosterItem(this, name));
109        connection().createPacketCollectorAndSend(packet).nextResultOrThrow();
110
111        // We have received a result response to the IQ set, the name was successfully changed
112        this.name = name;
113    }
114
115    /**
116     * Updates the state of the entry with the new values.
117     *
118     * @param name the nickname for the entry.
119     * @param type the subscription type.
120     * @param status the subscription status (related to subscriptions pending to be approbed).
121     */
122    void updateState(String name, RosterPacket.ItemType type, RosterPacket.ItemStatus status) {
123        this.name = name;
124        this.type = type;
125        this.status = status;
126    }
127
128    /**
129     * Returns an copied list of the roster groups that this entry belongs to.
130     *
131     * @return an iterator for the groups this entry belongs to.
132     */
133    public List<RosterGroup> getGroups() {
134        List<RosterGroup> results = new ArrayList<RosterGroup>();
135        // Loop through all roster groups and find the ones that contain this
136        // entry. This algorithm should be fine
137        for (RosterGroup group: roster.getGroups()) {
138            if (group.contains(this)) {
139                results.add(group);
140            }
141        }
142        return results;
143    }
144
145    /**
146     * Returns the roster subscription type of the entry. When the type is
147     * RosterPacket.ItemType.none or RosterPacket.ItemType.from,
148     * refer to {@link RosterEntry getStatus()} to see if a subscription request
149     * is pending.
150     *
151     * @return the type.
152     */
153    public RosterPacket.ItemType getType() {
154        return type;
155    }
156
157    /**
158     * Returns the roster subscription status of the entry. When the status is
159     * RosterPacket.ItemStatus.SUBSCRIPTION_PENDING, the contact has to answer the
160     * subscription request.
161     *
162     * @return the status.
163     */
164    public RosterPacket.ItemStatus getStatus() {
165        return status;
166    }
167
168    public String toString() {
169        StringBuilder buf = new StringBuilder();
170        if (name != null) {
171            buf.append(name).append(": ");
172        }
173        buf.append(user);
174        Collection<RosterGroup> groups = getGroups();
175        if (!groups.isEmpty()) {
176            buf.append(" [");
177            Iterator<RosterGroup> iter = groups.iterator();
178            RosterGroup group = iter.next();
179            buf.append(group.getName());
180            while (iter.hasNext()) {
181            buf.append(", ");
182                group = iter.next();
183                buf.append(group.getName());
184            }
185            buf.append("]");
186        }
187        return buf.toString();
188    }
189
190    @Override
191    public int hashCode() {
192        return (user == null ? 0 : user.hashCode());
193    }
194
195    public boolean equals(Object object) {
196        if (this == object) {
197            return true;
198        }
199        if (object != null && object instanceof RosterEntry) {
200            return user.equals(((RosterEntry)object).getUser());
201        }
202        else {
203            return false;
204        }
205    }
206
207    /**
208     * Indicates whether some other object is "equal to" this by comparing all members.
209     * <p>
210     * The {@link #equals(Object)} method returns <code>true</code> if the user JIDs are equal.
211     * 
212     * @param obj the reference object with which to compare.
213     * @return <code>true</code> if this object is the same as the obj argument; <code>false</code>
214     *         otherwise.
215     */
216    public boolean equalsDeep(Object obj) {
217        if (this == obj)
218            return true;
219        if (obj == null)
220            return false;
221        if (getClass() != obj.getClass())
222            return false;
223        RosterEntry other = (RosterEntry) obj;
224        if (name == null) {
225            if (other.name != null)
226                return false;
227        }
228        else if (!name.equals(other.name))
229            return false;
230        if (status == null) {
231            if (other.status != null)
232                return false;
233        }
234        else if (!status.equals(other.status))
235            return false;
236        if (type == null) {
237            if (other.type != null)
238                return false;
239        }
240        else if (!type.equals(other.type))
241            return false;
242        if (user == null) {
243            if (other.user != null)
244                return false;
245        }
246        else if (!user.equals(other.user))
247            return false;
248        return true;
249    }
250    
251    static RosterPacket.Item toRosterItem(RosterEntry entry) {
252        return toRosterItem(entry, entry.getName());
253    }
254
255    private static RosterPacket.Item toRosterItem(RosterEntry entry, String name) {
256        RosterPacket.Item item = new RosterPacket.Item(entry.getUser(), name);
257        item.setItemType(entry.getType());
258        item.setItemStatus(entry.getStatus());
259        // Set the correct group names for the item.
260        for (RosterGroup group : entry.getGroups()) {
261            item.addGroupName(group.getName());
262        }
263        return item;
264    }
265
266}