| /******************************************************************************* |
| * 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.core.resources.IFile; |
| import org.eclipse.search.ui.ISearchResult; |
| import org.eclipse.search.ui.ISearchResultListener; |
| import org.eclipse.search.ui.SearchResultEvent; |
| import org.eclipse.ui.IEditorPart; |
| |
| /** |
| * An abstract superclass for text-match based search results. This search |
| * result implementation consists of a list of matches. No assumptions are made about |
| * the kind of elements these matches are reported against. |
| * see {@link org.eclipse.search.ui.text.Match} |
| * This class has abstract methods to map matches to both editors and files. |
| * If a client implements the methods related to editors, matches will be highlighted |
| * automatically in participating editors. Editors must implement or adapt to ITextEditor, |
| * or they must adapt to IAnnotationModel in order for match highlighting to work.<br> |
| * see {@link #findContainedMatches(IEditorPart)}<br> |
| * see {@link #isShownInEditor(Match,IEditorPart)}<br> |
| * If a client implements the methods related to files, matches will be automatically |
| * updated if the file is changed via the filebuffer infrastructure.<br> |
| * see {@link org.eclipse.core.filebuffers.FileBuffers}<br> |
| * see {@link #findContainedMatches(IFile)<br> |
| * see {@link #getFile(Object)<br> |
| */ |
| public abstract class AbstractTextSearchResult implements ISearchResult { |
| private Map fElementsToMatches; |
| private List fListeners; |
| private static final Match[] EMPTY_ARRAY= new Match[0]; |
| private MatchEvent fMatchEvent; |
| |
| /** |
| * Constructor |
| */ |
| protected AbstractTextSearchResult() { |
| fElementsToMatches= new HashMap(); |
| fListeners= new ArrayList(); |
| fMatchEvent= new MatchEvent(this); |
| } |
| |
| /** |
| * Returns an array with all matches reported against the given element. |
| * |
| * @see Match#getElement() |
| * @param element The element to report matches for. |
| * @return All matches reported for this element. |
| */ |
| 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 Match to this search result. This method does nothing if the |
| * Match is already present. |
| * Subclasses may extend this method. |
| * |
| * @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. |
| * Subclasses may extend this method. |
| * @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)) { |
| matches.add(match); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Removes all matches from this search result. |
| * Subclasses may extend this method. |
| */ |
| 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. |
| * Subclasses may extend this method. |
| * |
| * @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 |
| * Subclasses may extend this method. |
| * |
| * @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 |
| * @see ISearchResultListener |
| * @param e The event to be sent. |
| */ |
| 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 array with all matches contained in the given file. If the |
| * matches are not contained within an <code>IFile</code>, this method |
| * must return an empty array. |
| * |
| * @param file The file to find matches in. |
| * @return An array of matches (possibly empty). |
| */ |
| public abstract Match[] findContainedMatches(IFile file); |
| /** |
| * Returns the file associated with the given element (usually the file |
| * the element is contained in). If the element is not associated with a |
| * file, this method should return <code>null</code>. |
| * |
| * @param element An element associated with a match. |
| * @return The file associated with the element or null. |
| */ |
| public abstract IFile getFile(Object element); |
| /** |
| * Determines whether a match should be displayed in the given editor. |
| * For example, if a match is reported in a file, This method should return |
| * <code>true</code>, if the given editor displays the file. |
| * |
| * @param match The match. |
| * @param editor The editor that possibly contains the matches element. |
| * @return |
| */ |
| public abstract boolean isShownInEditor(Match match, IEditorPart editor); |
| /** |
| * Returns all matches that are contained in the element shown in the given |
| * editor. |
| * For example, if the editor shows a particular file, all matches in that file should |
| * be returned. |
| * |
| * @param editor The editor. |
| * @return All matches that are contained in the element that is shown in |
| * the given editor. |
| */ |
| public abstract Match[] findContainedMatches(IEditorPart editor); |
| |
| |
| } |