From fbeb769df3bb907433bc5a19b7b021a7f3aafc81 Mon Sep 17 00:00:00 2001 From: Emil Ivov Date: Sat, 28 Jan 2006 14:41:59 +0000 Subject: [PATCH] added foundations of icq support --- .../icq/AbstractContactGroupIcqImpl.java | 23 + .../protocol/icq/AccountManagerIcqImpl.java | 156 +++ .../impl/protocol/icq/Activator.java | 66 + .../protocol/icq/ContactGroupIcqImpl.java | 405 ++++++ .../impl/protocol/icq/ContactIcqImpl.java | 156 +++ .../impl/protocol/icq/IcqAccountID.java | 19 + ...OperationSetPersistentPresenceIcqImpl.java | 1193 +++++++++++++++++ .../icq/ProtocolProviderServiceIcqImpl.java | 429 ++++++ .../protocol/icq/RootContactGroupIcqImpl.java | 222 +++ .../icq/ServerStoredContactListIcqImpl.java | 884 ++++++++++++ .../protocol/icq/icq.provider.manifest.mf | 12 + 11 files changed, 3565 insertions(+) create mode 100644 src/net/java/sip/communicator/impl/protocol/icq/AbstractContactGroupIcqImpl.java create mode 100644 src/net/java/sip/communicator/impl/protocol/icq/AccountManagerIcqImpl.java create mode 100644 src/net/java/sip/communicator/impl/protocol/icq/Activator.java create mode 100644 src/net/java/sip/communicator/impl/protocol/icq/ContactGroupIcqImpl.java create mode 100644 src/net/java/sip/communicator/impl/protocol/icq/ContactIcqImpl.java create mode 100644 src/net/java/sip/communicator/impl/protocol/icq/IcqAccountID.java create mode 100644 src/net/java/sip/communicator/impl/protocol/icq/OperationSetPersistentPresenceIcqImpl.java create mode 100644 src/net/java/sip/communicator/impl/protocol/icq/ProtocolProviderServiceIcqImpl.java create mode 100644 src/net/java/sip/communicator/impl/protocol/icq/RootContactGroupIcqImpl.java create mode 100644 src/net/java/sip/communicator/impl/protocol/icq/ServerStoredContactListIcqImpl.java create mode 100644 src/net/java/sip/communicator/impl/protocol/icq/icq.provider.manifest.mf 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