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