/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * 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 net.java.sip.communicator.impl.protocol.jabber; import java.util.*; import net.java.sip.communicator.service.protocol.*; import org.jivesoftware.smack.*; /** * The Jabber implementation of the ContactGroup interface. Instances of this class * (contrary to RootContactGroupJabberImpl) may only contain buddies * and cannot have sub groups. Note that instances of this class only use the * corresponding smack source group for reading their names and only * initially fill their buddies java.util.List with * the ContactJabberImpl objects corresponding to those contained in the source * group at the moment it is being created. They would, however, never try to * sync or update their contents ulteriorly. This would have to be done through * the addContact()/removeContact() methods. * The content of buddies is created on creating of the group and when the smack * source group is changed. * * @author Damian Minkov * @author Emil Ivov */ public class ContactGroupJabberImpl extends AbstractContactGroupJabberImpl { /** * Maps all JIDs in our roster to the actual contacts so that we could * easily search the set of existing contacts. Note that we only store * lower case strings in the left column because JIDs in XMPP are not case * sensitive. */ private Map buddies = new Hashtable(); /** * Whether or not this contact group has been resolved against * the server. */ private boolean isResolved = false; /** * The Jabber Group id(the name), corresponding to this contact group. * Used to resolve the RosterGroup against the rouster. */ private String id = null; /** * a list that would always remain empty. We only use it so that we're able * to extract empty iterators */ private List dummyGroupsList = new LinkedList(); /** * A variable that we use as a means of detecting changes in the name * of this group. */ private String nameCopy = null; /** * Used when creating unresolved groups, temporally id. */ private String tempId = null; /** * The contact list handler that creates us. */ private final ServerStoredContactListJabberImpl ssclCallback; /** * Creates an Jabber group using the specified RosterGroup as * a source. The newly created group will always return the name of the * underlying RosterGroup and would thus automatically adapt to changes. * It would, however, not receive or try to poll for modifications of the * buddies it contains and would therefore have to be updated manually by * ServerStoredContactListImpl update will only be done if source group * is changed. * @param rosterGroup the Jabber Group corresponding to the group * @param groupMembers the group members that we should add to the group. * @param ssclCallback a callback to the server stored contact list * we're creating. * @param isResolved a boolean indicating whether or not the group has been * resolved against the server. */ ContactGroupJabberImpl( RosterGroup rosterGroup, Iterator groupMembers, ServerStoredContactListJabberImpl ssclCallback, boolean isResolved) { // rosterGroup can be null when creating volatile contact group if(rosterGroup != null) this.id = rosterGroup.getName(); this.isResolved = isResolved; this.ssclCallback = ssclCallback; // init the name copy if its not volatile if(rosterGroup != null) this.nameCopy = rosterGroup.getName(); while (groupMembers.hasNext()) { RosterEntry rEntry = groupMembers.next(); if(!ServerStoredContactListJabberImpl.isEntryDisplayable(rEntry)) continue; //only add the buddy if it doesn't already exist in some other group //this is necessary because XMPP would allow having one and the //same buddy in more than one group. if(ssclCallback.findContactById(rEntry.getUser()) != null) { continue; } addContact( new ContactJabberImpl(rEntry, ssclCallback, true, true)); } } /** * Used when creating unresolved groups. * @param id the id of the group. * @param ssclCallback the contact list handler that created us. */ ContactGroupJabberImpl( String id, ServerStoredContactListJabberImpl ssclCallback) { this.tempId = id; this.isResolved = false; this.ssclCallback = ssclCallback; } /** * Returns the number of Contact members of this * ContactGroup * * @return an int indicating the number of Contacts, * members of this ContactGroup. */ public int countContacts() { return buddies.size(); } /** * Returns a reference to the root group which in Jabber is the parent of * any other group since the protocol does not support subgroups. * @return a reference to the root group. */ public ContactGroup getParentContactGroup() { return ssclCallback.getRootGroup(); } /** * Adds the specified contact to the end of this group. * @param contact the new contact to add to this group */ public void addContact(ContactJabberImpl contact) { buddies.put(contact.getAddress().toLowerCase(), contact); } /** * Removes the specified contact from this contact group * @param contact the contact to remove. */ void removeContact(ContactJabberImpl contact) { buddies.remove(contact.getAddress().toLowerCase()); } /** * Returns an Iterator over all contacts, member of this * ContactGroup. * * @return a java.util.Iterator over all contacts inside this * ContactGroup. In case the group doesn't contain any * members it will return an empty iterator. */ public Iterator contacts() { return buddies.values().iterator(); } /** * Returns the Contact with the specified address or * identifier. * @param id the address or identifier of the Contact we are * looking for. * @return the Contact with the specified id or address. */ public Contact getContact(String id) { return this.findContact(id); } /** * Returns the name of this group. * @return a String containing the name of this group. */ public String getGroupName() { if(isResolved) // now we use the id field to store the rosterGroup name // for later retrieval from roster. //return rosterGroup.getName(); return id; else return tempId; } /** * Determines whether the group may contain subgroups or not. * * @return always false since only the root group may contain subgroups. */ public boolean canContainSubgroups() { return false; } /** * Returns the subgroup with the specified index (i.e. always null since * this group may not contain subgroups). * * @param index the index of the ContactGroup to retrieve. * @return always null */ public ContactGroup getGroup(int index) { return null; } /** * Returns the subgroup with the specified name. * @param groupName the name of the ContactGroup to retrieve. * @return the ContactGroup with the specified index. */ public ContactGroup getGroup(String groupName) { return null; } /** * Returns an empty iterator. Subgroups may only be present in the root * group. * * @return an empty iterator */ public Iterator subgroups() { return dummyGroupsList.iterator(); } /** * Returns the number of subgroups contained by this group, which is * always 0 since sub groups in the protocol may only be contained * by the root group - RootContactGroupImpl. * @return a 0 int. */ public int countSubgroups() { return 0; } /** * Returns a hash code value for the object, which is actually the hashcode * value of the groupname. * * @return a hash code value for this ContactGroup. */ @Override public int hashCode() { return getGroupName().hashCode(); } /** * Indicates whether some other object is "equal to" this group. * * @param obj the reference object with which to compare. * @return true if this object is the same as the obj * argument; false otherwise. */ @Override public boolean equals(Object obj) { if( obj == this ) return true; if (obj == null || !(obj instanceof ContactGroupJabberImpl) ) return false; if(!((ContactGroup)obj).getGroupName().equals(getGroupName())) return false; if(getProtocolProvider() != ((ContactGroup)obj).getProtocolProvider()) return false; //since Jabber does not support having two groups with the same name // at this point we could bravely state that the groups are the same // and not bother to compare buddies. (gotta check that though) return true; } /** * Returns the protocol provider that this group belongs to. * @return a reference to the ProtocolProviderService instance that this * ContactGroup belongs to. */ public ProtocolProviderService getProtocolProvider() { return this.ssclCallback.getParentProvider(); } /** * Returns a string representation of this group, in the form * JabberGroup.GroupName[size]{ buddy1.toString(), buddy2.toString(), ...}. * @return a String representation of the object. */ @Override public String toString() { StringBuffer buff = new StringBuffer("JabberGroup."); buff.append(getGroupName()) .append(", childContacts=") .append(countContacts()) .append(":["); Iterator contacts = contacts(); while (contacts.hasNext()) { Contact contact = contacts.next(); buff.append(contact.toString()); if(contacts.hasNext()) buff.append(", "); } return buff.append("]").toString(); } /** * Returns the contact encapsulating with the spcieified name or * null if no such contact was found. * * @param id the id for the contact we're looking for. * @return the ContactJabberImpl corresponding to the specified * screnname or null if no such contact existed. */ ContactJabberImpl findContact(String id) { if(id == null) return null; return (ContactJabberImpl)buddies.get(id.toLowerCase()); } /** * Sets the name copy field that we use as a means of detecting changes in * the group name. * @param newName String */ void setNameCopy(String newName) { this.nameCopy = newName; } /** * Returns the name of the group as it was at the last call of initNameCopy. * @return a String containing a copy of the name of this group as it was * last time when we called initNameCopy. */ String getNameCopy() { return this.nameCopy; } /** * Determines whether or not this contact group is being stored by the * server. Non persistent contact groups exist for the sole purpose of * containing non persistent contacts. * @return true if the contact group is persistent and false otherwise. */ public boolean isPersistent() { return true; } /** * Returns null as no persistent data is required and the contact address is * sufficient for restoring the contact. *

* @return null as no such data is needed. */ public String getPersistentData() { return null; } /** * Determines whether or not this contact group has been resolved against * the server. Unresolved group are used when initially loading a contact * list that has been stored in a local file until the presence operation * set has managed to retrieve all the contact list from the server and has * properly mapped contacts and groups to their corresponding on-line * buddies. * @return true if the contact has been resolved (mapped against a buddy) * and false otherwise. */ public boolean isResolved() { return isResolved; } /** * Resolve this contact group against the specified group * @param source the server stored group */ void setResolved(RosterGroup source) { if(isResolved) return; this.isResolved = true; this.id = source.getName(); for (RosterEntry item : source.getEntries()) { ContactJabberImpl contact = ssclCallback.findContactById(item.getUser()); // some services automatically adds contacts from an addressbook // to our roster and this contacts are with subscription none. // if such already exist, remove it. This is typically our // own contact if(!ServerStoredContactListJabberImpl.isEntryDisplayable(item)) { if(contact != null) { removeContact(contact); ssclCallback.fireContactRemoved(this, contact); } continue; } if(contact != null) { contact.setResolved(item); ssclCallback.fireContactResolved(this, contact); } else { ContactJabberImpl newContact = new ContactJabberImpl(item, ssclCallback, true, true); addContact(newContact); ssclCallback.fireContactAdded(this, newContact); } } } /** * Returns a String that uniquely represents the group. In this we * use the name of the group as an identifier. This may cause problems * though, in case the name is changed by some other application between * consecutive runs of the sip-communicator. * * @return a String representing this group in a unique and persistent * way. */ public String getUID() { return getGroupName(); } /** * The source group we are encapsulating * @return RosterGroup */ RosterGroup getSourceGroup() { return ssclCallback.getRosterGroup(id); } /** * Change the source group, used when renaming groups. * * @param newGroup RosterGroup */ void setSourceGroup(RosterGroup newGroup) { this.id = newGroup.getName(); } }