/**
*
*/
package com.soundlooper.system.search;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import javafx.application.Platform;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
/**
* ====================================================================
*
* Sound Looper is an audio player that allow user to loop between two points
* Copyright (C) 2014 Alexandre NEDJARI
*
* This program 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.
*
* This program 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
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* Process searchs
*
* @author Alexandre NEDJARI
* @since 8 mars 2011
*
* ====================================================================
*/
public class SearchEngine {
/**
* Search state : not initialized
*/
public static final String STATE_NOT_INITIALIZED = "Not initialized";
/**
* Search state : initialized but not started
*/
public static final String STATE_NOT_STARTED = "Not started";
/**
* Search state : Search is running
*/
public static final String STATE_RUNNING = "Running";
/**
* Search state : Search was canceled
*/
public static final String STATE_CANCELED = "Canceled";
/**
* Search state : Search is ended
*/
public static final String STATE_TERMINATED = "Terminated";
private static String PAS_DE_RECHERCHE_EN_ATTENTE = "PAS_DE_RECHERCHE_EN_ATTENTE";
/**
* The transformer list to apply to the string used for search (To have a
* permissive search)
*/
protected ArrayList<StringTransformer> transformers;
/**
* The listeners to notifie when a search event occurred
*/
// protected ArrayList<SearchListener> listeners;
protected List<Searchable> searchables;
// protected ArrayList<Searchable> lastResult = new ArrayList<Searchable>();
protected ListProperty<Searchable> lastResult = new SimpleListProperty<Searchable>(
FXCollections.observableArrayList());
protected String lastSeach = null;
protected String waitingSearch = SearchEngine.PAS_DE_RECHERCHE_EN_ATTENTE;
/**
* The search thread. initialized when a search is launched
*/
private ThreadSearch search;
/**
* Constructor
*/
public SearchEngine(List<? extends Searchable> searchables) {
this.transformers = new ArrayList<StringTransformer>();
// this.listeners = new ArrayList<SearchListener>();
this.searchables = new ArrayList<Searchable>();
this.searchables.addAll(searchables);
}
// /**
// * Add a search listener
// * @param listener the listener
// */
// public void addSearchListener(SearchListener listener) {
// this.listeners.add(listener);
// }
/**
* Add a string transformer
*
* @param transformer
* the string transformer
*/
public void addTransformer(StringTransformer transformer) {
this.transformers.add(transformer);
}
/**
* Launch a search. When the search ended, the listeners are notified
*
* @param stringToSearch
* the String to search
* @param searchables
* the searched objects
*/
public void performSearch(String stringToSearch) {
synchronized (this.waitingSearch) {
if (!this.getSearchState().equals(SearchEngine.STATE_RUNNING)) {
this.search = new ThreadSearch(stringToSearch);
this.search.start();
} else {
this.waitingSearch = stringToSearch;
}
}
}
/**
* Get the search state
*
* @return the search state
*/
public String getSearchState() {
if (this.search == null) {
return SearchEngine.STATE_NOT_INITIALIZED;
}
return this.search.getSearchState();
}
/**
* Cancel the current search if any
*/
public void cancelSearch() {
if (this.search != null) {
this.search.cancelSearch();
}
}
/**
*
* ====================================================================
*
* Search is make in a thread
*
* @author Alexandre NEDJARI
* @since 9 mars 2011
*
* ==================================================================
* ==
*/
private class ThreadSearch extends Thread {
/**
* The serach state, equal to a constant of SearchEngine class
*/
private String searchState;
/**
* is the search canceled?
*/
boolean canceled = false;
/**
* The String to search
*/
protected String stringToSearch;
/**
* The searchables
*/
private List<Searchable> searchables;
/**
* The result. updated when a new result is found
*/
protected final ArrayList<Searchable> result = new ArrayList<Searchable>();
/**
* Constructor
*
* @param stringToSearch
* The String to search
* @param searchables
* The searchables
*/
public ThreadSearch(String stringToSearch) {
super();
this.searchState = SearchEngine.STATE_NOT_STARTED;
this.stringToSearch = stringToSearch;
// this.firstResult = true;
if ((SearchEngine.this.lastSeach != null)
&& stringToSearch.contains(SearchEngine.this.lastSeach)) {
// affine le r�sultat actuel
this.searchables = SearchEngine.this.lastResult;
} else {
// Sinon traite l'ensemble
this.searchables = SearchEngine.this.searchables;
}
}
/**
* Get the search state
*
* @return the search state
*/
public String getSearchState() {
return this.searchState;
}
@Override
public void run() {
this.searchState = SearchEngine.STATE_RUNNING;
String transformedStringToSearch = this
.getTransformedString(this.stringToSearch);
for (Searchable searchableToCheck : this.searchables) {
if (this.canceled) {
break;
}
String transformedStringToCheck = this
.getTransformedString(searchableToCheck
.getSearchableString());
boolean match = this.isMatch(transformedStringToSearch,
transformedStringToCheck);
if (match) {
this.result.add(searchableToCheck);
}
}
if (!this.canceled) {
this.searchState = SearchEngine.STATE_TERMINATED;
SearchEngine.this.lastSeach = this.stringToSearch;
Platform.runLater(() -> SearchEngine.this.lastResult
.setAll(this.result));
} else {
this.searchState = SearchEngine.STATE_CANCELED;
}
// this.notifieSearchListenersOfFullResult();
synchronized (SearchEngine.this.waitingSearch) {
if (!SearchEngine.this.waitingSearch
.equals(SearchEngine.PAS_DE_RECHERCHE_EN_ATTENTE)) {
// il a une recherche en attente, on l'ex�cute
SearchEngine.this
.performSearch(SearchEngine.this.waitingSearch);
// suppression de la recherche en attente
SearchEngine.this.waitingSearch = SearchEngine.PAS_DE_RECHERCHE_EN_ATTENTE;
}
}
}
/**
* Check if the string to search match with the string to search
*
* @param theTransformedStringToSearch
* transformed string to search
* @param transformedStringToCheck
* transformed string to check (transformed representation of
* one of the Searchable)
* @return
*/
protected boolean isMatch(String theTransformedStringToSearch,
String transformedStringToCheck) {
StringTokenizer tokenizer = new StringTokenizer(
theTransformedStringToSearch,
StringTransformer.SPACE_STRING);
boolean allFinded = true;
tokenLoop: while (tokenizer.hasMoreElements()) {
String partOfStringToSearch = tokenizer.nextToken();
if (!transformedStringToCheck.contains(partOfStringToSearch)) {
allFinded = false;
break tokenLoop;
}
}
return allFinded;
}
/**
* Get the transformed strings to search
*
* @return the string with all the transformer applied
*/
protected String getTransformedString(String stringToTransform) {
String transformedString = stringToTransform;
for (StringTransformer transformer : SearchEngine.this.transformers) {
transformedString = transformer
.processTransformation(transformedString);
}
return transformedString;
}
/**
* Cancel the current search
*/
public void cancelSearch() {
this.canceled = true;
}
}
public final ObservableList<Searchable> getLastResult() {
return lastResult.get();
}
public final void setLastResult(ObservableList<Searchable> value) {
lastResult.set(value);
}
public final ListProperty<Searchable> lasResult() {
return lastResult;
}
}