package org.limewire.core.impl.search; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.limewire.collection.glazedlists.GlazedListsFactory; import org.limewire.core.api.URN; import org.limewire.core.api.search.GroupedSearchResult; import org.limewire.core.api.search.Search; import org.limewire.core.api.search.SearchDetails; import org.limewire.core.api.search.SearchListener; import org.limewire.core.api.search.SearchResult; import org.limewire.core.api.search.SearchResultList; import org.limewire.io.GUID; import org.limewire.listener.EventListener; import org.limewire.listener.EventListenerList; import ca.odell.glazedlists.BasicEventList; import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.util.concurrent.Lock; /** * Implementation of SearchResultList for the live core. CoreSearchResultList * handles search results from multiple background threads for Gnutella and * friend results. */ class CoreSearchResultList implements SearchResultList { private final Search search; private final SearchDetails searchDetails; private final EventListenerList<Collection<GroupedSearchResult>> listListeners; private final Comparator<Object> resultFinder; private final SearchListener searchListener; private final EventList<GroupedSearchResult> groupedUrnResultList; private final EventList<GroupedSearchResult> threadSafeResultList; private volatile int resultCount; /** * Constructs a SearchResultList for the specified search. */ public CoreSearchResultList(Search search, SearchDetails searchDetails) { this.search = search; this.searchDetails = searchDetails; listListeners = new EventListenerList<Collection<GroupedSearchResult>>(); resultFinder = new UrnResultFinder(); searchListener = new SearchListenerImpl(); // Create list of grouped results. groupedUrnResultList = new BasicEventList<GroupedSearchResult>(); threadSafeResultList = GlazedListsFactory.threadSafeList(groupedUrnResultList); // Add search listener. search.addSearchListener(searchListener); } @Override public GUID getGuid() { if (search instanceof CoreSearch) { return ((CoreSearch) search).getQueryGuid(); } else { return null; } } @Override public int getResultCount() { return resultCount; } @Override public Search getSearch() { return search; } @Override public String getSearchQuery() { return searchDetails.getSearchQuery(); } @Override public GroupedSearchResult getGroupedResult(URN urn) { Lock lock = groupedUrnResultList.getReadWriteLock().writeLock(); lock.lock(); try { int idx = Collections.binarySearch(groupedUrnResultList, urn, resultFinder); if (idx >= 0) { return groupedUrnResultList.get(idx); } else { return null; } } finally { lock.unlock(); } } @Override public EventList<GroupedSearchResult> getGroupedResults() { return threadSafeResultList; } @Override public void addListener(EventListener<Collection<GroupedSearchResult>> listener) { listListeners.addListener(listener); } @Override public boolean removeListener(EventListener<Collection<GroupedSearchResult>> listener) { return listListeners.removeListener(listener); } @Override public void clear() { Lock lock = groupedUrnResultList.getReadWriteLock().writeLock(); lock.lock(); try { groupedUrnResultList.clear(); resultCount = 0; } finally { lock.unlock(); } } @Override public void dispose() { search.removeSearchListener(searchListener); } /** * Adds the specified result to the list. */ void addResult(SearchResult result) { addResultsInternal(Collections.singletonList(result)); } /** * Adds the specified collection of results to the list. */ void addResults(Collection<? extends SearchResult> results) { addResultsInternal(results); } /** * Adds the specified collection of results to the list of grouped results. * This method obtains a write lock on the list because results may be * generated by different threads for Gnutella and friend results. */ private void addResultsInternal(Collection<? extends SearchResult> results) { // Create list of new results. List<GroupedSearchResult> newResults = new ArrayList<GroupedSearchResult>(); // Obtain write lock on result list. Lock lock = groupedUrnResultList.getReadWriteLock().writeLock(); lock.lock(); try { for (SearchResult result : results) { URN urn = result.getUrn(); // Some results can be missing a URN, specifically secure results. // For now, we drop these. We should figure out a way to show // them later on. if (urn != null) { int idx = Collections.binarySearch(groupedUrnResultList, urn, resultFinder); if (idx >= 0) { // Found URN so add result to grouping. GroupedSearchResultImpl gsr = (GroupedSearchResultImpl) groupedUrnResultList.get(idx); gsr.addNewSource(result, searchDetails.getSearchQuery()); groupedUrnResultList.set(idx, gsr); newResults.add(gsr); } else { // URN not found so add new result at insertion point. // This keeps the list in sorted order. idx = -(idx + 1); GroupedSearchResult gsr = new GroupedSearchResultImpl(result, searchDetails.getSearchQuery()); groupedUrnResultList.add(idx, gsr); newResults.add(gsr); } resultCount++; } } } finally { // Release lock. lock.unlock(); } // Forward added results to list listeners. if (newResults.size() > 0) { notifyResultsAdded(newResults); } } /** * Forwards the specified collection of added results to all registered * listeners. */ private void notifyResultsAdded(Collection<GroupedSearchResult> results) { listListeners.broadcast(results); } /** * Handler for search events to add results to the list. */ private class SearchListenerImpl implements SearchListener { @Override public void handleSearchResult(Search search, SearchResult searchResult) { addResult(searchResult); } @Override public void handleSearchResults(Search search, Collection<? extends SearchResult> searchResults) { addResults(searchResults); } @Override public void searchStarted(Search search) { } @Override public void searchStopped(Search search) { } } /** * Comparator to search for GroupedSearchResult objects by URN. This is * only used to perform a binary search by URN, never to perform the sort, * so the compare() method does not need to be symmetric. */ private static class UrnResultFinder implements Comparator<Object> { @Override public int compare(Object o1, Object o2) { return ((GroupedSearchResult) o1).getUrn().compareTo((URN) o2); } } }