/* * 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.ssi.*; import net.kano.joustsim.*; import net.kano.joustsim.oscar.*; import net.kano.joustsim.oscar.oscar.service.ssi.*; /** * 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 * @author Damian Minkov */ 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 final RootContactGroupIcqImpl rootGroup; /** * 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(); /** * Used for retrieveing missing nicks on specified contacts */ private NickRetriever nickRetriever = null; /** * Used for retrieveing missing nicks on specified contacts */ static String awaitingAuthorizationGroupName = new String("Awaiting authorization"); /** * Creates a ServerStoredContactList wrapper for the specified BuddyList. * * @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. */ ServerStoredContactListIcqImpl( OperationSetPersistentPresenceIcqImpl parentOperationSet, ProtocolProviderServiceIcqImpl icqProvider) { //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 //We need to init these as early as possible to ensure that the provider //and the operationsset would not be null in the incoming events. this.parentOperationSet = parentOperationSet; this.icqProvider = icqProvider; this.rootGroup = new RootContactGroupIcqImpl(this.icqProvider); // waiting for the first contact to come // to start retreiving the missing nicknames if(icqProvider.USING_ICQ) { nickRetriever = new NickRetriever(); parentOperationSet.addContactPresenceStatusListener(nickRetriever); // start the nick retreiver thread nickRetriever.start(); } } /** * 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 listener the ServerStoredGroupListener to register for group events */ void addGroupListener(ServerStoredGroupListener listener) { synchronized(serverStoredGroupListeners) { if(!serverStoredGroupListeners.contains(listener)) this.serverStoredGroupListeners.add(listener); } } /** * Removes the specified group listener so that it won't receive further * events on group modification/creation/destruction. * @param listener the ServerStoredGroupListener to unregister */ void removeGroupListener(ServerStoredGroupListener listener) { synchronized(serverStoredGroupListeners) { this.serverStoredGroupListeners.remove(listener); } } /** * 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. */ void fireGroupEvent(ContactGroup group, int eventID) { //bail out if no one's listening if(parentOperationSet == null){ logger.debug("No presence op. set available. Bailing out."); return; } ServerStoredGroupEvent evt = new ServerStoredGroupEvent( group , eventID , parentOperationSet.getServerStoredContactListRoot() , icqProvider , parentOperationSet); logger.trace("Will dispatch the following grp event: " + evt); Iterable listeners; synchronized (serverStoredGroupListeners) { listeners = new ArrayList( serverStoredGroupListeners); } for (ServerStoredGroupListener listener : listeners) { if (eventID == ServerStoredGroupEvent.GROUP_REMOVED_EVENT) listener.groupRemoved(evt); else if (eventID == ServerStoredGroupEvent.GROUP_RENAMED_EVENT) listener.groupNameChanged(evt); else if (eventID == ServerStoredGroupEvent.GROUP_CREATED_EVENT) listener.groupCreated(evt); else if (eventID == ServerStoredGroupEvent.GROUP_RESOLVED_EVENT) listener.groupResolved(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( ContactGroup parentGroup, Contact contact) { //bail out if no one's listening if(parentOperationSet == null){ logger.debug("No presence op. set available. Bailing out."); return; } //dispatch parentOperationSet.fireSubscriptionEvent( contact, parentGroup, SubscriptionEvent.SUBSCRIPTION_CREATED); } /** * Make the parent persistent presence operation set dispatch a contact * resolved event. * @param parentGroup the group that the resolved contact belongs to. * @param contact the contact that was resolved */ private void fireContactResolved( ContactGroup parentGroup, Contact contact) { //bail out if no one's listening if(parentOperationSet == null){ logger.debug("No presence op. set available. Bailing out."); return; } //dispatch parentOperationSet.fireSubscriptionEvent( contact, parentGroup, SubscriptionEvent.SUBSCRIPTION_RESOLVED); } /** * Make the parent persistent presence operation set dispatch a subscription * moved event. * @param oldParentGroup the group where the source contact was located * before being moved * @param newParentGroup the group that the source contact is currently in. * @param contact the contact that was added * @param index the index at which it was added. */ private void fireContactMoved( ContactGroup oldParentGroup, ContactGroup newParentGroup, Contact 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.fireSubscriptionMovedEvent( contact, oldParentGroup, newParentGroup); } /** * 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( ContactGroup parentGroup, Contact contact) { //bail out if no one's listening if(parentOperationSet == null){ logger.debug("No presence op. set available. Bailing out."); return; } //dispatch parentOperationSet.fireSubscriptionEvent( contact, parentGroup, SubscriptionEvent.SUBSCRIPTION_REMOVED); } /** * Retrns a reference to the provider that created us. * @return a reference to a ProtocolProviderServiceIcqImpl instance. */ ProtocolProviderServiceIcqImpl getParentProvider() { return icqProvider; } /** * 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 ConntactGroup with the specified name or null if no such * group was found. *

* @param name the name of the group we're looking for. * @return a reference to the ContactGroupIcqImpl instance we're looking for * or null if no such group was found. */ public ContactGroupIcqImpl findContactGroup(String name) { Iterator contactGroups = rootGroup.subgroups(); while(contactGroups.hasNext()) { ContactGroup contactGroup = contactGroups.next(); if (contactGroup.getGroupName().equals(name)) return (ContactGroupIcqImpl)contactGroup; } return null; } /** * 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 Contact with the specified screenname (or icq UIN) or null if * no such screenname was found. * * @param buddy the buddy (or ICQ UIN) of the contact to find. * @return the Contact carrying the specified * screenName or null if no such contact exits. */ public ContactIcqImpl findContactByJoustSimBuddy(Buddy buddy) { Iterator contactGroups = rootGroup.subgroups(); String screenName = buddy.getScreenname().getFormatted(); 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 = getFirstPersistentGroup(); addContact(parent, screenname); } /** * Creates a non persistent contact for the specified address. This would * also create (if necessary) a group for volatile contacts that would not * be added to the server stored contact list. This method would have no * effect on the server stored contact list. * @param screenname the UIN/Screenname of the contact to create. * @return the newly created volatile ContactIcqImpl */ ContactIcqImpl createVolatileContact(Screenname screenname) { logger.trace("createVolatileContact " + screenname); //First create the new volatile contact; Buddy volatileBuddy = new VolatileBuddy(screenname); ContactIcqImpl newVolatileContact = new ContactIcqImpl(volatileBuddy, this, false, false); //Check whether a volatile group already exists and if not create //one ContactGroupIcqImpl theVolatileGroup = getNonPersistentGroup(); //if the parent group is null then add necessary create the group if (theVolatileGroup == null) { List emptyBuddies = new LinkedList(); theVolatileGroup = new ContactGroupIcqImpl( new VolatileGroup(), emptyBuddies, this, false); theVolatileGroup.addContact(newVolatileContact); this.rootGroup.addSubGroup(theVolatileGroup); fireGroupEvent(theVolatileGroup , ServerStoredGroupEvent.GROUP_CREATED_EVENT); } else { theVolatileGroup.addContact(newVolatileContact); fireContactAdded(theVolatileGroup, newVolatileContact); } return newVolatileContact; } /** * Creates a non resolved contact for the specified address and inside the * specified group. The newly created contact would be added to the local * contact list as a standard contact but when an event is received from the * server concerning this contact, then it will be reused and only its * isResolved field would be updated instead of creating the whole contact * again. * * @param parentGroup the group where the unersolved contact is to be * created * @param screenname the UIN/Screenname of the contact to create. * @return the newly created unresolved ContactIcqImpl */ ContactIcqImpl createUnresolvedContact(ContactGroupIcqImpl parentGroup, Screenname screenname) { logger.trace("createUnresolvedContact " + screenname); //First create the new volatile contact; Buddy volatileBuddy = new VolatileBuddy(screenname); ContactIcqImpl newUnresolvedContact = new ContactIcqImpl(volatileBuddy, this, false, false); parentGroup.addContact(newUnresolvedContact); fireContactAdded( parentGroup , newUnresolvedContact); return newUnresolvedContact; } /** * Creates a non resolved contact group for the specified name. The newly * created group would be added to the local contact list as any other group * but when an event is received from the server concerning this group, then * it will be reused and only its isResolved field would be updated instead * of creating the whole group again. *

* @param groupName the name of the group to create. * @return the newly created unresolved ContactGroupIcqImpl */ ContactGroupIcqImpl createUnresolvedContactGroup(String groupName) { //First create the new volatile contact; List emptyBuddies = new LinkedList(); ContactGroupIcqImpl newUnresolvedGroup = new ContactGroupIcqImpl( new VolatileGroup(groupName), emptyBuddies, this, false); this.rootGroup.addSubGroup(newUnresolvedGroup); fireGroupEvent(newUnresolvedGroup , ServerStoredGroupEvent.GROUP_CREATED_EVENT); return newUnresolvedGroup; } /** * 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("Adding contact " + screenname + " to parent=" + parent.getGroupName()); //if the contact is already in the contact list and is not volatile, //then only broadcast an event final ContactIcqImpl existingContact = findContactByScreenName(screenname); if( existingContact != null && existingContact.isPersistent() ) { 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( existingContact, findContactGroup(existingContact), SubscriptionEvent.SUBSCRIPTION_CREATED); } }.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) { if(contact.isPersistent()) { List contactsToMove = new ArrayList(); contactsToMove.add(contact.getJoustSimBuddy()); buddyList.moveBuddies(contactsToMove, newParent.getJoustSimSourceGroup()); } else { // if the contact buddy is volatile // just add the buddy to the new group // if everything is ok. The volatile contact will be reused addContact(newParent, contact.getUIN()); } } /** * 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. */ void init( SsiService joustSimSsiService ) { this.jSimSsiService = joustSimSsiService; jSimSsiService.addItemChangeListener(jsimItemChangeListener); this.buddyList = jSimSsiService.getBuddyList(); buddyList.addRetroactiveLayoutListener(buddyListListener); } /** * Returns the first persistent group * * @return ContactGroupIcqImpl */ private ContactGroupIcqImpl getFirstPersistentGroup() { for (int i = 0; i < getRootGroup().countSubgroups(); i++) { ContactGroupIcqImpl gr = (ContactGroupIcqImpl)getRootGroup().getGroup(i); if(gr.isPersistent()) return gr; } return null; } /** * Returns the volatile group * * @return ContactGroupIcqImpl */ private ContactGroupIcqImpl getNonPersistentGroup() { for (int i = 0; i < getRootGroup().countSubgroups(); i++) { ContactGroupIcqImpl gr = (ContactGroupIcqImpl)getRootGroup().getGroup(i); if(!gr.isPersistent() && !gr.getGroupName().equals(awaitingAuthorizationGroupName)) return gr; } return null; } /** * when there is no alias for contact we must retreive its nickname from server * but when the contact list is loaded the client is not yet registered to * server we wait this and then retreive the nicknames * * @param c ContactIcqImpl */ protected void addContactForUpdate(ContactIcqImpl c) { if(icqProvider.USING_ICQ) nickRetriever.addContact(c); } protected void addAwaitingAuthorizationContact(Buddy buddy) { //Check whether a Awaiting authorization group already exists and if //not create one ContactGroupIcqImpl theAwaitingAuthorizationGroup = findContactGroup(awaitingAuthorizationGroupName); if(theAwaitingAuthorizationGroup == null) { List emptyBuddies = new LinkedList(); theAwaitingAuthorizationGroup = new ContactGroupIcqImpl( new VolatileGroup(awaitingAuthorizationGroupName), emptyBuddies, this, false); this.rootGroup.addSubGroup(theAwaitingAuthorizationGroup); fireGroupEvent(theAwaitingAuthorizationGroup , ServerStoredGroupEvent.GROUP_CREATED_EVENT); } ContactGroupIcqImpl oldParentGroup = null; ContactIcqImpl newContact = findContactByJoustSimBuddy(buddy); if(newContact != null) oldParentGroup = (ContactGroupIcqImpl)newContact .getParentContactGroup(); boolean fireResolvedEvent = false; if(newContact == null) { newContact = new ContactIcqImpl( buddy, ServerStoredContactListIcqImpl.this, true, true); } else { oldParentGroup.removeContact(newContact); newContact.setJoustSimBuddy(buddy); newContact.setPersistent(true); if(!newContact.isResolved()) { newContact.setResolved(true); fireResolvedEvent = true; } } theAwaitingAuthorizationGroup.addContact(newContact); int index = theAwaitingAuthorizationGroup.findContactIndex(newContact); //register a listener for name changes of this buddy buddy.addBuddyListener(jsimBuddyListener); //tell listeners about the added group if(oldParentGroup == null) { fireContactAdded(theAwaitingAuthorizationGroup, newContact); } else if(oldParentGroup != theAwaitingAuthorizationGroup) { fireContactMoved(oldParentGroup, theAwaitingAuthorizationGroup , newContact, index); } //fire an event in case the contact has just been resolved. if(fireResolvedEvent) { fireContactResolved(theAwaitingAuthorizationGroup, newContact); } } protected void moveAwaitingAuthorizationContact(ContactIcqImpl contact) { ContactGroupIcqImpl parentGroup = findGroup(contact.getJoustSimBuddy()); if(parentGroup == null) return; findContactGroup(awaitingAuthorizationGroupName).removeContact(contact); parentGroup.addContact(contact); fireContactMoved(findContactGroup(awaitingAuthorizationGroupName), parentGroup, contact, parentGroup.findContactIndex(contact)); } ContactGroupIcqImpl findGroup(Buddy buddy) { Iterator iter = rootGroup.subgroups(); while (iter.hasNext()) { ContactGroupIcqImpl elem = (ContactGroupIcqImpl)iter.next(); if(!elem.isPersistent() || !elem.isResolved()) continue; for (Buddy b : elem.getJoustSimSourceGroup().getBuddiesCopy()) { if(b == buddy) return elem; } } return null; } 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 = findContactGroup(group.getName()); //verify that this is indeed a new group if(newGroup == null) { newGroup = new ContactGroupIcqImpl( (MutableGroup) group , buddies , ServerStoredContactListIcqImpl.this, true); //this is the first group so insert at 0. rootGroup.addSubGroup(newGroup); //tell listeners about the added group fireGroupEvent(newGroup , ServerStoredGroupEvent.GROUP_CREATED_EVENT); } else { // if this is not a new group then it must be a unresolved one. // set it to resolved, do the same with its child buddies, fire // the corresponding events and bail out. List newContacts = new ArrayList(); List deletedContacts = new ArrayList(); newGroup.updateGroup((MutableGroup)group, buddies , newContacts, deletedContacts); //fire an event saying that the group has been resolved fireGroupEvent(newGroup , ServerStoredGroupEvent.GROUP_RESOLVED_EVENT); //fire events for contacts that have been removed; for (ContactIcqImpl contact : deletedContacts) fireContactRemoved(newGroup, contact); //fire events for that contacts have been resolved or added Iterator contactsIter = newGroup.contacts(); while(contactsIter.hasNext()) { Contact contact = contactsIter.next(); if(newContacts.contains(contact)) fireContactAdded(newGroup, contact); else fireContactResolved(newGroup, contact); } } //add a joust sim buddy listener to all of the buddies in this group for (Buddy buddy : buddies) buddy.addBuddyListener(jsimBuddyListener); //register a listener for name changes of this group group.addGroupListener(jsimGroupChangeListener); } /** * 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); if (index == -1) { logger.debug("non existing group: " + group.getName()); return; } ContactGroup removedGroup = rootGroup.getGroup(index); 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) { logger.trace("Received buddyAdded " + buddy); //it is possible that the buddy being added is already in our //contact list. For example if they have sent a message to us they //would have been added to the local contact list as a //volatile/non-persistent contact without being added to the server //stored contact list. if this is the case make sure we keep the //same contact instance and issue a contact moved event instead of //a contact added event. ContactGroupIcqImpl oldParentGroup = null; ContactIcqImpl newContact = findContactByJoustSimBuddy(buddy); ContactGroupIcqImpl parentGroup = findContactGroup(joustSimGroup); boolean fireResolvedEvent = false; if (parentGroup == null) { logger.debug("no parent group " + joustSimGroup + " found for buddy: " + buddy); return; } if(buddy.isAwaitingAuthorization()) { addAwaitingAuthorizationContact(buddy); return; } if(newContact == null) { newContact = new ContactIcqImpl( buddy, ServerStoredContactListIcqImpl.this, true, true); } else { oldParentGroup = findContactGroup(newContact); if(oldParentGroup != parentGroup) oldParentGroup.removeContact(newContact); newContact.setJoustSimBuddy(buddy); newContact.setPersistent(true); if(!newContact.isResolved()) { newContact.setResolved(true); fireResolvedEvent = true; } } parentGroup.addContact(newContact); int index = parentGroup.findContactIndex(newContact); //register a listener for name changes of this buddy buddy.addBuddyListener(jsimBuddyListener); //tell listeners about the added group if(oldParentGroup == null) { fireContactAdded(parentGroup, newContact); } else if(oldParentGroup != parentGroup) { fireContactMoved(oldParentGroup, parentGroup , newContact, index); } //fire an event in case the contact has just been resolved. if(fireResolvedEvent) { fireContactResolved(parentGroup, newContact); } } /** * 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); if(contactToRemove == null) { // this buddy is not in this group // it can be in awaiting authorization group // will search it there ContactGroupIcqImpl theAwaitingAuthorizationGroup = findContactGroup(awaitingAuthorizationGroupName); if(theAwaitingAuthorizationGroup != null) { contactToRemove = theAwaitingAuthorizationGroup. findContact(buddy.getScreenname().getFormatted()); if(contactToRemove == null) return; theAwaitingAuthorizationGroup.removeContact(contactToRemove); buddy.removeBuddyListener(jsimBuddyListener); fireContactRemoved(theAwaitingAuthorizationGroup, contactToRemove); } } else { 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) { //we don't support this any longer. check out SVN archives if //you need it for some reason. } /** * 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(); for (Group group : newOrder) { 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); return; } //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 static 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); } /** * 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() */="); } /** * 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() */"); } /** * 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() */"); } /** * 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() */"); } /** * 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() */"); } public void awaitingAuthChanged(Buddy simpleBuddy, boolean oldAwaitingAuth, boolean newAwaitingAuth) { /** @todo */ logger.debug("awaitingAuthChanged for " + simpleBuddy + " oldAwaitingAuth: " + oldAwaitingAuth + " newAwaitingAuth: " + newAwaitingAuth); } } /** * A dummy implementation of the JoustSIM SsiItemChangeListener. * * @author Emil Ivov */ private static 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()); } } /** * Thread retreiving nickname and firing event for the change */ private class NickRetriever extends Thread implements ContactPresenceStatusListener { /** * list with the accounts with missing nicknames */ private final Vector contactsForUpdate = new Vector(); private boolean isReadyForRetreive = false; public void run() { try { Collection copyContactsForUpdate = null; while (true) { synchronized(contactsForUpdate){ if(contactsForUpdate.isEmpty()) contactsForUpdate.wait(); copyContactsForUpdate = new Vector(contactsForUpdate); contactsForUpdate.clear(); } Iterator iter = copyContactsForUpdate.iterator(); while (iter.hasNext()) { ContactIcqImpl contact = iter.next(); String oldNickname = contact.getUIN(); String nickName = null; try { nickName = getParentProvider(). getInfoRetreiver().getNickName(contact.getUIN()); } catch (Exception e) { // if something happens do not interrupt // the nickname retreiver } if(nickName != null) { contact.setNickname(nickName); parentOperationSet.fireContactPropertyChangeEvent( ContactPropertyChangeEvent. PROPERTY_DISPLAY_NAME, contact, oldNickname, nickName); } else contact.setNickname(oldNickname); } } } catch (InterruptedException ex) { logger.error("NickRetriever error waiting will stop now!", ex); } } /** * Add contact for retrieving * if the provider is register notify the retriever to get the nicks * if we are not registered add a listener to wait for registering * * @param contact ContactIcqImpl */ synchronized void addContact(ContactIcqImpl contact) { synchronized(contactsForUpdate){ if (!contactsForUpdate.contains(contact)) { if (isReadyForRetreive) { contactsForUpdate.add(contact); contactsForUpdate.notifyAll(); } else { contactsForUpdate.add(contact); } } } } /** * This is one of the first events after the client ready command * Used to start retrieving. * @param evt ContactPresenceStatusChangeEvent */ public void contactPresenceStatusChanged( ContactPresenceStatusChangeEvent evt) { if(!isReadyForRetreive) { isReadyForRetreive = true; synchronized(contactsForUpdate){ contactsForUpdate.notifyAll(); } } } } }