View | Details | Raw Unified | Return to bug 360894 | Differences between
and this patch

Collapse All | Expand All

(-)a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/dialogs/PatternFilter.java (-217 / +239 lines)
Lines 10-20 Link Here
10
 *******************************************************************************/
10
 *******************************************************************************/
11
package org.eclipse.ui.dialogs;
11
package org.eclipse.ui.dialogs;
12
12
13
import com.ibm.icu.text.BreakIterator;
13
import java.util.ArrayList;
14
import java.util.ArrayList;
14
import java.util.HashMap;
15
import java.util.HashMap;
15
import java.util.List;
16
import java.util.List;
16
import java.util.Map;
17
import java.util.Map;
17
18
import org.eclipse.jface.viewers.AbstractTreeViewer;
18
import org.eclipse.jface.viewers.AbstractTreeViewer;
19
import org.eclipse.jface.viewers.ILabelProvider;
19
import org.eclipse.jface.viewers.ILabelProvider;
20
import org.eclipse.jface.viewers.ITreeContentProvider;
20
import org.eclipse.jface.viewers.ITreeContentProvider;
Lines 22-29 Link Here
22
import org.eclipse.jface.viewers.Viewer;
22
import org.eclipse.jface.viewers.Viewer;
23
import org.eclipse.jface.viewers.ViewerFilter;
23
import org.eclipse.jface.viewers.ViewerFilter;
24
import org.eclipse.ui.internal.misc.StringMatcher;
24
import org.eclipse.ui.internal.misc.StringMatcher;
25
26
import com.ibm.icu.text.BreakIterator;
27
25
28
/**
26
/**
29
 * A filter used in conjunction with <code>FilteredTree</code>. In order to
27
 * A filter used in conjunction with <code>FilteredTree</code>. In order to
Lines 39-133 Link Here
39
	/*
37
	/*
40
	 * Cache of filtered elements in the tree
38
	 * Cache of filtered elements in the tree
41
	 */
39
	 */
42
    private Map cache = new HashMap();
40
	protected Map cache = new HashMap();
43
    
41
44
    /*
42
	/*
45
     * Maps parent elements to TRUE or FALSE
43
	 * Maps parent elements to TRUE or FALSE
46
     */
44
	 */
47
    private Map foundAnyCache = new HashMap();
45
	protected Map foundAnyCache = new HashMap();
48
    
46
49
    private boolean useCache = false;
47
	protected boolean useCache = false;
50
    
48
51
	/**
49
	/**
52
	 * Whether to include a leading wildcard for all provided patterns.  A
50
	 * Whether to include a leading wildcard for all provided patterns. A
53
	 * trailing wildcard is always included.
51
	 * trailing wildcard is always included.
54
	 */
52
	 */
55
	private boolean includeLeadingWildcard = false;
53
	private boolean includeLeadingWildcard = false;
56
54
57
	/**
55
	/**
58
	 * The string pattern matcher used for this pattern filter.  
56
	 * The string pattern matcher used for this pattern filter.
59
	 */
57
	 */
60
    private StringMatcher matcher;
58
	protected StringMatcher matcher;
61
    
62
    private boolean useEarlyReturnIfMatcherIsNull = true;
63
    
64
    private static Object[] EMPTY = new Object[0];
65
59
66
    /* (non-Javadoc)
60
	protected boolean useEarlyReturnIfMatcherIsNull = true;
67
     * @see org.eclipse.jface.viewers.ViewerFilter#filter(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object[])
61
68
     */
62
	private static Object[] EMPTY = new Object[0];
69
    public final Object[] filter(Viewer viewer, Object parent, Object[] elements) {
63
70
    	// we don't want to optimize if we've extended the filter ... this
64
	/*
71
    	// needs to be addressed in 3.4
65
	 * (non-Javadoc)
72
    	// https://bugs.eclipse.org/bugs/show_bug.cgi?id=186404
66
	 * 
73
        if (matcher == null && useEarlyReturnIfMatcherIsNull) {
67
	 * @see
68
	 * org.eclipse.jface.viewers.ViewerFilter#filter(org.eclipse.jface.viewers
69
	 * .Viewer, java.lang.Object, java.lang.Object[])
70
	 */
71
	public Object[] filter(Viewer viewer, Object parent, Object[] elements) {
72
		// we don't want to optimize if we've extended the filter ... this
73
		// needs to be addressed in 3.4
74
		// https://bugs.eclipse.org/bugs/show_bug.cgi?id=186404
75
		if (matcher == null && useEarlyReturnIfMatcherIsNull) {
74
			return elements;
76
			return elements;
75
		}
77
		}
76
78
77
        if (!useCache) {
79
		if (!useCache) {
78
        	return super.filter(viewer, parent, elements);
80
			return super.filter(viewer, parent, elements);
79
        }
81
		}
80
        
81
        Object[] filtered = (Object[]) cache.get(parent);
82
        if (filtered == null) {
83
        	Boolean foundAny = (Boolean) foundAnyCache.get(parent);
84
        	if (foundAny != null && !foundAny.booleanValue()) {
85
        		filtered = EMPTY;
86
        	} else {
87
        		filtered = super.filter(viewer, parent, elements);
88
        	}
89
            cache.put(parent, filtered);
90
        }
91
        return filtered;
92
    }
