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.system.IcyExceptionHandler; 022import icy.system.thread.SingleProcessor; 023import icy.system.thread.ThreadUtil; 024import icy.util.StringUtil; 025 026import java.util.ArrayList; 027import java.util.List; 028 029/** 030 * The SearchResultProducer create {@link SearchResult} objects from given search keywords.<br> 031 * These {@link SearchResult} are then consumed by a {@link SearchResultConsumer}. 032 * 033 * @author Stephane Dallongeville 034 */ 035public abstract class SearchResultProducer implements Comparable<SearchResultProducer> 036{ 037 private class SearchRunner implements Runnable 038 { 039 private final String text; 040 private final SearchResultConsumer consumer; 041 042 public SearchRunner(String text, SearchResultConsumer consumer) 043 { 044 super(); 045 046 this.text = text; 047 this.consumer = consumer; 048 } 049 050 @Override 051 public void run() 052 { 053 // perform search if text is not empty 054 if (!StringUtil.isEmpty(text)) 055 { 056 try 057 { 058 doSearch(text, consumer); 059 } 060 catch (Throwable t) 061 { 062 // just display the exception and continue 063 IcyExceptionHandler.showErrorMessage(t, true, true); 064 } 065 } 066 else 067 { 068 final boolean notEmpty; 069 070 synchronized (results) 071 { 072 // clear the list if necessary 073 notEmpty = !results.isEmpty(); 074 if (notEmpty) 075 results.clear(); 076 } 077 078 // avoid death lock by sending event after synchronization 079 if (notEmpty) 080 consumer.resultsChanged(SearchResultProducer.this); 081 } 082 083 // search completed (do it after searching set to false) 084 consumer.searchCompleted(SearchResultProducer.this); 085 } 086 } 087 088 /** Result list */ 089 protected List<SearchResult> results; 090 091 /** Internals */ 092 protected final SingleProcessor processor; 093 094 public SearchResultProducer() 095 { 096 super(); 097 098 results = new ArrayList<SearchResult>(); 099 processor = new SingleProcessor(true, this.getClass().getSimpleName()); 100 } 101 102 /** Returns the result producer order */ 103 public int getOrder() 104 { 105 // default 106 return 10; 107 } 108 109 /** Returns the result producer name */ 110 public abstract String getName(); 111 112 /** 113 * Returns the tooltip displayed on the menu (in small under the label). 114 */ 115 public String getTooltipText() 116 { 117 return "Click to run"; 118 } 119 120 /** Returns the result list */ 121 public List<SearchResult> getResults() 122 { 123 return results; 124 } 125 126 /** 127 * @deprecated Use {@link #search(String, SearchResultConsumer)} instead. 128 */ 129 @Deprecated 130 public void search(String[] words, SearchResultConsumer consumer) 131 { 132 if (words.length > 0) 133 { 134 String t = words[0]; 135 136 for (int i = 1; i < words.length; i++) 137 t += " " + words[i]; 138 139 processor.submit(new SearchRunner(t, consumer)); 140 } 141 else 142 processor.submit(new SearchRunner("", consumer)); 143 } 144 145 /** 146 * Performs the search request (asynchronous), mostly build the search result list.<br> 147 * Only one search request should be processed at one time so take care of waiting for previous 148 * search request completion. 149 * 150 * @param text 151 * Search text, it can contains several words and use operators.<br> 152 * Examples:<br> 153 * <li><i>spot detector</i> : any of word should be present</li> 154 * <li><i>+spot +detector</i> : both words should be present</li> 155 * <li>"spot detector"</i> : the exact expression should be present</li> 156 * <li><i>+"spot detector" -tracking</i> : <i>spot detector</i> should be present and <i>tracking</i> absent</li> 157 * @param consumer 158 * Search result consumer for this search request.<br> 159 * The consumer should be notified of new results by using the 160 * {@link SearchResultConsumer#resultsChanged(SearchResultProducer)} method. 161 */ 162 public void search(String text, SearchResultConsumer consumer) 163 { 164 processor.submit(new SearchRunner(text, consumer)); 165 } 166 167 /** 168 * @deprecated Use {@link #doSearch(String, SearchResultConsumer)} instead 169 */ 170 @Deprecated 171 public void doSearch(String[] words, SearchResultConsumer consumer) 172 { 173 // default implementation, does nothing... 174 } 175 176 /** 177 * Performs the search request (internal).<br> 178 * The method is responsible for filling the <code>results</code> list :<br> 179 * - If no result correspond to the requested search then <code>results</code> should be 180 * cleared.<br> 181 * - otherwise it should contains the founds results.<br> 182 * <code>results</code> variable access should be synchronized as it can be externally accessed.<br> 183 * The method could return earlier if {@link #hasWaitingSearch()} returns true. 184 * 185 * @param text 186 * Search text, it can contains several words and use operators.<br> 187 * Examples:<br> 188 * <li><i>spot detector</i> : any of word should be present</li> 189 * <li><i>+spot +detector</i> : both words should be present</li> 190 * <li>"spot detector"</i> : the exact expression should be present</li> 191 * <li><i>+"spot detector" -tracking</i> : <i>spot detector</i> should be present and <i>tracking</i> absent</li> 192 * @param consumer 193 * Search result consumer for this search request.<br> 194 * The consumer should be notified of new results by using the 195 * {@link SearchResultConsumer#resultsChanged(SearchResultProducer)} method. 196 * @see #hasWaitingSearch() 197 */ 198 public void doSearch(String text, SearchResultConsumer consumer) 199 { 200 // by default this implementation use separated word search for backward compatibility 201 doSearch(text.split(" "), consumer); 202 } 203 204 /** 205 * Wait for the search request to complete. 206 */ 207 public void waitSearchComplete() 208 { 209 while (isSearching()) 210 ThreadUtil.sleep(1); 211 } 212 213 /** 214 * Returns true if the search result producer is currently processing a search request. 215 */ 216 public boolean isSearching() 217 { 218 return processor.isProcessing(); 219 } 220 221 /** 222 * Returns true if there is a waiting search pending.<br> 223 * This method should be called during search process to cancel it if another search is waiting. 224 */ 225 public boolean hasWaitingSearch() 226 { 227 return processor.hasWaitingTasks(); 228 } 229 230 /** 231 * Add the SearchResult to the result list. 232 * 233 * @param result 234 * Result to add to the result list. 235 * @param consumer 236 * If not null then consumer is notified about result change 237 */ 238 public void addResult(SearchResult result, SearchResultConsumer consumer) 239 { 240 if (result != null) 241 { 242 synchronized (results) 243 { 244 results.add(result); 245 } 246 247 // notify change to consumer 248 if (consumer != null) 249 consumer.resultsChanged(this); 250 } 251 } 252 253 @Override 254 public int compareTo(SearchResultProducer o) 255 { 256 // sort on order 257 return getOrder() - o.getOrder(); 258 } 259}