diff --git a/src/net/java/sip/communicator/impl/protocol/icq/AbstractContactGroupIcqImpl.java b/src/net/java/sip/communicator/impl/protocol/icq/AbstractContactGroupIcqImpl.java
new file mode 100644
index 000000000..2c8640301
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/icq/AbstractContactGroupIcqImpl.java
@@ -0,0 +1,23 @@
+package net.java.sip.communicator.impl.protocol.icq;
+
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ * The ICQ implementation of the service.protocol.ContactGroup interface. There
+ * are two types of groups possible here. RootContactGroupIcqImpl
+ * which is the root node of the ContactList itself and
+ * ContactGroupIcqImpl which represents standard icq groups. The
+ * reason for having those 2 is that generally, ICQ groups may not contain
+ * subgroups. A contact list on the other hand may not directly contain buddies.
+ *
+ *
+ * The reason for having an abstract class is only - being able to esily
+ * recognize our own (ICQ) contacts.
+ * @author Emil Ivov
+ */
+public abstract class AbstractContactGroupIcqImpl
+ implements ContactGroup
+{
+
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/icq/AccountManagerIcqImpl.java b/src/net/java/sip/communicator/impl/protocol/icq/AccountManagerIcqImpl.java
new file mode 100644
index 000000000..03080d476
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/icq/AccountManagerIcqImpl.java
@@ -0,0 +1,156 @@
+/*
+ * 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.icq;
+
+import java.util.*;
+
+import org.osgi.framework.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.configuration.*;
+
+/**
+ * The ICQ implementation of the ProtocolAccountManager.
+ * @author Emil Ivov
+ */
+public class AccountManagerIcqImpl
+ implements AccountManager
+{
+ private Hashtable registeredAccounts = new Hashtable();
+
+
+ /**
+ * Creates an instance of the IcqAccountManager.
+ */
+ protected void IcqAccountManager()
+ {
+
+ }
+ /**
+ * Returns a copy of the list containing all accounts currently
+ * registered in this protocol provider.
+ *
+ * @return a copy of the llist containing all accounts currently installed
+ * in the protocol provider.
+ */
+ public ArrayList getRegisteredAcounts()
+ {
+ return new ArrayList(registeredAccounts.keySet());
+ }
+
+ /**
+ * Returns the ServiceReference for the protocol provider corresponding to
+ * the specified accountID or null if the accountID is unknown.
+ * @param accountID the accountID of the protocol provider we'd like to get
+ * @return a ServiceReference object to the protocol provider with the
+ * specified account id and null if the account id is unknwon to the
+ * account manager.
+ */
+ public ServiceReference getProviderForAccount(AccountID accountID)
+ {
+ ServiceRegistration registration
+ = (ServiceRegistration)registeredAccounts.get(accountID);
+
+ return (registration == null )
+ ? null
+ : registration.getReference();
+ }
+
+ /**
+ * Initializaed and creates an accoung corresponding to the specified
+ * accountProperties and registers the resulting ProtocolProvider in the
+ * context BundleContext parameter.
+ *
+ * @param context the BundleContext parameter where the newly created
+ * ProtocolProviderService would have to be registered.
+ * @param accountIDStr the user identifier for the new account
+ * @param accountProperties a set of protocol (or implementation)
+ * specific properties defining the new account.
+ * @return the AccountID of the newly created account
+ */
+ public AccountID installAccount( BundleContext context,
+ String accountIDStr,
+ Map accountProperties)
+ {
+ if(context == null)
+ throw new NullPointerException("The specified BundleContext was null");
+
+ if(accountIDStr == null)
+ throw new NullPointerException("The specified AccountID was null");
+
+ if(accountIDStr == null)
+ throw new NullPointerException("The specified property map was null");
+
+ AccountID accountID = new IcqAccountID(accountIDStr, accountProperties);
+
+ //make sure we haven't seen this account id before.
+ if( registeredAccounts.containsKey(accountID) )
+ throw new IllegalStateException(
+ "An account for id " + accountIDStr + " was already installed!");
+
+ Hashtable properties = new Hashtable();
+ properties.put(
+ AccountManager.PROTOCOL_PROPERTY_NAME, ProtocolNames.ICQ);
+ properties.put(
+ AccountManager.ACCOUNT_ID_PROPERTY_NAME, accountIDStr);
+
+ ProtocolProviderServiceIcqImpl icqProtocolProvider
+ = new ProtocolProviderServiceIcqImpl();
+
+ icqProtocolProvider.initialize(accountIDStr, accountProperties);
+
+ ServiceRegistration registration
+ = context.registerService( ProtocolProviderService.class.getName(),
+ icqProtocolProvider,
+ properties);
+
+ registeredAccounts.put(accountID, registration);
+ return accountID;
+ }
+
+ /**
+ * Removes the specified account from the list of accounts that this
+ * account manager is handling. If the specified accountID is unknown to the
+ * AccountManager, the call has no effect and false is returned. This method
+ * is persistent in nature and once called the account corresponding to the
+ * specified ID will not be loaded during future runs of the project.
+ *
+ * @param accountID the ID of the account to remove.
+ * @return true if an account with the specified ID existed and was removed
+ * and false otherwise.
+ */
+ public boolean uninstallAccount(AccountID accountID)
+ {
+ ServiceRegistration registration
+ = (ServiceRegistration)registeredAccounts.remove(accountID);
+
+ if(registration == null)
+ return false;
+
+ //kill the service
+ registration.unregister();
+
+ return true;
+
+ }
+
+ /**
+ * Loads all previously installed accounts that were stored in the
+ * configuration service. The method is only loading accounts the first
+ * time it gets called.
+ *
+ * @param context the context where icq protocol providers shouold be
+ * registered
+ * @param configurationService ConfigurationService
+ */
+ void loadStoredAccounts(BundleContext context,
+ ConfigurationService configurationService)
+ {
+ /** @todo implement loadStoredAccounts() */
+ //make sure we haven't already done so.
+ //load all accounts stored in the configuration service
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/icq/Activator.java b/src/net/java/sip/communicator/impl/protocol/icq/Activator.java
new file mode 100644
index 000000000..d2e3f825e
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/icq/Activator.java
@@ -0,0 +1,66 @@
+package net.java.sip.communicator.impl.protocol.icq;
+
+import org.osgi.framework.*;
+import net.java.sip.communicator.service.protocol.*;
+import java.util.Hashtable;
+import net.java.sip.communicator.service.configuration.*;
+
+/**
+ * Loads the ICQ account manager and registers it with service in the OSGI
+ * bundle context.
+ *
+ * @author Emil Ivov
+ */
+public class Activator
+ implements BundleActivator
+{
+ ServiceRegistration icqAccManRegistration = null;
+
+ /**
+ * Called when this bundle is started so the Framework can perform the
+ * bundle-specific activities necessary to start this bundle.
+ *
+ * @param context The execution context of the bundle being started.
+ * @throws Exception If this method throws an exception, this bundle is
+ * marked as stopped and the Framework will remove this bundle's
+ * listeners, unregister all services registered by this bundle, and
+ * release all services used by this bundle.
+ */
+ public void start(BundleContext context) throws Exception
+ {
+ Hashtable hashtable = new Hashtable();
+ hashtable.put(AccountManager.PROTOCOL_PROPERTY_NAME, "ICQ");
+
+ AccountManagerIcqImpl icqAccountManager =
+ new AccountManagerIcqImpl();
+
+ ServiceReference confReference
+ = context.getServiceReference(ConfigurationService.class.getName());
+ ConfigurationService configurationService
+ = (ConfigurationService)context.getService(confReference);
+
+ //load all icq providers
+ icqAccountManager.loadStoredAccounts(context, configurationService);
+
+ //reg the icq account man.
+ icqAccManRegistration = context.registerService(
+ AccountManager.class.getName(),
+ icqAccountManager,
+ hashtable);
+ }
+
+ /**
+ * Called when this bundle is stopped so the Framework can perform the
+ * bundle-specific activities necessary to stop the bundle.
+ *
+ * @param context The execution context of the bundle being stopped.
+ * @throws Exception If this method throws an exception, the bundle is
+ * still marked as stopped, and the Framework will remove the bundle's
+ * listeners, unregister all services registered by the bundle, and
+ * release all services used by the bundle.
+ */
+ public void stop(BundleContext context) throws Exception
+ {
+ icqAccManRegistration.unregister();
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/icq/ContactGroupIcqImpl.java b/src/net/java/sip/communicator/impl/protocol/icq/ContactGroupIcqImpl.java
new file mode 100644
index 000000000..b342ac30c
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/icq/ContactGroupIcqImpl.java
@@ -0,0 +1,405 @@
+/*
+ * 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.icq;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.*;
+import net.kano.joustsim.oscar.oscar.service.ssi.*;
+import net.kano.joustsim.*;
+
+/**
+ * The ICQ implementation of the ContactGroup interface. Intances of this class
+ * (contrary to RootContactGroupIcqImpl) may only contain buddies
+ * and cannot have sub groups. Note that instances of this class only use the
+ * corresponding joust sim source group for reading their names and only
+ * initially fill their buddies java.util.List with
+ * the ContactIcqImpl 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.
+ *
+ * @author Emil Ivov
+ */
+public class ContactGroupIcqImpl
+ extends AbstractContactGroupIcqImpl
+{
+ private List buddies = new LinkedList();
+
+ /**
+ * The JoustSIM Group corresponding to this contact group.
+ */
+ private MutableGroup joustSimSourceGroup = 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;
+
+ /**
+ * Creates an ICQ group using the specified joustSimGroup as
+ * a source. The newly created group will always return the name of the
+ * underlying joustSimGroup 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.
+ *
+ * Note that we MUST NOT use the list of buddies obtained through the + * getBuddiesCopy() of the joustSimGroup arg as we'd later need to be able + * to directly compare ( == ) instances of buddies we've stored and others + * that are returned by the framework. + *
+ * @param joustSimGroup the JoustSIM Group correspoinding to the group
+ * @param groupMembers the group members that we should add to the group.
+ *
+ * we're creating.
+ */
+ ContactGroupIcqImpl(MutableGroup joustSimGroup, List groupMembers)
+ {
+ this.joustSimSourceGroup = joustSimGroup;
+
+ //store a copy of the name now so that we can detect changes in the
+ //name of the underlying joustSimSourceGroup
+ initNameCopy();
+
+ //do not use the buddies in the joustSimGroup since we want to keep
+ //their real addresses and we can only get a list of copies from the
+ //group itself.
+
+ for (int i = 0; i < groupMembers.size(); i++)
+ {
+ addContact( new ContactIcqImpl((Buddy)groupMembers.get(i)) );
+ }
+
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * Adds the specified contact at the specified position.
+ * @param contact the new contact to add to this group
+ * @param index the position where the new contact should be added.
+ */
+ void addContact(int index, ContactIcqImpl contact)
+ {
+ buddies.add(index, contact);
+ }
+
+ /**
+ * Adds the specified contact to the end of this group.
+ * @param contact the new contact to add to this group
+ */
+ void addContact(ContactIcqImpl contact)
+ {
+ addContact(countContacts(), contact);
+ }
+
+
+ /**
+ * Removes the specified contact from this contact group
+ * @param contact the contact to remove.
+ */
+ void removeContact(ContactIcqImpl contact)
+ {
+ removeContact(buddies.indexOf(contact));
+ }
+
+ /**
+ * Removes the contact with the specified index.
+ * @param index the index of the cntact to remove
+ */
+ void removeContact(int index)
+ {
+ buddies.remove(index);
+ }
+
+ /**
+ * Removes all buddies in this group and reinsterts them as specified
+ * by the newOrder param. Contacts not contained in the
+ * newOrder list are left at the end of this group.
+ *
+ * @param newOrder a list containing all contacts in the order that is
+ * to be applied.
+ *
+ */
+ void reorderContacts(List newOrder)
+ {
+ buddies.removeAll(newOrder);
+ buddies.addAll(0, newOrder);
+ }
+
+ /**
+ * 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
+ * memebers it will return an empty iterator.
+ */
+ public Iterator contacts()
+ {
+ return buddies.iterator();
+ }
+
+ /**
+ * Returns the Contact with the specified index.
+ *
+ * @param index the index of the Contact to return.
+ * @return the Contact with the specified index.
+ */
+ public Contact getContact(int index)
+ {
+ return (ContactIcqImpl) buddies.get(index);
+ }
+
+ /**
+ * Returns the Contact with the specified address or
+ * identifier.
+ * @param id the addres 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()
+ {
+ return joustSimSourceGroup.getName();
+ }
+
+ /**
+ * 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 icq protocol may only be contained
+ * by the root group - RootContactGroupIcqImpl.
+ * @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.
+ */
+ public int hashCode()
+ {
+ return getGroupName().hashCode();
+ }
+
+ /**
+ * Returns the JoustSIM group that this class is encapsulating.
+ * @return the JoustSIM group corresponding to this SC group.
+ */
+ MutableGroup getJoustSimSourceGroup()
+ {
+ return joustSimSourceGroup;
+ }
+
+ /**
+ * Indicates whether some other object is "equal to" this group. A group is
+ * considered equal to another group if it hase the same sets of (equal)
+ * contacts.
+ *
+ *
+ * @param obj the reference object with which to compare.
+ * @return true if this object is the same as the obj
+ * argument; false otherwise.
+ */
+ public boolean equals(Object obj)
+ {
+ if( obj == this )
+ return true;
+
+ if (obj == null
+ || !(obj instanceof ContactGroupIcqImpl) )
+ return false;
+
+ if(!((ContactGroup)obj).getGroupName().equals(getGroupName()))
+ return false;
+
+ //since ICQ 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 a string representation of this group, in the form
+ * IcqGroup.GroupName[size]{ buddy1.toString(), buddy2.toString(), ...}.
+ * @return a String representation of the object.
+ */
+ public String toString()
+ {
+ StringBuffer buff = new StringBuffer("IcqGroup.");
+ buff.append(getGroupName());
+ buff.append(", childContacts="+countContacts()+":[");
+
+ Iterator contacts = contacts();
+ while (contacts.hasNext())
+ {
+ ContactIcqImpl contact = (ContactIcqImpl) contacts.next();
+ buff.append(contact.toString());
+ if(contacts.hasNext())
+ buff.append(", ");
+ }
+ return buff.append("]").toString();
+ }
+
+ /**
+ * Returns the icq contact encapsulating the specified joustSim buddy or null
+ * if no such buddy was found.
+ *
+ * @param joustSimBuddy the buddy whose encapsulating contact we're looking
+ * for.
+ * @return the ContactIcqImpl corresponding to the specified
+ * joustSimBuddy or null if no such contact was found.
+ */
+ ContactIcqImpl findContact(Buddy joustSimBuddy)
+ {
+ Iterator contacts = contacts();
+ while (contacts.hasNext())
+ {
+ ContactIcqImpl item = (ContactIcqImpl) contacts.next();
+ if(item.getJoustSimBuddy() == joustSimBuddy)
+ return item;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the index of icq contact encapsulating the specified joustSim
+ * buddy or -1 if no such buddy was found.
+ *
+ * @param joustSimBuddy the buddy whose encapsulating contact's index we're
+ * looking for.
+ * @return the index of the contact corresponding to the specified
+ * joustSimBuddy or null if no such contact was found.
+ */
+ int findContactIndex(Buddy joustSimBuddy)
+ {
+ Iterator contacts = contacts();
+ int i = 0;
+ while (contacts.hasNext())
+ {
+ ContactIcqImpl item = (ContactIcqImpl) contacts.next();
+ if(item.getJoustSimBuddy() == joustSimBuddy)
+ return i;
+ i++;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the icq contact encapsulating with the spcieified screen name or
+ * null if no such contact was found.
+ *
+ * @param screenName the screenName (or icq UIN) for the contact we're
+ * looking for.
+ * @return the ContactIcqImpl corresponding to the specified
+ * screnname or null if no such contact existed.
+ */
+ ContactIcqImpl findContact(String screenName)
+ {
+ Iterator contacts = contacts();
+ while (contacts.hasNext())
+ {
+ ContactIcqImpl item = (ContactIcqImpl) contacts.next();
+ if(item.getJoustSimBuddy().getScreenname()
+ .equals(new Screenname(screenName)))
+ return item;
+ }
+ return null;
+ }
+
+ /**
+ * Sets the name copy field that we use as a means of detecing changes in
+ * the group name.
+ */
+ void initNameCopy()
+ {
+ this.nameCopy = getGroupName();
+ }
+
+ /**
+ * 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;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/icq/ContactIcqImpl.java b/src/net/java/sip/communicator/impl/protocol/icq/ContactIcqImpl.java
new file mode 100644
index 000000000..5cebbc496
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/icq/ContactIcqImpl.java
@@ -0,0 +1,156 @@
+package net.java.sip.communicator.impl.protocol.icq;
+
+import net.java.sip.communicator.service.protocol.*;
+import net.kano.joscar.snaccmd.*;
+import net.kano.joustsim.oscar.oscar.service.ssi.*;
+import net.java.sip.communicator.service.protocol.icqconstants.*;
+
+/**
+ * The ICQ implementation of the service.protocol.Contact interface.
+ * @author Emil Ivov
+ */
+public class ContactIcqImpl
+ implements Contact
+{
+ Buddy joustSimBuddy = null;
+ private boolean isLocal = false;
+ private byte[] image = null;
+ private PresenceStatus icqStatus = IcqStatusEnum.OFFLINE;
+
+ /**
+ * Creates an IcqContactImpl
+ * @param buddy the JoustSIM object that we will be encapsulating.
+ * @param isLocal specifies whether this is the representation of the local
+ * contact (i.e. the user we are using to sign on icq)
+ */
+ ContactIcqImpl(Buddy buddy, boolean isLocal)
+ {
+ this.joustSimBuddy = buddy;
+ this.isLocal = isLocal;
+ }
+
+ /**
+ * Creates an IcqContactImpl for a non local contact
+ * @param buddy FullUserInfo
+ */
+ ContactIcqImpl(Buddy buddy)
+ {
+ this(buddy, false );
+ }
+
+ /**
+ * Returns the ICQ uin (or AIM screen name)of this contact
+ * @return the ICQ uin (or AIM screen name)of this contact
+ */
+ public String getUIN()
+ {
+ return joustSimBuddy.getScreenname().getFormatted();
+ }
+
+ /**
+ * Returns the ICQ uin (or AIM screen name)of this contact
+ * @return the ICQ uin (or AIM screen name)of this contact
+ */
+ public String getAddress(){
+ return getUIN();
+ }
+
+ /**
+ * Determines whether or not this Contact instance represents the user used
+ * by this protocol provider to connect to the service.
+ *
+ * @return true if this Contact represents us (the local user) and false
+ * otherwise.
+ */
+ public boolean isLocal()
+ {
+ return isLocal;
+ }
+
+ public byte[] getImage()
+ {
+ return image;
+ }
+
+ /**
+ * Returns a hashCode for this contact. The returned hashcode is actually
+ * that of the Contact's UIN
+ * @return the hashcode of this Contact
+ */
+ public int hashCode()
+ {
+ return getUIN().hashCode();
+ }
+
+ /**
+ * Indicates whether some other object is "equal to" this one.
+ *
+ *
+ * @param obj the reference object with which to compare.
+ * @return true if this object is the same as the obj
+ * argument; false otherwise.
+ */
+ public boolean equals(Object obj)
+ {
+ if (obj == null
+ || !(obj instanceof ContactIcqImpl)
+ || !((ContactIcqImpl)obj).getUIN().equals(getUIN()))
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Returns the joust sim buddy that this Contact is encapsulating.
+ * @return Buddy
+ */
+ Buddy getJoustSimBuddy()
+ {
+ return joustSimBuddy;
+ }
+
+ /**
+ * Returns a string representation of this contact, containing most of its
+ * representative details.
+ *
+ * @return a string representation of this contact.
+ */
+ public String toString()
+ {
+ StringBuffer buff = new StringBuffer("IcqContact[ uin=");
+ buff.append(getAddress()).append(", alias=")
+ .append(getJoustSimBuddy().getAlias()).append("]");
+
+ return buff.toString();
+ }
+
+ /**
+ * Sets the status that this contact is currently in. The method is to
+ * only be called as a result of a status update received from the AIM
+ * server.
+ *
+ * @param status the IcqStatusEnum that this contact is currently in.
+ */
+ void updatePresenceStatus(PresenceStatus status)
+ {
+ this.icqStatus = status;
+ }
+
+ /**
+ * Returns the status of the contact as per the last status update we've
+ * received for it. Note that this method is not to perform any network
+ * operations and will simply return the status received in the last
+ * status update message. If you want a reliable way of retrieving someone's
+ * status, you should use the queryContactStatus() method in
+ * OperationSetPresence.
+ * @return the PresenceStatus that we've received in the last status update
+ * pertaining to this contact.
+ */
+ public PresenceStatus getPresenceStatus()
+ {
+ return icqStatus;
+ }
+
+
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/icq/IcqAccountID.java b/src/net/java/sip/communicator/impl/protocol/icq/IcqAccountID.java
new file mode 100644
index 000000000..47b5d62f5
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/icq/IcqAccountID.java
@@ -0,0 +1,19 @@
+package net.java.sip.communicator.impl.protocol.icq;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ * The ICQ implementation of a sip-communicator AccountID
+ *
+ * @author Emil Ivov
+ */
+public class IcqAccountID
+ extends AccountID
+{
+ IcqAccountID(String accountID, Map accountProperties)
+ {
+ super(accountID, accountProperties);
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/icq/OperationSetPersistentPresenceIcqImpl.java b/src/net/java/sip/communicator/impl/protocol/icq/OperationSetPersistentPresenceIcqImpl.java
new file mode 100644
index 000000000..b167a9010
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/icq/OperationSetPersistentPresenceIcqImpl.java
@@ -0,0 +1,1193 @@
+/*
+ * 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.icq;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.util.*;
+import net.kano.joscar.snaccmd.loc.*;
+import net.kano.joustsim.*;
+import net.kano.joscar.snac.*;
+import net.kano.joscar.flapcmd.*;
+import net.kano.joscar.snaccmd.*;
+
+import net.kano.joustsim.oscar.oscar.service.bos.*;
+import net.kano.joscar.snaccmd.conn.*;
+import net.kano.joustsim.oscar.*;
+import net.java.sip.communicator.service.protocol.icqconstants.*;
+import net.kano.joscar.snaccmd.error.*;
+import net.kano.joustsim.oscar.oscar.service.ssi.*;
+import net.kano.joustsim.oscar.oscar.service.buddy.*;
+import java.beans.PropertyChangeEvent;
+
+/**
+ * The ICQ implementation of a Persistent Presence Operation set. This class
+ * manages our own presence status as well as subscriptions for the presence
+ * status of our buddies. It also offers methods for retrieving and modifying
+ * the buddy contact list and adding listeners for changes in its layout.
+ *
+ * @todo add consistent logging
+ *
+ * @author Emil Ivov
+ */
+public class OperationSetPersistentPresenceIcqImpl
+ implements OperationSetPersistentPresence
+{
+ private static final Logger logger =
+ Logger.getLogger(OperationSetPersistentPresenceIcqImpl.class);
+
+ /**
+ * A callback to the ICQ provider that created us.
+ */
+ private ProtocolProviderServiceIcqImpl icqProvider = null;
+
+ /**
+ * The list of presence status listeners interested in receiving presence
+ * notifications of changes in status of contacts in our contact list.
+ */
+ private Vector contactPresenceStatusListeners = new Vector();
+
+ /**
+ * The list of subscription listeners interested in receiving notifications
+ * whenever .
+ */
+ private Vector subscriptionListeners = new Vector();
+
+ /**
+ * The list of listeners interested in receiving changes in our local
+ * presencestatus.
+ */
+ private Vector providerPresenceStatusListeners = new Vector();
+
+ /**
+ * Listeners notified upon changes occurring with server stored contact
+ * groups.
+ */
+ private Vector serverStoredGroupChangeListeners = new Vector();
+
+ /**
+ * This one should actually be in joscar. But since it isn't we might as
+ * well define it here.
+ */
+ private static final long ICQ_ONLINE_MASK = 0x01000000L;
+
+ /**
+ * The IcqContact representing the local protocol provider.
+ */
+ private ContactIcqImpl localContact = null;
+
+ /**
+ * The listener that would react upon changes of the registration state of
+ * our provider
+ */
+ private RegistrationStateListener registrationStateListener
+ = new RegistrationStateListener();
+
+ /**
+ * The listener that would receive joust sim status updates for budddies in
+ * our contact list
+ */
+ private JoustSimBuddyServiceListener joustSimBuddySerListener
+ = new JoustSimBuddyServiceListener();
+
+ /**
+ * emcho: I think Bos stands for Buddy Online-status Service ... or at least
+ * it seems like a plausible translation. This listener follows changes
+ * in our own presence status and translates them in the corresponding
+ * protocol provider events.
+ */
+ private JoustSimBosListener joustSimBosListener = new JoustSimBosListener();
+
+ /**
+ * Contains our current status message. Note that this field would only
+ * be changed once the server has confirmed the new status message and
+ * not immediately upon setting a new one..
+ */
+ private String currentStatusMessage = "";
+
+ /**
+ * The presence status that we were last notified of etnering.
+ */
+ private long currentIcqStatus = -1;
+
+ /**
+ * The array list we use when returning from the getSupportedStatusSet()
+ * method.
+ */
+ private static final ArrayList supportedPresenceStatusSet = new ArrayList();
+ static{
+ supportedPresenceStatusSet.add(IcqStatusEnum.AWAY);
+ supportedPresenceStatusSet.add(IcqStatusEnum.DO_NOT_DISTURB);
+ supportedPresenceStatusSet.add(IcqStatusEnum.FREE_FOR_CHAT);
+ supportedPresenceStatusSet.add(IcqStatusEnum.INVISIBLE);
+ supportedPresenceStatusSet.add(IcqStatusEnum.NOT_AVAILABLE);
+ supportedPresenceStatusSet.add(IcqStatusEnum.OCCUPIED);
+ supportedPresenceStatusSet.add(IcqStatusEnum.OFFLINE);
+ supportedPresenceStatusSet.add(IcqStatusEnum.ONLINE);
+ }
+
+
+ /**
+ * A map containing bindings between SIP Communicator's icq presence status
+ * instances and ICQ status codes
+ */
+ private static Map scToIcqStatusMappings = new Hashtable();
+ static{
+
+ scToIcqStatusMappings.put(IcqStatusEnum.AWAY,
+ new Long(FullUserInfo.ICQSTATUS_AWAY));
+ scToIcqStatusMappings.put(IcqStatusEnum.DO_NOT_DISTURB,
+ new Long(FullUserInfo.ICQSTATUS_DND ));
+ scToIcqStatusMappings.put(IcqStatusEnum.FREE_FOR_CHAT,
+ new Long(FullUserInfo.ICQSTATUS_FFC ));
+ scToIcqStatusMappings.put(IcqStatusEnum.INVISIBLE,
+ new Long(FullUserInfo.ICQSTATUS_INVISIBLE));
+ scToIcqStatusMappings.put(IcqStatusEnum.NOT_AVAILABLE,
+ new Long(FullUserInfo.ICQSTATUS_NA));
+ scToIcqStatusMappings.put(IcqStatusEnum.OCCUPIED,
+ new Long(FullUserInfo.ICQSTATUS_OCCUPIED));
+ scToIcqStatusMappings.put(IcqStatusEnum.ONLINE,
+ new Long(ICQ_ONLINE_MASK));
+
+ }
+
+ /**
+ * The server stored contact list that will be encapsulating joustsim's
+ * buddy list.
+ */
+ private ServerStoredContactListIcqImpl ssContactList
+ = new ServerStoredContactListIcqImpl();
+
+ /**
+ * Creates a new Presence OperationSet over the specified icq provider.
+ * @param icqProvider IcqProtocolProviderServiceImpl
+ * @param uin the UIN of our account.
+ */
+ protected OperationSetPersistentPresenceIcqImpl(
+ ProtocolProviderServiceIcqImpl icqProvider,
+ String uin)
+ {
+ this.icqProvider = icqProvider;
+
+ //add a listener that'll follow the provider's state.
+ icqProvider.addRegistrationStateChangeListener(
+ registrationStateListener);
+ /** @todo create local contact here */
+
+
+ }
+
+ /**
+ * Registers a listener that would receive a presence status change event
+ * every time a contact, whose status we're subscribed for, changes her
+ * status.
+ * Note that, for reasons of simplicity and ease of implementation, there
+ * is only a means of registering such "global" listeners that would receive
+ * updates for status changes for any contact and it is not currently
+ * possible to register such contacts for a single contact or a subset of
+ * contacts.
+ *
+ * @param listener the listener that would received presence status
+ * updates for contacts.
+ */
+ public void addContactPresenceStatusListener(
+ ContactPresenceStatusListener listener)
+ {
+ synchronized(contactPresenceStatusListeners){
+ this.contactPresenceStatusListeners.add(listener);
+ }
+ }
+
+ /**
+ * Removes the specified listener so that it won't receive any further
+ * updates on contact presence status changes
+ * @param listener the listener to remove.
+ */
+ public void removeContactPresenceStatusListener(
+ ContactPresenceStatusListener listener)
+ {
+ synchronized(contactPresenceStatusListeners){
+ contactPresenceStatusListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Registers a listener that would get notifications any time a new
+ * subscription was succesfully added, has failed or was removed.
+ * @param listener the SubscriptionListener to register
+ */
+ public void addSubsciptionListener(SubscriptionListener listener)
+ {
+ synchronized(subscriptionListeners){
+ subscriptionListeners.add(listener);
+ }
+ }
+
+ /**
+ * Removes the specified subscription listener.
+ * @param listener the listener to remove.
+ */
+ public void removeSubsciptionListener(SubscriptionListener listener)
+ {
+ synchronized(subscriptionListeners){
+ subscriptionListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Get the PresenceStatus for a particular contact. This method is not meant
+ * to be used by the user interface (which would simply register as a
+ * presence listener and always follow contact status) but rather by other
+ * plugins that may for some reason need to know the status of a particular
+ * contact.
+ *
+ * @param contactIdentifier the dientifier of the contact whose status we're
+ * interested in.
+ * @return PresenceStatus the PresenceStatus of the specified
+ * contact
+ * @throws java.lang.IllegalStateException if the provider is not signed
+ * on ICQ
+ * @throws java.lang.IllegalArgumentException if contact is not
+ * a valid IcqContact
+ */
+ public PresenceStatus queryContactStatus(String contactIdentifier)
+ throws IllegalStateException, IllegalArgumentException
+ {
+ verifyConnected();
+
+ //these are commented since we now use identifiers.
+ // if (!(contact instanceof ContactIcqImpl))
+ // throw new IllegalArgumentException(
+ // "Cannont get status for a non-ICQ contact! ("
+ // + contact + ")");
+ //
+ // ContactIcqImpl contactImpl = (ContactIcqImpl)contact;
+
+ StatusResponseRetriever responseRetriever =
+ new StatusResponseRetriever();
+
+ GetInfoCmd getInfoCmd =
+ new GetInfoCmd(GetInfoCmd.CMD_USER_INFO, contactIdentifier);
+
+ icqProvider.getAimConnection().getInfoService()
+ .sendSnacRequest(getInfoCmd, responseRetriever);
+
+ synchronized(responseRetriever)
+ {
+ try{
+ responseRetriever.wait(10000);
+ }
+ catch (InterruptedException ex){
+ //we don't care
+ }
+ }
+
+ return icqStatusLongToPresenceStatus(responseRetriever.status);
+ }
+
+ /**
+ * Converts the specified icqstatus to one of the status fields of the
+ * IcqStatusEnum class.
+ *
+ * @param icqStatus the icqStatus as retured in FullUserInfo by the joscar
+ * stack
+ * @return a PresenceStatus instance representation of the "long" icqStatus
+ * parameter. The returned result is one of the IcqStatusEnum fields.
+ */
+ private IcqStatusEnum icqStatusLongToPresenceStatus(long icqStatus)
+ {
+ if ( icqStatus == -1)
+ {
+ return IcqStatusEnum.OFFLINE;
+ }
+ else if ( (icqStatus & FullUserInfo.ICQSTATUS_AWAY ) != 0)
+ {
+ return IcqStatusEnum.AWAY;
+ }
+ else if ( (icqStatus & FullUserInfo.ICQSTATUS_DND ) != 0)
+ {
+ return IcqStatusEnum.DO_NOT_DISTURB;
+ }
+ else if ( (icqStatus & FullUserInfo.ICQSTATUS_FFC ) != 0)
+ {
+ return IcqStatusEnum.FREE_FOR_CHAT;
+ }
+ else if ( (icqStatus & FullUserInfo.ICQSTATUS_INVISIBLE ) != 0)
+ {
+ return IcqStatusEnum.INVISIBLE;
+ }
+ else if ( (icqStatus & FullUserInfo.ICQSTATUS_NA ) != 0)
+ {
+ return IcqStatusEnum.NOT_AVAILABLE;
+ }
+ else if ( (icqStatus & FullUserInfo.ICQSTATUS_OCCUPIED ) != 0)
+ {
+ return IcqStatusEnum.OCCUPIED;
+ }
+ else if ((icqStatus & ICQ_ONLINE_MASK) == 0 )
+ {
+ return IcqStatusEnum.OFFLINE;
+ }
+
+ return IcqStatusEnum.ONLINE;
+ }
+
+ /**
+ * Converts the specified IcqStatusEnum member to the corresponding ICQ
+ * flag.
+ *
+ * @param status the icqStatus as retured in FullUserInfo by the joscar
+ * stack
+ * @return a PresenceStatus instance representation of the "long" icqStatus
+ * parameter. The returned result is one of the IcqStatusEnum fields.
+ */
+ private long presenceStatusToIcqStatusLong(IcqStatusEnum status)
+ {
+ return ((Long)scToIcqStatusMappings.get(status)).longValue();
+ }
+
+ /**
+ * Adds a subscription for the presence status of the contact corresponding
+ * to the specified contactIdentifier. Apart from an exception in the case
+ * of an immediate failure, the method won't return any indication of
+ * success or failure. That would happen later on through a
+ * SubscriptionEvent generated by one of the methods of the
+ * SubscriptionListener.
+ *
+ * This subscription is not going to be persistent (as opposed to + * subscriptions added from the OperationSetPersistentPresence.subscribe() + * method) + *
+ * @param contactIdentifier the identifier of the contact whose status + * updates we are subscribing for. + *
+ * @throws OperationFailedException with code NETWORK_FAILURE if subscribing
+ * fails due to errors experienced during network communication
+ * @throws IllegalArgumentException if contact is not a contact
+ * known to the underlying protocol provider
+ * @throws IllegalStateException if the underlying protocol provider is not
+ * registered/signed on a public service.
+ */
+ public void subscribe(String contactIdentifier)
+ throws IllegalArgumentException,
+ IllegalStateException,
+ OperationFailedException
+ {
+ verifyConnected();
+
+ ssContactList.addContact(contactIdentifier);
+ }
+
+ /**
+ * Persistently adds a subscription for the presence status of the contact
+ * corresponding to the specified contactIdentifier and indicates that it
+ * should be added to the specified group of the server stored contact list.
+ * Note that apart from an exception in the case of an immediate failure,
+ * the method won't return any indication of success or failure. That would
+ * happen later on through a SubscriptionEvent generated by one of the
+ * methods of the SubscriptionListener.
+ *
+ * @param contactIdentifier the contact whose status updates we are subscribing + * for. + * @param parent the parent group of the server stored contact list where + * the contact should be added. + *
+ *
+ * @throws OperationFailedException with code NETWORK_FAILURE if subscribing
+ * fails due to errors experienced during network communication
+ * @throws IllegalArgumentException if contact or
+ * parent are not a contact known to the underlying protocol
+ * provider.
+ * @throws IllegalStateException if the underlying protocol provider is not
+ * registered/signed on a public service.
+ */
+ public void subscribe(ContactGroup parent, String contactIdentifier)
+ throws IllegalArgumentException,
+ IllegalStateException,
+ OperationFailedException
+ {
+ verifyConnected();
+
+ if(! (parent instanceof ContactGroupIcqImpl) )
+ throw new IllegalArgumentException(
+ "Argument is not an icq contact group (group=" + parent + ")");
+
+ ssContactList.addContact((ContactGroupIcqImpl)parent, contactIdentifier);
+ }
+
+ /**
+ * Removes a subscription for the presence status of the specified contact.
+ * @param contact the contact whose status updates we are unsubscribing from.
+ *
+ * @throws OperationFailedException with code NETWORK_FAILURE if unsubscribing
+ * fails due to errors experienced during network communication
+ * @throws IllegalArgumentException if contact is not a contact
+ * known to this protocol provider or is not an ICQ contact
+ * @throws IllegalStateException if the underlying protocol provider is not
+ * registered/signed on a public service.
+ */
+ public void unsubscribe(Contact contact) throws IllegalArgumentException,
+ IllegalStateException, OperationFailedException
+ {
+ verifyConnected();
+
+ if(! (contact instanceof ContactIcqImpl) )
+ throw new IllegalArgumentException(
+ "Argument is not an icq contact (contact=" + contact + ")");
+
+ ContactIcqImpl contactIcqImpl = (ContactIcqImpl)contact;
+
+ ContactGroupIcqImpl contactGroup
+ = ssContactList.findContactGroup(contactIcqImpl);
+
+ if (contactGroup == null)
+ throw new IllegalArgumentException(
+ "The specified contact was not found on the local "
+ +"contact/subscription list: " + contact);
+
+ MutableGroup joustSimContactGroup = contactGroup.getJoustSimSourceGroup();
+
+ joustSimContactGroup.deleteBuddy(contactIcqImpl.getJoustSimBuddy());
+ }
+
+ /**
+ * Returns a reference to the contact with the specified ID in case we have
+ * a subscription for it and null otherwise/
+ * @param contactID a String identifier of the contact which we're seeking a
+ * reference of.
+ * @return a reference to the Contact with the specified
+ * contactID or null if we don't have a subscription for the
+ * that identifier.
+ */
+ public Contact findContactByID(String contactID)
+ {
+ return ssContactList.findContactByScreenName(contactID);
+ }
+
+ /**
+ * Requests the provider to enter into a status corresponding to the
+ * specified paramters. Note that calling this method does not necessarily
+ * imply that the requested status would be entered. This method would
+ * return right after being called and the caller should add itself as
+ * a listener to this class in order to get notified when the state has
+ * actually changed.
+ *
+ * @param status the PresenceStatus as returned by getRequestableStatusSet
+ * @param statusMessage the message that should be set as the reason to
+ * enter that status
+ * @throws IllegalArgumentException if the status requested is not a valid
+ * PresenceStatus supported by this provider.
+ * @throws java.lang.IllegalStateException if the provider is not currently
+ * registered.
+ * @throws OperationFailedException with code NETWORK_FAILURE if publishing
+ * the status fails due to a network error.
+ */
+ public void publishPresenceStatus(PresenceStatus status,
+ String statusMessage) throws
+ IllegalArgumentException, IllegalStateException,
+ OperationFailedException
+ {
+ verifyConnected();
+
+ if (!(status instanceof IcqStatusEnum))
+ throw new IllegalArgumentException(
+ status + " is not a valid ICQ status");
+
+ long icqStatus = presenceStatusToIcqStatusLong((IcqStatusEnum)status);
+
+ logger.debug("Will set status: " + status + " long=" + icqStatus);
+
+ MainBosService bosService
+ = icqProvider.getAimConnection().getBosService();
+
+ bosService.sendSnac(new SetExtraInfoCmd(icqStatus));
+ bosService.setStatusMessage(statusMessage);
+
+ //so that everyone sees the change.
+ queryContactStatus(
+ icqProvider.getAimConnection().getScreenname().getFormatted());
+ }
+
+ /**
+ * Returns the status message that was confirmed by the serfver
+ * @return the last status message that we have requested and the aim server
+ * has confirmed.
+ */
+ public String getCurrentStatusMessage()
+ {
+ return this.currentStatusMessage;
+ }
+
+ /**
+ * Returns the protocol specific contact instance representing the local
+ * user.
+ *
+ * @return the Contact (address, phone number, or uin) that the Provider
+ * implementation is communicating on behalf of.
+ */
+ public Contact getLocalContact()
+ {
+ return localContact;
+ }
+
+ /**
+ * Creates a group with the specified name and parent in the server stored
+ * contact list.
+ * @param groupName the name of the new group to create.
+ * @param parent the group where the new group should be created
+ *
+ * @throws OperationFailedException with code NETWORK_FAILURE if unsubscribing
+ * fails due to errors experienced during network communication
+ * @throws IllegalArgumentException if contact is not a contact
+ * known to the underlying protocol provider
+ * @throws IllegalStateException if the underlying protocol provider is not
+ * registered/signed on a public service.
+ */
+ public void createServerStoredContactGroup(ContactGroup parent,
+ String groupName)
+ {
+ verifyConnected();
+
+ if (!parent.canContainSubgroups())
+ throw new IllegalArgumentException(
+ "The specified contact group cannot contain child groups. Group:"
+ + parent );
+
+ ssContactList.createGroup(groupName);
+ }
+
+ /**
+ * Removes the specified group from the server stored contact list.
+ * @param group the group to remove.
+ *
+ * @throws OperationFailedException with code NETWORK_FAILURE if deleting
+ * the group fails because of a network error.
+ * @throws IllegalArgumentException if parent is not a contact
+ * known to the underlying protocol provider.
+ * @throws IllegalStateException if the underlying protocol provider is not
+ * registered/signed on a public service.
+ */
+ public void removeServerStoredContactGroup(ContactGroup group)
+ {
+ verifyConnected();
+
+ if( !(group instanceof ContactGroupIcqImpl) )
+ throw new IllegalArgumentException(
+ "The specified group is not an icq contact group: " + group);
+
+ ssContactList.removeGroup((ContactGroupIcqImpl)group);
+ }
+
+ /**
+ * Renames the specified group from the server stored contact list. This
+ * method would return before the group has actually been renamed. A
+ * ServerStoredGroupEvent would be dispatched once new name
+ * has been acknowledged by the server.
+ *
+ * @param group the group to rename.
+ * @param newName the new name of the group.
+ *
+ * @throws OperationFailedException with code NETWORK_FAILURE if deleting
+ * the group fails because of a network error.
+ * @throws IllegalArgumentException if parent is not a contact
+ * known to the underlying protocol provider.
+ * @throws IllegalStateException if the underlying protocol provider is not
+ * registered/signed on a public service.
+ */
+ public void renameServerStoredContactGroup(
+ ContactGroup group, String newName)
+ {
+ verifyConnected();
+
+ if( !(group instanceof ContactGroupIcqImpl) )
+ throw new IllegalArgumentException(
+ "The specified group is not an icq contact group: " + group);
+
+ ssContactList.renameGroup((ContactGroupIcqImpl)group, newName);
+ }
+
+ /**
+ * Removes the specified contact from its current parent and places it
+ * under newParent.
+ * @param contactToMove the Contact to move
+ * @param newParent the ContactGroup where Contact
+ * would be placed.
+ */
+ public void moveContactToGroup(Contact contactToMove,
+ ContactGroup newParent)
+ {
+ verifyConnected();
+
+ if( !(contactToMove instanceof ContactIcqImpl) )
+ throw new IllegalArgumentException(
+ "The specified contact is not an icq contact." + contactToMove);
+ if( !(newParent instanceof ContactGroupIcqImpl) )
+ throw new IllegalArgumentException(
+ "The specified group is not an icq contact group."
+ + newParent);
+
+ ssContactList.moveContact((ContactIcqImpl)contactToMove,
+ (ContactGroupIcqImpl)newParent);
+ }
+
+ /**
+ * Returns a snapshot ieves a server stored list of subscriptions/contacts that have been
+ * made previously. Note that the contact list returned by this method may
+ * be incomplete as it is only a snapshot of what has been retrieved through
+ * the network up to the moment when the method is called.
+ * @return a ConactGroup containing all previously made subscriptions stored
+ * on the server.
+ */
+ ServerStoredContactListIcqImpl getServerStoredContactList()
+ {
+ return ssContactList;
+ }
+
+ /**
+ * Returns a PresenceStatus instance representing the state this provider
+ * is currently in.
+ *
+ * @return PresenceStatus
+ */
+ public PresenceStatus getPresenceStatus()
+ {
+ return icqStatusLongToPresenceStatus(currentIcqStatus);
+ }
+
+ /**
+ * Returns the set of PresenceStatus objects that a user of this service
+ * may request the provider to enter.
+ *
+ * @return Iterator a PresenceStatus array containing "enterable" status
+ * instances.
+ */
+ public Iterator getSupportedStatusSet()
+ {
+ return supportedPresenceStatusSet.iterator();
+ }
+
+ /**
+ * Handler for incoming authorization requests. An authorization request
+ * notifies the user that someone is trying to add her to their contact list
+ * and requires her to approve or reject authorization for that action.
+ * @param handler an instance of an AuthorizationHandler for authorization
+ * requests coming from other users requesting permission add us to their
+ * contact list.
+ */
+ public void setAuthorizationHandler(AuthorizationHandler handler)
+ {
+ /** @todo implement setAuthorizationHandler(); */
+ }
+
+ /**
+ * The StatusResponseRetriever is used as a one time handler for responses
+ * to requests sent through the sendSnacRequest method of one of joustsim's
+ * Services. The StatusResponseRetriever would ignore everything apart from
+ * the first response, which will be stored in the status field. In the
+ * case of a timeout, the status would remain on -1. Both a response and
+ * a timeout would make the StatusResponseRetriever call its notifyAll
+ * method so that those that are waiting on it be notified.
+ */
+ private class StatusResponseRetriever extends SnacRequestAdapter
+ {
+ private boolean ran = false;
+ private long status = -1;
+
+
+ public void handleResponse(SnacResponseEvent e) {
+ SnacCommand snac = e.getSnacCommand();
+
+ synchronized(this) {
+ if (ran) return;
+ ran = true;
+ }
+
+ Object value = null;
+ if (snac instanceof UserInfoCmd)
+ {
+ UserInfoCmd uic = (UserInfoCmd) snac;
+
+ FullUserInfo userInfo = uic.getUserInfo();
+ if (userInfo != null)
+ {
+ this.status = userInfo.getIcqStatus();
+
+ //it is possible that the status was not included in
+ //the UserInfoCmd. Yet the fact that we got one
+ //guarantees that she is not offline. we'll therefore
+ //make sure it does not remain on -1.
+ if (this.status == -1)
+ status = ICQ_ONLINE_MASK;
+
+ synchronized(this){
+ this.notifyAll();
+ }
+ }
+ }
+ else if( snac instanceof SnacError)
+ {
+ //this is most probably a CODE_USER_UNAVAILABLE, but
+ //whatever it is it means that to us the buddy in question
+ //is as good as offline so leave status at -1 and notify.
+
+ logger.debug("status is" + status);
+ synchronized(this){
+ this.notifyAll();
+ }
+ }
+
+ }
+
+ public void handleTimeout(SnacRequestTimeoutEvent event) {
+ synchronized(this) {
+ if (ran) return;
+ ran = true;
+ notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Utility method throwing an exception if the icq stack is not properly
+ * initialized.
+ * @throws java.lang.IllegalStateException if the underlying ICQ stack is
+ * not registered and initialized.
+ */
+ private void verifyConnected() throws IllegalStateException
+ {
+ if (icqProvider == null)
+ throw new IllegalStateException(
+ "The icq provider must be non-null and signed on the ICQ "
+ +"service before being able to communicate.");
+ if (!icqProvider.isRegistered())
+ throw new IllegalStateException(
+ "The icq provider must be signed on the ICQ service before "
+ +"being able to communicate.");
+ }
+
+ /**
+ * Adds a listener that would receive events upon changes of the provider
+ * presence status.
+ * @param listener the listener to register for changes in our PresenceStatus.
+ */
+ public void addProviderPresenceStatusListener(
+ ProviderPresenceStatusListener listener)
+ {
+ synchronized(providerPresenceStatusListeners){
+ providerPresenceStatusListeners.add(listener);
+ }
+ }
+
+ /**
+ * Unregisters the specified listener so that it does not receive further
+ * events upon changes in local presence status.
+ * @param listener ProviderPresenceStatusListener
+ */
+ public void removeProviderPresenceStatusListener(
+ ProviderPresenceStatusListener listener)
+ {
+ synchronized(providerPresenceStatusListeners){
+ providerPresenceStatusListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Returns the root group of the server stored contact list. Most often this
+ * would be a dummy group that user interface implementations may better not
+ * show.
+ *
+ * @return the root ContactGroup for the ContactList stored by this service.
+ */
+ public ContactGroup getServerStoredContactListRoot()
+ {
+ return ssContactList.getRootGroup();
+ }
+
+ /**
+ * Registers a listener that would receive events upong changes in server
+ * stored groups.
+ * @param listener a ServerStoredGroupChangeListener impl that would receive
+ * events upong group changes.
+ */
+ public void addServerStoredGroupChangeListener(
+ ServerStoredGroupListener listener)
+ {
+ ssContactList.addGroupListener(listener);
+ }
+
+ /**
+ * Removes the specified group change listener so that it won't receive
+ * any further events.
+ * @param listener the ServerStoredGroupChangeListener to remove
+ */
+ public void removeServerStoredGroupChangeListener(
+ ServerStoredGroupListener listener)
+ {
+ ssContactList.removeGroupListener(listener);
+ }
+
+ /**
+ * Notify all provider presence listeners of the corresponding event change
+ * @param oldStatusL the status our icq stack had so far
+ * @param newStatusL the status we have from now on
+ */
+ private void fireProviderPresenceStatusChangeEvent(
+ long oldStatusL, long newStatusL)
+ {
+ PresenceStatus oldStatus = icqStatusLongToPresenceStatus(oldStatusL);
+ PresenceStatus newStatus = icqStatusLongToPresenceStatus(newStatusL);
+
+ if(oldStatus.equals(newStatus)){
+ logger.debug("Ignored prov stat. change evt. old==new = "
+ + oldStatus);
+ return;
+ }
+
+ ProviderPresenceStatusChangeEvent evt =
+ new ProviderPresenceStatusChangeEvent(
+ icqProvider, oldStatus, newStatus);
+
+ synchronized (providerPresenceStatusListeners){
+ Iterator listeners = this.providerPresenceStatusListeners.iterator();
+
+ logger.debug("Dispatching Provider Status Change. Listeners="
+ + providerPresenceStatusListeners.size()
+ + " evt=" + evt);
+
+ while (listeners.hasNext())
+ {
+ ProviderPresenceStatusListener listener =
+ (ProviderPresenceStatusListener) listeners.next();
+ listener.providerStatusChanged(evt);
+ }
+ }
+ }
+
+ /**
+ * Notify all provider presence listeners that a new status message has
+ * been set.
+ * @param oldStatusMessage the status message our icq stack had so far
+ * @param newStatusMessage the status message we have from now on
+ */
+ private void fireProviderStatusMessageChangeEvent(
+ String oldStatusMessage, String newStatusMessage)
+ {
+
+ PropertyChangeEvent evt = new PropertyChangeEvent(
+ icqProvider, ProviderPresenceStatusListener.STATUS_MESSAGE,
+ oldStatusMessage, newStatusMessage);
+
+ synchronized (providerPresenceStatusListeners){
+ Iterator listeners = this.providerPresenceStatusListeners.iterator();
+
+ logger.debug("Dispatching stat. msg change. Listeners="
+ + providerPresenceStatusListeners.size()
+ + " evt=" + evt);
+
+ while (listeners.hasNext())
+ {
+ ProviderPresenceStatusListener listener =
+ (ProviderPresenceStatusListener) listeners.next();
+ listener.providerStatusMessageChanged(evt);
+ }
+ }
+ }
+
+ /**
+ * Notify all subscription listeners of the corresponding event.
+ *
+ * @param eventID the int ID of the event to dispatch
+ * @param sourceContact the ContactIcqImpl instance that this event is
+ * pertaining to.
+ * @param parentGroup the ContactGroupIcqImpl under which the corresponding
+ * subscription is located.
+ */
+ void fireSubscriptionEvent( int eventID,
+ ContactIcqImpl sourceContact,
+ ContactGroupIcqImpl parentGroup)
+ {
+ SubscriptionEvent evt =
+ new SubscriptionEvent(sourceContact, icqProvider, parentGroup,
+ eventID);
+
+ logger.debug("Dispatching a Subscription Event to"
+ +subscriptionListeners.size() + " listeners. Evt="+evt);
+
+ synchronized(subscriptionListeners){
+ Iterator listeners = subscriptionListeners.iterator();
+
+ while (listeners.hasNext())
+ {
+ SubscriptionListener listener =
+ (SubscriptionListener) listeners.next();
+ if (evt.getEventID() == SubscriptionEvent.SUBSCRIPTION_CREATED)
+ listener.subscriptionCreated(evt);
+ else if (evt.getEventID() == SubscriptionEvent.SUBSCRIPTION_REMOVED)
+ listener.subscriptionRemoved(evt);
+ else if (evt.getEventID() == SubscriptionEvent.SUBSCRIPTION_FAILED)
+ listener.subscriptionFailed(evt);
+
+ }
+ }
+ }
+
+ /**
+ * Notify all contact presence listeners of the corresponding event change
+ * @param contact the contact that changed its status
+ * @param oldStatus the status that the specified contact had so far
+ * @param newStatus the status that the specified contact is currently in.
+ * @param parentGroup the group containing the contact which caused the event
+ */
+ private void fireContactPresenceStatusChangeEvent(
+ Contact contact,
+ ContactGroup parentGroup,
+ PresenceStatus oldStatus,
+ PresenceStatus newStatus)
+ {
+ ContactPresenceStatusChangeEvent evt =
+ new ContactPresenceStatusChangeEvent(
+ contact, icqProvider, parentGroup, oldStatus, newStatus);
+
+ synchronized(contactPresenceStatusListeners){
+ Iterator listeners = contactPresenceStatusListeners.iterator();
+
+ logger.debug("Dispatching Contact Status Change. Listeners="
+ + contactPresenceStatusListeners.size()
+ + " evt=" + evt);
+
+ while (listeners.hasNext())
+ {
+ ContactPresenceStatusListener listener =
+ (ContactPresenceStatusListener) listeners.next();
+ listener.contactPresenceStatusChanged(evt);
+ }
+ }
+ }
+
+ /**
+ * Our listener that will tell us when we're registered to icq and joust
+ * sim is ready to accept us as a listener.
+ */
+ private class RegistrationStateListener
+ implements RegistrationStateChangeListener
+ {
+ /**
+ * The method is called by a ProtocolProvider implementation whenver
+ * a change in the registration state of the corresponding provider had
+ * occurred.
+ * @param evt ProviderStatusChangeEvent the event describing the status
+ * change.
+ */
+ public void registrationStateChanged(RegistrationStateChangeEvent evt)
+ {
+ logger.debug("The ICQ provider changed state from: "
+ + evt.getOldState()
+ + " to: " + evt.getNewState());
+ if(evt.getNewState() == RegistrationState.REGISTERED)
+ {
+ System.out.println("adding a Bos Service Listener");
+ icqProvider.getAimConnection().getBosService()
+ .addMainBosServiceListener(joustSimBosListener);
+
+ ssContactList.init(
+ icqProvider.getAimConnection().getSsiService(),
+ OperationSetPersistentPresenceIcqImpl.this,
+ icqProvider);
+
+// /**@todo implement the following
+ icqProvider.getAimConnection().getBuddyService()
+ .addBuddyListener(joustSimBuddySerListener);
+
+// @todo we really need this for following the status of our
+// contacts and we really need it here ...*/
+ icqProvider.getAimConnection().getBuddyInfoManager()
+ .addGlobalBuddyInfoListener(new GlobalBuddyInfoListener());
+ }
+ }
+
+ }
+
+ /**
+ * The listeners that would monitor the joust sim stack for changes in our
+ * own presence status.
+ */
+ private class JoustSimBosListener implements MainBosServiceListener
+ {
+ /**
+ * Notifications of exta information such as avail message, icon hash
+ * or certificate.
+ * @param extraInfos List
+ */
+ public void handleYourExtraInfo(List extraInfos)
+ {
+ logger.debug("Got extra info: " + extraInfos);
+ // @xxx we should one day probably do something here, like check
+ // whether the status message has been changed for example.
+ for (int i = 0; i < extraInfos.size(); i ++){
+ ExtraInfoBlock block = (ExtraInfoBlock) extraInfos.get(i);
+ if (block.getType() == ExtraInfoBlock.TYPE_AVAILMSG){
+ String statusMessage = ExtraInfoData.readAvailableMessage(
+ block.getExtraData());
+ logger.debug("Received a status message:" + statusMessage);
+
+ if ( getCurrentStatusMessage().equals(statusMessage)){
+ logger.debug("Status message is same as old. Ignoring");
+ return;
+ }
+
+ String oldStatusMessage = getCurrentStatusMessage();
+ currentStatusMessage = statusMessage;
+
+ fireProviderStatusMessageChangeEvent(
+ oldStatusMessage, getCurrentStatusMessage());
+ }
+ }
+ }
+
+ /**
+ * Fires the corresponding presence status chagne event. Note that this
+ * method will be called once per sendSnac packet. When setting a new
+ * status we generally send three packets - 1 for the status and 2 for
+ * the status message. Make sure that only one event goes outside of
+ * this package.
+ *
+ * @param service the source MainBosService instance
+ * @param userInfo our own info
+ */
+ public void handleYourInfo(MainBosService service,
+ FullUserInfo userInfo)
+ {
+ logger.debug("Received our own user info: " + userInfo);
+ logger.debug("previous status was: " + currentIcqStatus);
+
+ //update the last received field.
+ long oldStatus = currentIcqStatus;
+ currentIcqStatus = userInfo.getIcqStatus();
+
+ //it might happen that the info here is -1 (in case we're going back
+ //to online). Yet the fact that we're getting the event means
+ //that we're very much online so make sure we change accordingly
+ if (currentIcqStatus == -1 )
+ currentIcqStatus = ICQ_ONLINE_MASK;
+
+ //only notify of an event change if there was really one.
+ if( oldStatus != userInfo.getIcqStatus() )
+ fireProviderPresenceStatusChangeEvent(oldStatus,
+ currentIcqStatus);
+ }
+ }
+
+ /**
+ * Listens for status updates coming from the joust sim statck and generates
+ * the corresponding sip-communicator events.
+ * @author Emil Ivov
+ */
+ private class JoustSimBuddyServiceListener implements BuddyServiceListener
+ {
+
+ /**
+ * Updates the last received status in the corresponding contact
+ * and fires a contact presence status change event.
+ * @param service the BuddyService that generated the exception
+ * @param buddy the Screenname of the buddy whose status update we're
+ * receiving
+ * @param info the FullUserInfo containing the new status of the
+ * corresponding contact
+ */
+ public void gotBuddyStatus(BuddyService service, Screenname buddy,
+ FullUserInfo info)
+ {
+ logger.debug("Received a status update for buddy=" + buddy);
+ logger.debug("Updated user info is " + info);
+
+ ContactIcqImpl sourceContact
+ = ssContactList.findContactByScreenName(buddy.getFormatted());
+
+ if(sourceContact == null){
+ logger.warn("No source contact found for screenname=" + buddy);
+ return;
+ }
+ PresenceStatus oldStatus
+ = sourceContact.getPresenceStatus();
+ PresenceStatus newStatus
+ = icqStatusLongToPresenceStatus(info.getIcqStatus());
+ sourceContact.updatePresenceStatus(newStatus);
+
+ ContactGroupIcqImpl parent
+ = ssContactList.findContactGroup(sourceContact);
+
+ logger.debug("Will Dispatch the contact status event.");
+ fireContactPresenceStatusChangeEvent(sourceContact, parent,
+ oldStatus, newStatus);
+
+ List extraInfoBlocks = info.getExtraInfoBlocks();
+ if(extraInfoBlocks != null){
+ for (int i = 0; i < extraInfoBlocks.size(); i++)
+ {
+ ExtraInfoBlock block
+ = ( (ExtraInfoBlock) extraInfoBlocks.get(i));
+ if (block.getType() == ExtraInfoBlock.TYPE_AVAILMSG)
+ {
+ String status = ExtraInfoData.readAvailableMessage(
+ block.getExtraData());
+ logger.info("Status Message is: " + status + ".");
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates the last received status in the corresponding contact
+ * and fires a contact presence status change event.
+ *
+ * @param service the BuddyService that generated the exception
+ * @param buddy the Screenname of the buddy whose status update we're
+ * receiving
+ */
+ public void buddyOffline(BuddyService service, Screenname buddy)
+ {
+ logger.debug("Received a status update for buddy=" + buddy);
+
+ ContactIcqImpl sourceContact
+ = ssContactList.findContactByScreenName(buddy.getFormatted());
+
+ if(sourceContact == null)
+ return;
+
+ PresenceStatus oldStatus
+ = sourceContact.getPresenceStatus();
+ PresenceStatus newStatus = IcqStatusEnum.OFFLINE;
+
+ sourceContact.updatePresenceStatus(newStatus);
+
+ ContactGroupIcqImpl parent
+ = ssContactList.findContactGroup(sourceContact);
+
+ fireContactPresenceStatusChangeEvent(sourceContact, parent,
+ oldStatus, newStatus);
+ }
+ }
+
+ /**
+ * Apart from loggin - does nothing so far.
+ */
+ private class GlobalBuddyInfoListener extends GlobalBuddyInfoAdapter{
+ public void receivedStatusUpdate(BuddyInfoManager manager,
+ Screenname buddy, BuddyInfo info)
+ {
+ logger.debug("buddy=" + buddy);
+ logger.debug("info.getAwayMessage()=" + info.getAwayMessage());
+ logger.debug("info.getOnlineSince()=" + info.getOnlineSince());
+ logger.debug("info.getStatusMessage()=" + info.getStatusMessage());
+ }
+
+ }
+}
+
+
diff --git a/src/net/java/sip/communicator/impl/protocol/icq/ProtocolProviderServiceIcqImpl.java b/src/net/java/sip/communicator/impl/protocol/icq/ProtocolProviderServiceIcqImpl.java
new file mode 100644
index 000000000..32c0c2791
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/icq/ProtocolProviderServiceIcqImpl.java
@@ -0,0 +1,429 @@
+/*
+ * 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.icq;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.kano.joustsim.oscar.*;
+import net.kano.joustsim.*;
+import net.kano.joustsim.oscar.oscar.service.icbm.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * An implementation of the protocol provider service over the AIM/ICQ protocol
+ *
+ * @author Emil Ivov
+ */
+public class ProtocolProviderServiceIcqImpl
+ implements ProtocolProviderService
+{
+ private static final Logger logger =
+ Logger.getLogger(ProtocolProviderServiceIcqImpl.class);
+
+ /**
+ * The hashtable with the operation sets that we support locally.
+ */
+ private Hashtable supportedOperationSets = new Hashtable();
+
+ private DefaultAppSession session = null;
+
+ private AimSession aimSession = null;
+
+ private AimConnection aimConnection = null;
+
+ private IcbmService icbmService = null;
+
+ /**
+ * Listener that catches all connection events originating from joscar
+ * during connection to icq.
+ */
+ private AimConnStateListener aimConnStateListener = null;
+
+ /**
+ * Listener that catches all incoming and outgoing chat events generated
+ * by joscar.
+ */
+ private AimIcbmListener aimIcbmListener = null;
+
+ /**
+ * indicates whether or not the provider is initialized and ready for use.
+ */
+ private boolean isInitialized = false;
+ private Object initializationLock = new Object();
+
+ /**
+ * A list of all listeners registered for
+ * RegistrationStateChangeEvents.
+ */
+ private List registrationListeners = new ArrayList();
+
+ /**
+ * Returns the state of the registration of this protocol provider
+ * @return the RegistrationState that this provider is
+ * currently in or null in case it is in a unknown state.
+ */
+ public RegistrationState getRegistrationState()
+ {
+ State connState = getAimConnection().getState();
+
+ return joustSimStateToRegistrationState(connState);
+ }
+
+
+ /**
+ * Converts the specified joust sim connection state to a corresponding
+ * RegistrationState.
+ * @param joustSimConnState the joust sim connection state.
+ * @return a RegistrationState corresponding best to the specified
+ * joustSimState.
+ */
+ private RegistrationState joustSimStateToRegistrationState(
+ State joustSimConnState)
+ {
+ if(joustSimConnState == State.ONLINE)
+ return RegistrationState.REGISTERED;
+ else if (joustSimConnState == State.CONNECTING)
+ return RegistrationState.REGISTERING;
+ else if( joustSimConnState == State.AUTHORIZING)
+ return RegistrationState.AUTHENTICATING;
+ else if (joustSimConnState == State.CONNECTINGAUTH)
+ return RegistrationState.AUTHENTICATING;
+ else if (joustSimConnState == State.SIGNINGON)
+ return RegistrationState.REGISTERING;
+ else if (joustSimConnState == State.DISCONNECTED
+ || joustSimConnState == State.NOTCONNECTED)
+ return RegistrationState.UNREGISTERED;
+ else if (joustSimConnState == State.FAILED)
+ return RegistrationState.CONNECTION_FAILED;
+ else{
+ logger.warn("Unknown state " + joustSimConnState
+ + ". Defaulting to " + RegistrationState.UNREGISTERED);
+ return RegistrationState.UNREGISTERED;
+ }
+ }
+
+ /**
+ * Starts the registration process. Connection details such as
+ * registration server, user name/number are provided through the
+ * configuration service through implementation specific properties.
+ *
+ * @param authority the security authority that will be used for resolving
+ * any security challenges that may be returned during the
+ * registration or at any moment while wer're registered.
+ *
+ */
+ public void register(SecurityAuthority authority)
+ {
+ aimConnection.connect();
+ }
+
+ /**
+ * Ends the registration of this protocol provider with the service.
+ */
+ public void unregister()
+ {
+ aimConnection.disconnect(true);
+ }
+
+ /**
+ * Indicates whether or not this provider is signed on the icq service
+ * @return true if the provider is currently signed on (and hence online)
+ * and false otherwise.
+ */
+ public boolean isRegistered()
+ {
+ return getRegistrationState().equals(RegistrationState.REGISTERED);
+ }
+
+ /**
+ * Returns the short name of the protocol that the implementation of this
+ * provider is based upon (like SIP, Jabber, ICQ/AIM, or others for
+ * example).
+ *
+ * @return a String containing the short name of the protocol this
+ * service is taking care of.
+ */
+ public String getProtocolName()
+ {
+ return ProtocolNames.ICQ;
+ }
+
+ /**
+ * Returns an array containing all operation sets supported by the
+ * current implementation.
+ *
+ * @return an array of OperationSet-s supported by this protocol
+ * provider implementation.
+ */
+ public Map getSupportedOperationSets()
+ {
+ return supportedOperationSets;
+ }
+
+ /**
+ * Initialized the service implementation, and puts it in a sate where it
+ * could interoperate with other services. It is strongly recomended that
+ * properties in this Map be mapped to property names as specified by
+ * AccountProperties.
+ *
+ * @param screenname the account id/uin/screenname of the account that we're
+ * about to create
+ * @param initializationProperties all properties needed fo initializing the
+ * account.
+ *
+ * @see net.java.sip.communicator.service.protocol.AccountProperties
+ */
+ protected void initialize(String screenname, Map initializationProperties)
+ {
+ synchronized(initializationLock)
+ {
+ //extract the necessary properties and validate them
+ String password =
+ (String)initializationProperties.get(AccountProperties.PASSWORD);
+
+ //init the necessary objects
+ session = new DefaultAppSession();
+ aimSession = session.openAimSession(new Screenname(screenname));
+ aimConnection = aimSession.openConnection(
+ new AimConnectionProperties(new Screenname(screenname),
+ password));
+
+ aimConnStateListener = new AimConnStateListener();
+ aimConnection.addStateListener(aimConnStateListener);
+ aimIcbmListener = new AimIcbmListener();
+
+ //initialize all the supported operation sets
+ OperationSetPersistentPresence persistentPresence =
+ new OperationSetPersistentPresenceIcqImpl(this, screenname);
+
+ supportedOperationSets.put(
+ OperationSetPersistentPresence.class.getName(),
+ persistentPresence);
+
+ isInitialized = true;
+
+ }
+ }
+
+ /**
+ * Makes the service implementation close all open sockets and release
+ * any resources that it might have taken and prepare for
+ * shutdown/garbage collection.
+ */
+ public void shutdown()
+ {
+ /** @todo is there anything else to add here? */
+ synchronized(initializationLock){
+ icbmService = null;
+ session = null;
+ aimSession = null;
+ aimConnection = null;
+ aimConnStateListener = null;
+ aimIcbmListener = null;
+ isInitialized = false;
+ }
+ }
+
+ /**
+ * Returns true if the provider service implementation is initialized and
+ * ready for use by other services, and false otherwise.
+ *
+ * @return true if the provider is initialized and ready for use and false
+ * otherwise
+ */
+ public boolean isInitialized()
+ {
+ return isInitialized;
+ }
+
+ /**
+ * Removes the specified registration state change listener so that it does
+ * not receive any further notifications upon changes of the
+ * RegistrationState of this provider.
+ *
+ * @param listener the listener to register for
+ * RegistrationStateChangeEvents.
+ */
+ public void removeRegistrationStateChangeListener(
+ RegistrationStateChangeListener listener)
+ {
+ registrationListeners.remove(listener);
+ }
+
+ /**
+ * Registers the specified listener with this provider so that it would
+ * receive notifications on changes of its state or other properties such
+ * as its local address and display name.
+ *
+ * @param listener the listener to register.
+ */
+ public void addRegistrationStateChangeListener(RegistrationStateChangeListener listener)
+ {
+ registrationListeners.add(listener);
+ }
+
+ /**
+ * Creates a RegistrationStateChange event corresponding to the specified
+ * old and new joust sim states and notifies all currently registered
+ * listeners.
+ *
+ * @param oldJoustSimState the state that the joust sim connection had
+ * before the change occurred
+ * @param newJoustSimState the state that the underlying joust sim
+ * connection is currently in.
+ */
+ private void fireRegistrationStateChanged( State oldJoustSimState,
+ State newJoustSimState)
+ {
+ RegistrationState oldRegistrationState
+ = joustSimStateToRegistrationState(oldJoustSimState);
+ RegistrationState newRegistrationState
+ = joustSimStateToRegistrationState(newJoustSimState);
+
+ RegistrationStateChangeEvent event =
+ new RegistrationStateChangeEvent(
+ this, oldRegistrationState, newRegistrationState);
+
+ logger.debug("Dispatching " + event + " to "
+ + registrationListeners.size()+ " listeners.");
+
+ for (int i = 0; i < registrationListeners.size(); i++)
+ {
+ RegistrationStateChangeListener listener
+ = (RegistrationStateChangeListener)registrationListeners.get(i);
+ listener.registrationStateChanged(event);
+ }
+
+ logger.trace("Done.");
+ }
+
+ /**
+ * This class handles connection state events that have originated in this
+ * provider's aim connection. Events are acted upon accordingly and,
+ * if necessary, forwarded to registered listeners (asynchronously).
+ */
+ private class AimConnStateListener implements StateListener
+ {
+ public void handleStateChange(StateEvent event)
+ {
+ State newState = event.getNewState();
+ State oldState = event.getOldState();
+
+ AimConnection conn = event.getAimConnection();
+ logger.debug("ICQ protocol provider " + getProtocolName()
+ + "changed registration status from "
+ + oldState + " to " + newState);
+
+ if (newState == State.ONLINE)
+ {
+ icbmService = conn.getIcbmService();
+ icbmService.addIcbmListener(aimIcbmListener);
+ }
+ else if (newState == State.DISCONNECTED
+ || newState == State.FAILED)
+ {
+ logger.debug("The aim Connection was disconnected!");
+ }
+
+ //now tell all interested parties about what happened.
+ fireRegistrationStateChanged(oldState, newState);
+
+ }
+ }
+
+ /**
+ * Returns the AimSession opened by this provider.
+ * @return a reference to the AimSession that this provider
+ * last opened.
+ */
+ protected AimSession getAimSession()
+ {
+ return aimSession;
+ }
+
+ /**
+ * Returns the AimConnectionopened by this provider
+ * @return a reference to the AimConnection last opened by this provider.
+ */
+ protected AimConnection getAimConnection()
+ {
+ return aimConnection;
+ }
+
+ public class AimIcbmListener implements IcbmListener
+ {
+
+ public void newConversation(IcbmService service, Conversation conv)
+ {
+ System.out.println("Received a new conversation event");
+ conv.addConversationListener(new AimConversationListener());
+ }
+
+ public void buddyInfoUpdated(IcbmService service, Screenname buddy,
+ IcbmBuddyInfo info)
+ {
+ System.out.println("Got a BuddINFO event");
+ }
+ }
+
+ public class AimConversationListener implements ConversationListener{
+ public void sentOtherEvent(Conversation conversation,
+ ConversationEventInfo event)
+ {
+ System.out.println("reveived ConversationEventInfo:" + event);
+ }
+
+ // This may be called without ever calling conversationOpened
+ public void conversationClosed(Conversation c)
+ {
+ System.out.println("conversation closed");
+ }
+
+ public void gotOtherEvent(Conversation conversation,
+ ConversationEventInfo event)
+ {
+ System.out.println("goet other event");
+ if(event instanceof TypingInfo)
+ {
+ TypingInfo ti = (TypingInfo)event;
+ System.out.println("got typing info and state is: " + ti.getTypingState());
+ }
+ else if (event instanceof MessageInfo)
+ {
+ MessageInfo ti = (MessageInfo)event;
+ System.out.println("got message info for msg: " + ti.getMessage());
+ }
+ }
+
+ public void canSendMessageChanged(Conversation c, boolean canSend)
+ {
+ System.out.println("can send message event");
+ }
+
+ // This may never be called
+ public void conversationOpened(Conversation c)
+ {
+ System.out.println("conversation opened event");
+ }
+
+ // This may be called after conversationClosed is called
+ public void sentMessage(Conversation c, MessageInfo minfo)
+ {
+ System.out.println("sent message event");
+ }
+
+ // This may be called after conversationClosed is called.
+ public void gotMessage(Conversation c, MessageInfo minfo)
+ {
+ System.out.println("got message event" + minfo.getMessage().getMessageBody());
+ }
+
+ }
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/icq/RootContactGroupIcqImpl.java b/src/net/java/sip/communicator/impl/protocol/icq/RootContactGroupIcqImpl.java
new file mode 100644
index 000000000..42641d583
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/icq/RootContactGroupIcqImpl.java
@@ -0,0 +1,222 @@
+package net.java.sip.communicator.impl.protocol.icq;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ * A dummy ContactGroup implementation representing the ContactList root for
+ * ICQ contact lists.
+ * @author Emil Ivov
+ */
+public class RootContactGroupIcqImpl
+ extends AbstractContactGroupIcqImpl
+{
+ private String ROOT_CONTACT_GROUP_NAME = "ContactListRoot";
+ private List subGroups = new LinkedList();
+
+ /**
+ * An empty list that we use when returning an iterator.
+ */
+ private List dummyContacts = new LinkedList();
+
+
+ /**
+ * The ContactListRoot in ICQ is the only group that can contain subgroups.
+ *
+ * @return true (always)
+ */
+ public boolean canContainSubgroups()
+ {
+ return true;
+ }
+
+ /**
+ * Returns the name of this group which is always
+ * ROOT_CONTACT_GROUP_NAME.
+ *
+ * @return a String containing the name of this group.
+ */
+ public String getGroupName()
+ {
+ return ROOT_CONTACT_GROUP_NAME;
+ }
+
+ /**
+ * Adds the specified group at the specified position in the list of sub
+ * groups.
+ *
+ * @param index the position at which the specified group should be added.
+ * @param group the ContactGroup to add
+ */
+ void addSubGroup(int index, ContactGroupIcqImpl group)
+ {
+ subGroups.add(index, group);
+ }
+
+ /**
+ * Adds the specified group to the end of the list of sub groups.
+ * @param group the group to add.
+ */
+ void addSubGroup(ContactGroupIcqImpl group)
+ {
+ addSubGroup(countContacts(), group);
+ }
+
+ /**
+ * Removes the specified from the list of sub groups
+ * @param group the group to remove.
+ */
+ void removeSubGroup(ContactGroupIcqImpl group)
+ {
+ removeSubGroup(subGroups.indexOf(group));
+ }
+
+ /**
+ * Removes the sub group with the specified index.
+ * @param index the index of the group to remove
+ */
+ void removeSubGroup(int index)
+ {
+ subGroups.remove(index);
+ }
+
+ /**
+ * Removes all contact sub groups and reinsterts them as specified
+ * by the newOrder param. Contact groups not contained in the
+ * newOrder list are left at the end of this group.
+ *
+ * @param newOrder a list containing all contact groups in the order that is
+ * to be applied.
+ *
+ */
+ void reorderSubGroups(List newOrder)
+ {
+ subGroups.removeAll(newOrder);
+ subGroups.addAll(0, newOrder);
+ }
+
+ /**
+ * Returns the number of subgroups contained by this
+ * RootContactGroupIcqImpl.
+ *
+ * @return an int indicating the number of subgroups that this
+ * ContactGroup contains.
+ */
+ public int countSubGroups()
+ {
+ return subGroups.size();
+ }
+
+ /**
+ * Returns the subgroup with the specified index.
+ *
+ * @param index the index of the ContactGroup to retrieve.
+ * @return the ContactGroup with the specified index.
+ */
+ public ContactGroup getGroup(int index)
+ {
+ return (ContactGroupIcqImpl)subGroups.get(index);
+ }
+
+ /**
+ * 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)
+ {
+ Iterator subgroups = subGroups();
+ while (subgroups.hasNext())
+ {
+ ContactGroupIcqImpl grp = (ContactGroupIcqImpl)subgroups.next();
+
+ if (grp.getGroupName().equals(groupName))
+ return grp;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the Contact with the specified address or
+ * identifier.
+ * @param id the addres or identifier of the Contact we are
+ * looking for.
+ * @return the Contact with the specified id or address.
+ */
+ public Contact getContact(String id)
+ {
+ //no contacts in the root group for this icq impl.
+ return null;
+ }
+
+ /**
+ * Returns an iterator over the sub groups that this
+ * ContactGroup contains.
+ *
+ * @return a java.util.Iterator over the ContactGroup
+ * children of this group (i.e. subgroups).
+ */
+ public Iterator subGroups()
+ {
+ return subGroups.iterator();
+ }
+
+ /**
+ * Returns the number, which is always 0, of Contact members
+ * of this ContactGroup
+ * @return an int indicating the number of Contacts, members
+ * of this ContactGroup.
+ */
+ public int countContacts()
+ {
+ return 0;
+ }
+
+ /**
+ * Returns an Iterator over all contacts, member of this
+ * ContactGroup.
+ * @return a java.util.Iterator over all contacts inside this
+ * ContactGroup
+ */
+ public Iterator contacts()
+ {
+ return dummyContacts.iterator();
+ }
+
+ /**
+ * A dummy impl of the corresponding interface method - always returns null.
+ *
+ * @param index the index of the Contact to return.
+ * @return the Contact with the specified index, i.e. always
+ * null.
+ */
+ public Contact getContact(int index)
+ {
+ return null;
+ }
+
+ /**
+ * Returns a string representation of the root contact group that contains
+ * all subgroups and subcontacts of this group.
+ *
+ * @return a string representation of this root contact group.
+ */
+ public String toString()
+ {
+ StringBuffer buff = new StringBuffer(getGroupName());
+ buff.append(".subGroups="+countSubGroups()+":\n");
+
+ Iterator subGroups = subGroups();
+ while (subGroups.hasNext())
+ {
+ ContactGroup group = (ContactGroup) subGroups.next();
+ buff.append(group.toString());
+ if(subGroups.hasNext())
+ buff.append("\n");
+ }
+ return buff.toString();
+ }
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/icq/ServerStoredContactListIcqImpl.java b/src/net/java/sip/communicator/impl/protocol/icq/ServerStoredContactListIcqImpl.java
new file mode 100644
index 000000000..4d175f491
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/icq/ServerStoredContactListIcqImpl.java
@@ -0,0 +1,884 @@
+
+/*
+ * 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.icq;
+
+import net.java.sip.communicator.service.protocol.*;
+import net.kano.joustsim.oscar.oscar.service.ssi.*;
+import java.util.*;
+import net.java.sip.communicator.util.*;
+import net.kano.joustsim.Screenname;
+import net.kano.joscar.snaccmd.ssi.SsiItem;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.kano.joscar.ssiitem.*;
+import net.kano.joustsim.oscar.*;
+
+/**
+ * This class encapsulates the net.kano BuddyList class. Once created, it will
+ * register itself as a listener to the encapsulated BuddyList and modify it's
+ * local copy of Contacts and ContactGroups every time an event is generated
+ * by the underlying joustsim framework. The class would also generate
+ * corresponding sip-communicator events to all events coming from joustsim.
+ *
+ * @author Emil Ivov
+ */
+public class ServerStoredContactListIcqImpl
+ implements BuddyInfoTrackerListener
+{
+ private static final Logger logger =
+ Logger.getLogger(ServerStoredContactListIcqImpl.class);
+ /**
+ * The joustsim buddy list that we encapsulate
+ */
+ private MutableBuddyList buddyList = null;
+
+ /**
+ * Our joustsim buddy list event listener
+ */
+ private BuddyListListener buddyListListener = new BuddyListListener();
+
+ /**
+ * Our joustsim group change listener.
+ */
+ private GroupChangeListener jsimGroupChangeListener
+ = new GroupChangeListener();
+
+ /**
+ * A joust sim item change listener.
+ */
+ private JoustSimItemChangeListener jsimItemChangeListener
+ = new JoustSimItemChangeListener();
+
+ /**
+ * Our joustsim buddy change listener.
+ */
+ private JoustSimBuddyListener jsimBuddyListener
+ = new JoustSimBuddyListener();
+
+ /**
+ * The root contagroup. The container for all ICQ buddies and groups.
+ */
+ private RootContactGroupIcqImpl rootGroup
+ = new RootContactGroupIcqImpl();
+
+ /**
+ * Listeners that others registered with us for contact list events.
+ */
+ private Vector contactListListeners = new Vector();
+
+ /**
+ * The joust sim service that deals with server stored information.
+ */
+ private SsiService jSimSsiService = null;
+
+ /**
+ * The operation set that created us and that we could use when dispatching
+ * subscription events.
+ */
+ private OperationSetPersistentPresenceIcqImpl parentOperationSet = null;
+
+ /**
+ * The icqProvider that is on top of us.
+ */
+ private ProtocolProviderServiceIcqImpl icqProvider = null;
+
+ /**
+ * Listeners that would receive event notifications for changes in group
+ * names or other properties, removal or creation of groups.
+ */
+ private Vector serverStoredGroupListeners = new Vector();
+
+ /**
+ * Creates a ServerStoredContactList wrapper for the specified BuddyList.
+ */
+ ServerStoredContactListIcqImpl()
+ {
+ //don't add the sub ICQ groups to rootGroup here as we'll be having
+ //event notifications for every one of them through the
+ //RetroactiveBuddyListListener
+ }
+
+ /**
+ * Returns the root group of the contact list.
+ *
+ * @return the root ContactGroup for the ContactList
+ */
+ public ContactGroup getRootGroup()
+ {
+ return rootGroup;
+ }
+
+ /**
+ * Registers the specified group listener so that it would receive events
+ * on group modification/creation/destruction.
+ * @param l the ServerStoredGroupListener to register for group events
+ */
+ void addGroupListener(ServerStoredGroupListener l)
+ {
+ synchronized(serverStoredGroupListeners){
+ this.serverStoredGroupListeners.add(l);
+ }
+ }
+
+ /**
+ * Removes the specified group listener so that it won't receive further
+ * events on group modification/creation/destruction.
+ * @param l the ServerStoredGroupListener to unregister
+ */
+ void removeGroupListener(ServerStoredGroupListener l)
+ {
+ synchronized(serverStoredGroupListeners){
+ this.serverStoredGroupListeners.remove(l);
+ }
+ }
+
+ /**
+ * Creates the corresponding event and notifies all
+ * ServerStoredGroupListeners that the source group has been
+ * removed, changed, renamed or whatever happened to it.
+ * @param group the ContactGroup that has been created/modified/removed
+ * @param eventID the id of the event to generate.
+ */
+ private void fireGroupEvent(ContactGroupIcqImpl group, int eventID)
+ {
+ ServerStoredGroupEvent evt = new ServerStoredGroupEvent(group, eventID,
+ icqProvider, parentOperationSet);
+
+ logger.trace("Will dispatch the following grp event: " + evt);
+
+ synchronized (serverStoredGroupListeners){
+ Iterator listeners = this.serverStoredGroupListeners.iterator();
+
+ while (listeners.hasNext())
+ {
+ ServerStoredGroupListener l
+ = (ServerStoredGroupListener) listeners.next();
+ if (eventID == ServerStoredGroupEvent.GROUP_REMOVED_EVENT)
+ l.groupRemoved(evt);
+ else if (eventID == ServerStoredGroupEvent.GROUP_RENAMED_EVENT)
+ l.groupNameChanged(evt);
+ else if (eventID == ServerStoredGroupEvent.GROUP_CREATED_EVENT)
+ l.groupCreated(evt);
+ }
+ }
+ }
+
+ private void fireGroupsReordered()
+ {
+ /** @todo implement fireGroupsReordered *///no need of args since it
+ //could only mean one thing
+ }
+
+ /**
+ * Make the parent persistent presence operation set dispatch a contact
+ * added event.
+ * @param parentGroup the group where the new contact was added
+ * @param contact the contact that was added
+ * @param index the index at which it was added.
+ */
+ private void fireContactAdded( ContactGroupIcqImpl parentGroup,
+ ContactIcqImpl contact,
+ int index)
+ {
+ //bail out if no one's listening
+ if(parentOperationSet == null){
+ logger.debug("No presence op. set available. Bailing out.");
+ return;
+ }
+
+ //dispatch
+ parentOperationSet.fireSubscriptionEvent(
+ SubscriptionEvent.SUBSCRIPTION_CREATED, contact, parentGroup);
+ }
+
+ /**
+ * Make the parent persistent presence operation set dispatch a contact
+ * removed event.
+ * @param parentGroup the group where that the removed contact belonged to.
+ * @param contact the contact that was removed.
+ */
+ private void fireContactRemoved( ContactGroupIcqImpl parentGroup,
+ ContactIcqImpl contact)
+ {
+ //bail out if no one's listening
+ if(parentOperationSet == null){
+ logger.debug("No presence op. set available. Bailing out.");
+ return;
+ }
+
+ //dispatch
+ parentOperationSet.fireSubscriptionEvent(
+ SubscriptionEvent.SUBSCRIPTION_CREATED, contact, parentGroup);
+ }
+
+ private void fireContactsReordered( ContactGroupIcqImpl parentGroup)
+ {
+ /** @todo implement fireContactsReordered() */
+ }
+
+ /**
+ * Returns the index of the ContactGroup containing the specified joust sim
+ * group.
+ * @param joustSimGroup the joust sim group we're looking for.
+ * @return the index of the ContactGroup containing the specified
+ * joustSimGroup or -1 if no containing ContactGroup exists.
+ */
+ public int findContactGroupIndex(Group joustSimGroup)
+ {
+ Iterator contactGroups = rootGroup.subGroups();
+ int index = 0;
+
+ for (; contactGroups.hasNext(); index++)
+ {
+ ContactGroupIcqImpl contactGroup
+ = (ContactGroupIcqImpl) contactGroups.next();
+
+ if (joustSimGroup == contactGroup.getJoustSimSourceGroup())
+ return index;
+
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the ContactGroup corresponding to the specified joust sim group.
+ * @param joustSimGroup the joust sim group we're looking for.
+ * @return the ContactGroup corresponding to the specified joustSimGroup
+ * null if no containing ContactGroup exists.
+ */
+ public ContactGroupIcqImpl findContactGroup(Group joustSimGroup)
+ {
+ Iterator contactGroups = rootGroup.subGroups();
+
+ while(contactGroups.hasNext())
+ {
+ ContactGroupIcqImpl contactGroup
+ = (ContactGroupIcqImpl) contactGroups.next();
+
+ if (joustSimGroup == contactGroup.getJoustSimSourceGroup())
+ return contactGroup;
+
+ }
+ return null;
+ }
+
+ /**
+ * Returns the Contact with the specified screenname (or icq UIN) or null if
+ * no such screenname was found.
+ *
+ * @param screenName the screen name (or ICQ UIN) of the contact to find.
+ * @return the Contact carrying the specified
+ * screenName or null if no such contact exits.
+ */
+ public ContactIcqImpl findContactByScreenName(String screenName)
+ {
+ Iterator contactGroups = rootGroup.subGroups();
+ ContactIcqImpl result = null;
+
+ while(contactGroups.hasNext())
+ {
+ ContactGroupIcqImpl contactGroup
+ = (ContactGroupIcqImpl) contactGroups.next();
+
+ result = contactGroup.findContact(screenName);
+
+ if (result != null)
+ return result;
+
+ }
+ return null;
+ }
+
+
+ /**
+ * Returns the ContactGroup containing the specified contact or null
+ * if no such group or contact exist.
+ *
+ * @param child the contact whose parent group we're looking for.
+ * @return the ContactGroup containing the specified
+ * contact or null if no such groupo or contact
+ * exist.
+ */
+ public ContactGroupIcqImpl findContactGroup(ContactIcqImpl child)
+ {
+ Iterator contactGroups = rootGroup.subGroups();
+
+ while(contactGroups.hasNext())
+ {
+ ContactGroupIcqImpl contactGroup
+ = (ContactGroupIcqImpl) contactGroups.next();
+
+ if( contactGroup.findContact(child.getJoustSimBuddy())!= null)
+ return contactGroup;
+ }
+ return null;
+ }
+
+ /**
+ * Adds a new contact with the specified screenname to the list under a
+ * default location.
+ * @param screenname the screenname or icq uin of the contact to add.
+ */
+ public void addContact(String screenname)
+ {
+ ContactGroupIcqImpl parent =
+ (ContactGroupIcqImpl)getRootGroup().getGroup(0);
+
+ addContact(parent, screenname);
+ }
+
+ /**
+ * Adds a new contact with the specified screenname to the list under the
+ * specified group.
+ * @param screenname the screenname or icq uin of the contact to add.
+ * @param parent the group under which we want the new contact placed.
+ */
+ public void addContact(ContactGroupIcqImpl parent, String screenname)
+ {
+ logger.trace("Addint contact " + screenname
+ + " to parent=" + parent.getGroupName());
+
+ //if the contact is already in the contact list, only broadcast an event
+ final ContactIcqImpl existingContact
+ = findContactByScreenName(screenname);
+
+ //if the contact already exists - just issue an event.
+ if( existingContact != null)
+ {
+ logger.debug("Contact " + screenname + " already exists. Gen. evt.");
+ //broadcast the event in a separate thread so that we don't
+ //block the calling thread.
+ new Thread(){
+ public void run(){
+ parentOperationSet.fireSubscriptionEvent(
+ SubscriptionEvent.SUBSCRIPTION_CREATED,
+ existingContact,
+ findContactGroup(existingContact));
+ }
+ }.start();
+ return;
+ }
+
+ logger.trace("Adding the contact to the specified group.");
+ //extract the top level group
+ AddMutableGroup group = parent.getJoustSimSourceGroup();
+
+ group.addBuddy(screenname);
+ }
+
+ /**
+ * Creates the specified group on the server stored contact list.
+ * @param groupName a String containing the name of the new group.
+ */
+ public void createGroup(String groupName)
+ {
+ logger.trace("Creating group: " + groupName);
+ buddyList.addGroup(groupName);
+ logger.trace("Group " +groupName+ " created.");
+ }
+
+ /**
+ * Removes the specified group from the icq buddy list.
+ * @param groupToRemove the group that we'd like removed.
+ */
+ public void removeGroup(ContactGroupIcqImpl groupToRemove)
+ {
+ buddyList.deleteGroupAndBuddies(
+ groupToRemove.getJoustSimSourceGroup());
+ }
+
+ /**
+ * Renames the specified group according to the specified new name..
+ * @param groupToRename the group that we'd like removed.
+ * @param newName the new name of the group
+ */
+ public void renameGroup(ContactGroupIcqImpl groupToRename, String newName)
+ {
+ groupToRename.getJoustSimSourceGroup().rename(newName);
+ }
+
+
+
+ /**
+ * Moves the specified contact to the group indicated by
+ * newParent.
+ * @param contact the contact that we'd like moved under the new group.
+ * @param newParent the group where we'd like the parent placed.
+ */
+ public void moveContact(ContactIcqImpl contact,
+ ContactGroupIcqImpl newParent)
+ {
+ List contactsToMove = new ArrayList();
+ contactsToMove.add(contact);
+
+ buddyList.moveBuddies(contactsToMove,
+ newParent.getJoustSimSourceGroup());
+ }
+
+ /**
+ * Sets a reference to the currently active and valid instance of
+ * the JoustSIM SsiService that this list is to use for retrieving
+ * server stored information
+ * @param joustSimSsiService a valid reference to the currently active JoustSIM
+ * SsiService.
+ * @param parentOperationSet the operation set that created us and that
+ * we could use for dispatching subscription events
+ * @param icqProvider the icqProvider that has instantiated us.
+ */
+ void init( SsiService joustSimSsiService,
+ OperationSetPersistentPresenceIcqImpl parentOperationSet,
+ ProtocolProviderServiceIcqImpl icqProvider)
+ {
+ this.jSimSsiService = joustSimSsiService;
+ jSimSsiService.addItemChangeListener(jsimItemChangeListener);
+
+ this.buddyList = jSimSsiService.getBuddyList();
+ buddyList.addRetroactiveLayoutListener(buddyListListener);
+
+ this.parentOperationSet = parentOperationSet;
+
+ this.icqProvider = icqProvider;
+
+ }
+
+ private class BuddyListListener
+ implements BuddyListLayoutListener
+ {
+ /**
+ * Called by joustsim as a notification of the fact that the server has
+ * sent the specified group and that it is actually a member from
+ * our contact list. We copy the group locally and generate the
+ * corresponding sip-communicator events
+ *
+ * @param list the BuddyList where this is happening.
+ * @param oldItems we don't use it
+ * @param newItems we don't use it
+ * @param group the new Group that has been added
+ * @param buddies the members of the new group.
+ */
+ public void groupAdded(BuddyList list, List oldItems, List newItems,
+ Group group, List buddies)
+ {
+ logger.trace("Group added: " + group.getName());
+ logger.trace("Buddies: " + buddies);
+ ContactGroupIcqImpl newGroup
+ = new ContactGroupIcqImpl((MutableGroup)group, buddies);
+
+ //add a joust sim buddy listener to all of the buddies in this group
+ for(int i = 0; i < buddies.size(); i++)
+ ((Buddy)buddies.get(i)).addBuddyListener(jsimBuddyListener);
+
+
+ //elements in the newItems list may include groups that have not
+ //yet been reported through this method. In order to make sure that
+ //we keep the order specified by the server, we try to add after a
+ //newItems member that has a corresponding ContactGroup entry in our
+ //contact list, and add the new entry after it
+ int groupIndex = newItems.indexOf(group);
+
+ assert groupIndex != -1:group + " was not present in newItems"
+ + newItems;
+ int insertPos = 0;
+ if (groupIndex == 0)
+ {
+ //this is the first group so insert at 0.
+ rootGroup.addSubGroup(insertPos, newGroup);
+ }
+ else
+ {
+ for (; groupIndex >= 0; groupIndex--)
+ {
+ int prevContactGroupIndex
+ = findContactGroupIndex( (Group) newItems.get(groupIndex));
+
+ //if we've found the nearest previous group that we already
+ //know of we should insert the new group behind it.
+ if (prevContactGroupIndex != -1)
+ insertPos = prevContactGroupIndex + 1;
+ }
+ rootGroup.addSubGroup(insertPos, newGroup);
+ }
+
+ //register a listener for name changes of this group
+ group.addGroupListener(jsimGroupChangeListener);
+
+ //tell listeners about the added group
+ fireGroupEvent(newGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT);
+ }
+
+ /**
+ * Called by joust sim when a group is removed.
+ *
+ * @param list the BuddyList owning the removed group.
+ * @param oldItems the list of items as it was before removing the group.
+ * @param newItems the list of items as it is after the group is removed.
+ * @param group the group that was removed.
+ */
+ public void groupRemoved(BuddyList list, List oldItems, List newItems,
+ Group group)
+ {
+ logger.trace("Group Removed: " + group.getName());
+ int index = findContactGroupIndex(group);
+ ContactGroupIcqImpl removedGroup
+ = (ContactGroupIcqImpl) rootGroup.getGroup(index);
+
+ if (index == -1)
+ {
+ logger.debug("non existing group: " + group.getName());
+ return;
+ }
+
+ group.removeGroupListener(jsimGroupChangeListener);
+
+ rootGroup.removeSubGroup(index);
+
+ fireGroupEvent(removedGroup,
+ ServerStoredGroupEvent.GROUP_REMOVED_EVENT);
+ }
+
+ /**
+ * Called by joust sim to notify us that a new buddy has been added
+ * to the contact list.
+ *
+ * @param list the BuddyList owning the newly added buddy.
+ * @param joustSimGroup the parent group of the added buddy.
+ * @param oldItems unused
+ * @param newItems unused
+ * @param buddy the newly added buddy
+ */
+ public void buddyAdded(BuddyList list, Group joustSimGroup, List oldItems,
+ List newItems, Buddy buddy)
+ {
+ ContactIcqImpl newContact
+ = new ContactIcqImpl(buddy);
+ ContactGroupIcqImpl parentGroup = findContactGroup(joustSimGroup);
+
+ if (parentGroup == null)
+ {
+ logger.debug("no parent group "
+ + joustSimGroup + " found for buddy: " + buddy);
+ return;
+ }
+
+ int buddyIndex = newItems.indexOf(buddy);
+ if( buddyIndex == -1 ){
+ logger.debug(buddy+" was not present in newItems"+newItems);
+ }
+
+ //elements in the newItems list may include buddies that have not
+ //yet been reported through this method. In order to make sure that
+ //we keep the order specified by the server, we try to add after a
+ //newItems member that has a corresponding ContactGroup entry in our
+ //contact list, and add the new entry after it
+
+ int insertPos = 0;
+ if (buddyIndex == 0)
+ {
+ //this is the first group so insert at 0.
+ parentGroup.addContact(insertPos, newContact);
+ }
+ else
+ {
+ for (; buddyIndex >= 0; buddyIndex--)
+ {
+
+ int prevContactIndex = parentGroup.findContactIndex(
+ (Buddy) newItems.get(buddyIndex));
+
+ //if we've found the nearest previous group that we already
+ //know of we should insert the new group behind it.
+ if (prevContactIndex != -1)
+ insertPos = prevContactIndex + 1;
+ }
+ parentGroup.addContact(insertPos, newContact);
+ }
+
+ //register a listener for name changes of this buddy
+ buddy.addBuddyListener(jsimBuddyListener);
+
+ //tell listeners about the added group
+ fireContactAdded(parentGroup, newContact, insertPos);
+ }
+
+ /**
+
+b * Called by joust sim when a buddy is removed
+ *
+ * @param list the BuddyList containing the buddy
+ * @param group the joust sim group that the buddy is removed from.
+ * @param oldItems unused
+ * @param newItems unused
+ * @param buddy Buddy
+ */
+ public void buddyRemoved(BuddyList list, Group group, List oldItems,
+ List newItems, Buddy buddy)
+ {
+ ContactGroupIcqImpl parentGroup = findContactGroup(group);
+ ContactIcqImpl contactToRemove = parentGroup.findContact(buddy);
+
+ parentGroup.removeContact(contactToRemove);
+
+ buddy.removeBuddyListener(jsimBuddyListener);
+
+ fireContactRemoved(parentGroup, contactToRemove);
+ }
+
+ /**
+ * Called by joust sim when contacts in a group have been reordered.
+ * Removes all Contacts from the concerned group and reinserts them
+ * in the right order.
+ *
+ * @param list the BuddyList where all this happens
+ * @param group the group whose buddies have been reordered.
+ * @param oldBuddies unused
+ * @param newBuddies the list containing the buddies in their new order.
+ */
+ public void buddiesReordered(BuddyList list, Group group,
+ List oldBuddies, List newBuddies)
+ {
+ ContactGroupIcqImpl contactGroup = findContactGroup(group);
+
+ if (contactGroup == null)
+ {
+ logger.debug(
+ "buddies reordered event received for unknown group"
+ + group);
+ }
+
+ List reorderedContacts = new ArrayList();
+ Iterator newBuddiesIter = newBuddies.iterator();
+ while (newBuddiesIter.hasNext())
+ {
+ Buddy buddy = (Buddy) newBuddiesIter.next();
+ ContactIcqImpl contact = contactGroup.findContact(buddy);
+
+ //make sure that this was not an empty buddy.
+ if (contact == null)
+ continue;
+ reorderedContacts.add(contact);
+ }
+
+ contactGroup.reorderContacts(reorderedContacts);
+
+ fireContactsReordered(contactGroup);
+ }
+
+ /**
+ * Called by joust sim to indicate that the server stored groups
+ * have been reordered. We filter this list for contact groups that
+ * we've already heard of and pass it to the root contact group
+ * so that it woul reorder its subgroups.
+ *
+ * @param list the BuddyList where all this is happening
+ * @param oldOrder unused
+ * @param newOrder the order in which groups are now stored by the
+ * AIM/ICQ server.
+ */
+ public void groupsReordered(BuddyList list, List oldOrder,
+ List newOrder)
+ {
+ List reorderedGroups = new ArrayList();
+ Iterator newOrderIter = newOrder.iterator();
+ while (newOrderIter.hasNext())
+ {
+ Group group = (Group) newOrderIter.next();
+ ContactGroupIcqImpl contactGroup = findContactGroup(group);
+
+ //make sure that this was not an empty buddy.
+ if (contactGroup == null)
+ continue;
+ reorderedGroups.add(contactGroup);
+ }
+
+ rootGroup.reorderSubGroups(reorderedGroups);
+
+ fireGroupsReordered();
+ }
+ }
+
+ /**
+ * Proxies events notifying of a change in the group name.
+ */
+ private class GroupChangeListener
+ implements GroupListener
+ {
+ /**
+ * Verifies whether the concerned group really exists and fires
+ * a corresponding event
+ * @param group the group that changed name.
+ * @param oldName the name, before it changed
+ * @param newName the current name of the group.
+ */
+ public void groupNameChanged(Group group, String oldName,
+ String newName)
+ {
+ logger.trace("Group name for "+group.getName()+"changed from="
+ + oldName + " to=" + newName);
+ ContactGroupIcqImpl contactGroup = findContactGroup(group);
+
+ if (contactGroup == null)
+ {
+ logger.debug(
+ "group name changed event received for unknown group"
+ + group);
+ }
+
+ //check whether the name has really changed (the joust sim stack
+ //would call this method even when the name has not really changed
+ //and values of oldName and newName would almost always be null)
+ if (contactGroup.getGroupName()
+ .equals( contactGroup.getNameCopy() )){
+ logger.trace("Group name hasn't really changed("
+ +contactGroup.getGroupName()+"). Ignoring");
+ return;
+ }
+
+ //we do have a new name. store a copy of it for our next deteciton
+ //and fire the corresponding event.
+ logger.trace("Dispatching group change event.");
+ contactGroup.initNameCopy();
+
+ fireGroupEvent(contactGroup,
+ ServerStoredGroupEvent.GROUP_RENAMED_EVENT);
+ }
+
+ }
+
+ private class JoustSimBuddyListener implements BuddyListener
+ {
+ /**
+ * screennameChanged
+ *
+ * @param buddy Buddy
+ * @param oldScreenname Screenname
+ * @param newScreenname Screenname
+ */
+ public void screennameChanged(Buddy buddy, Screenname oldScreenname,
+ Screenname newScreenname)
+ {
+ /** @todo implement screennameChanged() */
+ logger.debug("/** @todo implement screennameChanged() */=");
+ logger.debug("buddy="+buddy);
+ System.out.println("oldScreenname=" + oldScreenname);
+ System.out.println("newScreenname=" + newScreenname);
+ }
+
+ /**
+ * alertActionChanged
+ *
+ * @param buddy Buddy
+ * @param oldAlertAction int
+ * @param newAlertAction int
+ */
+ public void alertActionChanged(Buddy buddy, int oldAlertAction,
+ int newAlertAction)
+ {
+ /** @todo implement alertActionChanged() */
+ logger.debug("/** @todo implement alertActionChanged() */=");
+ System.out.println("buddy=" + buddy);
+ System.out.println("oldAlertAction=" + oldAlertAction);
+ System.out.println("newAlertAction=" + newAlertAction);
+ }
+
+ /**
+ * alertSoundChanged
+ *
+ * @param buddy Buddy
+ * @param oldAlertSound String
+ * @param newAlertSound String
+ */
+ public void alertSoundChanged(Buddy buddy, String oldAlertSound,
+ String newAlertSound)
+ {
+ /** @todo implement alertSoundChanged() */
+ logger.debug("/** @todo implement alertSoundChanged() */");
+ System.out.println("buddy=" + buddy);
+ System.out.println("oldAlertSound=" + oldAlertSound);
+ System.out.println("newAlertSound=" + newAlertSound);
+ }
+
+ /**
+ * alertTimeChanged
+ *
+ * @param buddy Buddy
+ * @param oldAlertEvent int
+ * @param newAlertEvent int
+ */
+ public void alertTimeChanged(Buddy buddy, int oldAlertEvent,
+ int newAlertEvent)
+ {
+ /** @todo implement alertTimeChanged() */
+ logger.debug("/** @todo implement alertTimeChanged() */");
+ System.out.println("buddy=" + buddy);
+ System.out.println("oldAlertEvent=" + oldAlertEvent);
+ System.out.println("newAlertEvent=" + newAlertEvent);
+ }
+
+ /**
+ * aliasChanged
+ *
+ * @param buddy Buddy
+ * @param oldAlias String
+ * @param newAlias String
+ */
+ public void aliasChanged(Buddy buddy, String oldAlias, String newAlias)
+ {
+ /** @todo implement aliasChanged() */
+ logger.debug("/** @todo implement aliasChanged() */");
+ System.out.println("buddy=" + buddy);
+ System.out.println("oldAlias=" + oldAlias);
+ System.out.println("newAlias=" + newAlias);
+ }
+
+ /**
+ * buddyCommentChanged
+ *
+ * @param buddy Buddy
+ * @param oldComment String
+ * @param newComment String
+ */
+ public void buddyCommentChanged(Buddy buddy, String oldComment,
+ String newComment)
+ {
+ /** @todo implement buddyCommentChanged() */
+ logger.debug("/** @todo implement buddyCommentChanged() */");
+ System.out.println("buddy=" + buddy);
+ System.out.println("oldComment=" + oldComment);
+ System.out.println("newComment=" + newComment);
+ }
+
+ }
+
+ /**
+ * A dummy implementation of the JoustSIM SsiItemChangeListener.
+ *
+ * @author Emil Ivov
+ */
+ private class JoustSimItemChangeListener implements SsiItemChangeListener
+ {
+ public void handleItemCreated(SsiItem item)
+ {
+ /** @todo implement handleItemCreated() */
+ logger.debug("!!! TODO: implement handleItemCreated() !!!" + item
+ + " DATA=" + item.getData().toString());
+ }
+
+ public void handleItemDeleted(SsiItem item)
+ {
+ /** @todo implement handleItemDeleted() */
+ logger.debug("!!! TODO: implement handleItemDeleted()!!!" + item);
+ }
+
+ public void handleItemModified(SsiItem item)
+ {
+ /** @todo implement handleItemModified() */
+ logger.debug("!!! TODO: implement handleItemModified() !!!" + item
+ + " DATA=" + item.getData().toString());
+ }
+
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/icq/icq.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/icq/icq.provider.manifest.mf
new file mode 100644
index 000000000..7a42d6d0e
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/icq/icq.provider.manifest.mf
@@ -0,0 +1,12 @@
+Bundle-Activator: net.java.sip.communicator.impl.protocol.icq.Activator
+Bundle-Name: ICQ Protocol Provider Implementation
+Bundle-Description: An ICQ/AIM implementation of the Protocol Provider Service.
+Bundle-Vendor: sip-communicator.org
+Bundle-Version: 0.0.1
+Import-Package: org.osgi.framework,
+ net.java.sip.communicator.service.configuration,
+ net.java.sip.communicator.util,
+ net.java.sip.communicator.service.configuration.event,
+Export-Package: net.java.sip.communicator.service.protocol,
+ net.java.sip.communicator.service.protocol.icqconstants,
+ net.java.sip.communicator.service.protocol.event