package org.limewire.ui.swing.search;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.limewire.core.api.connection.ConnectionStrength;
import org.limewire.core.api.connection.GnutellaConnectionManager;
import org.limewire.core.api.lifecycle.LifeCycleEvent;
import org.limewire.core.api.lifecycle.LifeCycleManager;
import org.limewire.core.api.search.Search;
import org.limewire.core.api.search.SearchFactory;
import org.limewire.core.api.search.SearchListener;
import org.limewire.core.api.search.SearchResult;
import org.limewire.core.api.search.sponsored.SponsoredResult;
import org.limewire.listener.EventListener;
import org.limewire.listener.SwingEDTEvent;
import org.limewire.ui.swing.nav.NavItem;
import org.limewire.ui.swing.nav.NavItemListener;
import org.limewire.ui.swing.search.model.SearchResultsModel;
import org.limewire.ui.swing.search.model.SearchResultsModelFactory;
import org.limewire.ui.swing.util.SwingUtils;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
/**
* The implementation of SearchHandler used to initiate regular text searches.
* Calling the <code>doSearch(SearchInfo)</code> method will create a new
* search results tab in the UI, and start the search operation.
*/
@Singleton
class TextSearchHandlerImpl implements SearchHandler {
private final SearchFactory searchFactory;
private final Provider<SearchResultsPanelFactory> searchResultPanelFactory;
private final SearchNavigator searchNavigator;
private final Provider<SearchResultsModelFactory> searchResultsModelFactory;
private final LifeCycleManager lifeCycleManager;
private final GnutellaConnectionManager connectionManager;
private SearchResultsModelFactory modelFactory;
private SearchResultsPanelFactory panelFactory;
/**
* Constructs a TextSearchHandlerImpl with the specified services and
* factories.
*/
@Inject
TextSearchHandlerImpl(SearchFactory searchFactory,
Provider<SearchResultsPanelFactory> searchResultPanelFactory,
SearchNavigator searchNavigator,
Provider<SearchResultsModelFactory> searchResultsModelFactory,
LifeCycleManager lifeCycleManager,
GnutellaConnectionManager connectionManager) {
this.searchNavigator = searchNavigator;
this.searchFactory = searchFactory;
this.searchResultPanelFactory = searchResultPanelFactory;
this.searchResultsModelFactory = searchResultsModelFactory;
this.lifeCycleManager = lifeCycleManager;
this.connectionManager = connectionManager;
}
/**
* Performs a search operation using the specified SearchInfo object.
* The method always returns true.
*/
@Override
public boolean doSearch(final SearchInfo info) {
// Create search request.
Search search = searchFactory.createSearch(info);
String panelTitle = info.getTitle();
if(modelFactory == null)
modelFactory = searchResultsModelFactory.get();
if(panelFactory == null)
panelFactory = searchResultPanelFactory.get();
// Create search results data model and display panel.
SearchResultsModel searchModel = modelFactory.createSearchResultsModel(info, search);
SearchResultsPanel searchPanel = panelFactory.createSearchResultsPanel(searchModel);
// Add search results display to the UI, and select its navigation item.
SearchNavItem item = searchNavigator.addSearch(panelTitle, searchPanel, search, searchModel);
item.select();
// Add listeners for connection events.
addConnectionWarnings(search, searchPanel, item);
// Start search operation.
startSearch(searchModel, searchPanel, item);
return true;
}
/**
* Initiates a search using the specified search model, search panel,
* and navigation item.
*/
private void startSearch(final SearchResultsModel searchModel,
final SearchResultsPanel searchPanel, final SearchNavItem navItem) {
// prevent search from starting until lifecycle manager completes loading
if (lifeCycleManager.isStarted()) {
searchModel.start(new SwingSearchListener(searchModel, searchPanel, navItem));
} else {
searchPanel.setLifeCycleComplete(false);
final EventListener<LifeCycleEvent> listener = new EventListener<LifeCycleEvent>() {
@SwingEDTEvent
public void handleEvent(LifeCycleEvent event) {
if(event == LifeCycleEvent.STARTED) {
searchPanel.setLifeCycleComplete(true);
searchModel.start(new SwingSearchListener(searchModel, searchPanel, navItem));
lifeCycleManager.removeListener(this);
}
}
};
lifeCycleManager.addListener(listener);
navItem.addNavItemListener(new NavItemListener() {
public void itemRemoved(boolean wasSelected) {
lifeCycleManager.removeListener(listener);
}
public void itemSelected(boolean selected) {}
});
}
}
/**
* Notifies the specified SearchResultsPanel if the specified
* ConnectionStrength indicates a full connection. Returns true if fully
* connected, false otherwise.
*/
private boolean setConnectionStrength(ConnectionStrength type, SearchResultsPanel searchPanel) {
switch(type) {
case TURBO:
case FULL:
searchPanel.setFullyConnected(true);
return true;
}
return false;
}
/**
* Removes the specified listeners from the Search request and connection
* manager.
*/
private void removeListeners(Search search, AtomicReference<SearchListener> searchListenerRef, AtomicReference<PropertyChangeListener> connectionListenerRef) {
SearchListener searchListener = searchListenerRef.get();
if(searchListener != null) {
search.removeSearchListener(searchListener);
searchListenerRef.set(null);
}
PropertyChangeListener connectionListener = connectionListenerRef.get();
if(connectionListener != null) {
connectionManager.removePropertyChangeListener(connectionListener);
connectionListenerRef.set(null);
}
}
/**
* Adds listeners to the Search request and connection manager to update
* the UI based on connection events.
*/
private void addConnectionWarnings(final Search search,
final SearchResultsPanel searchPanel, NavItem navItem) {
// Define atomic listener references.
final AtomicReference<SearchListener> searchListenerRef = new AtomicReference<SearchListener>();
final AtomicReference<PropertyChangeListener> connectionListenerRef = new AtomicReference<PropertyChangeListener>();
// Create property change listener for connection strength. This
// updates an indicator in SearchResultsPanel, and removes all
// warning listeners when the strength is full.
connectionListenerRef.set(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if(evt.getPropertyName().equals("strength")) {
if(setConnectionStrength((ConnectionStrength)evt.getNewValue(), searchPanel)) {
removeListeners(search, searchListenerRef, connectionListenerRef);
}
}
}
});
// Create search listener to monitor number of results. If a certain
// number of search results comes in, for now 10, we assume that while
// not fully connected, we have a good enough search going so we remove
// all warning listeners.
searchListenerRef.set(new SearchListener() {
private final AtomicInteger numberOfResults = new AtomicInteger(0);
@Override
public void handleSearchResult(Search search, SearchResult searchResult) {
if(numberOfResults.addAndGet(1) > 10) {
SwingUtils.invokeLater(new Runnable() {
public void run() {
// while not fully connected, assume the
// connections we have are enough
// based on the number of results coming in.
searchPanel.setFullyConnected(true);
}
});
removeListeners(search, searchListenerRef, connectionListenerRef);
}
}
@Override
public void handleSearchResults(Search search, Collection<? extends SearchResult> searchResults) {
if(numberOfResults.addAndGet(searchResults.size()) > 10) {
SwingUtils.invokeLater(new Runnable() {
public void run() {
// while not fully connected, assume the
// connections we have are enough
// based on the number of results coming in.
searchPanel.setFullyConnected(true);
}
});
removeListeners(search, searchListenerRef, connectionListenerRef);
}
}
@Override public void handleSponsoredResults(Search search, List<SponsoredResult> sponsoredResults) {}
@Override public void searchStarted(Search search) {}
@Override public void searchStopped(Search search) {}
});
// Initialize search panel indicator, and install connection listener.
searchPanel.setFullyConnected(false);
connectionManager.addPropertyChangeListener(connectionListenerRef.get());
// If not fully connected, add search listener and navigation listener.
if(setConnectionStrength(connectionManager.getConnectionStrength(), searchPanel)) {
removeListeners(search, searchListenerRef, connectionListenerRef);
} else {
search.addSearchListener(searchListenerRef.get());
navItem.addNavItemListener(new NavItemListener() {
@Override
public void itemRemoved(boolean wasSelected) {
removeListeners(search, searchListenerRef, connectionListenerRef);
}
@Override public void itemSelected(boolean selected) {}
});
}
}
}