001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.search; 020 021import icy.plugin.PluginDescriptor; 022import icy.plugin.PluginLoader; 023import icy.plugin.PluginLoader.PluginLoaderEvent; 024import icy.plugin.PluginLoader.PluginLoaderListener; 025import icy.plugin.interface_.PluginSearchProvider; 026import icy.system.IcyExceptionHandler; 027import icy.system.thread.ThreadUtil; 028 029import java.util.ArrayList; 030import java.util.Collections; 031import java.util.List; 032 033/** 034 * SearchEngine for Icy. 035 * 036 * @author Stephane 037 */ 038public class SearchEngine implements SearchResultConsumer, PluginLoaderListener 039{ 040 public interface SearchEngineListener 041 { 042 public void resultChanged(SearchEngine source, SearchResult result); 043 044 public void resultsChanged(SearchEngine source); 045 046 public void searchStarted(SearchEngine source); 047 048 public void searchCompleted(SearchEngine source); 049 } 050 051 /** Search result producer list */ 052 final ArrayList<SearchResultProducer> producers; 053 054 /** Listener list */ 055 private final List<SearchEngineListener> listeners; 056 057 /** Internals */ 058 final Runnable searchProviderSetter; 059 String lastSearch; 060 061 public SearchEngine() 062 { 063 super(); 064 065 producers = new ArrayList<SearchResultProducer>(); 066 listeners = new ArrayList<SearchEngine.SearchEngineListener>(); 067 lastSearch = ""; 068 069 searchProviderSetter = new Runnable() 070 { 071 @Override 072 public void run() 073 { 074 final String savedSearch = lastSearch; 075 076 // cancel current search 077 cancelSearch(); 078 079 synchronized (producers) 080 { 081 producers.clear(); 082 } 083 084 // get search providers from plugin 085 for (PluginDescriptor plugin : PluginLoader.getPlugins(PluginSearchProvider.class)) 086 { 087 try 088 { 089 final PluginSearchProvider psp = (PluginSearchProvider) plugin.getPluginClass().newInstance(); 090 final SearchResultProducer producer = psp.getSearchProviderClass().newInstance(); 091 092 synchronized (producers) 093 { 094 producers.add(producer); 095 } 096 } 097 catch (Throwable t) 098 { 099 IcyExceptionHandler.handleException(plugin, t, true); 100 } 101 } 102 103 synchronized (producers) 104 { 105 Collections.sort(producers); 106 } 107 108 // restore last search 109 search(savedSearch); 110 } 111 }; 112 113 PluginLoader.addListener(this); 114 115 updateSearchProducers(); 116 } 117 118 private void updateSearchProducers() 119 { 120 ThreadUtil.runSingle(searchProviderSetter); 121 } 122 123 /** 124 * Cancel the previous search request 125 */ 126 public void cancelSearch() 127 { 128 search(""); 129 } 130 131 /** 132 * Performs the search request, mostly build the search result list.<br> 133 * Previous search is automatically canceled and replaced by the new one. 134 * 135 * @param text 136 * Text used for the search request, it can contains several words and use operators.<br> 137 * Examples:<br> 138 * <li><i>spot detector</i> : any of word should be present</li> 139 * <li><i>+spot +detector</i> : both words should be present</li> 140 * <li>"spot detector"</i> : the exact expression should be present</li> 141 * <li><i>+"spot detector" -tracking</i> : <i>spot detector</i> should be present and <i>tracking</i> absent</li> 142 * @see #cancelSearch() 143 */ 144 public void search(String text) 145 { 146 // save search string 147 lastSearch = text; 148 149 // notify search started 150 fireSearchStartedEvent(); 151 152 // launch new search 153 synchronized (producers) 154 { 155 for (SearchResultProducer producer : producers) 156 producer.search(text, this); 157 } 158 } 159 160 /** 161 * Returns {@link SearchResultProducer} attached to the search engine. 162 */ 163 public List<SearchResultProducer> getSearchResultProducers() 164 { 165 synchronized (producers) 166 { 167 return new ArrayList<SearchResultProducer>(producers); 168 } 169 } 170 171 /** 172 * Returns the number of currently producer processing a search request. 173 */ 174 public int getSearchingProducerCount() 175 { 176 int result = 0; 177 178 synchronized (producers) 179 { 180 for (SearchResultProducer producer : producers) 181 if (producer.isSearching()) 182 result++; 183 } 184 185 return result; 186 } 187 188 /** 189 * Returns true if the search engine is currently processing a search request. 190 */ 191 public boolean isSearching() 192 { 193 synchronized (producers) 194 { 195 for (SearchResultProducer producer : producers) 196 if (producer.isSearching()) 197 return true; 198 } 199 200 return false; 201 } 202 203 // /** 204 // * Set the list of provider classes. 205 // * 206 // * @param providers 207 // * : list of provider. 208 // */ 209 // public void setProducer(List<SearchResultProducer> providers) 210 // { 211 // synchronized (producers) 212 // { 213 // producers.clear(); 214 // producers.addAll(providers); 215 // } 216 // } 217 // 218 // /** 219 // * This method will register the provider class into the list of provider 220 // * classes. The {@link SearchResultProducer} object will not be used except for its 221 // * class. 222 // * 223 // * @param providerClass 224 // * : provider used to get the Class<?> from. 225 // */ 226 // public void addProducer(Class<? extends SearchResultProducer> providerClass) 227 // { 228 // if (!providerClasses.contains(providerClass)) 229 // providerClasses.add(providerClass); 230 // } 231 // 232 // /** 233 // * This method will unregister the provider class from the list of provider 234 // * class. 235 // * 236 // * @param providerClass 237 // * : provider used to get the Class<?> from. 238 // */ 239 // public void removeProducer(Class<? extends SearchResultProducer> providerClass) 240 // { 241 // providerClasses.remove(providerClass); 242 // } 243 244 /** 245 * Returns the last search text. 246 */ 247 public String getLastSearch() 248 { 249 return lastSearch; 250 } 251 252 /** 253 * Returns SearchResult at specified index. 254 */ 255 public SearchResult getResult(int index) 256 { 257 final List<SearchResult> results = getResults(); 258 259 if ((index >= 0) && (index < results.size())) 260 return results.get(index); 261 262 return null; 263 } 264 265 /** 266 * Return all current results from all {@link SearchResultProducer}. 267 */ 268 public List<SearchResult> getResults() 269 { 270 final List<SearchResult> results = new ArrayList<SearchResult>(); 271 272 synchronized (producers) 273 { 274 for (SearchResultProducer producer : producers) 275 { 276 final List<SearchResult> producerResults = producer.getResults(); 277 278 // prevent modification of results while adding it 279 synchronized (producerResults) 280 { 281 // sort producer results 282 Collections.sort(producerResults); 283 // and add 284 results.addAll(producerResults); 285 } 286 } 287 } 288 289 return results; 290 } 291 292 @Override 293 public void pluginLoaderChanged(PluginLoaderEvent e) 294 { 295 // refresh producer list 296 updateSearchProducers(); 297 } 298 299 @Override 300 public void resultChanged(SearchResultProducer producer, SearchResult result) 301 { 302 // notify listeners about results change 303 fireResultChangedEvent(result); 304 } 305 306 @Override 307 public void resultsChanged(SearchResultProducer producer) 308 { 309 // notify listeners about results change 310 fireResultsChangedEvent(); 311 } 312 313 @Override 314 public void searchCompleted(SearchResultProducer producer) 315 { 316 // last producer search completed ? --> notify listeners about it 317 if (getSearchingProducerCount() == 1) 318 fireSearchCompletedEvent(); 319 } 320 321 public List<SearchEngineListener> getListeners() 322 { 323 synchronized (listeners) 324 { 325 return new ArrayList<SearchEngineListener>(listeners); 326 } 327 } 328 329 public void addListener(SearchEngineListener listener) 330 { 331 synchronized (listener) 332 { 333 if (!listeners.contains(listener)) 334 listeners.add(listener); 335 } 336 } 337 338 public void removeListener(SearchEngineListener listener) 339 { 340 synchronized (listener) 341 { 342 listeners.remove(listener); 343 } 344 } 345 346 protected void fireResultChangedEvent(SearchResult result) 347 { 348 for (SearchEngineListener listener : getListeners()) 349 listener.resultChanged(this, result); 350 } 351 352 protected void fireResultsChangedEvent() 353 { 354 for (SearchEngineListener listener : getListeners()) 355 listener.resultsChanged(this); 356 } 357 358 protected void fireSearchStartedEvent() 359 { 360 for (SearchEngineListener listener : getListeners()) 361 listener.searchStarted(this); 362 } 363 364 protected void fireSearchCompletedEvent() 365 { 366 for (SearchEngineListener listener : getListeners()) 367 listener.searchCompleted(this); 368 } 369}