blob: fa4363278e19dbe5d409f25912f5b7be88e2de97 [file] [log] [blame]
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));
}
}