/*
* 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.contactsource;
import java.util.*;
import java.util.regex.*;
/**
* Provides an abstract implementation of a ContactQuery which runs in
* a separate Thread.
*
* @author Lyubomir Marinov
* @param the very type of ContactSourceService which performs the
* ContactQuery
*/
public abstract class AsyncContactQuery
extends AbstractContactQuery
{
/**
* The {@link #query} in the form of a String telephone number if
* such parsing, formatting and validation is possible; otherwise,
* null.
*/
private String phoneNumberQuery;
/**
* The Pattern for which the associated
* ContactSourceService is being queried.
*/
protected final Pattern query;
/**
* The indicator which determines whether there has been an attempt to
* convert {@link #query} to {@link #phoneNumberQuery}. If the conversion has
* been successful, phoneNumberQuery will be non-null.
*/
private boolean queryIsConvertedToPhoneNumber;
/**
* The SourceContacts which match {@link #query}.
*/
private final List queryResults
= new LinkedList();
/**
* The Thread in which this AsyncContactQuery is
* performing {@link #query}.
*/
private Thread thread;
/**
* Initializes a new AsyncContactQuery instance which is to perform
* a specific query on behalf of a specific contactSource.
*
* @param contactSource the ContactSourceService which is to
* perform the new ContactQuery instance
* @param query the Pattern for which contactSource is
* being queried
*/
protected AsyncContactQuery(T contactSource, Pattern query)
{
super(contactSource);
this.query = query;
}
/**
* Adds a specific SourceContact to the list of
* SourceContacts to be returned by this ContactQuery in
* response to {@link #getQueryResults()}.
*
* @param sourceContact the SourceContact to be added to the
* queryResults of this ContactQuery
* @return true if the queryResults of this
* ContactQuery has changed in response to the call
*/
protected boolean addQueryResult(SourceContact sourceContact)
{
boolean changed;
synchronized (queryResults)
{
changed = queryResults.add(sourceContact);
}
if (changed)
fireContactReceived(sourceContact);
return changed;
}
/**
* Gets the {@link #query} of this AsyncContactQuery as a
* String which represents a phone number (if possible).
*
* @return a String which represents the query of this
* AsyncContactQuery as a phone number if such parsing, formatting
* and validation is possible; otherwise, null
*/
protected String getPhoneNumberQuery()
{
if ((phoneNumberQuery != null) && !queryIsConvertedToPhoneNumber)
{
try
{
String pattern = query.pattern();
if (pattern != null)
{
int patternLength = pattern.length();
if ((patternLength > 2)
&& (pattern.charAt(0) == '^')
&& (pattern.charAt(patternLength - 1) == '$'))
{
phoneNumberQuery
= pattern.substring(1, patternLength - 1);
}
}
}
finally
{
queryIsConvertedToPhoneNumber = true;
}
}
return phoneNumberQuery;
}
/**
* Gets the number of SourceContacts which match this
* ContactQuery.
*
* @return the number of SourceContact which match this
* ContactQuery
*/
public int getQueryResultCount()
{
synchronized (queryResults)
{
return queryResults.size();
}
}
/**
* Gets the List of SourceContacts which match this
* ContactQuery.
*
* @return the List of SourceContacts which match this
* ContactQuery
* @see ContactQuery#getQueryResults()
*/
public List getQueryResults()
{
List qr;
synchronized (queryResults)
{
qr = new ArrayList(queryResults.size());
qr.addAll(queryResults);
}
return qr;
}
/**
* Returns the query string, this query was created for.
*
* @return the query string, this query was created for
*/
public String getQueryString()
{
return query.toString();
}
/**
* Normalizes a String phone number by converting alpha characters
* to their respective digits on a keypad and then stripping non-digit
* characters.
*
* @param phoneNumber a String which represents a phone number to
* normalize
* @return a String which is a normalized form of the specified
* phoneNumber
*/
protected abstract String normalizePhoneNumber(String phoneNumber);
/**
* Determines whether a specific String phone number matches the
* {@link #query} of this AsyncContactQuery.
*
* @param phoneNumber the String which represents the phone number
* to match to the query of this AsyncContactQuery
* @return true if the specified phoneNumber matches the
* query of this AsyncContactQuery; otherwise,
* false
*/
protected abstract boolean phoneNumberMatches(String phoneNumber);
/**
* Performs this ContactQuery in a background Thread.
*/
protected abstract void run();
/**
* Starts this AsyncContactQuery.
*/
public synchronized void start()
{
if (thread == null)
{
thread
= new Thread()
{
@Override
public void run()
{
boolean completed = false;
try
{
AsyncContactQuery.this.run();
completed = true;
}
finally
{
synchronized (AsyncContactQuery.this)
{
if (thread == Thread.currentThread())
stopped(completed);
}
}
}
};
thread.setDaemon(true);
thread.start();
}
else
throw new IllegalStateException("thread");
}
/**
* Notifies this AsyncContactQuery that it has stopped performing
* in the associated background Thread.
*
* @param completed true if this ContactQuery has
* successfully completed, false if an error has been encountered
* during its execution
*/
protected void stopped(boolean completed)
{
if (getStatus() == QUERY_IN_PROGRESS)
setStatus(completed ? QUERY_COMPLETED : QUERY_ERROR);
}
}