/*
* Copyright 2010-2015 Institut Pasteur.
*
* This file is part of Icy.
*
* Icy is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Icy is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Icy. If not, see <http://www.gnu.org/licenses/>.
*/
package icy.search;
import icy.plugin.PluginDescriptor;
import icy.plugin.PluginLoader;
import icy.plugin.PluginLoader.PluginLoaderEvent;
import icy.plugin.PluginLoader.PluginLoaderListener;
import icy.plugin.interface_.PluginSearchProvider;
import icy.system.IcyExceptionHandler;
import icy.system.thread.ThreadUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* SearchEngine for Icy.
*
* @author Stephane
*/
public class SearchEngine implements SearchResultConsumer, PluginLoaderListener
{
public interface SearchEngineListener
{
public void resultChanged(SearchEngine source, SearchResult result);
public void resultsChanged(SearchEngine source);
public void searchStarted(SearchEngine source);
public void searchCompleted(SearchEngine source);
}
/** Search result producer list */
final ArrayList<SearchResultProducer> producers;
/** Listener list */
private final List<SearchEngineListener> listeners;
/** Internals */
final Runnable searchProviderSetter;
String lastSearch;
public SearchEngine()
{
super();
producers = new ArrayList<SearchResultProducer>();
listeners = new ArrayList<SearchEngine.SearchEngineListener>();
lastSearch = "";
searchProviderSetter = new Runnable()
{
@Override
public void run()
{
final String savedSearch = lastSearch;
// cancel current search
cancelSearch();
synchronized (producers)
{
producers.clear();
}
// get search providers from plugin
for (PluginDescriptor plugin : PluginLoader.getPlugins(PluginSearchProvider.class))
{
try
{
final PluginSearchProvider psp = (PluginSearchProvider) plugin.getPluginClass().newInstance();
final SearchResultProducer producer = psp.getSearchProviderClass().newInstance();
synchronized (producers)
{
producers.add(producer);
}
}
catch (Throwable t)
{
IcyExceptionHandler.handleException(plugin, t, true);
}
}
synchronized (producers)
{
Collections.sort(producers);
}
// restore last search
search(savedSearch);
}
};
PluginLoader.addListener(this);
updateSearchProducers();
}
private void updateSearchProducers()
{
ThreadUtil.runSingle(searchProviderSetter);
}
/**
* Cancel the previous search request
*/
public void cancelSearch()
{
search("");
}
/**
* Performs the search request, mostly build the search result list.<br>
* Previous search is automatically canceled and replaced by the new one.
*
* @param text
* Text used for the search request.<br>
* If the text contains severals words then search is done by searching for all words in
* whatever order.
* @see #cancelSearch()
*/
public void search(String text)
{
// save search string
lastSearch = text;
// separate words
final String[] words = (text.split(" "));
// notify search started
fireSearchStartedEvent();
// launch new search
synchronized (producers)
{
for (SearchResultProducer producer : producers)
producer.search(words, this);
}
}
/**
* Returns {@link SearchResultProducer} attached to the search engine.
*/
public List<SearchResultProducer> getSearchResultProducers()
{
synchronized (producers)
{
return new ArrayList<SearchResultProducer>(producers);
}
}
/**
* Returns the number of currently producer processing a search request.
*/
public int getSearchingProducerCount()
{
int result = 0;
synchronized (producers)
{
for (SearchResultProducer producer : producers)
if (producer.isSearching())
result++;
}
return result;
}
/**
* Returns true if the search engine is currently processing a search request.
*/
public boolean isSearching()
{
synchronized (producers)
{
for (SearchResultProducer producer : producers)
if (producer.isSearching())
return true;
}
return false;
}
// /**
// * Set the list of provider classes.
// *
// * @param providers
// * : list of provider.
// */
// public void setProducer(List<SearchResultProducer> providers)
// {
// synchronized (producers)
// {
// producers.clear();
// producers.addAll(providers);
// }
// }
//
// /**
// * This method will register the provider class into the list of provider
// * classes. The {@link SearchResultProducer} object will not be used except for its
// * class.
// *
// * @param providerClass
// * : provider used to get the Class<?> from.
// */
// public void addProducer(Class<? extends SearchResultProducer> providerClass)
// {
// if (!providerClasses.contains(providerClass))
// providerClasses.add(providerClass);
// }
//
// /**
// * This method will unregister the provider class from the list of provider
// * class.
// *
// * @param providerClass
// * : provider used to get the Class<?> from.
// */
// public void removeProducer(Class<? extends SearchResultProducer> providerClass)
// {
// providerClasses.remove(providerClass);
// }
/**
* Returns the last search text.
*/
public String getLastSearch()
{
return lastSearch;
}
/**
* Returns SearchResult at specified index.
*/
public SearchResult getResult(int index)
{
final List<SearchResult> results = getResults();
if ((index >= 0) && (index < results.size()))
return results.get(index);
return null;
}
/**
* Return all current results from all {@link SearchResultProducer}.
*/
public List<SearchResult> getResults()
{
final List<SearchResult> results = new ArrayList<SearchResult>();
synchronized (producers)
{
for (SearchResultProducer producer : producers)
{
final List<SearchResult> producerResults = producer.getResults();
// prevent modification of results while adding it
synchronized (producerResults)
{
// sort producer results
Collections.sort(producerResults);
// and add
results.addAll(producerResults);
}
}
}
return results;
}
@Override
public void pluginLoaderChanged(PluginLoaderEvent e)
{
// refresh producer list
updateSearchProducers();
}
@Override
public void resultChanged(SearchResultProducer producer, SearchResult result)
{
// notify listeners about results change
fireResultChangedEvent(result);
}
@Override
public void resultsChanged(SearchResultProducer producer)
{
// notify listeners about results change
fireResultsChangedEvent();
}
@Override
public void searchCompleted(SearchResultProducer producer)
{
// last producer search completed ? --> notify listeners about it
if (getSearchingProducerCount() == 1)
fireSearchCompletedEvent();
}
public void addListener(SearchEngineListener listener)
{
if (!listeners.contains(listener))
listeners.add(listener);
}
public void removeListener(SearchEngineListener listener)
{
listeners.remove(listener);
}
protected void fireResultChangedEvent(SearchResult result)
{
for (SearchEngineListener listener : listeners)
listener.resultChanged(this, result);
}
protected void fireResultsChangedEvent()
{
for (SearchEngineListener listener : listeners)
listener.resultsChanged(this);
}
protected void fireSearchStartedEvent()
{
for (SearchEngineListener listener : listeners)
listener.searchStarted(this);
}
protected void fireSearchCompletedEvent()
{
for (SearchEngineListener listener : listeners)
listener.searchCompleted(this);
}
}