/*
* 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.protocol.jabber;
import java.util.*;
import java.util.concurrent.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.jabberconstants.*;
import org.jivesoftware.smack.*;
/**
* The Jabber implementation of the service.protocol.Contact interface.
*
* @author Damian Minkov
* @author Lubomir Marinov
*/
public class ContactJabberImpl
extends AbstractContact
{
/**
* The jid of the user entry in roster.
*/
private String jid = null;
/**
* The image of the contact.
*/
private byte[] image = null;
/**
* The status of the contact as per the last status update we've
* received for it.
*/
private PresenceStatus status;
/**
* A reference to the ServerStoredContactListImpl
* instance that created us.
*/
private final ServerStoredContactListJabberImpl ssclCallback;
/**
* Whether or not this contact is being stored by the server.
*/
private boolean isPersistent = false;
/**
* Whether or not this contact has been resolved against the
* server.
*/
private boolean isResolved = false;
/**
* Used to store contact id when creating unresolved contacts.
*/
private final String tempId;
/**
* The current status message of this contact.
*/
private String statusMessage = null;
/**
* The display name of the roster entry.
*/
private String serverDisplayName = null;
/**
* The contact resources list.
*/
private Map<String, ContactResourceJabberImpl> resources = null;
/**
* Whether this contact is a mobile one.
*/
private boolean mobile = false;
/**
* Indicates whether or not this Contact instance represents the user used
* by this protocol provider to connect to the service.
*/
private boolean isLocal = false;
/**
* Creates an JabberContactImpl
* @param rosterEntry the RosterEntry object that we will be encapsulating.
* @param ssclCallback a reference to the ServerStoredContactListImpl
* instance that created us.
* @param isPersistent determines whether this contact is persistent or not.
* @param isResolved specifies whether the contact has been resolved against
* the server contact list
*/
ContactJabberImpl(RosterEntry rosterEntry,
ServerStoredContactListJabberImpl ssclCallback,
boolean isPersistent,
boolean isResolved)
{
// rosterEntry can be null when creating volatile contact
if(rosterEntry != null)
{
this.jid = rosterEntry.getUser();
this.serverDisplayName = rosterEntry.getName();
}
this.tempId = null;
this.ssclCallback = ssclCallback;
this.isPersistent = isPersistent;
this.isResolved = isResolved;
this.status =
((ProtocolProviderServiceJabberImpl) getProtocolProvider())
.getJabberStatusEnum().getStatus(JabberStatusEnum.OFFLINE);
}
/**
* Used to create unresolved contacts with specified id.
*
* @param id contact id
* @param ssclCallback the contact list handler that creates us.
* @param isPersistent is the contact persistent.
*/
ContactJabberImpl(String id,
ServerStoredContactListJabberImpl ssclCallback,
boolean isPersistent)
{
this.tempId = id;
this.ssclCallback = ssclCallback;
this.isPersistent = isPersistent;
this.isResolved = false;
this.status =
((ProtocolProviderServiceJabberImpl) getProtocolProvider())
.getJabberStatusEnum().getStatus(JabberStatusEnum.OFFLINE);
}
/**
* Returns the Jabber Userid of this contact
* @return the Jabber Userid of this contact
*/
public String getAddress()
{
if(isResolved)
return this.jid;
else
return tempId;
}
/**
* Determines whether or not this Contact instance represents the user used
* by this protocol provider to connect to the service.
*
* @return true if this Contact represents us (the local user) and false
* otherwise.
*/
public boolean isLocal()
{
return isLocal;
}
/**
* Returns an avatar if one is already present or <tt>null</tt> in case it
* is not in which case it the method also queues the contact for image
* updates.
*
* @return the avatar of this contact or <tt>null</tt> if no avatar is
* currently available.
*/
public byte[] getImage()
{
return getImage(true);
}
/**
* Returns a reference to the image assigned to this contact. If no image
* is present and the retrieveIfNecessary flag is true, we schedule the
* image for retrieval from the server.
*
* @param retrieveIfNecessary specifies whether the method should queue
* this contact for avatar update from the server.
*
* @return a reference to the image currently stored by this contact.
*/
public byte[] getImage(boolean retrieveIfNecessary)
{
if(image == null && retrieveIfNecessary)
ssclCallback.addContactForImageUpdate(this);
return image;
}
/**
* Set the image of the contact
*
* @param imgBytes the bytes of the image that we'd like to set.
*/
public void setImage(byte[] imgBytes)
{
this.image = imgBytes;
}
/**
* Returns a hashCode for this contact. The returned hashcode is actually
* that of the Contact's Address
* @return the hashcode of this Contact
*/
@Override
public int hashCode()
{
return getAddress().toLowerCase().hashCode();
}
/**
* Indicates whether some other object is "equal to" this one.
* <p>
*
* @param obj the reference object with which to compare.
* @return <tt>true</tt> if this object is the same as the obj
* argument; <tt>false</tt> otherwise.
*/
@Override
public boolean equals(Object obj)
{
if (obj == null
|| !(obj instanceof String || (obj instanceof ContactJabberImpl)))
return false;
if (obj instanceof ContactJabberImpl
&& ((ContactJabberImpl)obj).getAddress()
.equalsIgnoreCase(getAddress())
&& ((ContactJabberImpl)obj).getProtocolProvider()
== getProtocolProvider())
{
return true;
}
if (obj instanceof String)
{
int atIndex = getAddress().indexOf("@");
if (atIndex > 0)
{
if (getAddress().equalsIgnoreCase((String) obj)
|| getAddress().substring(0, atIndex)
.equalsIgnoreCase((String) obj))
{
return true;
}
}
else if (getAddress().equalsIgnoreCase((String) obj))
return true;
}
return false;
}
/**
* Returns a string representation of this contact, containing most of its
* representative details.
*
* @return a string representation of this contact.
*/
@Override
public String toString()
{
StringBuffer buff = new StringBuffer("JabberContact[ id=");
buff.append(getAddress()).
append(", isPersistent=").append(isPersistent).
append(", isResolved=").append(isResolved).append("]");
return buff.toString();
}
/**
* Sets the status that this contact is currently in. The method is to
* only be called as a result of a status update received from the server.
*
* @param status the JabberStatusEnum that this contact is currently in.
*/
void updatePresenceStatus(PresenceStatus status)
{
this.status = status;
}
/**
* Returns the status of the contact as per the last status update we've
* received for it. Note that this method is not to perform any network
* operations and will simply return the status received in the last
* status update message. If you want a reliable way of retrieving someone's
* status, you should use the <tt>queryContactStatus()</tt> method in
* <tt>OperationSetPresence</tt>.
* @return the PresenceStatus that we've received in the last status update
* pertaining to this contact.
*/
public PresenceStatus getPresenceStatus()
{
return status;
}
/**
* Returns a String that could be used by any user interacting modules for
* referring to this contact. An alias is not necessarily unique but is
* often more human readable than an address (or id).
* @return a String that can be used for referring to this contact when
* interacting with the user.
*/
public String getDisplayName()
{
if(isResolved)
{
RosterEntry entry = ssclCallback.getRosterEntry(jid);
String name = null;
if (entry != null)
name = entry.getName();
if ((name != null) && (name.trim().length() != 0))
return name;
}
return getAddress();
}
/**
* Returns the display name used when the contact was resolved.
* Used to detect renames.
* @return the display name.
*/
String getServerDisplayName()
{
return serverDisplayName;
}
/**
* Changes locally stored server display name.
* @param newValue
*/
void setServerDisplayName(String newValue)
{
this.serverDisplayName = newValue;
}
/**
* Returns a reference to the contact group that this contact is currently
* a child of or null if the underlying protocol does not support persistent
* presence.
* @return a reference to the contact group that this contact is currently
* a child of or null if the underlying protocol does not support persistent
* presence.
*/
public ContactGroup getParentContactGroup()
{
return ssclCallback.findContactGroup(this);
}
/**
* Returns a reference to the protocol provider that created the contact.
* @return a refererence to an instance of the ProtocolProviderService
*/
public ProtocolProviderService getProtocolProvider()
{
return ssclCallback.getParentProvider();
}
/**
* Determines whether or not this contact is being stored by the server.
* Non persistent contacts are common in the case of simple, non-persistent
* presence operation sets. They could however also be seen in persistent
* presence operation sets when for example we have received an event
* from someone not on our contact list. Non persistent contacts are
* volatile even when coming from a persistent presence op. set. They would
* only exist until the application is closed and will not be there next
* time it is loaded.
* @return true if the contact is persistent and false otherwise.
*/
public boolean isPersistent()
{
return isPersistent;
}
/**
* Specifies whether this contact is to be considered persistent or not. The
* method is to be used _only_ when a non-persistent contact has been added
* to the contact list and its encapsulated VolatileBuddy has been repalced
* with a standard buddy.
* @param persistent true if the buddy is to be considered persistent and
* false for volatile.
*/
void setPersistent(boolean persistent)
{
this.isPersistent = persistent;
}
/**
* Resolve this contact against the given entry
* @param entry the server stored entry
*/
void setResolved(RosterEntry entry)
{
if(isResolved)
return;
this.isResolved = true;
this.isPersistent = true;
this.jid = entry.getUser();
this.serverDisplayName = entry.getName();
}
/**
* Returns the persistent data
* @return the persistent data
*/
public String getPersistentData()
{
return null;
}
/**
* Determines whether or not this contact has been resolved against the
* server. Unresolved contacts are used when initially loading a contact
* list that has been stored in a local file until the presence operation
* set has managed to retrieve all the contact list from the server and has
* properly mapped contacts to their on-line buddies.
* @return true if the contact has been resolved (mapped against a buddy)
* and false otherwise.
*/
public boolean isResolved()
{
return isResolved;
}
/**
* Not used.
* @param persistentData the persistent data.
*/
public void setPersistentData(String persistentData)
{
}
/**
* Get source entry
* @return RosterEntry
*/
RosterEntry getSourceEntry()
{
return ssclCallback.getRosterEntry(jid);
}
/**
* Return the current status message of this contact.
*
* @return the current status message
*/
public String getStatusMessage()
{
return statusMessage;
}
/**
* Sets the current status message for this contact
* @param statusMessage the message
*/
protected void setStatusMessage(String statusMessage)
{
this.statusMessage = statusMessage;
}
/**
* Indicates if this contact supports resources.
*
* @return <tt>false</tt> to indicate that this contact doesn't support
* resources
*/
@Override
public boolean supportResources()
{
return true;
}
/**
* Returns an iterator over the resources supported by this contact or null
* if it doesn't support resources.
*
* @return null, as this contact doesn't support resources
*/
@Override
public Collection<ContactResource> getResources()
{
if (resources != null)
return new ArrayList<ContactResource>(resources.values());
return null;
}
/**
* Finds the <tt>ContactResource</tt> corresponding to the given jid.
*
* @param jid the jid for which we're looking for a resource
* @return the <tt>ContactResource</tt> corresponding to the given jid.
*/
ContactResource getResourceFromJid(String jid)
{
return resources.get(jid);
}
Map<String, ContactResourceJabberImpl> getResourcesMap()
{
if (resources == null)
resources
= new ConcurrentHashMap<String, ContactResourceJabberImpl>();
return this.resources;
}
/**
* Notifies all registered <tt>ContactResourceListener</tt>s that an event
* has occurred.
*
* @param event the <tt>ContactResourceEvent</tt> to fire notification for
*/
public void fireContactResourceEvent(ContactResourceEvent event)
{
super.fireContactResourceEvent(event);
}
/**
* Used from volatile contacts to handle jid and resources.
* Volatile contacts are always unavailable so do not remove their
* resources from the contact as it will be the only resource we will use.
* @param fullJid the full jid of the contact.
*/
protected void setJid(String fullJid)
{
this.jid = fullJid;
if (resources == null)
resources
= new ConcurrentHashMap<String, ContactResourceJabberImpl>();
}
/**
* Whether contact is mobile one. Logged in from mobile device.
* @return whether contact is mobile one.
*/
public boolean isMobile()
{
if(!getPresenceStatus().isOnline())
return false;
return mobile;
}
/**
* Changes the mobile indicator value.
* @param mobile is mobile
*/
void setMobile(boolean mobile)
{
this.mobile = mobile;
}
/**
* Changes the isLocal indicator.
* @param isLocal the new value.
*/
void setLocal(boolean isLocal)
{
this.isLocal = isLocal;
}
}