93
82
94
    /**
83
		return doFilter(viewer, parent, elements);
95
     * Returns true if any of the elements makes it through the filter.
84
	}
96
     * This method uses caching if enabled; the computation is done in
85
97
     * computeAnyVisible.
86
	protected Object[] doFilter(Viewer viewer, Object parent, Object[] elements) {
98
     *  
87
		Object[] filtered = (Object[]) cache.get(parent);
99
     * @param viewer
88
		if (filtered == null) {
100
     * @param parent
89
			Boolean foundAny = (Boolean) foundAnyCache.get(parent);
101
     * @param elements the elements (must not be an empty array)
90
			if (foundAny != null && !foundAny.booleanValue()) {
102
     * @return true if any of the elements makes it through the filter.
91
				filtered = EMPTY;
103
     */
92
			} else {
104
    private boolean isAnyVisible(Viewer viewer, Object parent, Object[] elements) {
93
				filtered = super.filter(viewer, parent, elements);
105
    	if (matcher == null) {
94
			}
106
    		return true;
95
			cache.put(parent, filtered);
107
    	}
96
		}
108
    	
97
		return filtered;
109
    	if (!useCache) {
98
	}
110
    		return computeAnyVisible(viewer, elements);
99
111
    	}
100
	/**
112
    	
101
	 * Returns true if any of the elements makes it through the filter. This
113
    	Object[] filtered = (Object[]) cache.get(parent);
102
	 * method uses caching if enabled; the computation is done in
114
    	if (filtered != null) {
103
	 * computeAnyVisible.
115
    		return filtered.length > 0;
104
	 * 
116
    	}
105
	 * @param viewer
117
    	Boolean foundAny = (Boolean) foundAnyCache.get(parent);
106
	 * @param parent
118
    	if (foundAny == null) {
107
	 * @param elements
119
    		foundAny = computeAnyVisible(viewer, elements) ? Boolean.TRUE : Boolean.FALSE;
108
	 *            the elements (must not be an empty array)
120
    		foundAnyCache.put(parent, foundAny);
109
	 * @return true if any of the elements makes it through the filter.
121
    	}
110
	 */
122
    	return foundAny.booleanValue();
111
	protected boolean isAnyVisible(Viewer viewer, Object parent, Object[] elements) {
123
    }
112
		if (matcher == null) {
113
			return true;
114
		}
115
116
		if (!useCache) {
117
			return computeAnyVisible(viewer, elements);
118
		}
119
120
		Object[] filtered = (Object[]) cache.get(parent);
121
		if (filtered != null) {
122
			return filtered.length > 0;
123
		}
124
		Boolean foundAny = (Boolean) foundAnyCache.get(parent);
125
		if (foundAny == null) {
126
			foundAny = computeAnyVisible(viewer, elements) ? Boolean.TRUE : Boolean.FALSE;
127
			foundAnyCache.put(parent, foundAny);
128
		}
129
		return foundAny.booleanValue();
130
	}
124
131
125
	/**
132
	/**
126
	 * Returns true if any of the elements makes it through the filter.
133
	 * Returns true if any of the elements makes it through the filter.
127
	 * 
134
	 * 
128
	 * @param viewer the viewer
135
	 * @param viewer
129
	 * @param elements the elements to test
136
	 *            the viewer
130
	 * @return <code>true</code> if any of the elements makes it through the filter
137
	 * @param elements
138
	 *            the elements to test
139
	 * @return <code>true</code> if any of the elements makes it through the
140
	 *         filter
131
	 */
141
	 */
132
	private boolean computeAnyVisible(Viewer viewer, Object[] elements) {
142
	private boolean computeAnyVisible(Viewer viewer, Object[] elements) {
133
		boolean elementFound = false;
143
		boolean elementFound = false;
Lines 137-181 Link Here
137
		}
147
		}
