/* * 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); } }