| /*=============================================================================# |
| # Copyright (c) 2013, 2019 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.ecommons.workbench.search.ui; |
| |
| import java.util.ArrayList; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.search.ui.ISearchResult; |
| import org.eclipse.search.ui.ISearchResultListener; |
| import org.eclipse.search.ui.SearchResultEvent; |
| import org.eclipse.search.ui.text.AbstractTextSearchResult; |
| import org.eclipse.search.ui.text.IEditorMatchAdapter; |
| import org.eclipse.search.ui.text.IFileMatchAdapter; |
| import org.eclipse.search.ui.text.Match; |
| import org.eclipse.search.ui.text.MatchEvent; |
| import org.eclipse.search.ui.text.RemoveAllEvent; |
| |
| import org.eclipse.statet.jcommons.collections.SortedArraySet; |
| import org.eclipse.statet.jcommons.collections.SortedListSet; |
| |
| import org.eclipse.statet.ecommons.collections.FastList; |
| |
| |
| /** |
| * |
| * |
| * @param <E> element type |
| * @param <M> match type |
| */ |
| public abstract class ExtTextSearchResult<E, M extends Match> extends AbstractTextSearchResult { |
| |
| |
| private static class ChangeEvent extends MatchEvent { |
| |
| private static final long serialVersionUID= -5266244762347509979L; |
| |
| public ChangeEvent(final ISearchResult searchResult) { |
| super(searchResult); |
| } |
| |
| @Override |
| protected void setKind(final int kind) { |
| super.setKind(kind); |
| } |
| |
| @Override |
| protected void setMatch(final Match match) { |
| super.setMatch(match); |
| } |
| |
| @Override |
| protected void setMatches(final Match[] matches) { |
| super.setMatches(matches); |
| } |
| |
| } |
| |
| protected static class DefaultMatchComparator<M extends Match> implements Comparator<M> { |
| |
| public DefaultMatchComparator() { |
| } |
| |
| @Override |
| public int compare(final M o1, final M o2) { |
| final int d= o1.getOffset() - o2.getOffset(); |
| if (d != 0) { |
| return d; |
| } |
| return o1.getLength() - o2.getLength(); |
| } |
| |
| } |
| |
| |
| /* Locking: |
| * - All write changes => events are thrown in correct order: |
| * synchronized (this) |
| * - All access to toplevel collections {@link #elementMatches} and {@link #elementList}: |
| * synchronized (this.elementMatches) |
| * - All access to match lists, values of {@link #elementMatches}: |
| * synchronized (matches) |
| */ |
| |
| private final ElementMatchComparator<E, M> comparator; |
| |
| private final SortedListSet<E> elementList; |
| private final Map<E, SortedListSet<M>> elementMatches; |
| |
| private final FastList<ISearchResultListener> listeners= new FastList<>(ISearchResultListener.class, FastList.IDENTITY); |
| private final ChangeEvent changeEvent= new ChangeEvent(this); |
| |
| |
| public ExtTextSearchResult(final ElementMatchComparator<E, M> comparator) { |
| this.comparator= comparator; |
| |
| this.elementList= new SortedArraySet<>(comparator.getElement0(), comparator.getElementComparator()); |
| this.elementMatches= new HashMap<>(); |
| } |
| |
| |
| public ElementMatchComparator<E, M> getComparator() { |
| return this.comparator; |
| } |
| |
| @Override |
| public String getTooltip() { |
| return getLabel(); |
| } |
| |
| @Override |
| public IEditorMatchAdapter getEditorMatchAdapter() { |
| return null; |
| } |
| |
| @Override |
| public IFileMatchAdapter getFileMatchAdapter() { |
| return null; |
| } |
| |
| |
| @Override |
| public void addListener(final ISearchResultListener l) { |
| this.listeners.add(l); |
| } |
| |
| @Override |
| public void removeListener(final ISearchResultListener l) { |
| this.listeners.remove(l); |
| } |
| |
| protected ChangeEvent getChangeEvent(final int eventKind, final Match match) { |
| this.changeEvent.setKind(eventKind); |
| this.changeEvent.setMatch(match); |
| return this.changeEvent; |
| } |
| |
| protected ChangeEvent getChangeEvent(final int eventKind, final List<Match> matches) { |
| this.changeEvent.setKind(eventKind); |
| this.changeEvent.setMatches(matches.toArray(new Match[matches.size()])); |
| return this.changeEvent; |
| } |
| |
| @Override |
| protected void fireChange(final SearchResultEvent e) { |
| final ISearchResultListener[] listeners= this.listeners.toArray(); |
| for (final ISearchResultListener listener : listeners) { |
| listener.searchResultChanged(e); |
| } |
| } |
| |
| |
| public int getElementCount() { |
| synchronized (this.elementMatches) { |
| return this.elementList.size(); |
| } |
| } |
| |
| @Override |
| public E[] getElements() { |
| synchronized (this.elementMatches) { |
| return this.elementList.toArray(this.comparator.getElement0()); |
| } |
| } |
| |
| |
| @Override |
| public synchronized void addMatch(final Match match) { |
| final boolean done; |
| synchronized (this.elementMatches) { |
| done= doAddMatch((M) match); |
| } |
| if (done) { |
| fireChange(getChangeEvent(MatchEvent.ADDED, match)); |
| } |
| } |
| |
| @Override |
| public synchronized void addMatches(final Match[] matches) { |
| final List<Match> added= new ArrayList<>(matches.length); |
| synchronized (this.elementMatches) { |
| for (int i= 0; i < matches.length; i++) { |
| if (doAddMatch((M) matches[i])) { |
| added.add(matches[i]); |
| } |
| } |
| } |
| if (!added.isEmpty()) { |
| fireChange(getChangeEvent(MatchEvent.ADDED, added)); |
| } |
| } |
| |
| protected boolean doAddMatch(final M match) { |
| final E element= (E) match.getElement(); |
| if (element == null) { |
| return false; |
| } |
| SortedListSet<M> matches= this.elementMatches.get(element); |
| if (matches == null) { |
| if (this.elementList.addE(element) < 0) { |
| return false; |
| } |
| matches= new SortedArraySet<>(this.comparator.getMatch0(), this.comparator.getMatchComparator()); |
| this.elementMatches.put(element, matches); |
| } |
| synchronized (matches) { |
| if (matches.addE(match) >= 0) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public synchronized void removeMatch(final Match match) { |
| final boolean done; |
| synchronized (this.elementMatches) { |
| done= doRemoveMatch((M) match); |
| } |
| if (done) { |
| fireChange(getChangeEvent(MatchEvent.REMOVED, match)); |
| } |
| } |
| |
| @Override |
| public synchronized void removeMatches(final Match[] matches) { |
| final List<Match> removed= new ArrayList<>(matches.length); |
| synchronized (this.elementMatches) { |
| for (int i= 0; i < matches.length; i++) { |
| if (doRemoveMatch((M) matches[i])) { |
| removed.add(matches[i]); |
| } |
| } |
| } |
| if (!removed.isEmpty()) { |
| fireChange(getChangeEvent(MatchEvent.REMOVED, removed)); |
| } |
| } |
| |
| protected boolean doRemoveMatch(final M match) { |
| final E element= (E) match.getElement(); |
| if (element == null) { |
| return false; |
| } |
| final SortedListSet<M> matches= this.elementMatches.get(element); |
| if (matches == null) { |
| return false; |
| } |
| synchronized (matches) { |
| if (matches.removeE(match) < 0) { |
| return false; |
| } |
| } |
| if (matches.isEmpty()) { |
| this.elementList.remove(element); |
| this.elementMatches.remove(element); |
| } |
| return true; |
| } |
| |
| @Override |
| public synchronized void removeAll() { |
| synchronized (this.elementMatches) { |
| doRemoveAll(); |
| } |
| fireChange(new RemoveAllEvent(this)); |
| } |
| |
| protected void doRemoveAll() { |
| this.elementList.clear(); |
| this.elementMatches.clear(); |
| } |
| |
| @Override |
| public int getMatchCount() { |
| synchronized (this.elementMatches) { |
| int count= 0; |
| for (final List<M> matches : this.elementMatches.values()) { |
| count+= matches.size(); |
| } |
| return count; |
| } |
| } |
| |
| public boolean hasMatches(final Object element) { |
| synchronized (this.elementMatches) { |
| return this.elementMatches.containsKey(element); |
| } |
| } |
| |
| public boolean hasPickedMatches(final Object element) { |
| final SortedListSet<M> matches; |
| synchronized (this.elementMatches) { |
| matches= this.elementMatches.get(element); |
| } |
| if (matches != null) { |
| if (getActiveMatchFilters() == null) { |
| return true; |
| } |
| else { |
| synchronized (matches) { |
| for (final M match : matches) { |
| if (!match.isFiltered()) { |
| return true; |
| } |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public int getMatchCount(final Object element) { |
| synchronized (this.elementMatches) { |
| final SortedListSet<M> matches= this.elementMatches.get(element); |
| if (matches != null) { |
| return matches.size(); |
| } |
| return 0; |
| } |
| } |
| |
| public int getPickedMatchCount(final Object element) { |
| final SortedListSet<M> matches; |
| synchronized (this.elementMatches) { |
| matches= this.elementMatches.get(element); |
| } |
| if (matches != null) { |
| if (getActiveMatchFilters() == null) { |
| synchronized (matches) { |
| return matches.size(); |
| } |
| } |
| else { |
| synchronized (matches) { |
| int count= 0; |
| for (final M match : matches) { |
| if (!match.isFiltered()) { |
| count++; |
| } |
| } |
| return count; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| @Override |
| public M[] getMatches(final Object element) { |
| synchronized (this.elementMatches) { |
| final SortedListSet<M> matches= this.elementMatches.get(element); |
| if (matches != null) { |
| return matches.toArray(this.comparator.getMatch0()); |
| } |
| return this.comparator.getMatch0(); |
| } |
| } |
| |
| public M[] getPickedMatches(final Object element) { |
| final SortedListSet<M> matches; |
| synchronized (this.elementMatches) { |
| matches= this.elementMatches.get(element); |
| } |
| if (matches != null) { |
| if (getActiveMatchFilters() == null) { |
| synchronized (matches) { |
| return matches.toArray(this.comparator.getMatch0()); |
| } |
| } |
| else { |
| synchronized (matches) { |
| final List<M> filtered= new ArrayList<>(matches.size()); |
| for (final M match : matches) { |
| if (!match.isFiltered()) { |
| filtered.add(match); |
| } |
| } |
| return filtered.toArray(this.comparator.getMatch0()); |
| } |
| } |
| } |
| return this.comparator.getMatch0(); |
| } |
| |
| } |