blob: d786624313c9a282ce45c27c259cbf9ff237937e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.search.ui.text;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.search.ui.ISearchResult;
import org.eclipse.search.ui.ISearchResultListener;
import org.eclipse.search.ui.SearchResultEvent;
/**
* An abstract base implementation for text-match based search results. This search
* result implementation consists of a list of {@link org.eclipse.search.ui.text.Match matches}.
* No assumptions are made about the kind of elements these matches are reported against.
*
* @since 3.0
*/
public abstract class AbstractTextSearchResult implements ISearchResult {
private Map fElementsToMatches;
private List fListeners;
private static final Match[] EMPTY_ARRAY= new Match[0];
private MatchEvent fMatchEvent;
/**
* Constructs a new <code>AbstractTextSearchResult</code>
*/
protected AbstractTextSearchResult() {
fElementsToMatches= new HashMap();
fListeners= new ArrayList();
fMatchEvent= new MatchEvent(this);
}
/**
* Returns an array with all matches reported against the given element.
*
* @param element the element to report matches for
* @return all matches reported for this element
* @see Match#getElement()
*/
public Match[] getMatches(Object element) {
synchronized (fElementsToMatches) {
return doGetMatches(element);
}
}
private Match[] doGetMatches(Object element) {
List matches= (List) fElementsToMatches.get(element);
if (matches != null)
return (Match[]) matches.toArray(new Match[matches.size()]);
return EMPTY_ARRAY;
}
/**
* Adds a <code>Match</code> to this search result. This method does nothing if the
* match is already present.
* <p>
* Subclasses may extend this method.
* </p>
*
* @param match the match to add
*/
public void addMatch(Match match) {
boolean hasAdded= false;
synchronized (fElementsToMatches) {
hasAdded= doAddMatch(match);
}
if (hasAdded)
fireChange(getSearchResultEvent(match, MatchEvent.ADDED));
}
/**
* Adds a number of Matches to this search result. This method does nothing for
* matches that are already present.
* <p>
* Subclasses may extend this method.
* </p>
* @param matches the matches to add
*/
public void addMatches(Match[] matches) {
Set reallyAdded= new HashSet();
synchronized (fElementsToMatches) {
for (int i = 0; i < matches.length; i++) {
if (doAddMatch(matches[i]))
reallyAdded.add(matches[i]);
}
}
if (reallyAdded.size() > 0)
fireChange(getSearchResultEvent(reallyAdded, MatchEvent.ADDED));
}
private MatchEvent getSearchResultEvent(Match match, int eventKind) {
fMatchEvent.setKind(eventKind);
fMatchEvent.setMatch(match);
return fMatchEvent;
}
private MatchEvent getSearchResultEvent(Set matches, int eventKind) {
fMatchEvent.setKind(eventKind);
Match[] matchArray= new Match[matches.size()];
matches.toArray(matchArray);
fMatchEvent.setMatches(matchArray);
return fMatchEvent;
}
private boolean doAddMatch(Match match) {
List matches= (List) fElementsToMatches.get(match.getElement());
if (matches == null) {
matches= new ArrayList();
fElementsToMatches.put(match.getElement(), matches);
}
if (!matches.contains(match)) {
insertSorted(matches, match);
return true;
}
return false;
}
private static void insertSorted(List matches, Match match) {
if (matches.size() == 0) {
matches.add(match);
return;
}
int insertIndex= getInsertIndex(matches, match, 0, matches.size());
matches.add(insertIndex, match);
}
private static int getInsertIndex(List matches, Match match, int min, int max) {
if (min == max)
return min;
int middle= (min+max)/2;
int compareResult= compare(match, (Match) matches.get(middle));
if (compareResult == 0)
return middle+1;
if (compareResult > 0)
return getInsertIndex(matches, match, min, middle);
return getInsertIndex(matches, match, middle+1, max);
}
private static int compare(Match match1, Match match2) {
int diff= match2.getOffset()-match1.getOffset();
if (diff != 0)
return diff;
return match2.getLength()-match1.getLength();
}
/**
* Removes all matches from this search result.
* <p>
* Subclasses may extend this method.
* </p>
*/
public void removeAll() {
synchronized (fElementsToMatches) {
doRemoveAll();
}
fireChange(new RemoveAllEvent(this));
}
private void doRemoveAll() {
fElementsToMatches.clear();
}
/**
* Removes the given match from this search result. This method has no
* effect if the match is not found.
* <p>
* Subclasses may extend this method.
* </p>
* @param match the match to remove
*/
public void removeMatch(Match match) {
boolean existed= false;
synchronized (fElementsToMatches) {
existed= doRemoveMatch(match);
}
if (existed)
fireChange(getSearchResultEvent(match, MatchEvent.REMOVED));
}
/**
* Removes the given matches from this search result. This method has no
* effect for matches that are not found
* <p>
* Subclasses may extend this method.
* </p>
*
* @param matches the matches to remove
*/
public void removeMatches(Match[] matches) {
Set existing= new HashSet();
synchronized (fElementsToMatches) {
for (int i = 0; i < matches.length; i++) {
if (doRemoveMatch(matches[i]))
existing.add(matches[i]);
}
}
if (existing.size() > 0)
fireChange(getSearchResultEvent(existing, MatchEvent.REMOVED));
}
private boolean doRemoveMatch(Match match) {
boolean existed= false;
List matches= (List) fElementsToMatches.get(match.getElement());
if (matches != null) {
existed= matches.remove(match);
if (matches.isEmpty())
fElementsToMatches.remove(match.getElement());
}
return existed;
}
/**
* {@inheritDoc}
*/
public void addListener(ISearchResultListener l) {
synchronized (fListeners) {
fListeners.add(l);
}
}
/**
* {@inheritDoc}
*/
public void removeListener(ISearchResultListener l) {
synchronized (fListeners) {
fListeners.remove(l);
}
}
/**
* Send the given <code>SearchResultEvent</code> to all registered search
* result listeners.
*
* @param e the event to be sent
*
* @see ISearchResultListener
*/
protected void fireChange(SearchResultEvent e) {
HashSet copiedListeners= new HashSet();
synchronized (fListeners) {
copiedListeners.addAll(fListeners);
}
Iterator listeners= copiedListeners.iterator();
while (listeners.hasNext()) {
((ISearchResultListener) listeners.next()).searchResultChanged(e);
}
}
/**
* Returns the total number of matches contained in this search result.
*
* @return total number of matches
*/
public int getMatchCount() {
int count= 0;
synchronized (fElementsToMatches) {
for (Iterator elements= fElementsToMatches.values().iterator(); elements.hasNext();) {
List element= (List) elements.next();
if (element != null)
count+= element.size();
}
}
return count;
}
/**
* Returns the number of matches reported against a given element. This is
* equivalent to calling <code>getMatches(element).length</code>
*
* @param element the element to get the match count for
* @return the number of matches reported against the element
*/
public int getMatchCount(Object element) {
List matches= (List) fElementsToMatches.get(element);
if (matches != null)
return matches.size();
return 0;
}
/**
* Returns an array containing the set of all elements that matches are
* reported against in this search result.
*
* @return the set of elements in this search result
*/
public Object[] getElements() {
synchronized (fElementsToMatches) {
return fElementsToMatches.keySet().toArray();
}
}
/**
* Returns an implementation of <code>IEditorMatchAdapter</code> appropriate
* for this search result.
*
* @return an appropriate adapter or <code>null</code> if none has been implemented
*
* @see IEditorMatchAdapter
*/
public abstract IEditorMatchAdapter getEditorMatchAdapter();
/**
* Returns an implementation of <code>IFileMatchAdapter</code> appropriate
* for this search result.
*
* @return an appropriate adapter or <code>null</code> if none has been implemented
*
* @see IFileMatchAdapter
*/
public abstract IFileMatchAdapter getFileMatchAdapter();
}