/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.java.sip.communicator.impl.gui.main.chat;
import java.util.*;
import javax.swing.*;
import net.java.sip.communicator.impl.gui.*;
import net.java.sip.communicator.impl.gui.utils.*;
import net.java.sip.communicator.service.contactlist.*;
import net.java.sip.communicator.service.contactlist.event.*;
import net.java.sip.communicator.service.filehistory.*;
import net.java.sip.communicator.service.metahistory.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;
/**
* An implementation of the <tt>ChatSession</tt> interface that represents a
* user-to-user chat session.
*
* @author Yana Stamcheva
* @author Lubomir Marinov
*
*/
public class MetaContactChatSession
extends ChatSession
implements MetaContactListListener,
ContactResourceListener
{
private final MetaContact metaContact;
private final MetaContactListService metaContactListService;
private ChatTransport currentChatTransport;
private final ChatSessionRenderer sessionRenderer;
/**
* Creates an instance of <tt>MetaContactChatSession</tt> by specifying the
* renderer, which gives the connection with the UI, the meta contact
* corresponding to the session and the protocol contact to be used as
* transport.
*
* @param sessionRenderer the renderer, which gives the connection with
* the UI.
* @param metaContact the meta contact corresponding to the session and the
* protocol contact.
* @param protocolContact the protocol contact to be used as transport.
* @param contactResource the specific resource to be used as transport
*/
public MetaContactChatSession( ChatSessionRenderer sessionRenderer,
MetaContact metaContact,
Contact protocolContact,
ContactResource contactResource)
{
this.sessionRenderer = sessionRenderer;
this.metaContact = metaContact;
persistableAddress
= protocolContact.getPersistableAddress();
ChatContact<?> chatContact = new MetaContactChatContact(metaContact);
chatParticipants.add(chatContact);
this.initChatTransports(protocolContact, contactResource);
// Obtain the MetaContactListService and add this class to it as a
// listener of all events concerning the contact list.
metaContactListService = GuiActivator.getContactListService();
if (metaContactListService != null)
metaContactListService.addMetaContactListListener(this);
}
/**
* Returns the name of this chat.
*
* @return the name of this chat
*/
@Override
public String getChatName()
{
String displayName = metaContact.getDisplayName();
if (displayName != null && displayName.length() > 0)
return metaContact.getDisplayName();
return GuiActivator.getResources().getI18NString("service.gui.UNKNOWN");
}
/**
* Returns a collection of the last N number of messages given by count.
*
* @param count The number of messages from history to return.
* @return a collection of the last N number of messages given by count.
*/
@Override
public Collection<Object> getHistory(int count)
{
final MetaHistoryService metaHistory
= GuiActivator.getMetaHistoryService();
// If the MetaHistoryService is not registered we have nothing to do
// here. The history could be "disabled" from the user
// through one of the configuration forms.
if (metaHistory == null)
return null;
return metaHistory.findLast(
chatHistoryFilter,
metaContact,
ConfigurationUtils.getChatHistorySize());
}
/**
* Returns a collection of the last N number of messages given by count.
*
* @param date The date up to which we're looking for messages.
* @param count The number of messages from history to return.
* @return a collection of the last N number of messages given by count.
*/
@Override
public Collection<Object> getHistoryBeforeDate(Date date, int count)
{
final MetaHistoryService metaHistory
= GuiActivator.getMetaHistoryService();
// If the MetaHistoryService is not registered we have nothing to do
// here. The history could be "disabled" from the user
// through one of the configuration forms.
if (metaHistory == null)
return null;
return metaHistory.findLastMessagesBefore(
chatHistoryFilter,
metaContact, date, ConfigurationUtils.getChatHistorySize());
}
/**
* Returns a collection of the last N number of messages given by count.
*
* @param date The date from which we're looking for messages.
* @param count The number of messages from history to return.
* @return a collection of the last N number of messages given by count.
*/
@Override
public Collection<Object> getHistoryAfterDate(Date date, int count)
{
final MetaHistoryService metaHistory
= GuiActivator.getMetaHistoryService();
// If the MetaHistoryService is not registered we have nothing to do
// here. The history could be "disabled" from the user
// through one of the configuration forms.
if (metaHistory == null)
return null;
return metaHistory.findFirstMessagesAfter(
chatHistoryFilter,
metaContact, date, ConfigurationUtils.getChatHistorySize());
}
/**
* Returns the start date of the history of this chat session.
*
* @return the start date of the history of this chat session.
*/
@Override
public Date getHistoryStartDate()
{
Date startHistoryDate = new Date(0);
MetaHistoryService metaHistory
= GuiActivator.getMetaHistoryService();
// If the MetaHistoryService is not registered we have nothing to do
// here. The history could be "disabled" from the user
// through one of the configuration forms.
if (metaHistory == null)
return startHistoryDate;
Collection<Object> firstMessage = metaHistory
.findFirstMessagesAfter(
chatHistoryFilter, metaContact, new Date(0), 1);
if(firstMessage.size() > 0)
{
Iterator<Object> i = firstMessage.iterator();
Object o = i.next();
if(o instanceof MessageDeliveredEvent)
{
MessageDeliveredEvent evt
= (MessageDeliveredEvent) o;
startHistoryDate = evt.getTimestamp();
}
else if(o instanceof MessageReceivedEvent)
{
MessageReceivedEvent evt = (MessageReceivedEvent) o;
startHistoryDate = evt.getTimestamp();
}
else if (o instanceof FileRecord)
{
FileRecord fileRecord = (FileRecord) o;
startHistoryDate = fileRecord.getDate();
}
}
return startHistoryDate;
}
/**
* Returns the end date of the history of this chat session.
*
* @return the end date of the history of this chat session.
*/
@Override
public Date getHistoryEndDate()
{
Date endHistoryDate = new Date(0);
MetaHistoryService metaHistory
= GuiActivator.getMetaHistoryService();
// If the MetaHistoryService is not registered we have nothing to do
// here. The history could be "disabled" from the user
// through one of the configuration forms.
if (metaHistory == null)
return endHistoryDate;
Collection<Object> lastMessage = metaHistory
.findLastMessagesBefore(
chatHistoryFilter, metaContact, new Date(Long.MAX_VALUE), 1);
if(lastMessage.size() > 0)
{
Iterator<Object> i1 = lastMessage.iterator();
Object o1 = i1.next();
if(o1 instanceof MessageDeliveredEvent)
{
MessageDeliveredEvent evt
= (MessageDeliveredEvent) o1;
endHistoryDate = evt.getTimestamp();
}
else if(o1 instanceof MessageReceivedEvent)
{
MessageReceivedEvent evt = (MessageReceivedEvent) o1;
endHistoryDate = evt.getTimestamp();
}
else if (o1 instanceof FileRecord)
{
FileRecord fileRecord = (FileRecord) o1;
endHistoryDate = fileRecord.getDate();
}
}
return endHistoryDate;
}
/**
* Returns the default mobile number used to send sms-es in this session.
*
* @return the default mobile number used to send sms-es in this session.
*/
@Override
public String getDefaultSmsNumber()
{
String smsNumber = null;
List<String> detailsList = metaContact.getDetails("mobile");
if (detailsList != null && detailsList.size() > 0)
{
smsNumber = detailsList.iterator().next();
}
return smsNumber;
}
/**
* Sets the default mobile number used to send sms-es in this session.
*
* @param smsPhoneNumber The default mobile number used to send sms-es in
* this session.
*/
@Override
public void setDefaultSmsNumber(String smsPhoneNumber)
{
metaContact.addDetail("mobile", smsPhoneNumber);
}
/**
* Initializes all chat transports for this chat session.
*
* @param protocolContact the <tt>Contact</tt> which is to be selected into
* this instance as the current i.e. its <tt>ChatTransport</tt> is to be
* selected as <tt>currentChatTransport</tt>
* @param contactResource the <tt>ContactResource</tt>, which is to be
* selected into this instance as the current <tt>ChatTransport</tt> if
* indicated
*/
private void initChatTransports(Contact protocolContact,
ContactResource contactResource)
{
Iterator<Contact> protocolContacts = metaContact.getContacts();
while (protocolContacts.hasNext())
{
Contact contact = protocolContacts.next();
addChatTransports( contact,
(contactResource != null)
? contactResource.getResourceName()
: null,
(protocolContact != null
&& contact.equals(protocolContact)));
}
}
/**
* Returns the currently used transport for all operation within this chat
* session.
*
* @return the currently used transport for all operation within this chat
* session.
*/
@Override
public ChatTransport getCurrentChatTransport()
{
return currentChatTransport;
}
/**
* Sets the transport that will be used for all operations within this chat
* session.
*
* @param chatTransport The transport to set as a default transport for this
* session.
*/
@Override
public void setCurrentChatTransport(ChatTransport chatTransport)
{
this.currentChatTransport = chatTransport;
fireCurrentChatTransportChange();
}
public void childContactsReordered(MetaContactGroupEvent evt)
{}
public void metaContactAdded(MetaContactEvent evt)
{}
public void metaContactGroupAdded(MetaContactGroupEvent evt)
{}
public void metaContactGroupModified(MetaContactGroupEvent evt)
{}
public void metaContactGroupRemoved(MetaContactGroupEvent evt)
{}
public void metaContactModified(MetaContactModifiedEvent evt)
{}
public void metaContactMoved(MetaContactMovedEvent evt)
{}
public void metaContactRemoved(MetaContactEvent evt)
{}
public void metaContactAvatarUpdated(MetaContactAvatarUpdateEvent evt) {}
/**
* Implements <tt>MetaContactListListener.metaContactRenamed</tt> method.
* When a meta contact is renamed, updates all related labels in this
* chat panel.
*
* @param evt the <tt>MetaContactRenamedEvent</tt> that notified us
*/
public void metaContactRenamed(MetaContactRenamedEvent evt)
{
String newName = evt.getNewDisplayName();
if(evt.getSourceMetaContact().equals(metaContact))
{
ChatContact<?> chatContact
= findChatContactByMetaContact(evt.getSourceMetaContact());
sessionRenderer.setContactName(chatContact, newName);
}
}
/**
* Implements <tt>MetaContactListListener.protoContactAdded</tt> method.
* When a proto contact is added, updates the "send via" selector box.
*/
public void protoContactAdded(ProtoContactEvent evt)
{
if (evt.getNewParent().equals(metaContact))
{
addChatTransports(evt.getProtoContact(), null, false);
}
}
/**
* Implements <tt>MetaContactListListener.protoContactMoved</tt> method.
* When a proto contact is moved, updates the "send via" selector box.
*
* @param evt the <tt>ProtoContactEvent</tt> that contains information about
* the old and the new parent of the contact
*/
public void protoContactMoved(ProtoContactEvent evt)
{
if (evt.getOldParent().equals(metaContact))
{
protoContactRemoved(evt);
}
else if (evt.getNewParent().equals(metaContact))
{
protoContactAdded(evt);
}
}
/**
* Implements <tt>MetaContactListListener.protoContactRemoved</tt> method.
* When a proto contact is removed, updates the "send via" selector box.
*/
public void protoContactRemoved(ProtoContactEvent evt)
{
if (evt.getOldParent().equals(metaContact))
{
Contact protoContact = evt.getProtoContact();
List<ChatTransport> transports;
synchronized (chatTransports)
{
transports = new ArrayList<ChatTransport>(chatTransports);
}
for (ChatTransport chatTransport : transports)
{
if(((MetaContactChatTransport) chatTransport).getContact()
.equals(protoContact))
{
removeChatTransport(chatTransport);
}
}
}
}
/**
* Returns the <tt>ChatContact</tt> corresponding to the given
* <tt>MetaContact</tt>.
*
* @param metaContact the <tt>MetaContact</tt> to search for
* @return the <tt>ChatContact</tt> corresponding to the given
* <tt>MetaContact</tt>.
*/
private ChatContact<?> findChatContactByMetaContact(MetaContact metaContact)
{
for (ChatContact<?> chatContact : chatParticipants)
{
Object chatSourceContact = chatContact.getDescriptor();
if (chatSourceContact instanceof MetaContact)
{
MetaContact metaChatContact = (MetaContact) chatSourceContact;
if (metaChatContact.equals(metaContact))
return chatContact;
}
else
{
assert chatSourceContact instanceof ChatRoomMember;
ChatRoomMember metaChatContact =
(ChatRoomMember)chatSourceContact;
Contact contact = metaChatContact.getContact();
MetaContact parentMetaContact
= GuiActivator.getContactListService()
.findMetaContactByContact(contact);
if(parentMetaContact != null
&& parentMetaContact.equals(metaContact))
return chatContact;
}
}
return null;
}
/**
* Disposes this chat session.
*/
@Override
public void dispose()
{
if (metaContactListService != null)
metaContactListService.removeMetaContactListListener(this);
for (ChatTransport chatTransport : chatTransports)
{
((Contact) chatTransport.getDescriptor())
.removeResourceListener(this);
chatTransport.dispose();
}
}
/**
* Returns the <tt>ChatSessionRenderer</tt> that provides the connection
* between this chat session and its UI.
*
* @return The <tt>ChatSessionRenderer</tt>.
*/
@Override
public ChatSessionRenderer getChatSessionRenderer()
{
return sessionRenderer;
}
/**
* Returns the descriptor of this chat session.
*
* @return the descriptor of this chat session.
*/
@Override
public Object getDescriptor()
{
return metaContact;
}
/**
* Returns <code>true</code> if this contact is persistent, otherwise
* returns <code>false</code>.
* @return <code>true</code> if this contact is persistent, otherwise
* returns <code>false</code>.
*/
@Override
public boolean isDescriptorPersistent()
{
if(metaContact == null)
return false;
Contact defaultContact = metaContact.getDefaultContact(
OperationSetBasicInstantMessaging.class);
if(defaultContact == null)
return false;
ContactGroup parent = defaultContact.getParentContactGroup();
boolean isParentPersist = true;
boolean isParentResolved = true;
if(parent != null)
{
isParentPersist = parent.isPersistent();
isParentResolved = parent.isResolved();
}
if(!defaultContact.isPersistent() &&
!defaultContact.isResolved() &&
!isParentPersist &&
!isParentResolved)
{
return false;
}
else
{
return true;
}
}
/**
* Implements the <tt>ChatPanel.getChatStatusIcon</tt> method.
*
* @return the status icon corresponding to this chat room
*/
@Override
public ImageIcon getChatStatusIcon()
{
if (this.metaContact == null)
{
return null;
}
Contact c = this.metaContact.getDefaultContact();
if (c == null)
{
return null;
}
PresenceStatus status = c.getPresenceStatus();
if (status == null)
{
return null;
}
return new ImageIcon(Constants.getStatusIcon(status));
}
/**
* Returns the avatar icon of this chat session.
*
* @return the avatar icon of this chat session.
*/
@Override
public byte[] getChatAvatar()
{
return metaContact.getAvatar();
}
public void protoContactModified(ProtoContactEvent evt)
{}
/**
* Implements ChatSession#isContactListSupported().
*/
@Override
public boolean isContactListSupported()
{
return false;
}
/**
* Adds all chat transports for the given <tt>contact</tt>.
*
* @param contact the <tt>Contact</tt>, which transports to add
* @param resourceName the resource to be pre-selected
*/
private void addChatTransports( Contact contact,
String resourceName,
boolean isSelectedContact)
{
MetaContactChatTransport chatTransport = null;
Collection<ContactResource> contactResources = contact.getResources();
if (contact.supportResources()
&& contactResources != null
&& contactResources.size() > 0)
{
if (contactResources.size() > 1)
{
chatTransport = new MetaContactChatTransport(this, contact);
addChatTransport(chatTransport);
}
Iterator<ContactResource> resourcesIter
= contactResources.iterator();
while (resourcesIter.hasNext())
{
ContactResource resource = resourcesIter.next();
MetaContactChatTransport resourceTransport
= new MetaContactChatTransport(
this,
contact,
resource,
(contact.getResources().size() > 1)
? true : false);
addChatTransport(resourceTransport);
if ((resourceName != null
&& resource.getResourceName().equals(resourceName))
|| contactResources.size() == 1)
{
chatTransport = resourceTransport;
}
}
}
else
{
chatTransport = new MetaContactChatTransport(this, contact);
addChatTransport(chatTransport);
}
// If this is the selected contact we set it as a selected transport.
if (isSelectedContact)
{
currentChatTransport = chatTransport;
sessionRenderer.setSelectedChatTransport(chatTransport, false);
}
// If no current transport is set we choose
// the first online from the list.
if (currentChatTransport == null)
{
for(ChatTransport ct : chatTransports)
{
if(ct.getStatus() != null
&& ct.getStatus().isOnline())
{
currentChatTransport = ct;
break;
}
}
// if still nothing selected, choose the first one
if (currentChatTransport == null)
currentChatTransport = chatTransports.get(0);
sessionRenderer
.setSelectedChatTransport(currentChatTransport, false);
}
if (contact.supportResources())
{
contact.addResourceListener(this);
}
}
private void addChatTransport(ChatTransport chatTransport)
{
synchronized (chatTransports)
{
chatTransports.add(chatTransport);
}
sessionRenderer.addChatTransport(chatTransport);
}
/**
* Removes the given <tt>ChatTransport</tt>.
*
* @param chatTransport the <tt>ChatTransport</tt>.
*/
private void removeChatTransport(ChatTransport chatTransport)
{
synchronized (chatTransports)
{
chatTransports.remove(chatTransport);
}
sessionRenderer.removeChatTransport(chatTransport);
chatTransport.dispose();
if(chatTransport.equals(currentChatTransport))
currentChatTransport = null;
}
/**
* Removes the given <tt>ChatTransport</tt>.
*
* @param contact the <tt>ChatTransport</tt>.
*/
private void removeChatTransports(Contact contact)
{
List<ChatTransport> transports;
synchronized (chatTransports)
{
transports = new ArrayList<ChatTransport>(chatTransports);
}
Iterator<ChatTransport> transportsIter = transports.iterator();
while (transportsIter.hasNext())
{
MetaContactChatTransport metaTransport
= (MetaContactChatTransport) transportsIter.next();
if (metaTransport.getContact().equals(contact))
removeChatTransport(metaTransport);
}
contact.removeResourceListener(this);
}
/**
* Updates the chat transports for the given contact.
*
* @param contact the contact, which related transports to update
*/
private void updateChatTransports(Contact contact)
{
MetaContactChatTransport currentTransport
= (MetaContactChatTransport) getCurrentChatTransport();
boolean isSelectedContact
= currentTransport.getContact().equals(contact);
boolean isResourceSelected
= isSelectedContact
&& currentTransport.getResourceName() != null;
String resourceName
= currentTransport.getResourceName();
removeChatTransports(contact);
if (isResourceSelected)
addChatTransports( contact,
resourceName,
true);
else
addChatTransports(contact, null, isSelectedContact);
}
/**
* Called when a new <tt>ContactResource</tt> has been added to the list
* of available <tt>Contact</tt> resources.
*
* @param event the <tt>ContactResourceEvent</tt> that notified us
*/
public void contactResourceAdded(ContactResourceEvent event)
{
Contact contact = event.getContact();
if (metaContact.containsContact(contact))
{
updateChatTransports(contact);
}
}
/**
* Called when a <tt>ContactResource</tt> has been removed to the list
* of available <tt>Contact</tt> resources.
*
* @param event the <tt>ContactResourceEvent</tt> that notified us
*/
public void contactResourceRemoved(ContactResourceEvent event)
{
Contact contact = event.getContact();
if (metaContact.containsContact(contact))
{
updateChatTransports(contact);
}
}
/**
* Called when a <tt>ContactResource</tt> in the list of available
* <tt>Contact</tt> resources has been modified.
*
* @param event the <tt>ContactResourceEvent</tt> that notified us
*/
public void contactResourceModified(ContactResourceEvent event)
{
Contact contact = event.getContact();
if (metaContact.containsContact(contact))
{
ChatTransport transport
= findChatTransportForResource(event.getContactResource());
if (transport != null)
sessionRenderer.updateChatTransportStatus(transport);
}
}
/**
* Finds the <tt>ChatTransport</tt> corresponding to the given contact
* <tt>resource</tt>.
*
* @param resource the <tt>ContactResource</tt>, which corresponding
* transport we're looking for
* @return the <tt>ChatTransport</tt> corresponding to the given contact
* <tt>resource</tt>
*/
private ChatTransport findChatTransportForResource(ContactResource resource)
{
List<ChatTransport> transports;
synchronized (chatTransports)
{
transports = new ArrayList<ChatTransport>(chatTransports);
}
Iterator<ChatTransport> transportsIter = transports.iterator();
while (transportsIter.hasNext())
{
ChatTransport chatTransport = transportsIter.next();
if (chatTransport.getDescriptor().equals(resource.getContact())
&& chatTransport.getResourceName() != null
&& chatTransport.getResourceName()
.equals(resource.getResourceName()))
return chatTransport;
}
return null;
}
}