RosterEntry.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.jivesoftware.smack.roster;

  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Iterator;
  21. import java.util.List;

  22. import org.jivesoftware.smack.Manager;
  23. import org.jivesoftware.smack.SmackException.NoResponseException;
  24. import org.jivesoftware.smack.SmackException.NotConnectedException;
  25. import org.jivesoftware.smack.XMPPConnection;
  26. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  27. import org.jivesoftware.smack.packet.IQ;
  28. import org.jivesoftware.smack.packet.Presence;
  29. import org.jivesoftware.smack.roster.packet.RosterPacket;
  30. import org.jivesoftware.smack.util.EqualsUtil;

  31. import org.jxmpp.jid.BareJid;


  32. /**
  33.  * Each user in your roster is represented by a roster entry, which contains the user's
  34.  * JID and a name or nickname you assign.
  35.  *
  36.  * @author Matt Tucker
  37.  * @author Florian Schmaus
  38.  */
  39. public final class RosterEntry extends Manager {

  40.     private RosterPacket.Item item;
  41.     private final Roster roster;

  42.     /**
  43.      * Creates a new roster entry.
  44.      *
  45.      * @param item the Roster Stanza's Item entry.
  46.      * @param roster The Roster managing this entry.
  47.      * @param connection a connection to the XMPP server.
  48.      */
  49.     RosterEntry(RosterPacket.Item item, Roster roster, XMPPConnection connection) {
  50.         super(connection);
  51.         this.item = item;
  52.         this.roster = roster;
  53.     }

  54.     /**
  55.      * Returns the JID of the user associated with this entry.
  56.      *
  57.      * @return the user associated with this entry.
  58.      * @deprecated use {@link #getJid()} instead.
  59.      */
  60.     @Deprecated
  61.     public String getUser() {
  62.         return getJid().toString();
  63.     }

  64.     /**
  65.      * Returns the JID associated with this entry.
  66.      *
  67.      * @return the user associated with this entry.
  68.      */
  69.     public BareJid getJid() {
  70.         return item.getJid();
  71.     }

  72.     /**
  73.      * Returns the name associated with this entry.
  74.      *
  75.      * @return the name.
  76.      */
  77.     public String getName() {
  78.         return item.getName();
  79.     }

  80.     /**
  81.      * Sets the name associated with this entry.
  82.      *
  83.      * @param name the name.
  84.      * @throws NotConnectedException if the XMPP connection is not connected.
  85.      * @throws XMPPErrorException if there was an XMPP error returned.
  86.      * @throws NoResponseException if there was no response from the remote entity.
  87.      * @throws InterruptedException if the calling thread was interrupted.
  88.      */
  89.     public synchronized void setName(String name) throws NotConnectedException, NoResponseException, XMPPErrorException, InterruptedException {
  90.         // Do nothing if the name hasn't changed.
  91.         if (name != null && name.equals(getName())) {
  92.             return;
  93.         }

  94.         RosterPacket packet = new RosterPacket();
  95.         packet.setType(IQ.Type.set);

  96.         // Create a new roster item with the current RosterEntry and the *new* name. Note that we can't set the name of
  97.         // RosterEntry right away, as otherwise the updated event won't get fired, because equalsDeep would return true.
  98.         packet.addRosterItem(toRosterItem(this, name));
  99.         connection().sendIqRequestAndWaitForResponse(packet);

  100.         // We have received a result response to the IQ set, the name was successfully changed
  101.         item.setName(name);
  102.     }

  103.     /**
  104.      * Updates this entries item.
  105.      *
  106.      * @param item new item
  107.      */
  108.     void updateItem(RosterPacket.Item item) {
  109.         assert item != null;
  110.         this.item = item;
  111.     }

  112.     /**
  113.      * Returns the pre-approval state of this entry.
  114.      *
  115.      * @return the pre-approval state.
  116.      */
  117.     public boolean isApproved() {
  118.         return item.isApproved();
  119.     }

  120.     /**
  121.      * Returns a copied list of the roster groups that this entry belongs to.
  122.      *
  123.      * @return an iterator for the groups this entry belongs to.
  124.      */
  125.     public List<RosterGroup> getGroups() {
  126.         List<RosterGroup> results = new ArrayList<>();
  127.         // Loop through all roster groups and find the ones that contain this
  128.         // entry. This algorithm should be fine
  129.         for (RosterGroup group : roster.getGroups()) {
  130.             if (group.contains(this)) {
  131.                 results.add(group);
  132.             }
  133.         }
  134.         return results;
  135.     }

  136.     /**
  137.      * Returns the roster subscription type of the entry. When the type is
  138.      * RosterPacket.ItemType.none or RosterPacket.ItemType.from,
  139.      * refer to {@link RosterEntry getStatus()} to see if a subscription request
  140.      * is pending.
  141.      *
  142.      * @return the type.
  143.      */
  144.     public RosterPacket.ItemType getType() {
  145.         return item.getItemType();
  146.     }

  147.     /**
  148.      * Returns the roster subscription request status of the entry. If
  149.      * {@code true}, then the contact did not answer the subscription request
  150.      * yet.
  151.      *
  152.      * @return the status.
  153.      * @since 4.2
  154.      */
  155.     public boolean isSubscriptionPending() {
  156.         return item.isSubscriptionPending();
  157.     }

  158.     /**
  159.      * Check if the contact is subscribed to "my" presence. This allows the contact to see the presence information.
  160.      *
  161.      * @return true if the contact has a presence subscription.
  162.      * @since 4.2
  163.      */
  164.     public boolean canSeeMyPresence() {
  165.         switch (getType()) {
  166.         case from:
  167.         case both:
  168.             return true;
  169.         default:
  170.             return false;
  171.         }
  172.     }

  173.     /**
  174.      * Check if we are subscribed to the contact's presence. If <code>true</code> then the contact has allowed us to
  175.      * receive presence information.
  176.      *
  177.      * @return true if we are subscribed to the contact's presence.
  178.      * @since 4.2
  179.      */
  180.     public boolean canSeeHisPresence() {
  181.         switch (getType()) {
  182.         case to:
  183.         case both:
  184.             return true;
  185.         default:
  186.             return false;
  187.         }
  188.     }

  189.     /**
  190.      * Cancel the presence subscription the XMPP entity representing this roster entry has with us.
  191.      *
  192.      * @throws NotConnectedException if the XMPP connection is not connected.
  193.      * @throws InterruptedException if the calling thread was interrupted.
  194.      * @since 4.2
  195.      */
  196.     public void cancelSubscription() throws NotConnectedException, InterruptedException {
  197.         XMPPConnection connection = connection();
  198.         Presence unsubscribed = connection.getStanzaFactory().buildPresenceStanza()
  199.                 .to(item.getJid())
  200.                 .ofType(Presence.Type.unsubscribed)
  201.                 .build();
  202.         connection.sendStanza(unsubscribed);
  203.     }

  204.     @Override
  205.     public String toString() {
  206.         StringBuilder buf = new StringBuilder();
  207.         if (getName() != null) {
  208.             buf.append(getName()).append(": ");
  209.         }
  210.         buf.append(getJid());
  211.         Collection<RosterGroup> groups = getGroups();
  212.         if (!groups.isEmpty()) {
  213.             buf.append(" [");
  214.             Iterator<RosterGroup> iter = groups.iterator();
  215.             RosterGroup group = iter.next();
  216.             buf.append(group.getName());
  217.             while (iter.hasNext()) {
  218.             buf.append(", ");
  219.                 group = iter.next();
  220.                 buf.append(group.getName());
  221.             }
  222.             buf.append(']');
  223.         }
  224.         return buf.toString();
  225.     }

  226.     @Override
  227.     public int hashCode() {
  228.         return getJid().hashCode();
  229.     }

  230.     @Override
  231.     public boolean equals(Object object) {
  232.         return EqualsUtil.equals(this, object, (e, o) ->
  233.             e.append(getJid(), o.getJid())
  234.         );
  235.     }

  236.     /**
  237.      * Indicates whether some other object is "equal to" this by comparing all members.
  238.      * <p>
  239.      * The {@link #equals(Object)} method returns <code>true</code> if the user JIDs are equal.
  240.      *
  241.      * @param obj the reference object with which to compare.
  242.      * @return <code>true</code> if this object is the same as the obj argument; <code>false</code>
  243.      *         otherwise.
  244.      */
  245.     public boolean equalsDeep(Object obj) {
  246.         return EqualsUtil.equals(this, obj, (e, o) ->
  247.             e.append(item, o.item)
  248.         );
  249.     }

  250.     /**
  251.      * Convert the RosterEntry to a Roster stanza &lt;item/&gt; element.
  252.      *
  253.      * @param entry the roster entry.
  254.      * @return the roster item.
  255.      */
  256.     static RosterPacket.Item toRosterItem(RosterEntry entry) {
  257.         return toRosterItem(entry, entry.getName(), false);
  258.     }

  259.     /**
  260.      * Convert the RosterEntry to a Roster stanza &lt;item/&gt; element.
  261.      *
  262.      * @param entry the roster entry
  263.      * @param name the name of the roster item.
  264.      * @return the roster item.
  265.      */
  266.     static RosterPacket.Item toRosterItem(RosterEntry entry, String name) {
  267.         return toRosterItem(entry, name, false);
  268.     }

  269.     static RosterPacket.Item toRosterItem(RosterEntry entry, boolean includeAskAttribute) {
  270.         return toRosterItem(entry, entry.getName(), includeAskAttribute);
  271.     }

  272.     /**
  273.      * Convert a roster entry with the given name to a roster item. As per RFC 6121 ยง 2.1.2.2., clients MUST NOT include
  274.      * the 'ask' attribute, thus set {@code includeAskAttribute} to {@code false}.
  275.      *
  276.      * @param entry the roster entry.
  277.      * @param name the name of the roster item.
  278.      * @param includeAskAttribute whether to include the 'ask' attribute.
  279.      * @return the roster item.
  280.      */
  281.     private static RosterPacket.Item toRosterItem(RosterEntry entry, String name, boolean includeAskAttribute) {
  282.         RosterPacket.Item item = new RosterPacket.Item(entry.getJid(), name);
  283.         item.setItemType(entry.getType());
  284.         if (includeAskAttribute) {
  285.             item.setSubscriptionPending(entry.isSubscriptionPending());
  286.         }
  287.         item.setApproved(entry.isApproved());
  288.         // Set the correct group names for the item.
  289.         for (RosterGroup group : entry.getGroups()) {
  290.             item.addGroupName(group.getName());
  291.         }
  292.         return item;
  293.     }

  294. }