Smack/smack-im/src/main/java/org/jivesoftware/smack/roster/packet/RosterPacket.java

394 lines
11 KiB
Java

/**
*
* Copyright 2003-2007 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.roster.packet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.NamedElement;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.roster.RosterItemRecord;
import org.jivesoftware.smack.util.EqualsUtil;
import org.jivesoftware.smack.util.HashCode;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jxmpp.jid.BareJid;
/**
* Represents XMPP roster packets.
*
* @author Matt Tucker
* @author Florian Schmaus
*/
public final class RosterPacket extends IQ {
public static final String ELEMENT = QUERY_ELEMENT;
public static final String NAMESPACE = "jabber:iq:roster";
private final List<Item> rosterItems = new ArrayList<>();
private String rosterVersion;
public RosterPacket() {
super(ELEMENT, NAMESPACE);
}
/**
* Adds a roster item to the packet.
*
* @param item a roster item.
*/
public void addRosterItem(Item item) {
synchronized (rosterItems) {
rosterItems.add(item);
}
}
/**
* Returns the number of roster items in this roster packet.
*
* @return the number of roster items.
*/
public int getRosterItemCount() {
synchronized (rosterItems) {
return rosterItems.size();
}
}
/**
* Returns a copied list of the roster items in the packet.
*
* @return a copied list of the roster items in the packet.
*/
public List<Item> getRosterItems() {
synchronized (rosterItems) {
return new ArrayList<>(rosterItems);
}
}
@Override
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder buf) {
buf.optAttribute("ver", rosterVersion);
buf.rightAngleBracket();
synchronized (rosterItems) {
for (Item entry : rosterItems) {
buf.append(entry.toXML());
}
}
return buf;
}
public String getVersion() {
return rosterVersion;
}
public void setVersion(String version) {
rosterVersion = version;
}
/**
* A roster item, which consists of a JID, their name, the type of subscription, and
* the groups the roster item belongs to.
*/
// TODO Make this class immutable.
public static final class Item implements NamedElement, RosterItemRecord {
/**
* The constant value "{@value}".
*/
public static final String ELEMENT = Stanza.ITEM;
public static final String GROUP = "group";
private final BareJid jid;
/**
* TODO describe me. With link to the RFC. Is ask= attribute.
*/
private boolean subscriptionPending;
// TODO Make immutable.
private String name;
private ItemType itemType = ItemType.none;
private boolean approved;
private final Set<String> groupNames;
/**
* Creates a new roster item.
*
* @param jid
* @param name
*/
public Item(BareJid jid, String name) {
this(jid, name, false);
}
/**
* Creates a new roster item.
*
* @param jid the jid.
* @param name the user's name.
* @param subscriptionPending
*/
public Item(BareJid jid, String name, boolean subscriptionPending) {
this.jid = Objects.requireNonNull(jid);
this.name = name;
this.subscriptionPending = subscriptionPending;
groupNames = new CopyOnWriteArraySet<>();
}
@Override
public String getElementName() {
return ELEMENT;
}
/**
* Returns the user.
*
* @return the user.
* @deprecated use {@link #getJid()} instead.
*/
@Deprecated
public String getUser() {
return jid.toString();
}
/**
* Returns the JID of this item.
*
* @return the JID.
*/
@Override
public BareJid getJid() {
return jid;
}
/**
* Returns the user's name.
*
* @return the user's name.
*/
@Override
public String getName() {
return name;
}
/**
* Sets the user's name.
*
* @param name the user's name.
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns the roster item type.
*
* @return the roster item type.
*/
@Override
public ItemType getItemType() {
return itemType;
}
/**
* Sets the roster item type.
*
* @param itemType the roster item type.
*/
public void setItemType(ItemType itemType) {
this.itemType = Objects.requireNonNull(itemType, "itemType must not be null");
}
public void setSubscriptionPending(boolean subscriptionPending) {
this.subscriptionPending = subscriptionPending;
}
@Override
public boolean isSubscriptionPending() {
return subscriptionPending;
}
/**
* Returns the roster item pre-approval state.
*
* @return the pre-approval state.
*/
@Override
public boolean isApproved() {
return approved;
}
/**
* Sets the roster item pre-approval state.
*
* @param approved the pre-approval flag.
*/
public void setApproved(boolean approved) {
this.approved = approved;
}
/**
* Returns an unmodifiable set of the group names that the roster item
* belongs to.
*
* @return an unmodifiable set of the group names.
*/
@Override
public Set<String> getGroupNames() {
return Collections.unmodifiableSet(groupNames);
}
/**
* Adds a group name.
*
* @param groupName the group name.
*/
public void addGroupName(String groupName) {
groupNames.add(groupName);
}
/**
* Removes a group name.
*
* @param groupName the group name.
*/
public void removeGroupName(String groupName) {
groupNames.remove(groupName);
}
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this);
xml.attribute("jid", jid);
xml.optAttribute("name", name);
xml.optAttribute("subscription", itemType);
if (subscriptionPending) {
xml.append(" ask='subscribe'");
}
xml.optBooleanAttribute("approved", approved);
xml.rightAngleBracket();
for (String groupName : groupNames) {
xml.openElement(GROUP).escape(groupName).closeElement(GROUP);
}
xml.closeElement(this);
return xml;
}
@Override
public int hashCode() {
return HashCode.builder()
.append(groupNames)
.append(subscriptionPending)
.append(itemType)
.append(name)
.append(jid)
.append(approved)
.build();
}
@Override
public boolean equals(Object obj) {
return EqualsUtil.equals(this, obj, (e, o) ->
e.append(groupNames, o.groupNames)
.append(subscriptionPending, o.subscriptionPending)
.append(itemType, o.itemType)
.append(name, o.name)
.append(jid, o.jid)
.append(approved, o.approved)
);
}
}
public enum ItemType {
/**
* The user does not have a subscription to the contact's presence, and the contact does not
* have a subscription to the user's presence; this is the default value, so if the
* subscription attribute is not included then the state is to be understood as "none".
*/
none('⊥'),
/**
* The user has a subscription to the contact's presence, but the contact does not have a
* subscription to the user's presence.
*/
to('←'),
/**
* The contact has a subscription to the user's presence, but the user does not have a
* subscription to the contact's presence.
*/
from('→'),
/**
* The user and the contact have subscriptions to each other's presence (also called a
* "mutual subscription").
*/
both('↔'),
/**
* The user wishes to stop receiving presence updates from the subscriber.
*/
remove('⚡'),
;
private static final char ME = '●';
private final String symbol;
ItemType(char secondSymbolChar) {
StringBuilder sb = new StringBuilder(2);
sb.append(ME).append(secondSymbolChar);
symbol = sb.toString();
}
public static ItemType fromString(String string) {
if (StringUtils.isNullOrEmpty(string)) {
return none;
}
return ItemType.valueOf(string.toLowerCase(Locale.US));
}
/**
* Get a String containing symbols representing the item type. The first symbol in the
* string is a big dot, representing the local entity. The second symbol represents the
* established subscription relation and is typically an arrow. The head(s) of the arrow
* point in the direction presence messages are sent. For example, if there is only a head
* pointing to the big dot, then the local user will receive presence information from the
* remote entity.
*
* @return the symbolic representation of this item type.
*/
public String asSymbol() {
return symbol;
}
}
}