package org.iplantc.phyloviewer.viewer.client.services; import java.util.ArrayList; import java.util.List; import org.iplantc.phyloviewer.shared.model.ITree; import org.iplantc.phyloviewer.viewer.client.services.SearchService.SearchResult; import org.iplantc.phyloviewer.viewer.client.services.SearchService.SearchType; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.SuggestOracle; /** * A proxy for SearchServiceAsync that stores the last query and last result */ public class SearchServiceAsyncImpl extends SuggestOracle implements SearchServiceAsync { public static final int MIN_QUERY_LENGTH = 3; private SearchServiceAsync searchService = GWT.create(SearchService.class); private String lastQuery; private SearchResult[] lastResult = new SearchResult[0]; private ITree tree; private ArrayList<SearchResultListener> listeners = new ArrayList<SearchResultListener>(); @Override public void find(final String query, final int treeID, SearchType type, final AsyncCallback<SearchResult[]> callback) { if (query == null || query.length() < MIN_QUERY_LENGTH) { lastQuery = query; lastResult = new SearchResult[0]; callback.onSuccess(lastResult); notifyListeners(lastResult, query, treeID); return; } else if (lastQuery != null && !lastQuery.isEmpty() && query.startsWith(lastQuery) && lastResult.length > 0) { //if the query just adds characters to the last query, we can just filter the last results instead of going back to the server. //(Alternatively, we could keep a stack of results to quickly handle added and deleted characters.) lastQuery = query; lastResult = filter(query, type, lastResult); callback.onSuccess(lastResult); notifyListeners(lastResult, query, treeID); return; } searchService.find(query, treeID, type, new AsyncCallback<SearchResult[]>(){ @Override public void onFailure(Throwable thrown) { callback.onFailure(thrown); } @Override public void onSuccess(SearchResult[] result) { lastQuery = query; lastResult = result; callback.onSuccess(result); notifyListeners(lastResult, query, treeID); } }); } @Override public void requestSuggestions(final Request request, final Callback callback) { if (tree != null) { find(request.getQuery(), tree.getId(), SearchType.PREFIX, new AsyncCallback<SearchResult[]>() { @Override public void onFailure(Throwable arg0) { //TODO } @Override public void onSuccess(SearchResult[] nodes) { callback.onSuggestionsReady(request, createResponse(nodes, request.getLimit())); } }); } } public String getLastQuery() { return lastQuery; } public SearchResult[] getLastResult() { return lastResult; } public void setTree(ITree tree) { this.tree = tree; lastQuery = null; lastResult = new SearchResult[0]; } public void addSearchResultListener(SearchResultListener listener) { listeners.add(listener); } public void removeSearchResultListener(SearchResultListener listener) { listeners.remove(listener); } private SuggestOracle.Response createResponse(SearchResult[] nodes, int limit) { List<RemoteNodeSuggestion> suggestions = new ArrayList<RemoteNodeSuggestion>(); for (SearchResult node : nodes) { suggestions.add(new RemoteNodeSuggestion(node)); } //TODO add size limit param to SearchService.find()? if (suggestions.size() > limit) { suggestions = suggestions.subList(0, limit); } return new Response(suggestions); } private void notifyListeners(SearchResult[] result, String query, int treeID) { for (SearchResultListener listener : listeners) { listener.handleSearchResult(result, query, treeID); } } private SearchResult[] filter(String query, SearchType type, SearchResult[] nodes) { ArrayList<SearchResult> filteredList = new ArrayList<SearchResult>(nodes.length); for (SearchResult result : nodes) { if (type.match(query, result.node.getLabel())) { filteredList.add(result); } } return filteredList.toArray(new SearchResult[filteredList.size()]); } public class RemoteNodeSuggestion implements SuggestOracle.Suggestion { private SearchResult result; public RemoteNodeSuggestion(SearchResult node) { this.result = node; } @Override public String getDisplayString() { return result.node.getLabel(); } @Override public String getReplacementString() { return result.node.getLabel(); } public SearchResult getResult() { return result; } } public interface SearchResultListener { /** Called when there is a new search result */ void handleSearchResult(SearchResult[] result, String query, int treeID); } }