blob: 342d527ab3c2461b93e664c443c36303d54a7637 [file] [log] [blame]
/*=============================================================================#
# 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();
}
}