RosterPacket.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.packet;

  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.List;
  21. import java.util.Locale;
  22. import java.util.Set;
  23. import java.util.concurrent.CopyOnWriteArraySet;

  24. import javax.xml.namespace.QName;

  25. import org.jivesoftware.smack.packet.ExtensionElement;
  26. import org.jivesoftware.smack.packet.IQ;
  27. import org.jivesoftware.smack.packet.Stanza;
  28. import org.jivesoftware.smack.util.EqualsUtil;
  29. import org.jivesoftware.smack.util.HashCode;
  30. import org.jivesoftware.smack.util.Objects;
  31. import org.jivesoftware.smack.util.StringUtils;
  32. import org.jivesoftware.smack.util.XmlStringBuilder;

  33. import org.jxmpp.jid.BareJid;

  34. /**
  35.  * Represents XMPP roster packets.
  36.  *
  37.  * @author Matt Tucker
  38.  * @author Florian Schmaus
  39.  */
  40. public final class RosterPacket extends IQ {

  41.     public static final String ELEMENT = QUERY_ELEMENT;
  42.     public static final String NAMESPACE = "jabber:iq:roster";

  43.     private final List<Item> rosterItems = new ArrayList<>();
  44.     private String rosterVersion;

  45.     public RosterPacket() {
  46.         super(ELEMENT, NAMESPACE);
  47.     }

  48.     /**
  49.      * Adds a roster item to the packet.
  50.      *
  51.      * @param item a roster item.
  52.      */
  53.     public void addRosterItem(Item item) {
  54.         synchronized (rosterItems) {
  55.             rosterItems.add(item);
  56.         }
  57.     }

  58.     /**
  59.      * Returns the number of roster items in this roster packet.
  60.      *
  61.      * @return the number of roster items.
  62.      */
  63.     public int getRosterItemCount() {
  64.         synchronized (rosterItems) {
  65.             return rosterItems.size();
  66.         }
  67.     }

  68.     /**
  69.      * Returns a copied list of the roster items in the packet.
  70.      *
  71.      * @return a copied list of the roster items in the packet.
  72.      */
  73.     public List<Item> getRosterItems() {
  74.         synchronized (rosterItems) {
  75.             return new ArrayList<>(rosterItems);
  76.         }
  77.     }

  78.     @Override
  79.     protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder buf) {
  80.         buf.optAttribute("ver", rosterVersion);
  81.         buf.rightAngleBracket();

  82.         synchronized (rosterItems) {
  83.             for (Item entry : rosterItems) {
  84.                 buf.append(entry.toXML());
  85.             }
  86.         }
  87.         return buf;
  88.     }

  89.     public String getVersion() {
  90.         return rosterVersion;
  91.     }

  92.     public void setVersion(String version) {
  93.         rosterVersion = version;
  94.     }

  95.     /**
  96.      * A roster item, which consists of a JID, their name, the type of subscription, and
  97.      * the groups the roster item belongs to.
  98.      */
  99.     // TODO Make this class immutable.
  100.     public static final class Item implements ExtensionElement {

  101.         /**
  102.          * The constant value "{@value}".
  103.          */
  104.         public static final String ELEMENT = Stanza.ITEM;

  105.         public static final QName QNAME = new QName(NAMESPACE, ELEMENT);

  106.         public static final String GROUP = "group";

  107.         private final BareJid jid;

  108.         /**
  109.          * TODO describe me. With link to the RFC. Is ask= attribute.
  110.          */
  111.         private boolean subscriptionPending;

  112.         // TODO Make immutable.
  113.         private String name;
  114.         private ItemType itemType = ItemType.none;
  115.         private boolean approved;
  116.         private final Set<String> groupNames;

  117.         /**
  118.          * Creates a new roster item.
  119.          *
  120.          * @param jid TODO javadoc me please
  121.          * @param name TODO javadoc me please
  122.          */
  123.         public Item(BareJid jid, String name) {
  124.             this(jid, name, false);
  125.         }

  126.         /**
  127.          * Creates a new roster item.
  128.          *
  129.          * @param jid the jid.
  130.          * @param name the user's name.
  131.          * @param subscriptionPending TODO javadoc me please
  132.          */
  133.         public Item(BareJid jid, String name, boolean subscriptionPending) {
  134.             this.jid = Objects.requireNonNull(jid);
  135.             this.name = name;
  136.             this.subscriptionPending = subscriptionPending;
  137.             groupNames = new CopyOnWriteArraySet<>();
  138.         }

  139.         @Override
  140.         public String getElementName() {
  141.             return QNAME.getLocalPart();
  142.         }

  143.         @Override
  144.         public String getNamespace() {
  145.             return QNAME.getNamespaceURI();
  146.         }

  147.         /**
  148.          * Returns the user.
  149.          *
  150.          * @return the user.
  151.          * @deprecated use {@link #getJid()} instead.
  152.          */
  153.         @Deprecated
  154.         public String getUser() {
  155.             return jid.toString();
  156.         }

  157.         /**
  158.          * Returns the JID of this item.
  159.          *
  160.          * @return the JID.
  161.          */
  162.         public BareJid getJid() {
  163.             return jid;
  164.         }

  165.         /**
  166.          * Returns the user's name.
  167.          *
  168.          * @return the user's name.
  169.          */
  170.         public String getName() {
  171.             return name;
  172.         }

  173.         /**
  174.          * Sets the user's name.
  175.          *
  176.          * @param name the user's name.
  177.          */
  178.         public void setName(String name) {
  179.             this.name = name;
  180.         }

  181.         /**
  182.          * Returns the roster item type.
  183.          *
  184.          * @return the roster item type.
  185.          */
  186.         public ItemType getItemType() {
  187.             return itemType;
  188.         }

  189.         /**
  190.          * Sets the roster item type.
  191.          *
  192.          * @param itemType the roster item type.
  193.          */
  194.         public void setItemType(ItemType itemType) {
  195.             this.itemType = Objects.requireNonNull(itemType, "itemType must not be null");
  196.         }

  197.         public void setSubscriptionPending(boolean subscriptionPending) {
  198.             this.subscriptionPending = subscriptionPending;
  199.         }

  200.         public boolean isSubscriptionPending() {
  201.             return subscriptionPending;
  202.         }

  203.         /**
  204.          * Returns the roster item pre-approval state.
  205.          *
  206.          * @return the pre-approval state.
  207.          */
  208.         public boolean isApproved() {
  209.             return approved;
  210.         }

  211.         /**
  212.          * Sets the roster item pre-approval state.
  213.          *
  214.          * @param approved the pre-approval flag.
  215.          */
  216.         public void setApproved(boolean approved) {
  217.             this.approved = approved;
  218.         }

  219.         /**
  220.          * Returns an unmodifiable set of the group names that the roster item
  221.          * belongs to.
  222.          *
  223.          * @return an unmodifiable set of the group names.
  224.          */
  225.         public Set<String> getGroupNames() {
  226.             return Collections.unmodifiableSet(groupNames);
  227.         }

  228.         /**
  229.          * Adds a group name.
  230.          *
  231.          * @param groupName the group name.
  232.          */
  233.         public void addGroupName(String groupName) {
  234.             groupNames.add(groupName);
  235.         }

  236.         /**
  237.          * Removes a group name.
  238.          *
  239.          * @param groupName the group name.
  240.          */
  241.         public void removeGroupName(String groupName) {
  242.             groupNames.remove(groupName);
  243.         }

  244.         @Override
  245.         public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
  246.             XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
  247.             xml.attribute("jid", jid);
  248.             xml.optAttribute("name", name);
  249.             xml.optAttribute("subscription", itemType);
  250.             if (subscriptionPending) {
  251.                 xml.append(" ask='subscribe'");
  252.             }
  253.             xml.optBooleanAttribute("approved", approved);
  254.             xml.rightAngleBracket();

  255.             for (String groupName : groupNames) {
  256.                 xml.openElement(GROUP).escape(groupName).closeElement(GROUP);
  257.             }
  258.             xml.closeElement(this);
  259.             return xml;
  260.         }

  261.         @Override
  262.         public int hashCode() {
  263.             return HashCode.builder()
  264.                 .append(groupNames)
  265.                 .append(subscriptionPending)
  266.                 .append(itemType)
  267.                 .append(name)
  268.                 .append(jid)
  269.                 .append(approved)
  270.                 .build();
  271.         }

  272.         @Override
  273.         public boolean equals(Object obj) {
  274.             return EqualsUtil.equals(this, obj, (e, o) ->
  275.                 e.append(groupNames, o.groupNames)
  276.                  .append(subscriptionPending, o.subscriptionPending)
  277.                  .append(itemType, o.itemType)
  278.                  .append(name, o.name)
  279.                  .append(jid, o.jid)
  280.                  .append(approved, o.approved)
  281.             );
  282.         }

  283.     }

  284.     public enum ItemType {

  285.         /**
  286.          * The user does not have a subscription to the contact's presence, and the contact does not
  287.          * have a subscription to the user's presence; this is the default value, so if the
  288.          * subscription attribute is not included then the state is to be understood as "none".
  289.          */
  290.         none('⊥'),

  291.         /**
  292.          * The user has a subscription to the contact's presence, but the contact does not have a
  293.          * subscription to the user's presence.
  294.          */
  295.         to('←'),

  296.         /**
  297.          * The contact has a subscription to the user's presence, but the user does not have a
  298.          * subscription to the contact's presence.
  299.          */
  300.         from('→'),

  301.         /**
  302.          * The user and the contact have subscriptions to each other's presence (also called a
  303.          * "mutual subscription").
  304.          */
  305.         both('↔'),

  306.         /**
  307.          * The user wishes to stop receiving presence updates from the subscriber.
  308.          */
  309.         remove('⚡'),
  310.         ;


  311.         private static final char ME = '●';

  312.         private final String symbol;

  313.         ItemType(char secondSymbolChar) {
  314.             StringBuilder sb = new StringBuilder(2);
  315.             sb.append(ME).append(secondSymbolChar);
  316.             symbol = sb.toString();
  317.         }

  318.         public static ItemType fromString(String string) {
  319.             if (StringUtils.isNullOrEmpty(string)) {
  320.                 return none;
  321.             }
  322.             return ItemType.valueOf(string.toLowerCase(Locale.US));
  323.         }

  324.         /**
  325.          * Get a String containing symbols representing the item type. The first symbol in the
  326.          * string is a big dot, representing the local entity. The second symbol represents the
  327.          * established subscription relation and is typically an arrow. The head(s) of the arrow
  328.          * point in the direction presence messages are sent. For example, if there is only a head
  329.          * pointing to the big dot, then the local user will receive presence information from the
  330.          * remote entity.
  331.          *
  332.          * @return the symbolic representation of this item type.
  333.          */
  334.         public String asSymbol() {
  335.             return symbol;
  336.         }
  337.     }
  338. }