138
		return elementFound;
148
		return elementFound;
139
	}
149
	}
140
    
150
141
    /* (non-Javadoc)
151
	/*
142
     * @see org.eclipse.jface.viewers.ViewerFilter#select(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
152
	 * (non-Javadoc)
143
     */
153
	 * 
144
    public final boolean select(Viewer viewer, Object parentElement,
154
	 * @see
145
			Object element) {
155
	 * org.eclipse.jface.viewers.ViewerFilter#select(org.eclipse.jface.viewers
146
        return isElementVisible(viewer, element);
156
	 * .Viewer, java.lang.Object, java.lang.Object)
147
    }
157
	 */
148
    
158
	public boolean select(Viewer viewer, Object parentElement, Object element) {
149
    /**
159
		return isElementVisible(viewer, element);
160
	}
161
162
	/**
150
	 * Sets whether a leading wildcard should be attached to each pattern
163
	 * Sets whether a leading wildcard should be attached to each pattern
151
	 * string.
164
	 * string.
152
	 * 
165
	 * 
153
	 * @param includeLeadingWildcard
166
	 * @param includeLeadingWildcard
154
	 *            Whether a leading wildcard should be added.
167
	 *            Whether a leading wildcard should be added.
155
	 */
168
	 */
156
	public final void setIncludeLeadingWildcard(
169
	public final void setIncludeLeadingWildcard(final boolean includeLeadingWildcard) {
157
			final boolean includeLeadingWildcard) {
158
		this.includeLeadingWildcard = includeLeadingWildcard;
170
		this.includeLeadingWildcard = includeLeadingWildcard;
159
	}
171
	}
160
172
161
    /**
173
	/**
162
     * The pattern string for which this filter should select 
174
	 * The pattern string for which this filter should select elements in the
163
     * elements in the viewer.
175
	 * viewer.
164
     * 
176
	 * 
165
     * @param patternString
177
	 * @param patternString
166
     */
178
	 */
167
    public void setPattern(String patternString) {
179
	public void setPattern(String patternString) {
168
    	// these 2 strings allow the PatternFilter to be extended in
180
		// these 2 strings allow the PatternFilter to be extended in
169
    	// 3.3 - https://bugs.eclipse.org/bugs/show_bug.cgi?id=186404
181
		// 3.3 - https://bugs.eclipse.org/bugs/show_bug.cgi?id=186404
170
    	if ("org.eclipse.ui.keys.optimization.true".equals(patternString)) { //$NON-NLS-1$
182
		if ("org.eclipse.ui.keys.optimization.true".equals(patternString)) { //$NON-NLS-1$
171
    		useEarlyReturnIfMatcherIsNull = true;
183
			useEarlyReturnIfMatcherIsNull = true;
172
    		return;
184
			return;
173
    	} else if ("org.eclipse.ui.keys.optimization.false".equals(patternString)) { //$NON-NLS-1$
185
		} else if ("org.eclipse.ui.keys.optimization.false".equals(patternString)) { //$NON-NLS-1$
174
    		useEarlyReturnIfMatcherIsNull = false;
186
			useEarlyReturnIfMatcherIsNull = false;
175
    		return;
187
			return;
176
    	}
188
		}
177
        clearCaches();
189
		clearCaches();
178
        if (patternString == null || patternString.equals("")) { //$NON-NLS-1$
190
		if (patternString == null || patternString.equals("")) { //$NON-NLS-1$
179
			matcher = null;
191
			matcher = null;
180
		} else {
192
		} else {
181
			String pattern = patternString + "*"; //$NON-NLS-1$
193
			String pattern = patternString + "*"; //$NON-NLS-1$
Lines 184-295 Link Here
184
			}
196
			}
185
			matcher = new StringMatcher(pattern, true, false);
197
			matcher = new StringMatcher(pattern, true, false);
186
		}
198
		}
187
    }
188
189
	/**
190
	 * Clears the caches used for optimizing this filter. Needs to be called whenever
191
	 * the tree content changes.
192
	 */
193
	/* package */ void clearCaches() {
194
		cache.clear();
195
        foundAnyCache.clear();
196
	}
199
	}
197
200
198
    /**
201
	/**
199
     * Answers whether the given String matches the pattern.
202
	 * Clears the caches used for optimizing this filter. Needs to be called
200
     * 
203
	 * whenever the tree content changes.
201
     * @param string the String to test
204
	 */
202
     * 
