/*
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.protocol.sip;
import java.util.*;
import net.java.sip.communicator.service.protocol.*;
/**
* A simple, straightforward implementation of a SIP ContactGroup. Since
* the SIP protocol is not a real one, we simply store all group details
* in class fields. You should know that when implementing a real protocol,
* the contact group implementation would rather encapsulate group objects from
* the protocol stack and group property values should be returned by consulting
* the encapsulated object.
*
* @author Emil Ivov
*/
public class ContactGroupSipImpl
implements ContactGroup
{
/**
* The name of this SIP contact group.
*/
private String groupName = null;
/**
* The list of this group's members.
*/
private Vector<Contact> contacts = new Vector<Contact>();
/**
* The list of sub groups belonging to this group.
*/
private Vector<ContactGroup> subGroups
= new Vector<ContactGroup>();
/**
* The group that this group belongs to (or null if this is the root group).
*/
private ContactGroupSipImpl parentGroup = null;
/**
* Determines whether this group is really in the contact list or whether
* it is here only temporarily and will be gone next time we restart.
*/
private boolean isPersistent = true;
/**
* The protocol provider that created us.
*/
private ProtocolProviderServiceSipImpl parentProvider = null;
/**
* Determines whether this group has been resolved on the server.
* Unresolved groups are groups that were available on previous runs and
* that the meta contact list has stored. During all next runs, when
* bootstrapping, the meta contact list would create these groups as
* unresolved. Once a protocol provider implementation confirms that the
* groups are still on the server, it would issue an event indicating that
* the groups are now resolved.
*/
private boolean isResolved = true;
/**
* An id uniquely identifying the group. For many protocols this could be
* the group name itself.
*/
private String uid = null;
private static final String UID_SUFFIX = ".uid";
/**
* Creates a ContactGroupSipImpl with the specified name.
*
* @param groupName the name of the group.
* @param parentProvider the protocol provider that created this group.
*/
public ContactGroupSipImpl(
String groupName,
ProtocolProviderServiceSipImpl parentProvider)
{
this.groupName = groupName;
this.uid = groupName + UID_SUFFIX;
this.parentProvider = parentProvider;
}
/**
* Determines whether the group may contain subgroups or not.
*
* @return always true in this implementation.
*/
public boolean canContainSubgroups()
{
return true;
}
/**
* Returns the protocol provider that this group belongs to.
* @return a regerence to the ProtocolProviderService instance that this
* ContactGroup belongs to.
*/
public ProtocolProviderService getProtocolProvider()
{
return parentProvider;
}
/**
* Returns an Iterator over all contacts, member of this
* <tt>ContactGroup</tt>.
*
* @return a java.util.Iterator over all contacts inside this
* <tt>ContactGroup</tt>
*/
public Iterator<Contact> contacts()
{
return contacts.iterator();
}
/**
* Adds the specified contact to this group.
* @param contactToAdd the ContactSipImpl to add to this group.
*/
public void addContact(ContactSipImpl contactToAdd)
{
this.contacts.add(contactToAdd);
contactToAdd.setParentGroup(this);
}
/**
* Returns the number of <tt>Contact</tt> members of this
* <tt>ContactGroup</tt>
*
* @return an int indicating the number of <tt>Contact</tt>s, members of
* this <tt>ContactGroup</tt>.
*/
public int countContacts()
{
return contacts.size();
}
/**
* Returns the number of subgroups contained by this
* <tt>ContactGroup</tt>.
*
* @return the number of subGroups currently added to this group.
*/
public int countSubgroups()
{
return subGroups.size();
}
/**
* Adds the specified contact group to the contained by this group.
* @param subgroup the ContactGroupSipImpl to add as a subgroup to this group.
*/
public void addSubgroup(ContactGroupSipImpl subgroup)
{
this.subGroups.add(subgroup);
subgroup.setParentGroup(this);
}
/**
* Sets the group that is the new parent of this group
* @param parent ContactGroupSipImpl
*/
void setParentGroup(ContactGroupSipImpl parent)
{
this.parentGroup = parent;
}
/**
* Returns the contact group that currently contains this group or null if
* this is the root contact group.
* @return the contact group that currently contains this group or null if
* this is the root contact group.
*/
public ContactGroup getParentContactGroup()
{
return this.parentGroup;
}
/**
* Removes the specified contact group from the this group's subgroups.
* @param subgroup the ContactGroupSipImpl subgroup to remove.
*/
public void removeSubGroup(ContactGroupSipImpl subgroup)
{
this.subGroups.remove(subgroup);
subgroup.setParentGroup(null);
}
/**
* Returns the <tt>Contact</tt> with the specified index.
*
* @param index the index of the <tt>Contact</tt> to return.
* @return the <tt>Contact</tt> with the specified index.
*/
public Contact getContact(int index)
{
return (ContactSipImpl)contacts.get(index);
}
/**
* Returns the group that is parent of the specified sipGroup or null
* if no parent was found.
* @param sipGroup the group whose parent we're looking for.
* @return the ContactGroupSipImpl instance that sipGroup
* belongs to or null if no parent was found.
*/
public ContactGroupSipImpl findGroupParent(
ContactGroupSipImpl sipGroup)
{
if ( subGroups.contains(sipGroup) )
return this;
Iterator<ContactGroup> subGroupsIter = subgroups();
while (subGroupsIter.hasNext())
{
ContactGroupSipImpl subgroup
= (ContactGroupSipImpl) subGroupsIter.next();
ContactGroupSipImpl parent
= subgroup.findGroupParent(sipGroup);
if(parent != null)
return parent;
}
return null;
}
/**
* Returns the group that is parent of the specified sipContact or
* null if no parent was found.
*
* @param sipContact the contact whose parent we're looking for.
* @return the ContactGroupSipImpl instance that sipContact
* belongs to or <tt>null</tt> if no parent was found.
*/
public ContactGroupSipImpl findContactParent(
ContactSipImpl sipContact)
{
if ( contacts.contains(sipContact) )
{
return this;
}
Iterator<ContactGroup> subGroupsIter = subgroups();
while (subGroupsIter.hasNext())
{
ContactGroupSipImpl subgroup
= (ContactGroupSipImpl) subGroupsIter.next();
ContactGroupSipImpl parent
= subgroup.findContactParent(sipContact);
if(parent != null)
return parent;
}
return null;
}
/**
* Returns the <tt>Contact</tt> with the specified address or identifier.
*
* @param id the addres or identifier of the <tt>Contact</tt> we are
* looking for.
* @return the <tt>Contact</tt> with the specified id or address.
*/
public Contact getContact(String id)
{
Iterator<Contact> contactsIter = contacts();
while (contactsIter.hasNext())
{
ContactSipImpl contact = (ContactSipImpl) contactsIter.next();
if (contact.getAddress().equals(id))
{
return contact;
}
}
return null;
}
/**
* Returns the subgroup with the specified index.
*
* @param index the index of the <tt>ContactGroup</tt> to retrieve.
* @return the <tt>ContactGroup</tt> with the specified index.
*/
public ContactGroup getGroup(int index)
{
return (ContactGroup)subGroups.get(index);
}
/**
* Returns the subgroup with the specified name.
*
* @param groupName the name of the <tt>ContactGroup</tt> to retrieve.
* @return the <tt>ContactGroup</tt> with the specified index.
*/
public ContactGroup getGroup(String groupName)
{
Iterator<ContactGroup> groupsIter = subgroups();
while (groupsIter.hasNext())
{
ContactGroupSipImpl contactGroup
= (ContactGroupSipImpl) groupsIter.next();
if (contactGroup.getGroupName().equals(groupName))
{
return contactGroup;
}
}
return null;
}
/**
* Returns the name of this group.
*
* @return a String containing the name of this group.
*/
public String getGroupName()
{
return this.groupName;
}
/**
* Sets this group a new name.
* @param newGrpName a String containing the new name of this group.
*/
public void setGroupName(String newGrpName)
{
this.groupName = newGrpName;
}
/**
* Returns an iterator over the sub groups that this
* <tt>ContactGroup</tt> contains.
*
* @return a java.util.Iterator over the <tt>ContactGroup</tt> children
* of this group (i.e. subgroups).
*/
public Iterator<ContactGroup> subgroups()
{
return subGroups.iterator();
}
/**
* Removes the specified contact from this group.
* @param contact the ContactSipImpl to remove from this group
*/
public void removeContact(ContactSipImpl contact)
{
this.contacts.remove(contact);
}
/**
* Returns the contact with the specified id or null if no such contact
* exists.
* @param id the id of the contact we're looking for.
* @return ContactSipImpl
*/
public ContactSipImpl findContactByID(String id)
{
//first go through the contacts that are direct children.
Iterator<Contact> contactsIter = contacts();
while(contactsIter.hasNext())
{
ContactSipImpl mContact = (ContactSipImpl)contactsIter.next();
if( mContact.getAddress().equals(id) )
return mContact;
}
//if we didn't find it here, let's try in the subougroups
Iterator<ContactGroup> groupsIter = subgroups();
while( groupsIter.hasNext() )
{
ContactGroupSipImpl mGroup = (ContactGroupSipImpl)groupsIter.next();
ContactSipImpl mContact = mGroup.findContactByID(id);
if (mContact != null)
return mContact;
}
return null;
}
/**
* Returns a String representation of this group and the contacts it
* contains (may turn out to be a relatively long string).
* @return a String representing this group and its child contacts.
*/
public String toString()
{
StringBuffer buff = new StringBuffer(getGroupName());
buff.append(".subGroups=" + countSubgroups() + ":\n");
Iterator<ContactGroup> subGroups = subgroups();
while (subGroups.hasNext())
{
ContactGroupSipImpl group = (ContactGroupSipImpl)subGroups.next();
buff.append(group.toString());
if (subGroups.hasNext())
buff.append("\n");
}
buff.append("\nChildContacts="+countContacts()+":[");
Iterator<Contact> contacts = contacts();
while (contacts.hasNext())
{
ContactSipImpl contact = (ContactSipImpl) contacts.next();
buff.append(contact.toString());
if(contacts.hasNext())
buff.append(", ");
}
return buff.append("]").toString();
}
/**
* Specifies whether or not this contact group is being stored by the server.
* Non persistent contact groups are common in the case of simple,
* non-persistent presence operation sets. They could however also be seen
* in persistent presence operation sets when for example we have received
* an event from someone not on our contact list and the contact that we
* associated with that user is placed in a non persistent group. Non
* persistent contact groups are volatile even when coming from a persistent
* presence op. set. They would only exist until the application is closed
* and will not be there next time it is loaded.
*
* @param isPersistent true if the contact group is to be persistent and
* false otherwise.
*/
public void setPersistent(boolean isPersistent)
{
this.isPersistent = isPersistent;
}
/**
* 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 isPersistent;
}
/**
* Returns null as no persistent data is required and the contact address is
* sufficient for restoring the contact.
* <p>
* @return null as no such data is needed.
*/
public String getPersistentData()
{
return null;
}
/**
* Determines whether or not this contact has been resolved against the
* server. Unresolved contacts 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 to their on-line buddies.
* @return true if the contact has been resolved (mapped against a buddy)
* and false otherwise.
*/
public boolean isResolved()
{
return isResolved;
}
/**
* Makes the group resolved or unresolved.
*
* @param resolved true to make the group resolved; false to
* make it unresolved
*/
public void setResolved(boolean resolved)
{
this.isResolved = resolved;
}
/**
* Returns a <tt>String</tt> that uniquely represnets the group inside
* the current protocol. The string MUST be persistent (it must not change
* across connections or runs of the application). In many cases (Jabber,
* ICQ) the string may match the name of the group as these protocols
* only allow a single level of contact groups and there is no danger of
* having the same name twice in the same contact list. Other protocols
* (no examples come to mind but that doesn't bother me ;) ) may be
* supporting mutilple levels of grooups so it might be possible for group
* A and group B to both contain groups named C. In such cases the
* implementation must find a way to return a unique identifier in this
* method and this UID should never change for a given group.
*
* @return a String representing this group in a unique and persistent
* way.
*/
public String getUID()
{
return uid;
}
/**
* Ugly but tricky conversion method.
* @param uid the uid we'd like to get a name from
* @return the name of the group with the specified <tt>uid</tt>.
*/
static String createNameFromUID(String uid)
{
return uid.substring(0, uid.length() - (UID_SUFFIX.length()));
}
/**
* Indicates whether some other object is "equal to" this one which in terms
* of contact groups translates to having the equal names and matching
* subgroups and child contacts. The resolved status of contactgroups and
* contacts is deliberately ignored so that groups and/or contacts would
* be assumed equal even if it differs.
* <p>
* @param obj the reference object with which to compare.
* @return <code>true</code> if this contact group has the equal child
* contacts and subgroups to those of the <code>obj</code> argument.
*/
public boolean equals(Object obj)
{
if(obj == null
|| !(obj instanceof ContactGroupSipImpl))
return false;
ContactGroupSipImpl sipGroup
= (ContactGroupSipImpl)obj;
if( ! sipGroup.getGroupName().equals(getGroupName())
|| ! sipGroup.getUID().equals(getUID())
|| sipGroup.countContacts() != countContacts()
|| sipGroup.countSubgroups() != countSubgroups())
return false;
//traverse child contacts
Iterator<Contact> theirContacts = sipGroup.contacts();
while(theirContacts.hasNext())
{
ContactSipImpl theirContact
= (ContactSipImpl)theirContacts.next();
ContactSipImpl ourContact
= (ContactSipImpl)getContact(theirContact.getAddress());
if(ourContact == null
|| !ourContact.equals(theirContact))
return false;
}
//traverse subgroups
Iterator<ContactGroup> theirSubgroups = sipGroup.subgroups();
while(theirSubgroups.hasNext())
{
ContactGroupSipImpl theirSubgroup
= (ContactGroupSipImpl)theirSubgroups.next();
ContactGroupSipImpl ourSubgroup
= (ContactGroupSipImpl)getGroup(
theirSubgroup.getGroupName());
if(ourSubgroup == null
|| !ourSubgroup.equals(theirSubgroup))
return false;
}
return true;
}
}