KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > RowFilter


1 /*
2  * @(#)RowFilter.java 1.6 06/03/15
3  *
4  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing;
8
9 import java.util.ArrayList JavaDoc;
10 import java.math.BigDecimal JavaDoc;
11 import java.math.BigInteger JavaDoc;
12 import java.util.Date JavaDoc;
13 import java.util.List JavaDoc;
14 import java.util.regex.Matcher JavaDoc;
15 import java.util.regex.Pattern JavaDoc;
16
17 /**
18  * <code>RowFilter</code> is used to filter out entries from the
19  * model so that they are not shown in the view. For example, a
20  * <code>RowFilter</code> associated with a <code>JTable</code> might
21  * only allow rows that contain a column with a specific string. The
22  * meaning of <em>entry</em> depends on the component type.
23  * For example, when a filter is
24  * associated with a <code>JTable</code>, an entry corresponds to a
25  * row; when associated with a <code>JTree</code>, an entry corresponds
26  * to a node.
27  * <p>
28  * Subclasses must override the <code>include</code> method to
29  * indicate whether the entry should be shown in the
30  * view. The <code>Entry</code> argument can be used to obtain the values in
31  * each of the columns in that entry. The following example shows an
32  * <code>include</code> method that allows only entries containing one or
33  * more values starting with the string "a":
34  * <pre>
35  * RowFilter&lt;Object,Object&gt; startsWithAFilter = new RowFilter&lt;Object,Object&gt;() {
36  * public boolean include(Entry&lt;? extends Object, ? extends Object&gt; entry) {
37  * for (int i = entry.getValueCount() - 1; i &gt;= 0; i--) {
38  * if (entry.getStringValue(i).startsWith("a")) {
39  * // The value starts with "a", include it
40  * return true;
41  * }
42  * }
43  * // None of the columns start with "a"; return false so that this
44  * // entry is not shown
45  * return false;
46  * }
47  * };
48  * </pre>
49  * <code>RowFilter</code> has two formal type parameters that allow
50  * you to create a <code>RowFilter</code> for a specific model. For
51  * example, the following assumes a specific model that is wrapping
52  * objects of type <code>Person</code>. Only <code>Person</code>s
53  * with an age over 20 will be shown:
54  * <pre>
55  * RowFilter&lt;PersonModel,Integer&gt; ageFilter = new RowFilter&lt;PersonModel,Integer&gt;() {
56  * public boolean include(Entry&lt;? extends PersonModel, ? extends Integer&gt; entry) {
57  * PersonModel personModel = entry.getModel();
58  * Person person = personModel.getPerson(entry.getIdentifier());
59  * if (person.getAge() &gt; 20) {
60  * // Returning true indicates this row should be shown.
61  * return true;
62  * }
63  * // Age is &lt;= 20, don't show it.
64  * return false;
65  * }
66  * };
67  * PersonModel model = createPersonModel();
68  * TableRowSorter&lt;PersonModel&gt; sorter = new TableRowSorter&lt;PersonModel&gt;(model);
69  * sorter.setRowFilter(ageFilter);
70  * </pre>
71  *
72  * @param <M> the type of the model; for example <code>PersonModel</code>
73  * @param <I> the type of the identifier; when using
74  * <code>TableRowSorter</code> this will be <code>Integer</code>
75  * @version 1.6 03/15/06
76  * @see javax.swing.table.TableRowSorter
77  * @since 1.6
78  */