205
	/* package */void clearCaches() {
203
     * @return whether the string matches the pattern
206
		cache.clear();
204
     */
207
		foundAnyCache.clear();
205
    private boolean match(String string) {
208
	}
206
    	if (matcher == null) {
209
210
	/**
211
	 * Answers whether the given String matches the pattern.
212
	 * 
213
	 * @param string
214
	 *            the String to test
215
	 * 
216
	 * @return whether the string matches the pattern
217
	 */
218
	protected boolean match(String string) {
219
		if (matcher == null) {
207
			return true;
220
			return true;
208
		}
221
		}
209
        return matcher.match(string);
222
		return matcher.match(string);
210
    }
223
	}
211
    
212
    /**
213
     * Answers whether the given element is a valid selection in 
214
     * the filtered tree.  For example, if a tree has items that 
215
     * are categorized, the category itself may  not be a valid 
216
     * selection since it is used merely to organize the elements.
217
     * 
218
     * @param element
219
     * @return true if this element is eligible for automatic selection
220
     */
221
    public boolean isElementSelectable(Object element){
222
    	return element != null;
223
    }
224
    
225
    /**
226
     * Answers whether the given element in the given viewer matches
227
     * the filter pattern.  This is a default implementation that will 
228
     * show a leaf element in the tree based on whether the provided  
229
     * filter text matches the text of the given element's text, or that 
230
     * of it's children (if the element has any).  
231
     * 
232
     * Subclasses may override this method.
233
     * 
234
     * @param viewer the tree viewer in which the element resides
235
     * @param element the element in the tree to check for a match
236
     * 
237
     * @return true if the element matches the filter pattern
238
     */
239
    public boolean isElementVisible(Viewer viewer, Object element){
240
    	return isParentMatch(viewer, element) || isLeafMatch(viewer, element);
241
    }
242
    
243
    /**
244
     * Check if the parent (category) is a match to the filter text.  The default 
245
     * behavior returns true if the element has at least one child element that is 
246
     * a match with the filter text.
247
     * 
248
     * Subclasses may override this method.
249
     * 
250
     * @param viewer the viewer that contains the element
251
     * @param element the tree element to check
252
     * @return true if the given element has children that matches the filter text
253
     */
254
    protected boolean isParentMatch(Viewer viewer, Object element){
255
        Object[] children = ((ITreeContentProvider) ((AbstractTreeViewer) viewer)
256
                .getContentProvider()).getChildren(element);
257
224
258
        if ((children != null) && (children.length > 0)) {
225
	/**
226
	 * Answers whether the given element is a valid selection in the filtered
227
	 * tree. For example, if a tree has items that are categorized, the category
228
	 * itself may not be a valid selection since it is used merely to organize
229
	 * the elements.
230
	 * 
231
	 * @param element
232
	 * @return true if this element is eligible for automatic selection
233
	 */
234
	public boolean isElementSelectable(Object element) {
235
		return element != null;
236
	}
237
238
	/**
239
	 * Answers whether the given element in the given viewer matches the filter
240
	 * pattern. This is a default implementation that will show a leaf element
241
	 * in the tree based on whether the provided filter text matches the text of
242
	 * the given element's text, or that of it's children (if the element has
243
	 * any).
244
	 * 
245
	 * Subclasses may override this method.
246
	 * 
247
	 * @param viewer
248
	 *            the tree viewer in which the element resides
249
	 * @param element
250
	 *            the element in the tree to check for a match
251
	 * 
252
	 * @return true if the element matches the filter pattern
253
	 */
254
	public boolean isElementVisible(Viewer viewer, Object element) {
255
		return isParentMatch(viewer, element) || isLeafMatch(viewer, element);
256
	}
257
258
	/**
259
	 * Check if the parent (category) is a match to the filter text. The default
260
	 * behavior returns true if the element has at least one child element that
261
	 * is a match with the filter text.
262
	 * 
263
	 * Subclasses may override this method.
264
	 * 
265
	 * @param viewer
266
	 *            the viewer that contains the element
267
	 * @param element
268
	 *            the tree element to check
269
	 * @return true if the given element has children that matches the filter
270
	 *         text
271
	 */
272
	protected boolean isParentMatch(Viewer viewer, Object element) {
273
		Object[] children = ((ITreeContentProvider) ((AbstractTreeViewer) viewer)
274
				.getContentProvider()).getChildren(element);
275
276
		if ((children != null) && (children.length > 0)) {
259
			return isAnyVisible(viewer, element, children);
277
			return isAnyVisible(viewer, element, children);
260
		}	
278
		}
261
        return false;
279
		return false;
262
    }
280
	}
