blob: f45c5737b4c6dcb23a839be9f4e09b6bd02b8730 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2007 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.amalgam.explorer.activity.ui.api.editor.pages.helper;
import java.text.BreakIterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.internal.misc.StringMatcher;
/**
* Copied from org.eclipse.ui.dialogs to export package methods.<br>
* A filter used in conjunction with <code>FilteredTree</code>. In order to
* determine if a node should be filtered it uses the content provider of the
* tree to do pattern matching on its children. This causes the entire tree
* structure to be realized.
*
* @see FilteredTree
* @since 3.2
*/
@SuppressWarnings({ "unchecked", "rawtypes", "restriction" })
public class PatternFilter extends ViewerFilter {
/*
* Cache of filtered elements in the tree
*/
private Map _cache = new HashMap();
/*
* Maps parent elements to TRUE or FALSE
*/
private Map _foundAnyCache = new HashMap();
private boolean _useCache = false;
/**
* Whether to include a leading wildcard for all provided patterns. A
* trailing wildcard is always included.
*/
private boolean _includeLeadingWildcard = false;
/**
* The string pattern _matcher used for this pattern filter.
*/
private StringMatcher _matcher;
private boolean _useEarlyReturnIfMatcherIsNull = true;
private static Object[] EMPTY = new Object[0];
/**
* @see org.eclipse.jface.viewers.ViewerFilter#filter(org.eclipse.jface.viewers.Viewer,
* java.lang.Object, java.lang.Object[])
*/
@Override
public final Object[] filter(Viewer viewer_p, Object parent_p, Object[] elements_p) {
// we don't want to optimize if we've extended the filter ... this
// needs to be addressed in 3.4
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=186404
if (_matcher == null && _useEarlyReturnIfMatcherIsNull) {
return elements_p;
}
if (!_useCache) {
return super.filter(viewer_p, parent_p, elements_p);
}
Object[] filtered = (Object[]) _cache.get(parent_p);
if (filtered == null) {
Boolean foundAny = (Boolean) _foundAnyCache.get(parent_p);
if (foundAny != null && !foundAny.booleanValue()) {
filtered = EMPTY;
} else {
filtered = super.filter(viewer_p, parent_p, elements_p);
}
_cache.put(parent_p, filtered);
}
return filtered;
}
/**
* Returns true if any of the elements makes it through the filter. This
* method uses caching if enabled; the computation is done in
* computeAnyVisible.
*
* @param viewer_p
* @param parent_p
* @param elements_p
* the elements (must not be an empty array)
* @return true if any of the elements makes it through the filter.
*/
protected boolean isAnyVisible(Viewer viewer_p, Object parent_p, Object[] elements_p) {
if (_matcher == null) {
return true;
}
if (!_useCache) {
return computeAnyVisible(viewer_p, parent_p, elements_p);
}
Object[] filtered = (Object[]) _cache.get(parent_p);
if (filtered != null) {
return filtered.length > 0;
}
Boolean foundAny = (Boolean) _foundAnyCache.get(parent_p);
if (foundAny == null) {
foundAny = computeAnyVisible(viewer_p, parent_p, elements_p) ? Boolean.TRUE : Boolean.FALSE;
_foundAnyCache.put(parent_p, foundAny);
}
return foundAny.booleanValue();
}
/**
* Returns true if any of the elements makes it through the filter.
*
* @param viewer_p
* @param parentElement_p
* @param elements_p
* @return
*/
private boolean computeAnyVisible(Viewer viewer_p, Object parentElement_p, Object[] elements_p) {
boolean elementFound = false;
for (int i = 0; i < elements_p.length && !elementFound; i++) {
Object element = elements_p[i];
elementFound = isElementVisible(viewer_p, parentElement_p, element);
}
return elementFound;
}
/**
* @see org.eclipse.jface.viewers.ViewerFilter#select(org.eclipse.jface.viewers.Viewer,
* java.lang.Object, java.lang.Object)
*/
@Override
public boolean select(Viewer viewer_p, Object parentElement_p, Object element_p) {
return isElementVisible(viewer_p, parentElement_p, element_p);
}
/**
* Sets whether a leading wildcard should be attached to each pattern
* string.
*
* @param includeLeadingWildcard_p
* Whether a leading wildcard should be added.
*/
public final void setIncludeLeadingWildcard(final boolean includeLeadingWildcard_p) {
_includeLeadingWildcard = includeLeadingWildcard_p;
}
/**
* The pattern string for which this filter should select elements in the
* viewer.
*
* @param patternString_p
*/
public void setPattern(String patternString_p) {
// these 2 strings allow the PatternFilter to be extended in
// 3.3 - https://bugs.eclipse.org/bugs/show_bug.cgi?id=186404
if ("org.eclipse.ui.keys.optimization.true".equals(patternString_p)) { //$NON-NLS-1$
_useEarlyReturnIfMatcherIsNull = true;
return;
} else if ("org.eclipse.ui.keys.optimization.false".equals(patternString_p)) { //$NON-NLS-1$
_useEarlyReturnIfMatcherIsNull = false;
return;
}
clearCaches();
if (patternString_p == null || patternString_p.equals("")) { //$NON-NLS-1$
_matcher = null;
} else {
String pattern = patternString_p + "*"; //$NON-NLS-1$
if (_includeLeadingWildcard) {
pattern = "*" + pattern; //$NON-NLS-1$
}
_matcher = new StringMatcher(pattern, true, false);
}
}
/**
* Clears the caches used for optimizing this filter. Needs to be called
* whenever the tree content changes.
*/
public void clearCaches() {
_cache.clear();
_foundAnyCache.clear();
}
/**
* Answers whether the given String matches the pattern.
*
* @param string_p
* the String to test
* @return whether the string matches the pattern
*/
private boolean match(String string_p) {
if (_matcher == null) {
return true;
}
return _matcher.match(string_p);
}
/**
* Answers whether the given element is a valid selection in the filtered
* tree. For example, if a tree has items that are categorized, the category
* itself may not be a valid selection since it is used merely to organize
* the elements.
*
* @param element_p
* @return true if this element is eligible for automatic selection
*/
public boolean isElementSelectable(Object element_p) {
return element_p != null;
}
/**
* Answers whether the given element in the given viewer matches the filter
* pattern. This is a default implementation that will show a leaf element
* in the tree based on whether the provided filter text matches the text of
* the given element's text, or that of it's children (if the element has
* any). Subclasses may override this method.
*
* @param viewer_p
* the tree viewer in which the element resides
* @param parentElement_p
* the parent element.
* @param element_p
* the element in the tree to check for a match
* @return true if the element matches the filter pattern
*/
public boolean isElementVisible(Viewer viewer_p, Object parentElement_p, Object element_p) {
return isParentMatch(viewer_p, parentElement_p, element_p) || isLeafMatch(viewer_p, parentElement_p, element_p);
}
/**
* Check if the parent (category) is a match to the filter text. The default
* behavior returns true if the element has at least one child element that
* is a match with the filter text. Subclasses may override this method.
*
* @param viewer_p
* the viewer that contains the element
* @param parentElement_p
* the parent element
* @param element_p
* the tree element to check
* @return true if the given element has children that matches the filter
* text
*/
protected boolean isParentMatch(Viewer viewer_p, Object parentElement_p, Object element_p) {
Object[] children = ((ITreeContentProvider) ((AbstractTreeViewer) viewer_p).getContentProvider())
.getChildren(element_p);
if ((children != null) && (children.length > 0)) {
return isAnyVisible(viewer_p, element_p, children);
}
return false;
}
/**
* Check if the current (leaf) element is a match with the filter text. The
* default behavior checks that the label of the element is a match.
* Subclasses should override this method.
*
* @param viewer_p
* the viewer that contains the element
* @param element_p
* the tree element to check
* @param parentElement_p
* the parent element
* @return true if the given element's label matches the filter text
*/
protected boolean isLeafMatch(Viewer viewer_p, Object parentElement_p, Object element_p) {
String labelText = ((ILabelProvider) ((StructuredViewer) viewer_p).getLabelProvider()).getText(element_p);
if (labelText == null) {
return false;
}
return wordMatches(labelText);
}
/**
* Is given leaf element as {@link EObject} instance is already filtered by
* other filters (a viewer can have multiple {@link ViewerFilter} ?
*
* @param viewer_p
* @param parent_p
* @param element_p
* @return <code>true</code> means the leaf is filtered out at least by
* another filter.
*/
protected boolean isLeafAlreadyFilteredOutByOtherFilters(StructuredViewer viewer_p, Object parent_p,
Object element_p) {
// Check if this leaf, which matches the regular expression, is not
// filtered out by another filter (the viewer can have multiple
// filters).
ViewerFilter[] filters = viewer_p.getFilters();
for (ViewerFilter filter : filters) {
// Don't check against this pattern filter its self.
if (filter != this) {
// Is given leaf element filtered (i.e excluded) ?
if (!filter.select(viewer_p, parent_p, element_p)) {
// Given leaf is filtered out by another filter, remove it
// from displayed elements.
return true;
}
}
}
return false;
}
/**
* Take the given filter text and break it down into words using a
* BreakIterator.
*
* @param text_p
* @return an array of words
*/
private String[] getWords(String text_p) {
List words = new ArrayList();
// Break the text up into words, separating based on whitespace and
// common punctuation.
// Previously used String.split(..., "\\W"), where "\W" is a regular
// expression (see the Javadoc for class Pattern).
// Need to avoid both String.split and regular expressions, in order to
// compile against JCL Foundation (bug 80053).
// Also need to do this in an NL-sensitive way. The use of BreakIterator
// was suggested in bug 90579.
BreakIterator iter = BreakIterator.getWordInstance();
iter.setText(text_p);
int i = iter.first();
while (i != java.text.BreakIterator.DONE && i < text_p.length()) {
int j = iter.following(i);
if (j == java.text.BreakIterator.DONE) {
j = text_p.length();
}
// match the word
if (Character.isLetterOrDigit(text_p.charAt(i))) {
String word = text_p.substring(i, j);
words.add(word);
}
i = j;
}
return (String[]) words.toArray(new String[words.size()]);
}
/**
* Return whether or not if any of the words in text satisfy the match
* criteria.
*
* @param text_p
* the text to match
* @return boolean <code>true</code> if one of the words in text satisfies
* the match criteria.
*/
protected boolean wordMatches(String text_p) {
if (text_p == null) {
return false;
}
// If the whole text matches we are all set
if (match(text_p)) {
return true;
}
// Otherwise check if any of the words of the text matches
String[] words = getWords(text_p);
for (int i = 0; i < words.length; i++) {
String word = words[i];
if (match(word)) {
return true;
}
}
return false;
}
/**
* Can be called by the filtered tree to turn on caching.
*
* @param useCache_p
* The _useCache to set.
*/
public void setUseCache(boolean useCache_p) {
this._useCache = useCache_p;
}
}