package net.sf.taverna.biocatalogue.model.search; | |
import java.io.Serializable; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.List; | |
import java.util.concurrent.CountDownLatch; | |
import javax.swing.Icon; | |
import net.sf.taverna.biocatalogue.model.Resource; | |
import net.sf.taverna.biocatalogue.model.ResourceManager; | |
import net.sf.taverna.biocatalogue.model.Tag; | |
import net.sf.taverna.biocatalogue.model.Util; | |
import net.sf.taverna.biocatalogue.ui.search_results.SearchResultsRenderer; | |
/** | |
* Class to hold settings for search instance. Objects of this type will | |
* be used to re-run a search instance at a later time -- or to apply | |
* filtering onto a previously executed search. | |
* | |
* @author Sergejs Aleksejevs | |
*/ | |
public class SearchInstance implements Comparable<SearchInstance>, Serializable | |
{ | |
private static final long serialVersionUID = -5236966374301885370L; | |
// CONSTANTS | |
public static enum TYPE | |
{ | |
QuerySearch(ResourceManager.getImageIcon(ResourceManager.SEARCH_ICON)), | |
TagSearch(ResourceManager.getImageIcon(ResourceManager.TAG_ICON)), | |
Filtering(ResourceManager.getImageIcon(ResourceManager.FILTER_ICON)); | |
private Icon icon; | |
/** | |
* @param icon Icon to represent search instances in different listings | |
* - for example in search history. | |
*/ | |
TYPE(Icon icon) { | |
this.icon = icon; | |
} | |
/** | |
* @return An icon that is most suitable to display search instance of this type in a UI component. | |
*/ | |
public Icon getIcon() { | |
return this.icon; | |
} | |
} | |
// SEARCH SETTINGS - for either search by query or search by tag | |
private TYPE searchType; | |
private final TYPE serviceFilteringBasedOn; // service filtering may be based on {@link TYPE.QuerySearch} or {@link TYPE.TagSearch} | |
private final Resource.TYPE resourceTypeToSearchFor; | |
private final String searchString; | |
private final List<Tag> searchTags; | |
// SERVICE FILTERING settings | |
private ServiceFilteringSettings filteringSettings; | |
// SEARCH RESULTS | |
private transient SearchResults searchResults; // don't want to store search results when serialising... | |
/** | |
* Constructs a query search instance for finding instance of a specific resource type. | |
* | |
* @param searchString | |
* @param resourceTypeToSearchFor | |
*/ | |
public SearchInstance(String searchString, Resource.TYPE resourceTypeToSearchFor) | |
{ | |
this.searchType = TYPE.QuerySearch; | |
this.serviceFilteringBasedOn = null; | |
this.resourceTypeToSearchFor = resourceTypeToSearchFor; | |
this.searchString = searchString; | |
this.searchTags = null; | |
} | |
/** | |
* Constructing a search instance for finding instance of a specific resource type by a single tag. | |
* | |
* @param searchTag | |
* @param resourceTypeToSearchFor | |
*/ | |
public SearchInstance(Tag searchTag, Resource.TYPE resourceTypeToSearchFor) | |
{ | |
this.searchType = TYPE.TagSearch; | |
this.serviceFilteringBasedOn = null; | |
this.resourceTypeToSearchFor = resourceTypeToSearchFor; | |
this.searchTags = Collections.singletonList(searchTag); | |
this.searchString = null; | |
} | |
/** | |
* Constructing a search instance for finding instance of a specific resource type by a list of tags. | |
* | |
* @param searchTags | |
* @param resourceTypeToSearchFor | |
*/ | |
public SearchInstance(List<Tag> searchTags, Resource.TYPE resourceTypeToSearchFor) | |
{ | |
this.searchType = TYPE.TagSearch; | |
this.serviceFilteringBasedOn = null; | |
this.resourceTypeToSearchFor = resourceTypeToSearchFor; | |
this.searchTags = searchTags; | |
this.searchString = null; | |
} | |
/** | |
* Constructing service filtering search instance. | |
* | |
* @param si SearchInstance to base the current on. | |
* Can be either {@link TYPE#TagSearch} or {@link TYPE#QuerySearch} type of SearchInstance. | |
* @param filteringSettings Filtering settings associated with this search instance. | |
*/ | |
public SearchInstance(SearchInstance si, ServiceFilteringSettings filteringSettings) throws IllegalArgumentException | |
{ | |
if (!si.isTagSearch() && !si.isQuerySearch()) { | |
throw new IllegalArgumentException("Cannot create Service Filtering search instance - " + | |
"supplied base search instance must be either QuerySearch or TagSearch"); | |
} | |
this.searchType = TYPE.Filtering; | |
this.serviceFilteringBasedOn = si.searchType; | |
this.resourceTypeToSearchFor = si.resourceTypeToSearchFor; | |
// this search instance inherits search term (i.e. search query or the tag) from the supplied search instance | |
this.searchString = si.isQuerySearch() ? si.searchString : null; | |
this.searchTags = si.isTagSearch() ? si.searchTags : null; | |
// also, store the filtering settings that are to be applied to the newly | |
// created search instance | |
this.filteringSettings = filteringSettings; | |
} | |
/** | |
* Determines whether the two search instances are identical. | |
*/ | |
// TODO - fix the equals() method | |
public boolean equals(Object other) | |
{ | |
if (other instanceof SearchInstance) | |
{ | |
SearchInstance s = (SearchInstance)other; | |
boolean bSearchTypesMatch = (this.searchType == s.getSearchType()); | |
if (bSearchTypesMatch) { | |
switch (this.searchType) { | |
case QuerySearch: bSearchTypesMatch = this.searchString.equals(s.getSearchString()); break; | |
case TagSearch: bSearchTypesMatch = this.searchTags.equals(s.getSearchTags()); break; | |
case Filtering: bSearchTypesMatch = this.serviceFilteringBasedOn == s.getServiceFilteringBasedOn(); | |
if (bSearchTypesMatch) { | |
if (this.serviceFilteringBasedOn == TYPE.QuerySearch) { | |
bSearchTypesMatch = this.searchString.equals(s.getSearchString()); | |
} | |
else { | |
bSearchTypesMatch = this.searchTags.equals(s.getSearchTags()); | |
} | |
} | |
if (bSearchTypesMatch) { | |
if (this.filteringSettings != null) { | |
bSearchTypesMatch = this.filteringSettings.equals(s.getFilteringSettings()); | |
} | |
else if (s.filteringSettings != null) { | |
// other isn't null, this one is - so 'false' | |
bSearchTypesMatch = false; | |
} | |
else { | |
// both could be null | |
bSearchTypesMatch = (this.filteringSettings == s.getFilteringSettings()); | |
} | |
} | |
break; | |
default: bSearchTypesMatch = false; | |
} | |
} | |
return (bSearchTypesMatch && | |
/* TODO re-enable this when limits are implemented -- this.iResultCountLimit == s.getResultCountLimit() && */ | |
this.resourceTypeToSearchFor == s.getResourceTypeToSearchFor()); | |
} | |
else | |
return (false); | |
} | |
public int compareTo(SearchInstance other) | |
{ | |
if (this.equals(other)) return(0); | |
else | |
{ | |
// this will return results in the descending order - which is | |
// fine, because the way this collection will be rendered will | |
// eventually traverse it from the rear end first; so results | |
// will be shown alphabetically | |
return (-1 * this.toString().compareTo(other.toString())); | |
} | |
} | |
/** | |
* See {@link SearchInstance#getDescriptionStringForSearchStatus(SearchInstance)} | |
*/ | |
public String getDescriptionStringForSearchStatus() { | |
return (getDescriptionStringForSearchStatus(this)); | |
} | |
/** | |
* @param si {@link SearchInstance} for which the method is executed. | |
* @return String that can be used as a description of the provided {@link SearchInstance} | |
* in the search status label. Returned strings may look like: <br/> | |
* - <code>empty search string</code><br/> | |
* - <code>query "[search_query]"</code><br/> | |
* - <code>tag "[search_tag]"</code><br/> | |
* - <code>tags "[tag1]", "[tag2]", "[tag3]"</code><br/> | |
* - <code>query "[search_query]" and X filter(s)</code><br/> | |
* - <code>tag "[search_tag]" and X filter(s)</code><br/> | |
* - <code>tags "[tag1]", "[tag2]", "[tag3]" and X filter(s)</code><br/> | |
*/ | |
public static String getDescriptionStringForSearchStatus(SearchInstance si) | |
{ | |
switch (si.searchType) | |
{ | |
case QuerySearch: String searchQuery = si.getSearchTerm(); | |
return (searchQuery.length() == 0 ? | |
"empty search string" : | |
"query " + si.getSearchTerm()); | |
case TagSearch: return (Util.pluraliseNoun("tag", si.getSearchTags().size()) + " " + si.getSearchTerm()); | |
case Filtering: int filterNumber = si.getFilteringSettings().getNumberOfFilteringCriteria(); | |
SearchInstance tempBaseSI = si.deepCopy(); | |
tempBaseSI.searchType = si.getServiceFilteringBasedOn(); | |
return getDescriptionStringForSearchStatus(tempBaseSI) + " and " + filterNumber + " " + Util.pluraliseNoun("filter", filterNumber); | |
default: return ("unexpected type of search"); | |
} | |
} | |
public String toString() | |
{ | |
String out = "<html>"; | |
if (this.isQuerySearch() || this.isTagSearch()) { | |
out += (this.isTagSearch() ? "Tag s" : "S") + "earch: '" + getSearchTerm() + "' [" + this.detailsAsString() + "]"; | |
} | |
else if (this.isServiceFilteringSearch()) { | |
out += "Filter:<br>" + | |
(getSearchTerm().length() > 0 ? ("- based on " + (this.isQuerySearch() ? "term" : "tag") + " '" + getSearchTerm() + "'<br>") : "") + | |
"- scope: " + detailsAsString() + "<br>" + | |
"- " + this.filteringSettings.detailsAsString(); | |
} | |
out += "</html>"; | |
return (out); | |
} | |
/** | |
* @return A string representation of search settings held in this object; | |
* actual search value (string/tag) are ignored - this only affects | |
* types to search and the number of objects to fetch. | |
*/ | |
public String detailsAsString() | |
{ | |
// include the name of the resource type collection that is to be / was searched for | |
String str = this.getResourceTypeToSearchFor().getCollectionName(); | |
// add the rest to the string representation of the search instance | |
str = str /* TODO re-enable when limits are implemented -- "; limit: " + this.iResultCountLimit +*/; | |
return (str); | |
} | |
// ***** Getters for all fields ***** | |
/** | |
* @return Type of this search instance. | |
*/ | |
public TYPE getSearchType() { | |
return (this.searchType); | |
} | |
/** | |
* @return True if this search settings instance describes a search by tag. | |
*/ | |
public boolean isTagSearch() { | |
return (this.searchType == TYPE.TagSearch); | |
} | |
/** | |
* @return True if this search settings instance describes a search by query. | |
*/ | |
public boolean isQuerySearch() { | |
return (this.searchType == TYPE.QuerySearch); | |
} | |
/** | |
* @return True if this search settings instance describes service filtering operation. | |
*/ | |
public boolean isServiceFilteringSearch() { | |
return (this.searchType == TYPE.Filtering); | |
} | |
/** | |
* Allows to test which type of search this filtering operation is based on -- any filtering | |
* operation can be: | |
* <li>derived from an initial search by tag(s) or by free-text query</li> | |
* <li>or can be just a standalone filtering operation, where filtering criteria are | |
* applied to all resources of the specified type, without prior search.</li> | |
* | |
* @return Value {@link TYPE#QuerySearch} or {@link TYPE#TagSearch} if this filtering operation has a known "parent",<br/> | |
* <code>null</code> if this is a proper search (not a filtering!) operation, or | |
* if this filtering operation was not based on any search. | |
*/ | |
public TYPE getServiceFilteringBasedOn() { | |
return serviceFilteringBasedOn; | |
} | |
public Resource.TYPE getResourceTypeToSearchFor() { | |
return this.resourceTypeToSearchFor; | |
} | |
/** | |
* @return Search string; only valid when SearchSettings object holds data about a search by query, not a tag search. | |
*/ | |
public String getSearchString() { | |
return searchString; | |
} | |
public List<Tag> getSearchTags() { | |
return searchTags; | |
} | |
/** | |
* This method is to be used when the type of search is not checked - in | |
* case of query search the method returns the search string, otherwise | |
* the tag(s) that is to be searched. | |
* | |
* @return The value will be returned in double quotes. | |
*/ | |
public String getSearchTerm() | |
{ | |
if (this.searchType == TYPE.QuerySearch || this.serviceFilteringBasedOn == TYPE.QuerySearch) { | |
return (this.searchString.length() == 0 ? | |
"" : | |
"\"" + this.searchString + "\""); | |
} | |
else { | |
List<String> tagDisplayNames = new ArrayList<String>(); | |
for (Tag t : this.searchTags) { | |
tagDisplayNames.add(t.getTagDisplayName()); | |
} | |
return (Util.join(tagDisplayNames, "\"", "\"", ", ")); | |
} | |
} | |
public ServiceFilteringSettings getFilteringSettings() { | |
return filteringSettings; | |
} | |
public void setFilteringSettings(ServiceFilteringSettings filteringSettings) { | |
this.filteringSettings = filteringSettings; | |
} | |
public SearchResults getSearchResults() { | |
return searchResults; | |
} | |
public void setSearchResults(SearchResults searchResults) { | |
this.searchResults = searchResults; | |
} | |
/** | |
* @return True if search results are available; | |
* False if no search results are available - probably search hasn't been carried out yet. | |
*/ | |
public boolean hasSearchResults() { | |
return (searchResults != null); | |
} | |
/** | |
* @return True if this is a new search; false otherwise. | |
* (Search is currently treated as new if there are no search results available yet.) | |
*/ | |
public boolean isNewSearch() { | |
return (!hasSearchResults()); | |
} | |
/** | |
* Removes any previous search results; after execution of | |
* this method this search instance is treated as "new search". | |
*/ | |
public void clearSearchResults() { | |
this.searchResults = null; | |
} | |
// *** Methods that call SearchEngine in order to start new / resume result fetching for a previous search *** | |
// | |
// They are used to relay external calls to these methods to the underlying instance | |
// of SearchEngine which will perform the actual search operations for this search instance. | |
/** | |
* @param activeSearchInstanceTracker Tracker of current search instances for different resource types - | |
* aids in early termination of older searches. | |
* @param doneSignal Means of notifying the parentSeachThread of completing the requested search operation. | |
* The parent thread will block until doneSignal is activated. | |
* @param renderer {@link SearchResultsRenderer} that will render results of this search. | |
*/ | |
public void startNewSearch(SearchInstanceTracker activeSearchInstanceTracker, | |
CountDownLatch doneSignal, SearchResultsRenderer renderer) | |
{ | |
new SearchEngine(this, activeSearchInstanceTracker, doneSignal, renderer).startNewSearch(); | |
} | |
/** | |
* @param activeSearchInstanceTracker Tracker of current search instances for different resource types - | |
* aids in early termination of older searches. | |
* @param doneSignal Means of notifying the parentSeachThread of completing the requested search operation. | |
* The parent thread will block until doneSignal is activated. | |
* @param renderer {@link SearchResultsRenderer} that will render results of this search. | |
* @param resultPageNumber | |
*/ | |
public void fetchMoreResults(SearchInstanceTracker activeSearchInstanceTracker, | |
CountDownLatch doneSignal, SearchResultsRenderer renderer, int resultPageNumber) | |
{ | |
new SearchEngine(this, activeSearchInstanceTracker, doneSignal, renderer).fetchMoreResults(resultPageNumber); | |
} | |
/** | |
* Used in the plugin, for example, to transfer search results from Search tab to | |
* Filtering tab. This way both tabs will remain completely independent. | |
* | |
* @return Deep copy of this SearchInstance object. If deep copying doesn't succeed, | |
* <code>null</code> is returned. | |
*/ | |
public SearchInstance deepCopy() { | |
return (SearchInstance)Util.deepCopy(this); | |
} | |
public boolean isEmptySearch() { | |
return ((searchString == null) || searchString.isEmpty()) && | |
((searchTags == null) || searchTags.isEmpty()) && | |
((filteringSettings == null) || (filteringSettings.getNumberOfFilteringCriteria() == 0)); | |
} | |
} |