263
    
281
264
    /**
282
	/**
265
     * Check if the current (leaf) element is a match with the filter text.  
283
	 * Check if the current (leaf) element is a match with the filter text. The
266
     * The default behavior checks that the label of the element is a match. 
284
	 * default behavior checks that the label of the element is a match.
267
     * 
285
	 * 
268
     * Subclasses should override this method.
286
	 * Subclasses should override this method.
269
     * 
287
	 * 
270
     * @param viewer the viewer that contains the element
288
	 * @param viewer
271
     * @param element the tree element to check
289
	 *            the viewer that contains the element
272
     * @return true if the given element's label matches the filter text
290
	 * @param element
273
     */
291
	 *            the tree element to check
274
    protected boolean isLeafMatch(Viewer viewer, Object element){
292
	 * @return true if the given element's label matches the filter text
275
        String labelText = ((ILabelProvider) ((StructuredViewer) viewer)
293
	 */
276
                .getLabelProvider()).getText(element);
294
	protected boolean isLeafMatch(Viewer viewer, Object element) {
277
        
295
		String labelText = ((ILabelProvider) ((StructuredViewer) viewer).getLabelProvider())
278
        if(labelText == null) {
296
				.getText(element);
297
		// system.out.println(labelText);
298
		if (labelText == null) {
279
			return false;
299
			return false;
280
		}
300
		}
281
        return wordMatches(labelText);  
301
		return wordMatches(labelText);
282
    }
302
	}
283
    
303
284
    /**
304
	/**
285
     * Take the given filter text and break it down into words using a 
305
	 * Take the given filter text and break it down into words using a
286
     * BreakIterator.  
306
	 * BreakIterator.
287
     * 
307
	 * 
288
     * @param text
308
	 * @param text
289
     * @return an array of words
309
	 * @return an array of words
290
     */
310
	 */
291
    private String[] getWords(String text){
311
	private String[] getWords(String text) {
292
    	List words = new ArrayList();
312
		List words = new ArrayList();
293
		// Break the text up into words, separating based on whitespace and
313
		// Break the text up into words, separating based on whitespace and
294
		// common punctuation.
314
		// common punctuation.
295
		// Previously used String.split(..., "\\W"), where "\W" is a regular
315
		// Previously used String.split(..., "\\W"), where "\W" is a regular
Lines 314-339 Link Here
314
			i = j;
334
			i = j;
315
		}
335
		}
316
		return (String[]) words.toArray(new String[words.size()]);
336
		return (String[]) words.toArray(new String[words.size()]);
317
    }
337
	}
318
    
338
319
	/**
339
	/**
320
	 * Return whether or not if any of the words in text satisfy the
340
	 * Return whether or not if any of the words in text satisfy the match
321
	 * match critera.
341
	 * critera.
322
	 * 
342
	 * 
323
	 * @param text the text to match
343
	 * @param text
324
	 * @return boolean <code>true</code> if one of the words in text 
344
	 *            the text to match
325
	 * 					satisifes the match criteria.
345
	 * @return boolean <code>true</code> if one of the words in text satisifes
346
	 *         the match criteria.
326
	 */
347
	 */
327
	protected boolean wordMatches(String text) {
348
	protected boolean wordMatches(String text) {
328
		if (text == null) {
349
		if (text == null) {
329
			return false;
350
			return false;
330
		}
351
		}
331
		
352
332
		//If the whole text matches we are all set
353
		// If the whole text matches we are all set
333
		if(match(text)) {
354
		if (match(text)) {
334
			return true;
355
			return true;
335
		}
356
		}
336
		
357
337
		// Otherwise check if any of the words of the text matches
358
		// Otherwise check if any of the words of the text matches
338
		String[] words = getWords(text);
359
		String[] words = getWords(text);
339
		for (int i = 0; i < words.length; i++) {
360
		for (int i = 0; i < words.length; i++) {
Lines 349-357 Link Here
349
	/**
370
	/**
350
	 * Can be called by the filtered tree to turn on caching.
371
	 * Can be called by the filtered tree to turn on caching.
351
	 * 
372
	 * 
352
	 * @param useCache The useCache to set.
373
	 * @param useCache
374
	 *            The useCache to set.
353
	 */
375
	 */
354
	void setUseCache(boolean useCache) {
376
	void setUseCache(boolean useCache) {
355
		this.useCache = useCache;
377
		this.useCache = useCache;
356
	}    
378
	}
357
}
379
}

Return to bug 360894