package org.limewire.ui.swing.search.model; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.limewire.core.api.Category; import org.limewire.core.api.FilePropertyKey; import org.limewire.core.api.URN; import org.limewire.core.api.endpoint.RemoteHost; import org.limewire.core.api.search.GroupedSearchResult; import org.limewire.core.api.search.SearchResult; import org.limewire.friend.api.Friend; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import org.limewire.ui.swing.util.PropertiableFileUtils; import org.limewire.ui.swing.util.PropertiableHeadings; import org.limewire.util.Objects; import com.google.inject.Provider; /** * An implementation of VisualSearchResult for displaying actual search * results. */ class SearchResultAdapter implements VisualSearchResult, Comparable { private static final Log LOG = LogFactory.getLog(SearchResultAdapter.class); private static final Matcher FIND_HTML_MARKUP; static { Pattern p = Pattern.compile("[<][/]?[\\w =\"\\./:#\\-\\!\\&\\?]*[>]"); FIND_HTML_MARKUP = p.matcher(""); } private final GroupedSearchResult groupedSearchResult; private final Provider<PropertiableHeadings> propertiableHeadings; private BasicDownloadState downloadState = BasicDownloadState.NOT_STARTED; private CopyOnWriteArrayList<VisualSearchResult> similarResults = null; private VisualSearchResult similarityParent; private boolean visible; private boolean childrenVisible; private Boolean spamCache; private boolean preExistingDownload = false; private String cachedHeading; private String cachedSubHeading; private final VisualSearchResultStatusListener changeListener; /** * Constructs a SearchResultAdapter with the specified grouped core results. */ public SearchResultAdapter(GroupedSearchResult groupedSearchResult, Provider<PropertiableHeadings> propertiableHeadings, VisualSearchResultStatusListener changeListener) { this.groupedSearchResult = groupedSearchResult; this.propertiableHeadings = propertiableHeadings; this.changeListener = changeListener; this.visible = true; this.childrenVisible = false; } @Override public boolean isAnonymous() { return groupedSearchResult.isAnonymous(); } @Override public Category getCategory() { return getCoreSearchResults().get(0).getCategory(); } @Override public List<SearchResult> getCoreSearchResults() { return groupedSearchResult.getSearchResults(); } @Override public String getFileExtension() { return getCoreSearchResults().get(0).getFileExtension(); } @Override public String getFileName() { return groupedSearchResult.getFileName(); } @Override public Collection<Friend> getFriends() { return groupedSearchResult.getFriends(); } @Override public Object getProperty(FilePropertyKey key) { // find the first non-null value in any of the search results. for (SearchResult result : getCoreSearchResults()) { Object value = result.getProperty(key); if (value != null) { return value; } } return null; } @Override public String getPropertyString(FilePropertyKey key) { Object value = getProperty(key); if (value != null) { return value.toString(); } else { return null; } } @Override public String getNameProperty(boolean useAudioArtist) { return PropertiableFileUtils.getNameProperty(this, useAudioArtist); } @Override public void addSimilarSearchResult(VisualSearchResult similarResult) { assert similarResult != this; if(similarResults == null) { similarResults = new CopyOnWriteArrayList<VisualSearchResult>(); } similarResults.addIfAbsent(similarResult); } @Override public void removeSimilarSearchResult(VisualSearchResult result) { if(similarResults != null) { similarResults.remove(result); } } @Override public List<VisualSearchResult> getSimilarResults() { return similarResults == null ? Collections.<VisualSearchResult>emptyList() : similarResults; } @Override public void setSimilarityParent(VisualSearchResult parent) { VisualSearchResult oldParent = this.similarityParent; this.similarityParent = parent; firePropertyChange(SIMILARITY_PARENT, oldParent, parent); } @Override public VisualSearchResult getSimilarityParent() { return similarityParent; } @Override public long getSize() { return getCoreSearchResults().get(0).getSize(); } @Override public Collection<RemoteHost> getSources() { return groupedSearchResult.getSources(); } @Override public BasicDownloadState getDownloadState() { return downloadState; } @Override public void setDownloadState(BasicDownloadState downloadState) { // If the download was aborted, recalculate the spam score if(downloadState == BasicDownloadState.NOT_STARTED) { boolean oldSpam = isSpam(); spamCache = null; boolean newSpam = isSpam(); firePropertyChange("spam-core", oldSpam, newSpam); } BasicDownloadState oldDownloadState = this.downloadState; this.downloadState = downloadState; firePropertyChange("downloadState", oldDownloadState, downloadState); } @Override public String toString() { return getCoreSearchResults().toString(); } @Override public boolean isVisible() { return visible; } @Override public void setVisible(boolean visible) { boolean oldValue = this.visible; this.visible = visible; firePropertyChange("visible", oldValue, visible); if(LOG.isDebugEnabled()) { LOG.debugf("Updating visible to {0} for urn: {1}", visible, getUrn()); } } @Override public boolean isChildrenVisible() { return childrenVisible; } @Override public boolean isSpam() { if (spamCache == null) { boolean spam = false; for (SearchResult result : getCoreSearchResults()) spam |= result.isSpam(); spamCache = spam; } return spamCache.booleanValue(); } @Override public void setSpam(boolean spam) { boolean oldSpam = isSpam(); spamCache = spam; firePropertyChange("spam-ui", oldSpam, spam); } @Override public void setChildrenVisible(boolean childrenVisible) { boolean oldChildrenVisible = childrenVisible; this.childrenVisible = childrenVisible; for (VisualSearchResult similarResult : getSimilarResults()) { similarResult.setVisible(childrenVisible); similarResult.setChildrenVisible(false); } firePropertyChange("childrenVisible", oldChildrenVisible, childrenVisible); } @Override public void toggleChildrenVisibility() { setChildrenVisible(!isShowingSimilarResults()); } private boolean isShowingSimilarResults() { return getSimilarResults().size() > 0 && isChildrenVisible(); } @Override public URN getUrn() { return groupedSearchResult.getUrn(); } @Override public URN getNavSelectionId() { return getUrn(); } @Override public String getMagnetLink() { //TODO: add other search results as alternative results to the magnet. // A bit too intensive for the release. if(getCoreSearchResults().size() > 0) return getCoreSearchResults().get(0).getMagnetURL(); else return null; } @Override public String getHeading() { if (cachedHeading == null) { cachedHeading = sanitize(propertiableHeadings.get().getHeading(this)); } return cachedHeading; } /** * This method checks for HTML encoding in the content of the supplied string. * If found, the encoding is stripped from the string and this result is marked as * spam. * @param input * @return The stripped string is returned (or the same string if no HTML encoding * is found). */ private String sanitize(String input) { Matcher matcher = FIND_HTML_MARKUP.reset(input); if (matcher.find()) { setSpam(true); return matcher.replaceAll(""); } return input; } @Override public String getSubHeading() { if (cachedSubHeading == null) { cachedSubHeading = sanitize(propertiableHeadings.get().getSubHeading(this)); } return cachedSubHeading; } @Override public float getRelevance() { return groupedSearchResult.getRelevance(); } /** * If any of the search results' lime xml docs contains a license string * the entire VisualSearchResult is considered "licensed". * */ @Override public boolean isLicensed() { for (SearchResult searchResult : getCoreSearchResults()) { if (searchResult.isLicensed()) { return true; } } return false; } @Override public boolean isPreExistingDownload() { return preExistingDownload; } @Override public void setPreExistingDownload(boolean preExistingDownload) { this.preExistingDownload = preExistingDownload; } @Override public int compareTo(Object o) { if(!(o instanceof SearchResultAdapter)) return -1; SearchResultAdapter sra = (SearchResultAdapter) o; return getHeading().compareTo(sra.getHeading()); } private void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { if (oldValue != newValue) { changeListener.resultChanged(this, propertyName, oldValue, newValue); } } private void firePropertyChange(String propertyName, Object oldValue, Object newValue) { if (!Objects.equalOrNull(oldValue, newValue)) { changeListener.resultChanged(this, propertyName, oldValue, newValue); } } }