79 public abstract class RowFilter<M,I> {
80     /**
81      * Enumeration of the possible comparison values supported by
82      * some of the default <code>RowFilter</code>s.
83      *
84      * @see RowFilter
85      * @since 1.6
86      */

87     public enum ComparisonType {
88         /**
89          * Indicates that entries with a value before the supplied
90          * value should be included.
91          */

92         BEFORE,
93
94         /**
95          * Indicates that entries with a value after the supplied
96          * value should be included.
97          */

98         AFTER,
99
100         /**
101          * Indicates that entries with a value equal to the supplied
102          * value should be included.
103          */

104         EQUAL,
105
106         /**
107          * Indicates that entries with a value not equal to the supplied
108          * value should be included.
109          */

110         NOT_EQUAL
111     }
112
113     /**
114      * Throws an IllegalArgumentException if any of the values in
115      * columns are < 0.
116      */

117     private static void checkIndices(int[] columns) {
118         for (int i = columns.length - 1; i >= 0; i--) {
119             if (columns[i] < 0) {
120                 throw new IllegalArgumentException JavaDoc("Index must be >= 0");
121             }
122         }
123     }
124
125     /**
126      * Returns a <code>RowFilter</code> that uses a regular
127      * expression to determine which entries to include. Only entries
128      * with at least one matching value are included. For
129      * example, the following creates a <code>RowFilter</code> that
130      * includes entries with at least one value starting with
131      * "a":
132      * <pre>
133      * RowFilter.regexFilter("^a");
134      * </pre>
135      * <p>
136      * The returned filter uses {@link java.util.regex.Matcher#find}
137      * to test for inclusion. To test for exact matches use the
138      * characters '^' and '$' to match the beginning and end of the
139      * string respectively. For example, "^foo$" includes only rows whose
140      * string is exactly "foo" and not, for example, "food". See
141      * {@link java.util.regex.Pattern} for a complete description of
142      * the supported regular-expression constructs.
143      *
144      * @param regex the regular expression to filter on
145      * @param indices the indices of the values to check. If not supplied all
146      * values are evaluated
147      * @return a <code>RowFilter</code> implementing the specified criteria
148      * @throws NullPointerException if <code>regex</code> is
149      * <code>null</code>
150      * @throws IllegalArgumentException if any of the <code>indices</code>
151      * are &lt; 0
152      * @throws PatternSyntaxException if <code>regex</code> is
153      * not a valid regular expression.
154      * @see java.util.regex.Pattern
155      */

156     public static <M,I> RowFilter JavaDoc<M,I> regexFilter(String JavaDoc regex,
157                                                        int... indices) {
158         return (RowFilter JavaDoc<M,I>)new RegexFilter(Pattern.compile(regex),
159                                                indices);
160     }
161
162     /**
163      * Returns a <code>RowFilter</code> that includes entries that
164      * have at least one <code>Date</code> value meeting the specified
165      * criteria. For example, the following <code>RowFilter</code> includes
166      * only entries with at least one date value after the current date:
167      * <pre>
168      * RowFilter.dateFilter(ComparisonType.AFTER, new Date());
169      * </pre>
170      *
171      * @param type the type of comparison to perform
172      * @param date the date to compare against
173      * @param indices the indices of the values to check. If not supplied all
174      * values are evaluated
175      * @return a <code>RowFilter</code> implementing the specified criteria
176      * @throws NullPointerException if <code>date</code> is
177      * <code>null</code>
178      * @throws IllegalArgumentException if any of the <code>indices</code>
179      * are &lt; 0 or <code>type</code> is
180      * <code>null</code>
181      * @see java.util.Calendar
182      * @see java.util.Date
183      */

184     public static <M,I> RowFilter JavaDoc<M,I> dateFilter(ComparisonType type,
185                                             Date JavaDoc date, int... indices) {
186         return (RowFilter JavaDoc<M,I>)new DateFilter(type, date.getTime(), indices);
187     }
188
189     /**
190      * Returns a <code>RowFilter</code> that includes entries that
191      * have at least one <code>Number</code> value meeting the
192      * specified criteria. For example, the following
193      * filter will only include entries with at
194      * least one number value equal to 10:
195      * <pre>
196      * RowFilter.numberFilter(ComparisonType.EQUAL, 10);
197      * </pre>
198      *
199      * @param type the type of comparison to perform
200      * @param indices the indices of the values to check. If not supplied all
201      * values are evaluated
202      * @return a <code>RowFilter</code> implementing the specified criteria
203      * @throws IllegalArgumentException if any of the <code>indices</code>
204      * are &lt; 0, <code>type</code> is <code>null</code>
205      * or <code>number</code> is <code>null</code>
206      */

207     public static <M,I> RowFilter JavaDoc<M,I> numberFilter(ComparisonType type,
208                                             Number JavaDoc number, int... indices) {
209         return (RowFilter JavaDoc<M,I>)new NumberFilter(type, number, indices);
210     }
211
212     /**
213      * Returns a <code>RowFilter</code> that includes entries if any
214      * of the supplied filters includes the entry.
215      * <p>
216      * The following example creates a <code>RowFilter</code> that will
217      * include any entries containing the string "foo" or the string
218      * "bar":
219      * <pre>
220      * List&lt;RowFilter&lt;Object,Object&gt;&gt; filters = new ArrayList&lt;RowFilter&lt;Object,Object&gt;&gt;(2);
221      * filters.add(RowFilter.regexFilter("foo"));
222      * filters.add(RowFilter.regexFilter("bar"));
223      * RowFilter&lt;Object,Object&gt; fooBarFilter = RowFilter.orFilter(filters);
224      * </pre>
225      *
226      * @param filters the <code>RowFilter</code>s to test
227      * @throws IllegalArgumentException if any of the filters
228      * are <code>null</code>
229      * @throws NullPointerException if <code>filters</code> is null
230      * @return a <code>RowFilter</code> implementing the specified criteria
231      * @see java.util.Arrays#asList
232      */

233     public static <M,I> RowFilter JavaDoc<M,I> orFilter(
234             Iterable JavaDoc<? extends RowFilter JavaDoc<? super M, ? super I>> filters) {
235         return new OrFilter<M,I>(filters);
236     }
237
238     /**
239      * Returns a <code>RowFilter</code> that includes entries if all
240      * of the supplied filters include the entry.
241      * <p>
242      * The following example creates a <code>RowFilter</code> that will
243      * include any entries containing the string "foo" and the string
244      * "bar":
245      * <pre>
246      * List&lt;RowFilter&lt;Object,Object&gt;&gt; filters = new ArrayList&lt;RowFilter&lt;Object,Object&gt;&gt;(2);
247      * filters.add(RowFilter.regexFilter("foo"));
248      * filters.add(RowFilter.regexFilter("bar"));
249      * RowFilter&lt;Object,Object&gt; fooBarFilter = RowFilter.andFilter(filters);
250      * </pre>
251      *
252      * @param filters the <code>RowFilter</code>s to test
253      * @return a <code>RowFilter</code> implementing the specified criteria
254      * @throws IllegalArgumentException if any of the filters
255      * are <code>null</code>
256      * @throws NullPointerException if <code>filters</code> is null
257      * @see java.util.Arrays#asList
258      */

259     public static <M,I> RowFilter JavaDoc<M,I> andFilter(
260             Iterable JavaDoc<? extends RowFilter JavaDoc<? super M, ? super I>> filters) {
261         return new AndFilter<M,I>(filters);
262     }
263
264     /**
265      * Returns a <code>RowFilter</code> that includes entries if the
266      * supplied filter does not include the entry.
267      *
268      * @param filter the <code>RowFilter</code> to negate
269      * @return a <code>RowFilter</code> implementing the specified criteria
270      * @throws IllegalArgumentException if <code>filter</code> is
271      * <code>null</code>
272      */

273     public static <M,I> RowFilter JavaDoc<M,I> notFilter(RowFilter JavaDoc<M,I> filter) {
274         return new NotFilter<M,I>(filter);
275     }
276
277     /**
278      * Returns true if the specified entry should be shown;
279      * returns false if the entry should be hidden.
280      * <p>
281      * The <code>entry</code> argument is valid only for the duration of
282      * the invocation. Using <code>entry</code> after the call returns
283      * results in undefined behavior.
284      *
285      * @param entry a non-<code>null</code> object that wraps the underlying
286      * object from the model
287      * @return true if the entry should be shown
288      */

289     public abstract boolean include(Entry<? extends M, ? extends I> entry);
290
291     //
292
// WARNING:
293
// Because of the method signature of dateFilter/numberFilter/regexFilter
294
// we can NEVER add a method to RowFilter that returns M,I. If we were
295
// to do so it would be possible to get a ClassCastException during normal
296
// usage.
297
//
298

299     /**
300      * An <code>Entry</code> object is passed to instances of
301      * <code>RowFilter</code>, allowing the filter to get the value of the
302      * entry's data, and thus to determine whether the entry should be shown.
303      * An <code>Entry</code> object contains information about the model
304      * as well as methods for getting the underlying values from the model.
305      *
306      * @param <M> the type of the model; for example <code>PersonModel</code>
307      * @param <I> the type of the identifier; when using
308      * <code>TableRowSorter</code> this will be <code>Integer</code>
309      * @see javax.swing.RowFilter
310      * @see javax.swing.DefaultRowSorter#setRowFilter(javax.swing.RowFilter)
311      * @since 1.6
312      */

313     public static abstract class Entry<M, I> {
314         /**
315          * Creates an <code>Entry</code>.
316          */

317         public Entry() {
318         }
319
320         /**
321          * Returns the underlying model.
322          *
323          * @return the model containing the data that this entry represents
324          */

325         public abstract M getModel();
326
327         /**
328          * Returns the number of values in the entry. For
329          * example, when used with a table this corresponds to the
330          * number of columns.
331          *
332          * @return number of values in the object being filtered
333          */

334         public abstract int getValueCount();
335
336         /**
337          * Returns the value at the specified index. This may return
338          * <code>null</code>. When used with a table, index
339          * corresponds to the column number in the model.
340          *
341          * @param index the index of the value to get
342          * @return value at the specified index
343          * @throws <code>IndexOutOfBoundsException</code> if index &lt; 0 or
344          * &gt;= getValueCount
345          */

346         public abstract Object JavaDoc getValue(int index);
347
348         /**
349          * Returns the string value at the specified index. If
350          * filtering is being done based on <code>String</code> values
351          * this method is preferred to that of <code>getValue</code>
352          * as <code>getValue(index).toString()</code> may return a
353          * different result than <code>getStringValue(index)</code>.
354          * <p>
355          * This implementation calls <code>getValue(index).toString()</code>
356          * after checking for <code>null</code>. Subclasses that provide
357          * different string conversion should override this method if
358          * necessary.
359          *
360          * @param index the index of the value to get
361          * @return {@code non-null} string at the specified index
362          * @throws <code>IndexOutOfBoundsException</code> if index &lt; 0 ||
363          * &gt;= getValueCount
364          */

365         public String JavaDoc getStringValue(int index) {
366             Object JavaDoc value = getValue(index);
367             return (value == null) ? "" : value.toString();
368         }
369
370         /**
371          * Returns the identifer (in the model) of the entry.
372          * For a table this corresponds to the index of the row in the model,
373          * expressed as an <code>Integer</code>.
374          *
375          * @return a model-based (not view-based) identifier for
376          * this entry
377          */

378         public abstract I getIdentifier();
379     }
380
381
382     private static abstract class GeneralFilter extends RowFilter JavaDoc<Object JavaDoc,Object JavaDoc> {
383         private int[] columns;
384
385         GeneralFilter(int[] columns) {
386             checkIndices(columns);
387             this.columns = columns;
388         }
389
390         public boolean include(Entry<? extends Object JavaDoc,? extends Object JavaDoc> value){
391             int count = value.getValueCount();
392             if (columns.length > 0) {
393                 for (int i = columns.length - 1; i >= 0; i--) {
394                     int index = columns[i];
395                     if (index < count) {
396                         if (include(value, index)) {
397                             return true;
398                         }
399                     }
400                 }
401             }
402             else {
403                 while (--count >= 0) {
404                     if (include(value, count)) {
405                         return true;
406                     }
407                 }
408             }
409             return false;
410         }
411
412         protected abstract boolean include(
413               Entry<? extends Object JavaDoc,? extends Object JavaDoc> value, int index);
414     }
415
416
417     private static class RegexFilter extends GeneralFilter {
418         private Matcher JavaDoc matcher;
419
420         RegexFilter(Pattern JavaDoc regex, int[] columns) {
421             super(columns);
422             if (regex == null) {
423                 throw new IllegalArgumentException JavaDoc("Pattern must be non-null");
424             }
425             matcher = regex.matcher("");
426         }
427
428         protected boolean include(
429                 Entry<? extends Object JavaDoc,? extends Object JavaDoc> value, int index) {
430             matcher.reset(value.getStringValue(index));
431             return matcher.find();
432         }
433     }
434
435
436     private static class DateFilter extends GeneralFilter {
437         private long date;
438         private ComparisonType type;
439
440         DateFilter(ComparisonType type, long date, int[] columns) {
441             super(columns);
442             if (type == null) {
443                 throw new IllegalArgumentException JavaDoc("type must be non-null");
444             }
445             this.type = type;
446             this.date = date;
447         }
448
449         protected boolean include(
450                 Entry<? extends Object JavaDoc,? extends Object JavaDoc> value, int index) {
451             Object JavaDoc v = value.getValue(index);
452
453             if (v instanceof Date JavaDoc) {
454                 long vDate = ((Date JavaDoc)v).getTime();
455                 switch(type) {
456                 case BEFORE:
457                     return (vDate < date);
458                 case AFTER:
459                     return (vDate > date);
460                 case EQUAL:
461                     return (vDate == date);
462                 case NOT_EQUAL:
463                     return (vDate != date);
464                 default:
465                     break;
466                 }
467             }
468             return false;
469         }
470     }
471
472
473
474
475     private static class NumberFilter extends GeneralFilter {
476         private boolean isComparable;
477         private Number JavaDoc number;
478         private ComparisonType type;
479
480         NumberFilter(ComparisonType type, Number JavaDoc number, int[] columns) {
481             super(columns);
482             if (type == null || number == null) {
483                 throw new IllegalArgumentException JavaDoc(
484                     "type and number must be non-null");
485             }
486             this.type = type;
487             this.number = number;
488             isComparable = (number instanceof Comparable JavaDoc);
489         }
490
491         @SuppressWarnings JavaDoc("unchecked")
492         protected boolean include(
493                 Entry<? extends Object JavaDoc,? extends Object JavaDoc> value, int index) {
494             Object JavaDoc v = value.getValue(index);
495
496             if (v instanceof Number JavaDoc) {
497                 boolean compared = true;
498                 int compareResult;
499                 Class JavaDoc vClass = v.getClass();
500                 if (number.getClass() == vClass && isComparable) {
501                     compareResult = ((Comparable JavaDoc)number).compareTo(v);
502                 }
503                 else {
504                     compareResult = longCompare((Number JavaDoc)v);
505                 }
506                 switch(type) {
507                 case BEFORE:
508                     return (compareResult > 0);
509                 case AFTER:
510                     return (compareResult < 0);
511                 case EQUAL:
512                     return (compareResult == 0);
513                 case NOT_EQUAL:
514                     return (compareResult != 0);
515                 default:
516                     break;
517                 }
518             }
519             return false;
520         }
521
522         private int longCompare(Number JavaDoc o) {
523             long diff = number.longValue() - o.longValue();
524
525             if (diff < 0) {
526                 return -1;
527             }
528             else if (diff > 0) {
529                 return 1;
530             }
531             return 0;
532         }
533     }
534
535
536     private static class OrFilter<M,I> extends RowFilter JavaDoc<M,I> {
537         List JavaDoc<RowFilter JavaDoc<? super M,? super I>> filters;
538
539         OrFilter(Iterable JavaDoc<? extends RowFilter JavaDoc<? super M, ? super I>> filters) {
540             this.filters = new ArrayList JavaDoc<RowFilter JavaDoc<? super M,? super I>>();
541             for (RowFilter JavaDoc<? super M, ? super I> filter : filters) {
542                 if (filter == null) {
543                     throw new IllegalArgumentException JavaDoc(
544                         "Filter must be non-null");
545                 }
546                 this.filters.add(filter);
547             }
548         }
549
550         public boolean include(Entry<? extends M, ? extends I> value) {
551             for (RowFilter JavaDoc<? super M,? super I> filter : filters) {
552                 if (filter.include(value)) {
553                     return true;
554                 }
555             }
556             return false;
557         }
558     }
559
560
561     private static class AndFilter<M,I> extends OrFilter<M,I> {
562         AndFilter(Iterable JavaDoc<? extends RowFilter JavaDoc<? super M,? super I>> filters) {
563             super(filters);
564         }
565
566         public boolean include(Entry<? extends M, ? extends I> value) {
567             for (RowFilter JavaDoc<? super M,? super I> filter : filters) {
568                 if (!filter.include(value)) {
569                     return false;
570                 }
571             }
572             return true;
573         }
574     }
575
576
577     private static class NotFilter<M,I> extends RowFilter JavaDoc<M,I> {
578         private RowFilter JavaDoc<M,I> filter;
579
580         NotFilter(RowFilter JavaDoc<M,I> filter) {
581             if (filter == null) {
582                 throw new IllegalArgumentException JavaDoc(
583                     "filter must be non-null");
584             }
585             this.filter = filter;
586         }
587
588         public boolean include(Entry<? extends M, ? extends I> value) {
589             return !filter.include(value);
590         }
591     }
592 }
593
Popular Tags