diff --git a/build.xml b/build.xml
index a8d0fb6db..a69c5e09a 100644
--- a/build.xml
+++ b/build.xml
@@ -1086,7 +1086,8 @@
bundle-globalshortcut,bundle-plugin-msofficecomm,bundle-libjitsi,
bundle-customcontactactions, bundle-phonenumbercontactsource,
bundle-demuxcontactsource, bundle-muc,
- bundle-desktoputil,bundle-globaldisplaydetails,
+ bundle-desktoputil,bundle-globaldisplaydetails,
+ bundle-usersearch,
bundle-plugin-propertieseditor,bundle-plugin-accountinfo,
bundle-guava,bundle-hsql"/>
@@ -2874,6 +2875,15 @@ javax.swing.event, javax.swing.border"/>
+
+
+
+
+
+
+
diff --git a/lib/felix.client.run.properties b/lib/felix.client.run.properties
index 2a45a4729..834617bb0 100644
--- a/lib/felix.client.run.properties
+++ b/lib/felix.client.run.properties
@@ -196,6 +196,7 @@ felix.auto.start.67= \
reference:file:sc-bundles/plugin-certconfig.jar \
reference:file:sc-bundles/phonenumbercontactsource.jar \
reference:file:sc-bundles/demuxcontactsource.jar \
+ reference:file:sc-bundles/usersearch.jar \
reference:file:sc-bundles/propertieseditor.jar
# Level 68 is for profiler4j. Either don't use it or change the build.xml file
diff --git a/lib/installer-exclude/smack.manifest.mf b/lib/installer-exclude/smack.manifest.mf
index cccc76701..0323a503e 100644
--- a/lib/installer-exclude/smack.manifest.mf
+++ b/lib/installer-exclude/smack.manifest.mf
@@ -39,6 +39,7 @@ Export-Package: org.jivesoftware.smack,
org.jivesoftware.smackx.jingle.listeners,
org.jivesoftware.smackx.filetransfer,
org.jivesoftware.smackx.provider,
+ org.jivesoftware.smackx.search,
org.jivesoftware.smack.sasl,
org.xmlpull.v1,
org.xmlpull.mxp1,
diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties
index 1256585c3..ba86dd855 100644
--- a/resources/languages/resources.properties
+++ b/resources/languages/resources.properties
@@ -1251,6 +1251,9 @@ plugin.updatechecker.DIALOG_NOUPDATE=Your version is up to date.
plugin.updatechecker.DIALOG_NOUPDATE_TITLE=No new version
plugin.updatechecker.DIALOG_MISSING_UPDATE=Update Installer is missing.
+# usersearch
+plugin.usersearch.USER_SEARCH=User search
+
# whiteboard
plugin.whiteboard.TITLE=Whiteboard [Beta]
plugin.whiteboard.MENU_ITEM=Whiteboard
diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeCellRenderer.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeCellRenderer.java
index 2bb6a1dd9..d063cf702 100644
--- a/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeCellRenderer.java
+++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/ContactListTreeCellRenderer.java
@@ -324,12 +324,12 @@ public void actionPerformed(ActionEvent e)
(MetaContact) contactDescriptor.getDescriptor());
}
else if(((SourceContact) contactDescriptor.getDescriptor())
- .getContactDetails(OperationSetMultiUserChat.class)
+ .getContactDetails(OperationSetMultiUserChat.class)
!= null)
{
SourceContact contact = (SourceContact)
contactDescriptor.getDescriptor();
- ChatRoomWrapper room
+ ChatRoomWrapper room
= GuiActivator.getMUCService()
.findChatRoomWrapperFromSourceContact(contact);
if(room != null)
@@ -425,11 +425,9 @@ public Component getTreeCellRendererComponent(JTree tree, Object value,
{
UIContactImpl contact
= ((ContactNode) value).getContactDescriptor();
-
+
if((contact.getDescriptor() instanceof SourceContact) &&
- ((SourceContact)contact.getDescriptor())
- .getPreferredContactDetail(OperationSetMultiUserChat.class)
- != null)
+ GuiActivator.getMUCService().isMUCSourceContact((SourceContact)contact.getDescriptor()))
{
setBackground(Constants.CHAT_ROOM_ROW_COLOR);
}
@@ -535,7 +533,7 @@ else if (value instanceof GroupNode)
statusIcon = expanded
? openedGroupIcon
: closedGroupIcon;
-
+
if(groupItem != treeContactList.getRootUIGroup())
{
this.statusLabel.setIcon(
@@ -547,7 +545,7 @@ else if (value instanceof GroupNode)
{
this.statusLabel.setIcon(null);
}
-
+
// We have no photo icon for groups.
this.rightLabel.setIcon(null);
this.rightLabel.setText("");
@@ -562,8 +560,8 @@ else if (value instanceof GroupNode)
this.initDisplayDetails(groupItem.getDisplayDetails());
this.initButtonsPanel(groupItem);
- this.setToolTipText((groupItem.getDescriptor() != null) ?
- groupItem.getDescriptor().toString() :
+ this.setToolTipText((groupItem.getDescriptor() != null) ?
+ groupItem.getDescriptor().toString() :
groupItem.getDisplayName());
}
@@ -846,7 +844,7 @@ private void initButtonsPanel(UIContact uiContact)
if (uiContact.getDescriptor() instanceof MetaContact)
imContact = uiContact.getDefaultContactDetail(
OperationSetBasicInstantMessaging.class);
-
+
if(imContact == null)
imContact = uiContact.getDefaultContactDetail(
OperationSetMultiUserChat.class);
diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/PresenceFilter.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/PresenceFilter.java
index 3ffe6b71c..fd68c3d23 100644
--- a/src/net/java/sip/communicator/impl/gui/main/contactlist/PresenceFilter.java
+++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/PresenceFilter.java
@@ -42,13 +42,13 @@ public class PresenceFilter
* directly to the contact list without firing events.
*/
private static final int INITIAL_CONTACT_COUNT = 30;
-
+
/**
- * Preferences for the external contact sources. Lists the type of contact
- * contact sources that will be displayed in the filter and the order of the
+ * Preferences for the external contact sources. Lists the type of contact
+ * contact sources that will be displayed in the filter and the order of the
* contact sources.
*/
- private static Map contactSourcePreferences
+ private static Map contactSourcePreferences
= new HashMap();
/**
@@ -61,7 +61,7 @@ public PresenceFilter()
}
/**
- * Initializes the contact source preferences. The preferences are for the
+ * Initializes the contact source preferences. The preferences are for the
* visibility of the contact source and their order.
*/
private void initContactSourcePreferences()
@@ -71,7 +71,7 @@ private void initContactSourcePreferences()
//The chat room sources will be ordered before the meta contact list.
contactSourcePreferences.put(ContactSourceService.CHAT_ROOM_TYPE, 0);
}
-
+
/**
* Applies this filter. This filter is applied over the
* MetaContactListService.
@@ -88,33 +88,33 @@ public void applyFilter(FilterQuery filterQuery)
for(int cssType : contactSourcePreferences.keySet())
{
- Iterator filterSources
+ Iterator filterSources
= GuiActivator.getContactList().getContactSources(cssType)
.iterator();
-
+
while (filterSources.hasNext())
{
UIContactSource filterSource = filterSources.next();
-
+
Integer prefValue = contactSourcePreferences.get(cssType);
- //We are setting the index from contactSourcePreferences map to
- //the contact source. This index is set to reorder the sources
+ //We are setting the index from contactSourcePreferences map to
+ //the contact source. This index is set to reorder the sources
//in the contact list.
if(prefValue != null)
filterSource.setContactSourceIndex(prefValue);
-
+
ContactSourceService sourceService
= filterSource.getContactSourceService();
-
- ContactQuery contactQuery
+
+ ContactQuery contactQuery
= sourceService.createContactQuery(null);
-
+
// Add this query to the filterQuery.
filterQuery.addContactQuery(contactQuery);
-
+
contactQuery.addContactQueryListener(
GuiActivator.getContactList());
-
+
contactQuery.start();
}
}
@@ -124,7 +124,7 @@ public void applyFilter(FilterQuery filterQuery)
query.addContactQueryListener(GuiActivator.getContactList());
int resultCount = 0;
-
+
addMatching(GuiActivator.getContactListService().getRoot(),
query,
resultCount);
@@ -221,8 +221,7 @@ public boolean isMatching(SourceContact contact)
return
isShowOffline
|| contact.getPresenceStatus().isOnline()
- || (contact.getPreferredContactDetail(OperationSetMultiUserChat.class)
- != null);
+ || GuiActivator.getMUCService().isMUCSourceContact(contact);
}
/**
@@ -284,12 +283,12 @@ private void addMatching( MetaContactGroup metaGroup,
if(isMatching(metaContact))
{
-
+
resultCount++;
if (resultCount <= INITIAL_CONTACT_COUNT)
{
UIGroup uiGroup = null;
-
+
if (!MetaContactListSource.isRootGroup(metaGroup))
{
synchronized (metaGroup)
@@ -309,7 +308,7 @@ private void addMatching( MetaContactGroup metaGroup,
UIContactImpl newUIContact;
synchronized (metaContact)
{
- newUIContact
+ newUIContact
= MetaContactListSource.getUIContact(metaContact);
if (newUIContact == null)
@@ -318,7 +317,7 @@ private void addMatching( MetaContactGroup metaGroup,
.createUIContact(metaContact);
}
}
-
+
GuiActivator.getContactList().addContact(
newUIContact,
uiGroup,
diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/SourceContactRightButtonMenu.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/SourceContactRightButtonMenu.java
index 81f7fb853..8aa4e558e 100644
--- a/src/net/java/sip/communicator/impl/gui/main/contactlist/SourceContactRightButtonMenu.java
+++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/SourceContactRightButtonMenu.java
@@ -93,8 +93,7 @@ private void initItems()
add(initCallMenu());
// Only create the menu if the add contact functionality is enabled.
- if ((sourceContact.getPreferredContactDetail(
- OperationSetMultiUserChat.class) == null)
+ if (!GuiActivator.getMUCService().isMUCSourceContact(sourceContact)
&& !ConfigurationUtils.isAddContactDisabled())
{
addContactComponent
@@ -103,8 +102,8 @@ private void initItems()
if (addContactComponent != null)
add(addContactComponent);
-
- for(JMenuItem item :
+
+ for(JMenuItem item :
sourceUIContact.getContactCustomActionMenuItems(true))
{
add(item);
diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/TreeContactList.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/TreeContactList.java
index beb9e1fdf..d2089c2e8 100644
--- a/src/net/java/sip/communicator/impl/gui/main/contactlist/TreeContactList.java
+++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/TreeContactList.java
@@ -164,7 +164,7 @@ public class TreeContactList
* The container, where this contact list component is added.
*/
private ContactListContainer parentCLContainer;
-
+
/**
* The Contacts group instance.
*/
@@ -213,7 +213,7 @@ public TreeContactList(ContactListContainer clContainer)
*/
public void contactReceived(ContactReceivedEvent event)
{
-
+
final SourceContact sourceContact = event.getContact();
ContactSourceService contactSource
@@ -223,7 +223,7 @@ public void contactReceived(ContactReceivedEvent event)
if (sourceUI == null)
return;
-
+
UIContact uiContact
= sourceUI.createUIContact(sourceContact);
@@ -303,10 +303,10 @@ public void contactChanged(ContactChangedEvent event)
synchronized (uiContact)
{
contactNode = ((UIContactImpl) uiContact).getContactNode();
-
+
if (contactNode == null)
return;
-
+
nodeChanged(contactNode);
}
@@ -324,7 +324,7 @@ public void contactChanged(ContactChangedEvent event)
if (groupNode == null)
return;
}
-
+
groupNode.sort(treeModel);
}
}
@@ -356,7 +356,7 @@ public void metaContactReceived(MetaContactQueryEvent event)
UIContactImpl newUIContact;
synchronized (metaContact)
{
- newUIContact
+ newUIContact
= MetaContactListSource.getUIContact(metaContact);
if (newUIContact == null)
@@ -365,13 +365,13 @@ public void metaContactReceived(MetaContactQueryEvent event)
.createUIContact(metaContact);
}
}
-
+
addContact( event.getQuerySource(),
newUIContact,
uiGroup,
true);
-
+
}
/**
@@ -480,7 +480,7 @@ public void setActiveContact(MetaContact metaContact, boolean isActive)
if (contactNode == null)
return;
}
-
+
contactNode.setActive(isActive);
if (isActive)
@@ -495,7 +495,7 @@ public void setActiveContact(MetaContact metaContact, boolean isActive)
activeContacts.remove(contactNode);
treeModel.nodeChanged(contactNode);
-
+
}
/**
@@ -563,22 +563,22 @@ else if (group instanceof UIGroupImpl)
if (groupNode == null)
{
GroupNode parentNode = treeModel.getRoot();
-
+
if (isGroupSorted)
groupNode = parentNode.sortedAddContactGroup(contactImpl);
else
groupNode = parentNode.addContactGroup(contactImpl);
-
+
if(group.getSourceIndex() < GuiActivator.getContactListService()
- .getSourceIndex() && rootUIGroup == null
- && (!(treeModel.getRoot().getChildAfter(groupNode)
+ .getSourceIndex() && rootUIGroup == null
+ && (!(treeModel.getRoot().getChildAfter(groupNode)
instanceof GroupNode)))
{
createMetaUIRootGroup();
}
}
}
-
+
}
if (groupNode == null)
@@ -591,7 +591,7 @@ else if (group instanceof UIGroupImpl)
ContactNode contactNode = null;
UIContactImpl contactImpl = (UIContactImpl) contact;
-
+
synchronized (contactImpl)
{
if(contactImpl.getContactNode() != null)
@@ -603,8 +603,8 @@ else if (group instanceof UIGroupImpl)
contactNode = groupNode.sortedAddContact(contactImpl);
else
contactNode = groupNode.addContact(contactImpl);
-
- if(rootUIGroup == null && groupNode == treeModel.getRoot()
+
+ if(rootUIGroup == null && groupNode == treeModel.getRoot()
&& treeModel.getRoot().getChildBefore(contactNode) instanceof GroupNode)
{
createMetaUIRootGroup();
@@ -623,9 +623,9 @@ else if (group instanceof UIGroupImpl)
*/
public UIGroupImpl getRootUIGroup()
{
- return rootUIGroup;
+ return rootUIGroup;
}
-
+
/**
* Creates UI group for the root meta contact group.
*/
@@ -633,82 +633,82 @@ private void createMetaUIRootGroup()
{
if(rootUIGroup != null)
return;
-
+
rootUIGroup = new UIGroupImpl()
{
private GroupNode groupNode = null;
-
+
@Override
public boolean isGroupCollapsed()
{
return true;
}
-
+
@Override
public int getSourceIndex()
{
return GuiActivator.getContactListService().getSourceIndex();
}
-
+
@Override
public Component getRightButtonMenu()
{
return null;
}
-
+
@Override
public UIGroup getParentGroup()
{
return null;
}
-
+
@Override
public String getId()
{
return null;
}
-
+
@Override
public String getDisplayName()
{
return GuiActivator.getResources()
.getI18NString("service.gui.CONTACTS");
}
-
+
@Override
public Object getDescriptor()
{
return null;
}
-
+
@Override
public int countOnlineChildContacts()
{
return -1;
}
-
+
@Override
public int countChildContacts()
{
return -1;
}
-
+
@Override
public void setGroupNode(GroupNode groupNode)
{
this.groupNode = groupNode;
}
-
+
@Override
public GroupNode getGroupNode()
{
return groupNode;
}
};
-
+
treeModel.getRoot().sortedAddContactGroup(rootUIGroup);
}
-
+
/**
* Removes the UI group associated with the root meta contact group.
*/
@@ -717,7 +717,7 @@ private void removeMetaUIRootGroup()
if(rootUIGroup == null)
return;
GroupNode parentNode = treeModel.getRoot();
-
+
parentNode.removeContactGroup(rootUIGroup);
rootUIGroup = null;
}
@@ -824,9 +824,9 @@ public void run()
// Nothing more to do here if we didn't find the parent.
if (parentGroupNode == null)
return;
-
+
parentGroupNode.removeContact((UIContactImpl) contact);
-
+
// If the parent group is empty remove it.
if (removeEmptyGroup && parentGroupNode.getChildCount() == 0)
{
@@ -836,12 +836,12 @@ public void run()
{
parent.removeContactGroup(parentGroup);
}
-
+
}
}
-
- if(rootUIGroup != null
- && (treeModel.getRoot().getChildAfter(rootUIGroup.getGroupNode())
+
+ if(rootUIGroup != null
+ && (treeModel.getRoot().getChildAfter(rootUIGroup.getGroupNode())
instanceof GroupNode || treeModel.getRoot().getChildBefore(
(rootUIGroup).getGroupNode()) == null))
{
@@ -886,7 +886,7 @@ public void run()
treeModel.nodeChanged(((UIContactImpl) contact).getContactNode());
}
-
+
}
/**
@@ -1015,7 +1015,7 @@ public Collection getContacts(final UIGroup group)
return null;
GroupNode groupNode;
-
+
if (group == null)
groupNode = treeModel.getRoot();
else
@@ -1024,12 +1024,12 @@ public Collection getContacts(final UIGroup group)
{
groupNode = ((UIGroupImpl) group).getGroupNode();
}
-
+
}
if (groupNode == null)
return null;
-
+
Collection contactNodes = groupNode.getContacts();
if (contactNodes == null)
@@ -1768,12 +1768,12 @@ public void startSelectedContactChat()
.startChat((MetaContact) uiContact.getDescriptor());
}
else if(((SourceContact) uiContact.getDescriptor())
- .getContactDetails(OperationSetMultiUserChat.class)
+ .getContactDetails(OperationSetMultiUserChat.class)
!= null)
{
SourceContact contact = (SourceContact)
uiContact.getDescriptor();
- ChatRoomWrapper room
+ ChatRoomWrapper room
= GuiActivator.getMUCService()
.findChatRoomWrapperFromSourceContact(contact);
if(room != null)
@@ -1896,7 +1896,7 @@ private void initContactSources()
else
contactSources.add(extContactSource);
}
-
+
}
GuiActivator.bundleContext.addServiceListener(
@@ -2123,7 +2123,7 @@ public void contactChanged(ContactChangedEvent event)
ContactSourceService contactSource
= sourceContact.getContactSource();
- UIContactSource sourceUI
+ UIContactSource sourceUI
= getContactSource(contactSource);
if (sourceUI == null)
@@ -2132,15 +2132,15 @@ public void contactChanged(ContactChangedEvent event)
UIContact uiContact
= sourceUI.getUIContact(sourceContact);
- if(uiContact == null
+ if(uiContact == null
|| !(uiContact instanceof UIContactImpl))
return;
-
+
synchronized (uiContact)
{
- ContactNode contactNode
+ ContactNode contactNode
= ((UIContactImpl) uiContact).getContactNode();
-
+
if (contactNode != null)
nodeChanged(contactNode);
}
@@ -2151,7 +2151,7 @@ public void contactChanged(ContactChangedEvent event)
contactSource).createContactQuery(filterPattern);
loadedQueries.add(query);
-
+
query.start();
// If the image search has been canceled from one of the
@@ -2263,7 +2263,9 @@ public static JMenuItem createAddContactMenu(SourceContact sourceContact)
{
JMenuItem addContactComponentTmp = null;
- List details = sourceContact.getContactDetails();
+ List details
+ = sourceContact.getContactDetails(
+ OperationSetPersistentPresence.class);
final String displayName = sourceContact.getDisplayName();
if (details.size() == 1)
@@ -2512,11 +2514,11 @@ public void run()
{
if (!(uiContact instanceof UIContactImpl))
return;
-
+
setSelectionPath(new TreePath(
((UIContactImpl) uiContact).getContactNode().getPath()));
}
-
+
}
/**
@@ -2546,7 +2548,7 @@ public void run()
setSelectionPath(new TreePath(
((UIGroupImpl) uiGroup).getGroupNode().getPath()));
}
-
+
}
/**
diff --git a/src/net/java/sip/communicator/impl/muc/MUCCustomContactActionService.java b/src/net/java/sip/communicator/impl/muc/MUCCustomContactActionService.java
index b566b932e..419e3788c 100644
--- a/src/net/java/sip/communicator/impl/muc/MUCCustomContactActionService.java
+++ b/src/net/java/sip/communicator/impl/muc/MUCCustomContactActionService.java
@@ -582,6 +582,9 @@ public byte[] getIcon()
@Override
public String getText(SourceContact actionSource)
{
+ if(!(actionSource instanceof ChatRoomSourceContact))
+ return "";
+
if(!name.equals("open_automatically"))
return text;
@@ -653,6 +656,8 @@ public boolean isSelected(SourceContact contact)
{
ChatRoomWrapper chatRoomWrapper = MUCActivator.getMUCService()
.findChatRoomWrapperFromSourceContact(contact);
+ if(chatRoomWrapper == null)
+ return false;
return chatRoomWrapper.isAutojoin();
}
diff --git a/src/net/java/sip/communicator/impl/muc/MUCServiceImpl.java b/src/net/java/sip/communicator/impl/muc/MUCServiceImpl.java
index 7b0194868..9a2dac203 100644
--- a/src/net/java/sip/communicator/impl/muc/MUCServiceImpl.java
+++ b/src/net/java/sip/communicator/impl/muc/MUCServiceImpl.java
@@ -22,24 +22,24 @@
/**
* The MUCServiceImpl class implements the service for the chat rooms.
- *
+ *
* @author Hristo Terezov
*/
public class MUCServiceImpl
extends MUCService
{
-
+
/**
* The list of persistent chat rooms.
*/
private final ChatRoomListImpl chatRoomList = new ChatRoomListImpl();
-
+
/**
* The Logger used by the MUCServiceImpl class and its
* instances for logging output.
*/
private static Logger logger = Logger.getLogger(MUCServiceImpl.class);
-
+
/**
* Called to accept an incoming invitation. Adds the invitation chat room
* to the list of chat rooms and joins it.
@@ -59,27 +59,27 @@ public void acceptInvitation(ChatRoomInvitation invitation)
/**
* Adds a change listener to the ChatRoomList.
- *
+ *
* @param l the listener.
*/
public void addChatRoomListChangeListener(ChatRoomListChangeListener l)
{
chatRoomList.addChatRoomListChangeListener(l);
}
-
+
/**
* Removes a change listener to the ChatRoomList.
- *
+ *
* @param l the listener.
*/
public void removeChatRoomListChangeListener(ChatRoomListChangeListener l)
{
chatRoomList.removeChatRoomListChangeListener(l);
}
-
+
/**
* Fires a ChatRoomListChangedEvent event.
- *
+ *
* @param chatRoomWrapper the chat room.
* @param eventID the id of the event.
*/
@@ -88,7 +88,7 @@ public void fireChatRoomListChangedEvent( ChatRoomWrapper chatRoomWrapper,
{
chatRoomList.fireChatRoomListChangedEvent(chatRoomWrapper, eventID);
}
-
+
/**
* Joins the given chat room with the given password and manages all the
@@ -100,7 +100,7 @@ public void fireChatRoomListChangedEvent( ChatRoomWrapper chatRoomWrapper,
* @param rememberPassword if true the password should be saved.
* @param isFirstAttempt is this the first attempt to join room, used
* to check whether to show some error messages
- * @param subject the subject which will be set to the room after the user
+ * @param subject the subject which will be set to the room after the user
* join successful.
*/
private void joinChatRoom( ChatRoomWrapper chatRoomWrapper,
@@ -115,7 +115,7 @@ private void joinChatRoom( ChatRoomWrapper chatRoomWrapper,
if(chatRoom == null)
{
MUCActivator.getAlertUIService().showAlertDialog(
- MUCActivator.getResources().getI18NString("service.gui.WARNING"),
+ MUCActivator.getResources().getI18NString("service.gui.WARNING"),
MUCActivator.getResources().getI18NString(
"service.gui.CHAT_ROOM_NOT_CONNECTED",
new String[]{chatRoomWrapper.getChatRoomName()}));
@@ -160,7 +160,7 @@ public void joinChatRoom( ChatRoomWrapper chatRoomWrapper,
* @param chatRoomWrapper the chat room to join.
* @param nickName the nickname we choose for the given chat room.
* @param password the password.
- * @param subject the subject which will be set to the room after the user
+ * @param subject the subject which will be set to the room after the user
* join successful.
*/
public void joinChatRoom( ChatRoomWrapper chatRoomWrapper,
@@ -185,7 +185,7 @@ public void joinChatRoom( ChatRoomWrapper chatRoomWrapper,
.start();
}
-
+
/**
* Join chat room.
* @param chatRoomWrapper
@@ -230,7 +230,7 @@ public void joinChatRoom( ChatRoom chatRoom,
= chatRoomList.findServerWrapperFromProvider(
chatRoom.getParentProvider());
- chatRoomWrapper
+ chatRoomWrapper
= new ChatRoomWrapperImpl(parentProvider, chatRoom);
chatRoomList.addChatRoom(chatRoomWrapper);
@@ -273,19 +273,19 @@ public void joinChatRoom( String chatRoomName,
{
ChatRoomWrapper chatRoomWrapper
= chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom);
-
+
if(chatRoomWrapper == null)
{
ChatRoomProviderWrapper parentProvider
= chatRoomList
.findServerWrapperFromProvider(
chatRoom.getParentProvider());
-
- chatRoomWrapper
+
+ chatRoomWrapper
= new ChatRoomWrapperImpl(parentProvider, chatRoom);
-
+
chatRoomList.addChatRoom(chatRoomWrapper);
-
+
fireChatRoomListChangedEvent(
chatRoomWrapper,
ChatRoomListChangeEvent.CHAT_ROOM_ADDED);
@@ -328,7 +328,7 @@ public ChatRoomWrapper createChatRoom(
roomName, protocolProvider, contacts, reason, true, persistent,
isPrivate);
}
-
+
/**
* Creates a chat room, by specifying the chat room name, the parent
* protocol provider and eventually, the contacts invited to participate in
@@ -387,17 +387,17 @@ public ChatRoomWrapper createChatRoom(
ChatRoom chatRoom = null;
try
{
-
-
- HashMap roomProperties =
+
+
+ HashMap roomProperties =
new HashMap();
roomProperties.put("isPrivate", isPrivate);
chatRoom = groupChatOpSet.createChatRoom(roomName, roomProperties);
-
+
if(join)
{
chatRoom.join();
-
+
for(String contact : contacts)
chatRoom.invite(contact, reason);
}
@@ -437,7 +437,7 @@ public ChatRoomWrapper createChatRoom(
if(chatRoomWrapper == null)
{
- chatRoomWrapper
+ chatRoomWrapper
= new ChatRoomWrapperImpl(parentProvider, chatRoom);
chatRoomWrapper.setPersistent(persistent);
chatRoomList.addChatRoom(chatRoomWrapper);
@@ -467,7 +467,7 @@ public ChatRoomWrapper createPrivateChatRoom(
return this.createChatRoom(
null, protocolProvider, contacts, reason, persistent, true);
}
-
+
/**
* Returns existing chat rooms for the given chatRoomProvider.
@@ -480,7 +480,7 @@ public List getExistingChatRooms(
{
if (chatRoomProvider == null)
return null;
-
+
ProtocolProviderService protocolProvider
= chatRoomProvider.getProtocolProvider();
@@ -493,7 +493,7 @@ public List getExistingChatRooms(
if (groupChatOpSet == null)
return null;
-
+
List chatRooms = null;
try
{
@@ -511,10 +511,10 @@ public List getExistingChatRooms(
logger.trace("Failed to obtain existing chat rooms for server: "
+ protocolProvider.getAccountID().getService(), e);
}
-
+
return chatRooms;
}
-
+
/**
* Rejects the given invitation with the specified reason.
*
@@ -529,7 +529,7 @@ public void rejectInvitation( OperationSetMultiUserChat multiUserChatOpSet,
{
multiUserChatOpSet.rejectInvitation(invitation, reason);
}
-
+
/**
* Leaves the given chat room.
*
@@ -569,7 +569,7 @@ public ChatRoomWrapper leaveChatRoom(ChatRoomWrapper chatRoomWrapper)
chatRoomWrapper.getParentProvider().getProtocolProvider(),
chatRoomWrapper.getChatRoomID(),
GlobalStatusEnum.OFFLINE_STATUS);
-
+
return existChatRoomWrapper;
}
@@ -601,14 +601,14 @@ private class JoinChatRoomTask
private final String nickName;
private final byte[] password;
-
+
private final boolean rememberPassword;
private final boolean isFirstAttempt;
-
+
private final String subject;
-
- private ResourceManagementService resources
+
+ private ResourceManagementService resources
= MUCActivator.getResources();
JoinChatRoomTask( ChatRoomWrapper chatRoomWrapper,
@@ -641,14 +641,14 @@ private class JoinChatRoomTask
}
this.rememberPassword = rememberPassword;
}
-
+
JoinChatRoomTask( ChatRoomWrapper chatRoomWrapper,
String nickName,
byte[] password)
{
this(chatRoomWrapper, nickName, password, false, true, null);
}
-
+
JoinChatRoomTask( ChatRoomWrapper chatRoomWrapper,
String nickName,
byte[] password,
@@ -658,7 +658,7 @@ private class JoinChatRoomTask
}
/**
- * @override {@link Thread}{@link #run()} to perform all asynchronous
+ * @override {@link Thread}{@link #run()} to perform all asynchronous
* tasks.
*/
@Override
@@ -791,7 +791,7 @@ else if(SUBSCRIPTION_ALREADY_EXISTS.equals(returnCode))
new String[]{chatRoomWrapper.getChatRoomName()});
}
- if (!SUCCESS.equals(returnCode) &&
+ if (!SUCCESS.equals(returnCode) &&
!AUTHENTICATION_FAILED.equals(returnCode))
{
MUCActivator.getAlertUIService().showAlertPopup(
@@ -804,7 +804,7 @@ else if(SUBSCRIPTION_ALREADY_EXISTS.equals(returnCode))
{
chatRoomWrapper.savePassword(new String(password));
}
-
+
if(subject != null)
{
try
@@ -819,9 +819,9 @@ else if(SUBSCRIPTION_ALREADY_EXISTS.equals(returnCode))
}
}
}
-
+
/**
- * Finds the ChatRoomWrapper instance associated with the
+ * Finds the ChatRoomWrapper instance associated with the
* source contact.
* @param contact the source contact.
* @return the ChatRoomWrapper instance.
@@ -833,17 +833,17 @@ public ChatRoomWrapper findChatRoomWrapperFromSourceContact(
return null;
ChatRoomSourceContact chatRoomContact = (ChatRoomSourceContact) contact;
return chatRoomList.findChatRoomWrapperFromChatRoomID(
- chatRoomContact.getChatRoomID(), chatRoomContact.getProvider());
+ chatRoomContact.getChatRoomID(), chatRoomContact.getProvider());
}
-
+
/**
- * Finds the ChatRoomWrapper instance associated with the
+ * Finds the ChatRoomWrapper instance associated with the
* chat room.
* @param chatRoomID the id of the chat room.
* @param pps the provider of the chat room.
* @return the ChatRoomWrapper instance.
*/
- public ChatRoomWrapper findChatRoomWrapperFromChatRoomID(String chatRoomID,
+ public ChatRoomWrapper findChatRoomWrapperFromChatRoomID(String chatRoomID,
ProtocolProviderService pps)
{
return chatRoomList.findChatRoomWrapperFromChatRoomID(chatRoomID, pps);
@@ -851,34 +851,34 @@ public ChatRoomWrapper findChatRoomWrapperFromChatRoomID(String chatRoomID,
/**
* Searches for chat room wrapper in chat room list by chat room.
- *
+ *
* @param chatRoom the chat room.
* @param create if true and the chat room wrapper is not found new
* chatRoomWrapper is created.
* @return found chat room wrapper or the created chat room wrapper.
*/
@Override
- public ChatRoomWrapper getChatRoomWrapperByChatRoom(ChatRoom chatRoom,
+ public ChatRoomWrapper getChatRoomWrapperByChatRoom(ChatRoom chatRoom,
boolean create)
{
ChatRoomWrapper chatRoomWrapper
= chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom);
-
+
if ((chatRoomWrapper == null) && create)
{
ChatRoomProviderWrapper parentProvider
= chatRoomList.findServerWrapperFromProvider(
chatRoom.getParentProvider());
-
- chatRoomWrapper
+
+ chatRoomWrapper
= new ChatRoomWrapperImpl(
parentProvider, chatRoom);
-
+
chatRoomList.addChatRoom(chatRoomWrapper);
}
return chatRoomWrapper;
}
-
+
/**
* Goes through the locally stored chat rooms list and for each
* {@link ChatRoomWrapper} tries to find the corresponding server stored
@@ -901,13 +901,13 @@ public void synchronizeOpSetWithLocalContactList(
{
chatRoomProvider = chatRoomList.addRegisteredChatProvider(protocolProvider);
}
-
+
if (chatRoomProvider != null)
{
chatRoomProvider.synchronizeProvider();
}
}
-
+
/**
* Returns an iterator to the list of chat room providers.
*
@@ -917,7 +917,7 @@ public Iterator getChatRoomProviders()
{
return chatRoomList.getChatRoomProviders();
}
-
+
/**
* Removes the given ChatRoom from the list of all chat rooms.
*
@@ -938,7 +938,7 @@ public void addChatRoomProviderWrapperListener(
{
chatRoomList.addChatRoomProviderWrapperListener(listener);
}
-
+
/**
* Removes the ChatRoomProviderWrapperListener to the listener list.
*
@@ -949,7 +949,7 @@ public void removeChatRoomProviderWrapperListener(
{
chatRoomList.removeChatRoomProviderWrapperListener(listener);
}
-
+
/**
* Returns the ChatRoomProviderWrapper that correspond to the
* given ProtocolProviderService. If the list doesn't contain a
@@ -964,7 +964,7 @@ public ChatRoomProviderWrapper findServerWrapperFromProvider(
{
return chatRoomList.findServerWrapperFromProvider(protocolProvider);
}
-
+
/**
* Returns the ChatRoomWrapper that correspond to the given
* ChatRoom. If the list of chat rooms doesn't contain a
@@ -978,10 +978,10 @@ public ChatRoomWrapper findChatRoomWrapperFromChatRoom(ChatRoom chatRoom)
{
return chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom);
}
-
+
/**
* Opens a chat window for the chat room.
- *
+ *
* @param room the chat room.
*/
public void openChatRoom(ChatRoomWrapper room)
@@ -990,7 +990,7 @@ public void openChatRoom(ChatRoomWrapper room)
{
room = createChatRoom(
room.getChatRoomName(),
- room.getParentProvider().getProtocolProvider(),
+ room.getParentProvider().getProtocolProvider(),
new ArrayList(),"", false, false, true);
// leave the chatroom because getChatRoom().isJoined() returns true
@@ -1007,22 +1007,22 @@ public void openChatRoom(ChatRoomWrapper room)
.getParentProvider().getProtocolProvider(), room
.getChatRoomID(), "userNickName");
String subject = null;
-
+
if (savedNick == null)
{
String[] joinOptions = ChatRoomJoinOptionsDialog.getJoinOptions(
- room.getParentProvider().getProtocolProvider(),
+ room.getParentProvider().getProtocolProvider(),
room.getChatRoomID(),
getDefaultNickname(
room.getParentProvider().getProtocolProvider()));
savedNick = joinOptions[0];
subject = joinOptions[1];
-
+
}
-
+
if (savedNick != null)
{
- joinChatRoom(room, savedNick, null,
+ joinChatRoom(room, savedNick, null,
subject);
}
else
@@ -1031,7 +1031,7 @@ public void openChatRoom(ChatRoomWrapper room)
MUCActivator.getUIService().openChatRoomWindow(room);
}
-
+
/**
* Returns default nickname for chat room based on the given provider.
* @param pps the given protocol provider service
@@ -1042,13 +1042,13 @@ public String getDefaultNickname(ProtocolProviderService pps)
final OperationSetServerStoredAccountInfo accountInfoOpSet
= pps.getOperationSet(
OperationSetServerStoredAccountInfo.class);
-
+
String displayName = "";
if (accountInfoOpSet != null)
{
displayName = AccountInfoUtils.getDisplayName(accountInfoOpSet);
}
-
+
if(displayName == null || displayName.length() == 0)
{
displayName = MUCActivator.getGlobalDisplayDetailsService()
@@ -1064,14 +1064,14 @@ public String getDefaultNickname(ProtocolProviderService pps)
}
}
}
-
+
return displayName;
}
-
+
/**
- * Returns instance of the ServerChatRoomContactSourceService
+ * Returns instance of the ServerChatRoomContactSourceService
* contact source.
- * @return instance of the ServerChatRoomContactSourceService
+ * @return instance of the ServerChatRoomContactSourceService
* contact source.
*/
public ContactSourceService getServerChatRoomsContactSourceForProvider(
@@ -1079,4 +1079,15 @@ public ContactSourceService getServerChatRoomsContactSourceForProvider(
{
return new ServerChatRoomContactSourceService(pps);
}
+
+ /**
+ * Returns true if the contact is ChatRoomSourceContact
+ *
+ * @param contact the contact
+ * @return true if the contact is ChatRoomSourceContact
+ */
+ public boolean isMUCSourceContact(SourceContact contact)
+ {
+ return (contact instanceof ChatRoomSourceContact);
+ }
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetUserSearchJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetUserSearchJabberImpl.java
new file mode 100644
index 000000000..a883272e1
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetUserSearchJabberImpl.java
@@ -0,0 +1,279 @@
+/*
+ * Jitsi, 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.jabber;
+
+import java.util.*;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smackx.*;
+import org.jivesoftware.smackx.ReportedData.*;
+
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * This operation set provides utility methods for user search implementation.
+ *
+ * @author Hristo Terezov
+ */
+public class OperationSetUserSearchJabberImpl
+ implements OperationSetUserSearch, RegistrationStateChangeListener
+{
+ /**
+ * The logger.
+ */
+ private static final Logger logger =
+ Logger.getLogger(OperationSetUserSearchJabberImpl.class);
+
+ /**
+ * The UserSearchManager instance which actually implements the
+ * user search.
+ */
+ private UserSearchManager searchManager = null;
+
+ /**
+ * The ProtocolProviderService instance.
+ */
+ private ProtocolProviderServiceJabberImpl provider;
+
+ /**
+ * The user search service name.
+ */
+ private String serviceName = null;
+
+ /**
+ * Whether the user search service is enabled or not.
+ */
+ private Boolean userSearchEnabled = false;
+
+ /**
+ * A list of UserSearchProviderListener listeners which will be
+ * notified when the provider user search feature is enabled or disabled.
+ */
+ private List listeners
+ = new ArrayList();
+
+ /**
+ * The property name of the user search service name.
+ */
+ private static final String USER_SEARCH_SERVICE_NAME
+ = "USER_SEARCH_SERVICE_NAME";
+
+ /**
+ * Constructs new OperationSetUserSearchJabberImpl instance.
+ * @param provider the provider associated with the operation set.
+ */
+ protected OperationSetUserSearchJabberImpl(
+ ProtocolProviderServiceJabberImpl provider)
+ {
+ this.provider = provider;
+ serviceName = provider.getAccountID().getAccountPropertyString(
+ USER_SEARCH_SERVICE_NAME, "");
+ if(serviceName.equals(""))
+ {
+ provider.addRegistrationStateChangeListener(this);
+ }
+ else
+ {
+ setUserSearchEnabled(true);
+ }
+ }
+
+ /**
+ * Sets the userSearchEnabled property and fires
+ * UserSearchProviderEvent event.
+ *
+ * @param isEnabled the value to be set.
+ */
+ private void setUserSearchEnabled(boolean isEnabled)
+ {
+ userSearchEnabled = isEnabled;
+ int type = (isEnabled? UserSearchProviderEvent.PROVIDER_ADDED
+ : UserSearchProviderEvent.PROVIDER_REMOVED);
+ fireUserSearchProviderEvent(new UserSearchProviderEvent(provider, type));
+ }
+
+ /**
+ * Fires UserSearchProviderEvent event.
+ * @param event the event to be fired.
+ */
+ private void fireUserSearchProviderEvent(UserSearchProviderEvent event)
+ {
+ List tmpListeners;
+ synchronized (listeners)
+ {
+ tmpListeners = new ArrayList(listeners);
+ }
+ for(UserSearchProviderListener l : tmpListeners)
+ l.onUserSearchProviderEvent(event);
+ }
+
+ @Override
+ public void registrationStateChanged(RegistrationStateChangeEvent evt)
+ {
+ if(evt.getNewState().equals(RegistrationState.REGISTERED))
+ {
+ discoverSearchService();
+ }
+ else if(evt.getNewState() == RegistrationState.UNREGISTERED
+ || evt.getNewState() == RegistrationState.AUTHENTICATION_FAILED
+ || evt.getNewState() == RegistrationState.CONNECTION_FAILED)
+ {
+ synchronized (userSearchEnabled)
+ {
+ setUserSearchEnabled(false);
+ }
+ }
+ }
+
+ /**
+ * Tries to discover the user search service name.
+ */
+ private void discoverSearchService()
+ {
+ new Thread()
+ {
+ public void run()
+ {
+ synchronized (userSearchEnabled)
+ {
+ List serviceNames
+ = UserSearchManager.getAvailableServiceNames(
+ provider);
+ if(!serviceNames.isEmpty())
+ {
+ serviceName = serviceNames.get(0);
+ setUserSearchEnabled(true);
+ }
+ else
+ {
+ setUserSearchEnabled(false);
+ }
+ }
+
+ };
+ }.start();
+ }
+
+ /**
+ * Creates the UserSearchManager instance.
+ */
+ public void createSearchManager()
+ {
+ if(searchManager == null)
+ {
+ searchManager
+ = new UserSearchManager(provider.getConnection(), serviceName);
+ }
+ }
+
+ /**
+ * Releases the UserSearchManager instance.
+ */
+ public void removeSearchManager()
+ {
+ searchManager = null;
+ }
+
+ /**
+ * Performs user search for the searched string and returns the JIDs of the
+ * found contacts.
+ *
+ * @param searchedString the text we want to query the server.
+ * @return the list of found JIDs
+ */
+ public List search(String searchedString)
+ {
+ ReportedData data = null;
+ try
+ {
+ data = searchManager.searchForString(searchedString);
+ }
+ catch (XMPPException e)
+ {
+ logger.error(e);
+ return null;
+ }
+
+ if(data == null)
+ {
+ logger.error("No data have been received from server.");
+ return null;
+ }
+ Iterator columns = data.getColumns();
+ Iterator rows = data.getRows();
+ if(columns == null || rows == null)
+ {
+ logger.error("The received data is corrupted.");
+ return null;
+ }
+
+ Column jidColumn = null;
+ while(columns.hasNext())
+ {
+ Column tmpCollumn = columns.next();
+ if(tmpCollumn.getType().equals(FormField.TYPE_JID_SINGLE))
+ {
+ jidColumn = tmpCollumn;
+ break;
+ }
+ }
+
+ if(jidColumn == null)
+ {
+ logger.error("No jid collumn provided by the server.");
+ return null;
+ }
+ List result = new ArrayList();
+ while(rows.hasNext())
+ {
+ Row row = rows.next();
+ result.add((String)row.getValues(jidColumn.getVariable()).next());
+ }
+ return result;
+ }
+
+ /**
+ * Adds UserSearchProviderListener instance to the list of
+ * listeners.
+ *
+ * @param l the listener to be added
+ */
+ public void addUserSearchProviderListener(UserSearchProviderListener l)
+ {
+ synchronized (listeners)
+ {
+ if(!listeners.contains(l))
+ listeners.add(l);
+ }
+ }
+
+ /**
+ * Removes UserSearchProviderListener instance from the list of
+ * listeners.
+ *
+ * @param l the listener to be removed
+ */
+ public void removeUserSearchProviderListener(UserSearchProviderListener l)
+ {
+ synchronized (listeners)
+ {
+ listeners.remove(l);
+ }
+ }
+
+ /**
+ * Returns true if the user search service is enabled.
+ *
+ * @return true if the user search service is enabled.
+ */
+ public boolean isEnabled()
+ {
+ return userSearchEnabled;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java
index d06553bd9..2b1c983b4 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java
@@ -220,6 +220,12 @@ public class ProtocolProviderServiceJabberImpl
private static final String XMPP_DSCP_PROPERTY =
"net.java.sip.communicator.impl.protocol.XMPP_DSCP";
+ /**
+ * Indicates if user search is disabled.
+ */
+ private static final String IS_USER_SEARCH_DISABLED_PROPERTY
+ = "USER_SEARCH_DISABLED";
+
/**
* Google voice domain name.
*/
@@ -1858,6 +1864,14 @@ protected void initialize(String screenname,
addSupportedOperationSet(OperationSetCusaxUtils.class,
opsetCusaxCusaxUtils);
+ boolean isUserSearchDisabled = accountID.getAccountPropertyBoolean(
+ IS_USER_SEARCH_DISABLED_PROPERTY, true);
+ if(!isUserSearchDisabled)
+ {
+ addSupportedOperationSet(OperationSetUserSearch.class,
+ new OperationSetUserSearchJabberImpl(this));
+ }
+
isInitialized = true;
}
}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProviderManagerExt.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProviderManagerExt.java
index 32e73e4ca..71b50d9bf 100644
--- a/src/net/java/sip/communicator/impl/protocol/jabber/ProviderManagerExt.java
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProviderManagerExt.java
@@ -6,6 +6,7 @@
*/
package net.java.sip.communicator.impl.protocol.jabber;
+import net.java.sip.communicator.impl.protocol.jabber.extensions.usersearch.*;
import net.java.sip.communicator.util.*;
import org.jivesoftware.smack.packet.*;
@@ -139,8 +140,7 @@ public void load()
LastActivity.class);
//
- //addProvider("query", "jabber:iq:search",
- // org.jivesoftware.smackx.search.UserSearch.Provider.class);
+ addProvider("query", "jabber:iq:search",UserSearchProvider.class);
//
//addProvider("sharedgroup", "http://www.jivesoftware.org/protocol/sharedgroup",
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/UserSearchManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/UserSearchManager.java
new file mode 100644
index 000000000..10975d78a
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/UserSearchManager.java
@@ -0,0 +1,185 @@
+/*
+ * Jitsi, 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.jabber;
+
+import net.java.sip.communicator.impl.protocol.jabber.extensions.usersearch.*;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smackx.*;
+import org.jivesoftware.smackx.packet.*;
+
+import java.util.*;
+
+/**
+ * The UserSearchManager implements the user search (XEP-0055). It
+ * implements searching a contacts by string.
+ *
+ * @author Hristo Terezov
+ */
+public class UserSearchManager {
+ /**
+ * The object that represents the connection to the server.
+ */
+ private Connection con;
+
+ /**
+ * Last received search form from the server.
+ */
+ private UserSearchIQ userSearchForm = null;
+
+ /**
+ * The user search service name.
+ */
+ private String searchService;
+
+ /**
+ * Creates a new UserSearchManager.
+ *
+ * @param con the Connection to use.
+ * @param searchService the user search service name.
+ */
+ public UserSearchManager(Connection con, String searchService) {
+ this.con = con;
+ this.searchService = searchService;
+ }
+
+ /**
+ * Returns a list with the available user search service names for a given
+ * provider.
+ * @param provider the provider.
+ * @return a list with the available user search service names for a given
+ * provider.
+ */
+ public static List getAvailableServiceNames(
+ ProtocolProviderServiceJabberImpl provider)
+ {
+ final List searchServices = new ArrayList();
+ ScServiceDiscoveryManager discoManager = provider.getDiscoveryManager();
+ DiscoverItems items;
+ try
+ {
+ items = discoManager.discoverItems(
+ provider.getConnection().getServiceName());
+ }
+ catch (XMPPException e)
+ {
+ return searchServices;
+ }
+ Iterator iter = items.getItems();
+ while (iter.hasNext())
+ {
+ DiscoverItems.Item item = iter.next();
+ try
+ {
+ DiscoverInfo info;
+ info = discoManager.discoverInfo(item.getEntityID());
+
+ if (info.containsFeature("jabber:iq:search")
+ && !info.containsFeature("http://jabber.org/protocol/muc"))
+ {
+ searchServices.add(item.getEntityID());
+ }
+ }
+ catch (Exception e) {
+ continue;
+ }
+ }
+ return searchServices;
+ }
+
+ /**
+ * Returns the form to fill out to perform a search.
+ *
+ * @return the form to fill out to perform a search.
+ * @throws XMPPException thrown if a server error has occurred.
+ */
+ private void getSearchForm() throws XMPPException {
+ UserSearchIQ search = new UserSearchIQ();
+ search.setType(IQ.Type.GET);
+ search.setTo(searchService);
+
+ PacketCollector collector = con.createPacketCollector(
+ new PacketIDFilter(search.getPacketID()));
+ con.sendPacket(search);
+
+ IQ response = (IQ) collector.nextResult(
+ SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ userSearchForm = (UserSearchIQ)response;
+ }
+
+ /**
+ * Performs user search for the searched string.
+ *
+ * @param searchedString
+ * @return the ReportedData instance which represents the search
+ * results.
+ * @throws XMPPException thrown if a server error has occurred.
+ */
+ public ReportedData searchForString(String searchedString) throws XMPPException
+ {
+ if(userSearchForm == null)
+ getSearchForm();
+ UserSearchIQ search = new UserSearchIQ();
+ search.setType(IQ.Type.SET);
+ search.setTo(searchService);
+
+ Form form = userSearchForm.getAnswerForm();
+ if(form != null)
+ {
+ Iterator fields = form.getFields();
+ while (fields.hasNext())
+ {
+ FormField formField = fields.next();
+ if(formField.getType().equals(FormField.TYPE_BOOLEAN))
+ {
+ form.setAnswer(formField.getVariable(), true);
+ }
+ else if(formField.getType().equals(FormField.TYPE_TEXT_SINGLE))
+ {
+ form.setAnswer(formField.getVariable(), searchedString);
+ }
+ }
+ search.setForm(form.getDataFormToSend());
+ }
+ else
+ {
+ for(String field : userSearchForm.getFields())
+ search.addField(field, searchedString);
+ }
+
+ PacketCollector collector = con.createPacketCollector(
+ new PacketIDFilter(search.getPacketID()));
+
+ con.sendPacket(search);
+
+ UserSearchIQ response = (UserSearchIQ) collector.nextResult(
+ SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+
+ return response.getData();
+ }
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/usersearch/UserSearchIQ.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/usersearch/UserSearchIQ.java
new file mode 100644
index 000000000..03b191eda
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/usersearch/UserSearchIQ.java
@@ -0,0 +1,135 @@
+/*
+ * Jitsi, 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.jabber.extensions.usersearch;
+
+import java.util.*;
+
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.util.*;
+import org.jivesoftware.smackx.*;
+import org.jivesoftware.smackx.packet.*;
+
+/**
+ * Implements the IQ packets for user search (XEP-0055)
+ *
+ * @author Hristo Terezov
+ */
+public class UserSearchIQ extends IQ {
+ /**
+ * This field represents the result of the search.
+ */
+ private ReportedData data;
+
+ /**
+ * This map stores the supported fields that are not defined in the data
+ * form and their values.
+ */
+ private Map simpleFieldsNames
+ = new HashMap();
+
+ @Override
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("");
+ if(getExtension("x", "jabber:x:data") != null)
+ {
+ buf.append(getExtensionsXML());
+ }
+ else
+ {
+ buf.append(getItemsToSearch());
+ }
+ buf.append("");
+ return buf.toString();
+ }
+
+ /**
+ * Sets the data property of the class.
+ * @param data the data to be set.
+ */
+ public void setData(ReportedData data)
+ {
+ this.data = data;
+ }
+
+ /**
+ * Returns the data property of the class.
+ * @return
+ */
+ public ReportedData getData()
+ {
+ ReportedData data = ReportedData.getReportedDataFrom(this);
+ if(data == null)
+ return this.data;
+ return data;
+ }
+
+ /**
+ * Adds filter field to the IQ packet and value for the field.
+ * @param field the field name.
+ * @param value the value of the field.
+ */
+ public void addField(String field, String value)
+ {
+ simpleFieldsNames.put(field, value);
+ }
+
+ /**
+ * Returns XML string with the fields that are not included in the data form
+ * @return XML string with the fields that are not included in the data form
+ */
+ private String getItemsToSearch() {
+ StringBuilder buf = new StringBuilder();
+
+ if (simpleFieldsNames.isEmpty()) {
+ return "";
+ }
+
+ for (String name : simpleFieldsNames.keySet())
+ {
+ String value = simpleFieldsNames.get(name);
+ if (value != null && value.trim().length() > 0) {
+ buf.append("<").append(name).append(">")
+ .append(StringUtils.escapeForXML(value)).append("")
+ .append(name).append(">");
+ }
+ }
+
+ return buf.toString();
+ }
+
+ /**
+ * Returns the names of the fields that are not included in the data form.
+ * @return the field names.
+ */
+ public Set getFields()
+ {
+ return simpleFieldsNames.keySet();
+ }
+
+ /**
+ * Creates and returns answer form.
+ * @return the answer form.
+ */
+ public Form getAnswerForm()
+ {
+ Form form = Form.getFormFrom(this);
+ if(form == null)
+ return null;
+ return form.createAnswerForm();
+ }
+
+ /**
+ * Sets data form in the IQ packet.
+ * @param form the form to be set.
+ */
+ public void setForm(DataForm form)
+ {
+ addExtension(form);
+ }
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/usersearch/UserSearchProvider.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/usersearch/UserSearchProvider.java
new file mode 100644
index 000000000..fb55d1312
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/usersearch/UserSearchProvider.java
@@ -0,0 +1,145 @@
+/*
+ * Jitsi, 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.jabber.extensions.usersearch;
+
+import java.util.*;
+
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.provider.*;
+import org.jivesoftware.smack.util.*;
+import org.jivesoftware.smackx.*;
+import org.jivesoftware.smackx.ReportedData.*;
+import org.xmlpull.v1.*;
+
+/**
+ * Internal Search service Provider. It parses the UserSeachIQ packets.
+ */
+public class UserSearchProvider implements IQProvider
+{
+ public IQ parseIQ(XmlPullParser parser) throws Exception
+ {
+ UserSearchIQ search = new UserSearchIQ();
+ boolean done = false;
+
+ while (!done)
+ {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG
+ && parser.getName().equals("item"))
+ {
+ search.setData(parseItems(parser));
+ return search;
+ }
+ else if (eventType == XmlPullParser.START_TAG
+ && parser.getName().equals("instructions"))
+ {
+ continue;
+ }
+ else if (eventType == XmlPullParser.START_TAG
+ && !parser.getNamespace().equals("jabber:x:data"))
+ {
+ search.addField(parser.getName(), null);
+
+ }
+ else if (eventType == XmlPullParser.START_TAG
+ && parser.getNamespace().equals("jabber:x:data"))
+ {
+ // Otherwise, it must be a packet extension.
+ search.addExtension(PacketParserUtils.parsePacketExtension(
+ parser.getName(), parser.getNamespace(), parser));
+
+ }
+ else if (eventType == XmlPullParser.END_TAG)
+ {
+ if (parser.getName().equals("query"))
+ done = true;
+ }
+ }
+
+ return search;
+ }
+
+ /**
+ * Parses the items from the result packet.
+ * @param parser the parser.
+ * @return ReportedData instance with the search results.
+ * @throws Exception if parser error occurred.
+ */
+ protected ReportedData parseItems(XmlPullParser parser) throws Exception
+ {
+ ReportedData data = new ReportedData();
+ data.addColumn(
+ new ReportedData.Column("JID", "jid", FormField.TYPE_JID_SINGLE));
+
+ boolean done = false;
+
+ List fields
+ = new ArrayList();
+
+ while (!done)
+ {
+ if (parser.getAttributeCount() > 0)
+ {
+ String jid = parser.getAttributeValue("", "jid");
+ List valueList = new ArrayList();
+ valueList.add(jid);
+ ReportedData.Field field = new ReportedData.Field("jid", valueList);
+ fields.add(field);
+ }
+
+ int eventType = parser.next();
+
+ if (eventType == XmlPullParser.START_TAG
+ && parser.getName().equals("item"))
+ {
+ fields = new ArrayList();
+ }
+ else if (eventType == XmlPullParser.END_TAG
+ && parser.getName().equals("item"))
+ {
+ ReportedData.Row row = new ReportedData.Row(fields);
+ data.addRow(row);
+ }
+ else if (eventType == XmlPullParser.START_TAG)
+ {
+ String name = parser.getName();
+ String value = parser.nextText();
+
+ List valueList = new ArrayList();
+ valueList.add(value);
+ ReportedData.Field field
+ = new ReportedData.Field(name, valueList);
+ fields.add(field);
+
+ boolean exists = false;
+ Iterator cols = data.getColumns();
+ while (cols.hasNext())
+ {
+ ReportedData.Column column = cols.next();
+ if (column.getVariable().equals(name))
+ exists = true;
+ }
+
+ // Column name should be the same
+ if (!exists)
+ {
+ ReportedData.Column column = new ReportedData.Column(
+ name, name, "text-single");
+ data.addColumn(column);
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG)
+ {
+ if (parser.getName().equals("query"))
+ done = true;
+ }
+
+ }
+
+ return data;
+ }
+}
diff --git a/src/net/java/sip/communicator/plugin/usersearch/UserSearchActivator.java b/src/net/java/sip/communicator/plugin/usersearch/UserSearchActivator.java
new file mode 100644
index 000000000..893688119
--- /dev/null
+++ b/src/net/java/sip/communicator/plugin/usersearch/UserSearchActivator.java
@@ -0,0 +1,376 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license. See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.plugin.usersearch;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.contactsource.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.util.*;
+
+import org.jitsi.service.resources.ResourceManagementService;
+import org.osgi.framework.*;
+
+/**
+ * Activates the user search plugin which includes the user search contact
+ * source.
+ *
+ * @author Hristo Terezov
+ */
+public class UserSearchActivator
+ implements BundleActivator
+{
+ /**
+ * List with the available protocol providers that may support user search.
+ */
+ private static LinkedList userSearchProviders;
+
+ /**
+ * The bundle context.
+ */
+ public static BundleContext bundleContext;
+
+ /**
+ * A listener for
+ */
+ private static UserSearchProviderStateListener userSeachListener = null;
+
+ /**
+ * A list with providers that support user search.
+ */
+ private static LinkedList supportedProviders
+ = new LinkedList();
+
+ /**
+ * A list of listeners that will be notified about adding and removing
+ * providers that support user search.
+ */
+ private static LinkedList listeners
+ = new LinkedList();
+
+ /**
+ * The ServiceRegistration instance for the contact source.
+ */
+ private static ServiceRegistration contactSourceRegistration = null;
+
+ /**
+ * The Logger used by the
+ * UserSearchActivator class for logging output.
+ */
+ private static Logger logger = Logger.getLogger(UserSearchActivator.class);
+
+ /**
+ * Contact source instance.
+ */
+ private static UserSearchContactSource userSearchContactSource = null;
+
+ /**
+ * The resource service.
+ */
+ private static ResourceManagementService resources = null;
+
+ /**
+ * Initializes a list of all currently providers and a list with the
+ * providers that support user search.
+ */
+ public static void initUserSearchProviders()
+ {
+ if (userSearchProviders != null)
+ return;
+
+ userSearchProviders = new LinkedList();
+
+ bundleContext.addServiceListener(new ProtocolProviderRegListener());
+
+ ServiceReference[] serRefs = null;
+ try
+ {
+ // get all registered provider factories
+ serRefs
+ = bundleContext.getServiceReferences(
+ ProtocolProviderFactory.class.getName(),
+ null);
+ }
+ catch (InvalidSyntaxException e)
+ {
+ logger.error("LoginManager : " + e);
+ }
+
+ if (serRefs != null)
+ {
+ for (ServiceReference serRef : serRefs)
+ {
+ ProtocolProviderFactory providerFactory
+ = (ProtocolProviderFactory)
+ bundleContext.getService(serRef);
+
+ ProtocolProviderService protocolProvider;
+
+ for (AccountID accountID
+ : providerFactory.getRegisteredAccounts())
+ {
+ serRef = providerFactory.getProviderForAccount(accountID);
+
+ protocolProvider
+ = (ProtocolProviderService) bundleContext
+ .getService(serRef);
+
+ handleProviderAdded(protocolProvider);
+
+ }
+ }
+ }
+ return;
+ }
+
+ /**
+ * Returns the list of providers that support user search.
+ * @return the list of providers that support user search.
+ */
+ public static LinkedList getSupportedProviders()
+ {
+ return supportedProviders;
+ }
+
+ /**
+ * Adds new UserSearchSupportedProviderListener to the list of
+ * listeners.
+ * @param listener the listener to be added.
+ */
+ public static void addUserSearchSupportedProviderListener(
+ UserSearchSupportedProviderListener listener)
+ {
+ synchronized (listeners)
+ {
+ if(!listeners.contains(listener))
+ listeners.add(listener);
+ }
+ }
+
+ /**
+ * Removes UserSearchSupportedProviderListener from the list of
+ * listeners.
+ * @param listener the listener to be removed.
+ */
+ public static void removeUserSearchSupportedProviderListener(
+ UserSearchSupportedProviderListener listener)
+ {
+ synchronized (listeners)
+ {
+ listeners.remove(listener);
+ }
+ }
+
+ /**
+ * Listens for ProtocolProviderService registrations.
+ */
+ private static class ProtocolProviderRegListener
+ implements ServiceListener
+ {
+ public void serviceChanged(ServiceEvent event)
+ {
+ ServiceReference serviceRef = event.getServiceReference();
+
+ // if the event is caused by a bundle being stopped, we don't want to
+ // know
+ if (serviceRef.getBundle().getState() == Bundle.STOPPING)
+ {
+ return;
+ }
+
+ Object service = bundleContext.getService(serviceRef);
+
+ // we don't care if the source service is not a protocol provider
+ if (!(service instanceof ProtocolProviderService))
+ {
+ return;
+ }
+
+ switch (event.getType())
+ {
+ case ServiceEvent.REGISTERED:
+ handleProviderAdded((ProtocolProviderService) service);
+ break;
+ case ServiceEvent.UNREGISTERING:
+ handleProviderRemoved((ProtocolProviderService) service);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Handles the registration of a new ProtocolProviderService. Adds
+ * the given protocolProvider to the list of queried providers.
+ *
+ * @param protocolProvider the ProtocolProviderService to add
+ */
+ private static void handleProviderAdded(
+ ProtocolProviderService protocolProvider)
+ {
+ if (protocolProvider.getOperationSet(
+ OperationSetServerStoredContactInfo.class) != null
+ && !userSearchProviders.contains(protocolProvider))
+ {
+ OperationSetUserSearch opSet
+ = protocolProvider.getOperationSet(OperationSetUserSearch.class);
+ if(opSet == null)
+ return;
+ if(userSeachListener == null)
+ userSeachListener = new UserSearchProviderStateListener();
+ opSet.addUserSearchProviderListener(userSeachListener);
+ if(opSet.isEnabled())
+ addSupportedProvider(protocolProvider);
+ userSearchProviders.add(protocolProvider);
+ }
+ }
+
+ /**
+ * Handles the un-registration of a ProtocolProviderService.
+ * Removes the given protocolProvider from the list of queried
+ * providers.
+ *
+ * @param protocolProvider the ProtocolProviderService to remove
+ */
+ private static void handleProviderRemoved(
+ ProtocolProviderService protocolProvider)
+ {
+ if (userSearchProviders.contains(protocolProvider))
+ {
+ userSearchProviders.remove(protocolProvider);
+ removeSupportedProvider(protocolProvider);
+ if(userSeachListener == null)
+ return;
+ OperationSetUserSearch opSet
+ = protocolProvider.getOperationSet(OperationSetUserSearch.class);
+ if(opSet == null)
+ return;
+ opSet.removeUserSearchProviderListener(userSeachListener);
+ }
+
+ }
+
+ /**
+ * Adds provider to the list of providers that support user search.
+ * @param provider the provider to be added
+ */
+ private static void addSupportedProvider(ProtocolProviderService provider)
+ {
+ if(!supportedProviders.contains(provider))
+ {
+ supportedProviders.add(provider);
+ LinkedList tmpListeners;
+ synchronized (listeners)
+ {
+ tmpListeners
+ = new LinkedList(
+ listeners);
+ }
+
+ for(UserSearchSupportedProviderListener l : tmpListeners)
+ {
+ l.providerAdded(provider);
+ }
+ if(supportedProviders.size() == 1)
+ {
+ if(userSearchContactSource == null)
+ userSearchContactSource = new UserSearchContactSource();
+ //register contact source
+ contactSourceRegistration = bundleContext.registerService(
+ ContactSourceService.class.getName(),
+ userSearchContactSource ,
+ null);
+ }
+ }
+
+ }
+
+ /**
+ * Removes provider from the list of providers that support user search.
+ * @param provider the procider to be removed.
+ */
+ private static void removeSupportedProvider(
+ ProtocolProviderService provider)
+ {
+ if(supportedProviders.contains(provider))
+ {
+ supportedProviders.remove(provider);
+ for(UserSearchSupportedProviderListener l : listeners)
+ {
+ l.providerRemoved(provider);
+ }
+
+ if(supportedProviders.isEmpty()
+ && contactSourceRegistration != null)
+ {
+ contactSourceRegistration.unregister();
+ contactSourceRegistration = null;
+ }
+ }
+ }
+
+ @Override
+ public void start(BundleContext context) throws Exception
+ {
+ bundleContext = context;
+
+ initUserSearchProviders();
+
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception
+ {
+ userSeachListener = null;
+ userSearchProviders.clear();
+ supportedProviders.clear();
+ listeners.clear();
+ contactSourceRegistration = null;
+ userSearchContactSource = null;
+ }
+
+ /**
+ * Returns a reference to the ResourceManagementService implementation
+ * currently registered in the bundle context or null if no such
+ * implementation was found.
+ *
+ * @return a reference to a ResourceManagementService implementation
+ * currently registered in the bundle context or null if no such
+ * implementation was found.
+ */
+ public static ResourceManagementService getResources()
+ {
+ if (resources == null)
+ {
+ resources
+ = ServiceUtils.getService(
+ bundleContext, ResourceManagementService.class);
+ }
+
+ return resources;
+ }
+
+ /**
+ * Listens for added or removed providers that support user search.
+ */
+ private static class UserSearchProviderStateListener
+ implements UserSearchProviderListener
+ {
+ @Override
+ public void onUserSearchProviderEvent(UserSearchProviderEvent event)
+ {
+ if(event.getType() == UserSearchProviderEvent.PROVIDER_ADDED)
+ {
+ addSupportedProvider(event.getProvider());
+ }
+ else if(event.getType() == UserSearchProviderEvent.PROVIDER_REMOVED)
+ {
+ removeSupportedProvider(event.getProvider());
+ }
+ }
+ }
+
+}
diff --git a/src/net/java/sip/communicator/plugin/usersearch/UserSearchContactSource.java b/src/net/java/sip/communicator/plugin/usersearch/UserSearchContactSource.java
new file mode 100644
index 000000000..762c577d8
--- /dev/null
+++ b/src/net/java/sip/communicator/plugin/usersearch/UserSearchContactSource.java
@@ -0,0 +1,64 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license. See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.plugin.usersearch;
+
+import net.java.sip.communicator.service.contactsource.*;
+
+/**
+ * The user search contact source.
+ *
+ * @author Hristo Terezov
+ */
+public class UserSearchContactSource
+ implements ContactSourceService
+{
+
+ /**
+ * The time to wait before actually start the searching. If the query is
+ * canceled before the timeout the query won't be started.
+ */
+ public static final long QUERY_DELAY = 500;
+
+ /**
+ * Returns the type of this contact source.
+ *
+ * @return the type of this contact source
+ */
+ public int getType()
+ {
+ return SEARCH_TYPE;
+ }
+
+ @Override
+ public String getDisplayName()
+ {
+ return UserSearchActivator.getResources().getI18NString(
+ "plugin.usersearch.USER_SEARCH");
+ }
+
+ @Override
+ public ContactQuery createContactQuery(String queryString)
+ {
+ return createContactQuery(queryString, -1);
+ }
+
+ @Override
+ public ContactQuery createContactQuery(String queryString, int contactCount)
+ {
+ if(queryString == null)
+ queryString = "";
+
+ UserSearchQuery query = new UserSearchQuery(queryString, this);
+ return query;
+ }
+
+ @Override
+ public int getIndex()
+ {
+ return 0;
+ }
+
+}
diff --git a/src/net/java/sip/communicator/plugin/usersearch/UserSearchQuery.java b/src/net/java/sip/communicator/plugin/usersearch/UserSearchQuery.java
new file mode 100644
index 000000000..0ba1fcb56
--- /dev/null
+++ b/src/net/java/sip/communicator/plugin/usersearch/UserSearchQuery.java
@@ -0,0 +1,295 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.plugin.usersearch;
+
+import java.util.*;
+import java.util.regex.*;
+
+import net.java.sip.communicator.service.contactsource.*;
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ * The UserSearchQuery is a query over
+ * UserSearchContactSource.
+ */
+public class UserSearchQuery
+ extends AsyncContactQuery
+ implements UserSearchSupportedProviderListener
+{
+ /**
+ * The query string.
+ */
+ private String queryString;
+
+ /**
+ * A list with the found contacts.
+ */
+ private List contacts
+ = new LinkedList();
+
+ /**
+ * A list of providers that are already displayed.
+ */
+ private List displayedProviders
+ = new LinkedList();
+
+ /**
+ * The number of the listeners added to the query.
+ */
+ private int contactQueryListenersCount = 0;
+
+ /**
+ * Creates an instance of ChatRoomQuery by specifying
+ * the parent contact source, the query string to match and the maximum
+ * result contacts to return.
+ *
+ * @param queryString the query string to match
+ * @param contactSource the parent contact source
+ */
+ public UserSearchQuery(String queryString,
+ UserSearchContactSource contactSource)
+ {
+ super(contactSource,
+ Pattern.compile(queryString, Pattern.CASE_INSENSITIVE
+ | Pattern.LITERAL), true);
+ this.queryString = queryString;
+
+ }
+
+ @Override
+ protected void run()
+ {
+ try
+ {
+ Thread.sleep(UserSearchContactSource.QUERY_DELAY);
+ }
+ catch (InterruptedException e)
+ { }
+ if(getStatus() == QUERY_CANCELED)
+ return;
+
+ for(ProtocolProviderService provider :
+ UserSearchActivator.getSupportedProviders())
+ {
+ providerAdded(provider);
+ }
+ }
+
+ /**
+ * Handles provider addition.
+ *
+ * @param provider the provider that was added.
+ */
+ public void providerAdded(ProtocolProviderService provider)
+ {
+ synchronized (displayedProviders)
+ {
+ if(displayedProviders.contains(provider))
+ return;
+ displayedProviders.add(provider);
+ }
+
+ OperationSetUserSearch opSetUserSearch
+ = provider.getOperationSet(
+ OperationSetUserSearch.class);
+ if(opSetUserSearch == null)
+ {
+ return;
+ }
+ opSetUserSearch.createSearchManager();
+ List jidList = opSetUserSearch.search(queryString);
+ if(jidList == null || jidList.isEmpty())
+ return;
+
+ List> supportedOpSets
+ = new ArrayList>(1);
+ supportedOpSets.add(OperationSetPersistentPresence.class);
+
+ ContactDetail detail = new ContactDetail(queryString);
+
+ for(String jid : jidList)
+ {
+ List contactDetails
+ = new LinkedList();
+
+ ContactDetail jidDetail = new ContactDetail(jid);
+ jidDetail.setSupportedOpSets(supportedOpSets);
+
+ contactDetails.add(jidDetail);
+
+ contactDetails.add(detail);
+
+ UserSearchContact contact
+ = new UserSearchContact(
+ this,
+ getContactSource(),
+ jid,
+ contactDetails,
+ provider);
+ contact.setContactAddress(jid);
+ synchronized (contacts)
+ {
+ contacts.add(contact);
+ }
+
+ fireContactReceived(contact);
+ }
+ }
+
+ /**
+ * Handles provider removal.
+ *
+ * @param provider the provider that was removed.
+ */
+ public void providerRemoved(ProtocolProviderService provider)
+ {
+ synchronized (displayedProviders)
+ {
+ displayedProviders.remove(provider);
+ }
+
+ OperationSetUserSearch opSetUserSearch
+ = provider.getOperationSet(
+ OperationSetUserSearch.class);
+ if(opSetUserSearch == null)
+ return;
+ opSetUserSearch.removeSearchManager();
+ List tmpContacts;
+ synchronized (contacts)
+ {
+ tmpContacts = new LinkedList(contacts);
+ }
+ for(UserSearchContact contact : tmpContacts)
+ {
+ if(contact.getProvider().equals(provider))
+ {
+ synchronized (contacts)
+ {
+ contacts.remove(contact);
+ }
+ fireContactRemoved(contact);
+ }
+ }
+ }
+
+ /**
+ * Releases the resources of the query.
+ */
+ private void dispose()
+ {
+ UserSearchActivator.removeUserSearchSupportedProviderListener(this);
+
+ synchronized (contacts)
+ {
+ contacts.clear();
+ }
+
+ synchronized (displayedProviders)
+ {
+ displayedProviders.clear();
+ }
+ }
+
+ /**
+ * Cancels this ContactQuery.
+ *
+ * @see ContactQuery#cancel()
+ */
+ public void cancel()
+ {
+ dispose();
+
+ super.cancel();
+ }
+
+ /**
+ * If query has status changed to cancel, let's clear listeners.
+ * @param status {@link ContactQuery#QUERY_CANCELED},
+ * {@link ContactQuery#QUERY_COMPLETED}
+ */
+ public void setStatus(int status)
+ {
+ if(status == QUERY_CANCELED)
+ dispose();
+
+ super.setStatus(status);
+ }
+
+ @Override
+ public void addContactQueryListener(ContactQueryListener l)
+ {
+ super.addContactQueryListener(l);
+ contactQueryListenersCount++;
+ if(contactQueryListenersCount == 1)
+ {
+ UserSearchActivator.addUserSearchSupportedProviderListener(this);
+ }
+ }
+
+ @Override
+ public void removeContactQueryListener(ContactQueryListener l)
+ {
+ super.removeContactQueryListener(l);
+ contactQueryListenersCount--;
+ if(contactQueryListenersCount == 0)
+ {
+ dispose();
+ }
+ }
+
+ /**
+ * A specific user search source contact.
+ */
+ private class UserSearchContact
+ extends SortedGenericSourceContact
+ {
+ /**
+ * The provider associated with the contact.
+ */
+ private ProtocolProviderService provider;
+
+ public UserSearchContact(ContactQuery parentQuery,
+ ContactSourceService cSourceService, String displayName,
+ List contactDetails,
+ ProtocolProviderService provider)
+ {
+ super(parentQuery, cSourceService, displayName, contactDetails);
+ this.provider = provider;
+ }
+
+ /**
+ * Returns the provider associated with the contact.
+ * @return the provider associated with the contact.
+ */
+ public ProtocolProviderService getProvider()
+ {
+ return provider;
+ }
+
+ }
+ @Override
+ public List getQueryResults()
+ {
+ List queryResults;
+ synchronized (contacts)
+ {
+ queryResults = new LinkedList(contacts);
+ }
+ return queryResults;
+ }
+
+ @Override
+ public int getQueryResultCount()
+ {
+ synchronized (contacts)
+ {
+ return contacts.size();
+ }
+ }
+
+}
+
diff --git a/src/net/java/sip/communicator/plugin/usersearch/UserSearchSupportedProviderListener.java b/src/net/java/sip/communicator/plugin/usersearch/UserSearchSupportedProviderListener.java
new file mode 100644
index 000000000..759df1a2f
--- /dev/null
+++ b/src/net/java/sip/communicator/plugin/usersearch/UserSearchSupportedProviderListener.java
@@ -0,0 +1,31 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.plugin.usersearch;
+
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ * A interface for a listener that will be notified when providers that support
+ * user search are added or removed.
+ * @author Hristo Terezov
+ */
+public interface UserSearchSupportedProviderListener
+{
+ /**
+ * Handles provider addition.
+ *
+ * @param provider the provider that was added.
+ */
+ public void providerAdded(ProtocolProviderService provider);
+
+ /**
+ * Handles provider removal.
+ *
+ * @param provider the provider that was removed.
+ */
+ public void providerRemoved(ProtocolProviderService provider);
+}
diff --git a/src/net/java/sip/communicator/plugin/usersearch/usersearch.manifest.mf b/src/net/java/sip/communicator/plugin/usersearch/usersearch.manifest.mf
new file mode 100644
index 000000000..30c377cb0
--- /dev/null
+++ b/src/net/java/sip/communicator/plugin/usersearch/usersearch.manifest.mf
@@ -0,0 +1,12 @@
+Bundle-Activator: net.java.sip.communicator.plugin.usersearch.UserSearchActivator
+Bundle-Name: User search contact source
+Bundle-Description: User search contact source
+Bundle-Vendor: jitsi.org
+Bundle-Version: 0.0.1
+Bundle-SymbolicName: net.java.sip.communicator.plugin.usersearch
+Import-Package: org.osgi.framework,
+ net.java.sip.communicator.service.contactsource,
+ net.java.sip.communicator.service.protocol,
+ net.java.sip.communicator.service.protocol.event,
+ net.java.sip.communicator.util,
+ org.jitsi.service.resources
diff --git a/src/net/java/sip/communicator/service/muc/MUCService.java b/src/net/java/sip/communicator/service/muc/MUCService.java
index 7728370ea..b2b157371 100644
--- a/src/net/java/sip/communicator/service/muc/MUCService.java
+++ b/src/net/java/sip/communicator/service/muc/MUCService.java
@@ -13,47 +13,47 @@
import net.java.sip.communicator.util.*;
/**
- * The MUC service provides interface for the chat rooms. It connects the GUI
+ * The MUC service provides interface for the chat rooms. It connects the GUI
* with the protcol.
- *
+ *
* @author Hristo Terezov
*/
public abstract class MUCService
{
/**
- * The value for chat room configuration property to open automatically on
- * activity
+ * The value for chat room configuration property to open automatically on
+ * activity
*/
public static String OPEN_ON_ACTIVITY = "on_activity";
-
+
/**
- * The value for chat room configuration property to open automatically on
- * message
+ * The value for chat room configuration property to open automatically on
+ * message
*/
public static String OPEN_ON_MESSAGE = "on_message";
-
+
/**
- * The value for chat room configuration property to open automatically on
- * important messages.
+ * The value for chat room configuration property to open automatically on
+ * important messages.
*/
public static String OPEN_ON_IMPORTANT_MESSAGE = "on_important_message";
-
+
/**
* Map for the auto open configuration values and their text representation
*/
public static Map autoOpenConfigValuesTexts
= new HashMap();
-
+
static
{
- autoOpenConfigValuesTexts.put(OPEN_ON_ACTIVITY,
+ autoOpenConfigValuesTexts.put(OPEN_ON_ACTIVITY,
"service.gui.OPEN_ON_ACTIVITY");
- autoOpenConfigValuesTexts.put(OPEN_ON_MESSAGE,
+ autoOpenConfigValuesTexts.put(OPEN_ON_MESSAGE,
"service.gui.OPEN_ON_MESSAGE");
- autoOpenConfigValuesTexts.put(OPEN_ON_IMPORTANT_MESSAGE,
+ autoOpenConfigValuesTexts.put(OPEN_ON_IMPORTANT_MESSAGE,
"service.gui.OPEN_ON_IMPORTANT_MESSAGE");
}
-
+
/**
* Sets chat room open automatically property
* @param pps the provider
@@ -69,7 +69,7 @@ public static void setChatRoomAutoOpenOption(
pps,
chatRoomId, "openAutomatically", value);
}
-
+
/**
* Returns the value of the chat room open automatically property
* @param pps the provider
@@ -87,13 +87,13 @@ public static String getChatRoomAutoOpenOption(
/**
* Fires a ChatRoomListChangedEvent event.
- *
+ *
* @param chatRoomWrapper the chat room.
* @param eventID the id of the event.
*/
public abstract void fireChatRoomListChangedEvent(
ChatRoomWrapper chatRoomWrapper, int eventID);
-
+
/**
* Joins the given chat room with the given password and manages all the
* exceptions that could occur during the join process.
@@ -101,12 +101,12 @@ public abstract void fireChatRoomListChangedEvent(
* @param chatRoomWrapper the chat room to join.
* @param nickName the nickname we choose for the given chat room.
* @param password the password.
- * @param subject the subject which will be set to the room after the user
+ * @param subject the subject which will be set to the room after the user
* join successful.
*/
public abstract void joinChatRoom( ChatRoomWrapper chatRoomWrapper,
String nickName, byte[] password, String subject);
-
+
/**
* Creates a chat room, by specifying the chat room name, the parent
* protocol provider and eventually, the contacts invited to participate in
@@ -122,9 +122,9 @@ public abstract void joinChatRoom( ChatRoomWrapper chatRoomWrapper,
public abstract ChatRoomWrapper createChatRoom(String roomName,
ProtocolProviderService protocolProvider, Collection contacts,
String reason, boolean persistent);
-
-
-
+
+
+
/**
* Creates a private chat room, by specifying the parent
* protocol provider and eventually, the contacts invited to participate in
@@ -139,8 +139,8 @@ public abstract ChatRoomWrapper createChatRoom(String roomName,
public abstract ChatRoomWrapper createPrivateChatRoom(
ProtocolProviderService protocolProvider, Collection contacts,
String reason, boolean persistent);
-
-
+
+
/**
* Creates a chat room, by specifying the chat room name, the parent
* protocol provider and eventually, the contacts invited to participate in
@@ -158,7 +158,7 @@ public abstract ChatRoomWrapper createPrivateChatRoom(
public abstract ChatRoomWrapper createChatRoom(String roomName,
ProtocolProviderService protocolProvider, Collection contacts,
String reason, boolean join, boolean persistent, boolean isPrivate);
-
+
/**
* Joins the room with the given name though the given chat room provider.
*
@@ -167,7 +167,7 @@ public abstract ChatRoomWrapper createChatRoom(String roomName,
*/
public abstract void joinChatRoom( String chatRoomName,
ChatRoomProviderWrapper chatRoomProvider);
-
+
/**
* Returns existing chat rooms for the given chatRoomProvider.
* @param chatRoomProvider the ChatRoomProviderWrapper, which
@@ -176,8 +176,8 @@ public abstract void joinChatRoom( String chatRoomName,
*/
public abstract List getExistingChatRooms(
ChatRoomProviderWrapper chatRoomProvider);
-
-
+
+
/**
* Called to accept an incoming invitation. Adds the invitation chat room
* to the list of chat rooms and joins it.
@@ -185,7 +185,7 @@ public abstract List getExistingChatRooms(
* @param invitation the invitation to accept.
*/
public abstract void acceptInvitation(ChatRoomInvitation invitation);
-
+
/**
* Rejects the given invitation with the specified reason.
*
@@ -197,7 +197,7 @@ public abstract List getExistingChatRooms(
public abstract void rejectInvitation( OperationSetMultiUserChat multiUserChatOpSet,
ChatRoomInvitation invitation,
String reason);
-
+
/**
* Determines whether a specific ChatRoom is private i.e.
* represents a one-to-one conversation which is not a channel. Since the
@@ -229,7 +229,7 @@ public static boolean isPrivate(ChatRoom chatRoom)
}
return false;
}
-
+
/**
* Leaves the given chat room.
*
@@ -239,18 +239,18 @@ public static boolean isPrivate(ChatRoom chatRoom)
public abstract ChatRoomWrapper leaveChatRoom(ChatRoomWrapper chatRoomWrapper);
/**
- * Finds ChatRoomWrapper instance associated with the given source
+ * Finds ChatRoomWrapper instance associated with the given source
* contact.
* @param contact the contact.
- * @return ChatRoomWrapper instance associated with the given
+ * @return ChatRoomWrapper instance associated with the given
* source contact.
*/
public abstract ChatRoomWrapper findChatRoomWrapperFromSourceContact(
SourceContact contact);
-
+
/**
* Searches for chat room wrapper in chat room list by chat room.
- *
+ *
* @param chatRoom the chat room.
* @param create if true and the chat room wrapper is not found new
* chatRoomWrapper is created.
@@ -258,7 +258,7 @@ public abstract ChatRoomWrapper findChatRoomWrapperFromSourceContact(
*/
public abstract ChatRoomWrapper getChatRoomWrapperByChatRoom(
ChatRoom chatRoom, boolean create);
-
+
/**
* Returns the multi user chat operation set for the given protocol provider.
*
@@ -277,7 +277,7 @@ public static OperationSetMultiUserChat getMultiUserChatOpSet(
? (OperationSetMultiUserChat) opSet
: null;
}
-
+
/**
* Goes through the locally stored chat rooms list and for each
* {@link ChatRoomWrapper} tries to find the corresponding server stored
@@ -292,21 +292,21 @@ public static OperationSetMultiUserChat getMultiUserChatOpSet(
public abstract void synchronizeOpSetWithLocalContactList(
ProtocolProviderService protocolProvider,
final OperationSetMultiUserChat opSet);
-
+
/**
* Returns an iterator to the list of chat room providers.
*
* @return an iterator to the list of chat room providers.
*/
public abstract Iterator getChatRoomProviders();
-
+
/**
* Removes the given ChatRoom from the list of all chat rooms.
*
* @param chatRoomWrapper the ChatRoomWrapper to remove
*/
public abstract void removeChatRoom(ChatRoomWrapper chatRoomWrapper);
-
+
/**
* Adds a ChatRoomProviderWrapperListener to the listener list.
*
@@ -314,7 +314,7 @@ public abstract void synchronizeOpSetWithLocalContactList(
*/
public abstract void addChatRoomProviderWrapperListener(
ChatRoomProviderWrapperListener listener);
-
+
/**
* Removes the ChatRoomProviderWrapperListener to the listener list.
*
@@ -322,7 +322,7 @@ public abstract void addChatRoomProviderWrapperListener(
*/
public abstract void removeChatRoomProviderWrapperListener(
ChatRoomProviderWrapperListener listener);
-
+
/**
* Returns the ChatRoomProviderWrapper that correspond to the
* given ProtocolProviderService. If the list doesn't contain a
@@ -334,7 +334,7 @@ public abstract void removeChatRoomProviderWrapperListener(
*/
public abstract ChatRoomProviderWrapper findServerWrapperFromProvider(
ProtocolProviderService protocolProvider);
-
+
/**
* Returns the ChatRoomWrapper that correspond to the given
* ChatRoom. If the list of chat rooms doesn't contain a
@@ -346,14 +346,14 @@ public abstract ChatRoomProviderWrapper findServerWrapperFromProvider(
*/
public abstract ChatRoomWrapper findChatRoomWrapperFromChatRoom(
ChatRoom chatRoom);
-
+
/**
* Opens a chat window for the chat room.
- *
+ *
* @param room the chat room.
*/
public abstract void openChatRoom(ChatRoomWrapper room);
-
+
/**
* Returns default nickname for chat room based on the given provider.
* @param pps the given protocol provider service
@@ -361,14 +361,21 @@ public abstract ChatRoomWrapper findChatRoomWrapperFromChatRoom(
*/
public abstract String getDefaultNickname(
ProtocolProviderService pps);
-
+
/**
- * Returns instance of the ServerChatRoomContactSourceService
+ * Returns instance of the ServerChatRoomContactSourceService
* contact source.
- * @return instance of the ServerChatRoomContactSourceService
+ * @return instance of the ServerChatRoomContactSourceService
* contact source.
*/
- public abstract ContactSourceService
+ public abstract ContactSourceService
getServerChatRoomsContactSourceForProvider(ChatRoomProviderWrapper pps);
+ /**
+ * Returns true if the contact is ChatRoomSourceContact
+ *
+ * @param contact the contact
+ * @return true if the contact is ChatRoomSourceContact
+ */
+ public abstract boolean isMUCSourceContact(SourceContact contact);
}
diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetUserSearch.java b/src/net/java/sip/communicator/service/protocol/OperationSetUserSearch.java
new file mode 100644
index 000000000..d5e4759b9
--- /dev/null
+++ b/src/net/java/sip/communicator/service/protocol/OperationSetUserSearch.java
@@ -0,0 +1,62 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.service.protocol;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.event.*;
+
+/**
+ * This operation set provides interface for user search service.
+ *
+ * @author Hristo Terezov
+ */
+public interface OperationSetUserSearch
+ extends OperationSet
+{
+ /**
+ * Creates the search manager.
+ */
+ public void createSearchManager();
+
+ /**
+ * Removes search manager.
+ */
+ public void removeSearchManager();
+
+ /**
+ * Performs user search for the searched string and returns the contact
+ * addresses of the found contacts.
+ *
+ * @param searchedString the text we want to query the server.
+ * @return the list of found contact addresses.
+ */
+ public List search(String searchedString);
+
+ /**
+ * Returns true if the user search service is enabled.
+ *
+ * @return true if the user search service is enabled.
+ */
+ public boolean isEnabled();
+
+ /**
+ * Adds UserSearchProviderListener instance to the list of
+ * listeners.
+ *
+ * @param l the listener to be added
+ */
+ public void addUserSearchProviderListener(UserSearchProviderListener l);
+
+ /**
+ * Removes UserSearchProviderListener instance from the list of
+ * listeners.
+ *
+ * @param l the listener to be removed
+ */
+ public void removeUserSearchProviderListener(UserSearchProviderListener l);
+}
diff --git a/src/net/java/sip/communicator/service/protocol/event/UserSearchProviderEvent.java b/src/net/java/sip/communicator/service/protocol/event/UserSearchProviderEvent.java
new file mode 100644
index 000000000..763c7db9a
--- /dev/null
+++ b/src/net/java/sip/communicator/service/protocol/event/UserSearchProviderEvent.java
@@ -0,0 +1,69 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license. See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.service.protocol.event;
+
+import java.util.EventObject;
+
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ * Notifies UserSearchProviderListener that a provider that supports
+ * user search is added or removed.
+ * @author Hristo Terezov
+ */
+public class UserSearchProviderEvent
+ extends EventObject
+{
+ /**
+ * The serial ID.
+ */
+ private static final long serialVersionUID = -1285649707213476360L;
+
+ /**
+ * A type that indicates that the provider is added.
+ */
+ public static int PROVIDER_ADDED = 0;
+
+ /**
+ * A type that indicates that the provider is removed.
+ */
+ public static int PROVIDER_REMOVED = 1;
+
+ /**
+ * The type of the event.
+ */
+ private final int type;
+
+ /**
+ * Constructs new UserSearchProviderEvent event.
+ * @param provider the provider.
+ * @param type the type of the event.
+ */
+ public UserSearchProviderEvent(ProtocolProviderService provider, int type)
+ {
+ super(provider);
+ this.type = type;
+ }
+
+ /**
+ * Returns the provider associated with the event.
+ * @return the provider associated with the event.
+ */
+ public ProtocolProviderService getProvider()
+ {
+ return (ProtocolProviderService) getSource();
+ }
+
+ /**
+ * Returns the type of the event.
+ * @return the type of the event.
+ */
+ public int getType()
+ {
+ return type;
+ }
+
+}
diff --git a/src/net/java/sip/communicator/service/protocol/event/UserSearchProviderListener.java b/src/net/java/sip/communicator/service/protocol/event/UserSearchProviderListener.java
new file mode 100644
index 000000000..a3ed0ff60
--- /dev/null
+++ b/src/net/java/sip/communicator/service/protocol/event/UserSearchProviderListener.java
@@ -0,0 +1,20 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license. See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.service.protocol.event;
+
+/**
+ * An interface for listener that will be notified when provider that supports
+ * user search is added or removed.
+ * @author Hristo Terezov
+ */
+public interface UserSearchProviderListener
+{
+ /**
+ * Notifies the listener with UserSearchProviderEvent event.
+ * @param event the event
+ */
+ public void onUserSearchProviderEvent(UserSearchProviderEvent event);
+}