1 /*
2  * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 package javax.swing;
26 
27 import java.util.ArrayList;
28 import java.math.BigDecimal;
29 import java.math.BigInteger;
30 import java.util.Date;
31 import java.util.List;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34 import java.util.regex.PatternSyntaxException;
35 
36 /**
37  * <code>RowFilter</code> is used to filter out entries from the
38  * model so that they are not shown in the view.  For example, a
39  * <code>RowFilter</code> associated with a <code>JTable</code> might
40  * only allow rows that contain a column with a specific string. The
41  * meaning of <em>entry</em> depends on the component type.
42  * For example, when a filter is
43  * associated with a <code>JTable</code>, an entry corresponds to a
44  * row; when associated with a <code>JTree</code>, an entry corresponds
45  * to a node.
46  * <p>
47  * Subclasses must override the <code>include</code> method to
48  * indicate whether the entry should be shown in the
49  * view.  The <code>Entry</code> argument can be used to obtain the values in
50  * each of the columns in that entry.  The following example shows an
51  * <code>include</code> method that allows only entries containing one or
52  * more values starting with the string "a":
53  * <pre>
54  * RowFilter&lt;Object,Object&gt; startsWithAFilter = new RowFilter&lt;Object,Object&gt;() {
55  *   public boolean include(Entry&lt;? extends Object, ? extends Object&gt; entry) {
56  *     for (int i = entry.getValueCount() - 1; i &gt;= 0; i--) {
57  *       if (entry.getStringValue(i).startsWith("a")) {
58  *         // The value starts with "a", include it
59  *         return true;
60  *       }
61  *     }
62  *     // None of the columns start with "a"; return false so that this
63  *     // entry is not shown
64  *     return false;
65  *   }
66  * };
67  * </pre>
68  * <code>RowFilter</code> has two formal type parameters that allow
69  * you to create a <code>RowFilter</code> for a specific model. For
70  * example, the following assumes a specific model that is wrapping
71  * objects of type <code>Person</code>.  Only <code>Person</code>s
72  * with an age over 20 will be shown:
73  * <pre>
74  * RowFilter&lt;PersonModel,Integer&gt; ageFilter = new RowFilter&lt;PersonModel,Integer&gt;() {
75  *   public boolean include(Entry&lt;? extends PersonModel, ? extends Integer&gt; entry) {
76  *     PersonModel personModel = entry.getModel();
77  *     Person person = personModel.getPerson(entry.getIdentifier());
78  *     if (person.getAge() &gt; 20) {
79  *       // Returning true indicates this row should be shown.
80  *       return true;
81  *     }
82  *     // Age is &lt;= 20, don't show it.
83  *     return false;
84  *   }
85  * };
86  * PersonModel model = createPersonModel();
87  * TableRowSorter&lt;PersonModel&gt; sorter = new TableRowSorter&lt;PersonModel&gt;(model);
88  * sorter.setRowFilter(ageFilter);
89  * </pre>
90  *
91  * @param <M> the type of the model; for example <code>PersonModel</code>
92  * @param <I> the type of the identifier; when using
93  *            <code>TableRowSorter</code> this will be <code>Integer</code>
94  * @see javax.swing.table.TableRowSorter
95  * @since 1.6
96  */
97 public abstract class RowFilter<M,I> {
98     /**
99      * Enumeration of the possible comparison values supported by
100      * some of the default <code>RowFilter</code>s.
101      *
102      * @see RowFilter
103      * @since 1.6
104      */
105     public enum ComparisonType {
106         /**
107          * Indicates that entries with a value before the supplied
108          * value should be included.
109          */
110         BEFORE,
111 
112         /**
113          * Indicates that entries with a value after the supplied
114          * value should be included.
115          */
116         AFTER,
117 
118         /**
119          * Indicates that entries with a value equal to the supplied
120          * value should be included.
121          */
122         EQUAL,
123 
124         /**
125          * Indicates that entries with a value not equal to the supplied
126          * value should be included.
127          */
128         NOT_EQUAL
129     }
130 
131     /**
132      * Constructor for subclasses to call.
133      */
RowFilter()134     protected RowFilter() {}
135 
136     /**
137      * Throws an IllegalArgumentException if any of the values in
138      * columns are {@literal <} 0.
139      */
checkIndices(int[] columns)140     private static void checkIndices(int[] columns) {
141         for (int i = columns.length - 1; i >= 0; i--) {
142             if (columns[i] < 0) {
143                 throw new IllegalArgumentException("Index must be >= 0");
144             }
145         }
146     }
147 
148     /**
149      * Returns a <code>RowFilter</code> that uses a regular
150      * expression to determine which entries to include.  Only entries
151      * with at least one matching value are included.  For
152      * example, the following creates a <code>RowFilter</code> that
153      * includes entries with at least one value starting with
154      * "a":
155      * <pre>
156      *   RowFilter.regexFilter("^a");
157      * </pre>
158      * <p>
159      * The returned filter uses {@link java.util.regex.Matcher#find}
160      * to test for inclusion.  To test for exact matches use the
161      * characters '^' and '$' to match the beginning and end of the
162      * string respectively.  For example, "^foo$" includes only rows whose
163      * string is exactly "foo" and not, for example, "food".  See
164      * {@link java.util.regex.Pattern} for a complete description of
165      * the supported regular-expression constructs.
166      *
167      * @param <M> the type of the model to which the {@code RowFilter} applies
168      * @param <I> the type of the identifier passed to the {@code RowFilter}
169      * @param regex the regular expression to filter on
170      * @param indices the indices of the values to check.  If not supplied all
171      *               values are evaluated
172      * @return a <code>RowFilter</code> implementing the specified criteria
173      * @throws NullPointerException if <code>regex</code> is
174      *         <code>null</code>
175      * @throws IllegalArgumentException if any of the <code>indices</code>
176      *         are &lt; 0
177      * @throws PatternSyntaxException if <code>regex</code> is
178      *         not a valid regular expression.
179      * @see java.util.regex.Pattern
180      */
regexFilter(String regex, int... indices)181     public static <M,I> RowFilter<M,I> regexFilter(String regex,
182                                                        int... indices) {
183         return new RegexFilter<M, I>(Pattern.compile(regex), indices);
184     }
185 
186     /**
187      * Returns a <code>RowFilter</code> that includes entries that
188      * have at least one <code>Date</code> value meeting the specified
189      * criteria.  For example, the following <code>RowFilter</code> includes
190      * only entries with at least one date value after the current date:
191      * <pre>
192      *   RowFilter.dateFilter(ComparisonType.AFTER, new Date());
193      * </pre>
194      *
195      * @param <M> the type of the model to which the {@code RowFilter} applies
196      * @param <I> the type of the identifier passed to the {@code RowFilter}
197      * @param type the type of comparison to perform
198      * @param date the date to compare against
199      * @param indices the indices of the values to check.  If not supplied all
200      *               values are evaluated
201      * @return a <code>RowFilter</code> implementing the specified criteria
202      * @throws NullPointerException if <code>date</code> is
203      *          <code>null</code>
204      * @throws IllegalArgumentException if any of the <code>indices</code>
205      *         are &lt; 0 or <code>type</code> is
206      *         <code>null</code>
207      * @see java.util.Calendar
208      * @see java.util.Date
209      */
dateFilter(ComparisonType type, Date date, int... indices)210     public static <M,I> RowFilter<M,I> dateFilter(ComparisonType type,
211                                             Date date, int... indices) {
212         return new DateFilter<M, I>(type, date.getTime(), indices);
213     }
214 
215     /**
216      * Returns a <code>RowFilter</code> that includes entries that
217      * have at least one <code>Number</code> value meeting the
218      * specified criteria.  For example, the following
219      * filter will only include entries with at
220      * least one number value equal to 10:
221      * <pre>
222      *   RowFilter.numberFilter(ComparisonType.EQUAL, 10);
223      * </pre>
224      *
225      * @param <M> the type of the model to which the {@code RowFilter} applies
226      * @param <I> the type of the identifier passed to the {@code RowFilter}
227      * @param type the type of comparison to perform
228      * @param number a {@code Number} value to compare against
229      * @param indices the indices of the values to check.  If not supplied all
230      *               values are evaluated
231      * @return a <code>RowFilter</code> implementing the specified criteria
232      * @throws IllegalArgumentException if any of the <code>indices</code>
233      *         are &lt; 0, <code>type</code> is <code>null</code>
234      *         or <code>number</code> is <code>null</code>
235      */
numberFilter(ComparisonType type, Number number, int... indices)236     public static <M,I> RowFilter<M,I> numberFilter(ComparisonType type,
237                                             Number number, int... indices) {
238         return new NumberFilter<M, I>(type, number, indices);
239     }
240 
241     /**
242      * Returns a <code>RowFilter</code> that includes entries if any
243      * of the supplied filters includes the entry.
244      * <p>
245      * The following example creates a <code>RowFilter</code> that will
246      * include any entries containing the string "foo" or the string
247      * "bar":
248      * <pre>
249      *   List&lt;RowFilter&lt;Object,Object&gt;&gt; filters = new ArrayList&lt;RowFilter&lt;Object,Object&gt;&gt;(2);
250      *   filters.add(RowFilter.regexFilter("foo"));
251      *   filters.add(RowFilter.regexFilter("bar"));
252      *   RowFilter&lt;Object,Object&gt; fooBarFilter = RowFilter.orFilter(filters);
253      * </pre>
254      *
255      * @param <M> the type of the model to which the {@code RowFilter} applies
256      * @param <I> the type of the identifier passed to the {@code RowFilter}
257      * @param filters the <code>RowFilter</code>s to test
258      * @throws IllegalArgumentException if any of the filters
259      *         are <code>null</code>
260      * @throws NullPointerException if <code>filters</code> is null
261      * @return a <code>RowFilter</code> implementing the specified criteria
262      * @see java.util.Arrays#asList
263      */
orFilter( Iterable<? extends RowFilter<? super M, ? super I>> filters)264     public static <M,I> RowFilter<M,I> orFilter(
265             Iterable<? extends RowFilter<? super M, ? super I>> filters) {
266         return new OrFilter<M,I>(filters);
267     }
268 
269     /**
270      * Returns a <code>RowFilter</code> that includes entries if all
271      * of the supplied filters include the entry.
272      * <p>
273      * The following example creates a <code>RowFilter</code> that will
274      * include any entries containing the string "foo" and the string
275      * "bar":
276      * <pre>
277      *   List&lt;RowFilter&lt;Object,Object&gt;&gt; filters = new ArrayList&lt;RowFilter&lt;Object,Object&gt;&gt;(2);
278      *   filters.add(RowFilter.regexFilter("foo"));
279      *   filters.add(RowFilter.regexFilter("bar"));
280      *   RowFilter&lt;Object,Object&gt; fooBarFilter = RowFilter.andFilter(filters);
281      * </pre>
282      *
283      * @param <M> the type of the model the {@code RowFilter} applies to
284      * @param <I> the type of the identifier passed to the {@code RowFilter}
285      * @param filters the <code>RowFilter</code>s to test
286      * @return a <code>RowFilter</code> implementing the specified criteria
287      * @throws IllegalArgumentException if any of the filters
288      *         are <code>null</code>
289      * @throws NullPointerException if <code>filters</code> is null
290      * @see java.util.Arrays#asList
291      */
andFilter( Iterable<? extends RowFilter<? super M, ? super I>> filters)292     public static <M,I> RowFilter<M,I> andFilter(
293             Iterable<? extends RowFilter<? super M, ? super I>> filters) {
294         return new AndFilter<M,I>(filters);
295     }
296 
297     /**
298      * Returns a <code>RowFilter</code> that includes entries if the
299      * supplied filter does not include the entry.
300      *
301      * @param <M> the type of the model to which the {@code RowFilter} applies
302      * @param <I> the type of the identifier passed to the {@code RowFilter}
303      * @param filter the <code>RowFilter</code> to negate
304      * @return a <code>RowFilter</code> implementing the specified criteria
305      * @throws IllegalArgumentException if <code>filter</code> is
306      *         <code>null</code>
307      */
notFilter(RowFilter<M,I> filter)308     public static <M,I> RowFilter<M,I> notFilter(RowFilter<M,I> filter) {
309         return new NotFilter<M,I>(filter);
310     }
311 
312     /**
313      * Returns true if the specified entry should be shown;
314      * returns false if the entry should be hidden.
315      * <p>
316      * The <code>entry</code> argument is valid only for the duration of
317      * the invocation.  Using <code>entry</code> after the call returns
318      * results in undefined behavior.
319      *
320      * @param entry a non-<code>null</code> object that wraps the underlying
321      *              object from the model
322      * @return true if the entry should be shown
323      */
include(Entry<? extends M, ? extends I> entry)324     public abstract boolean include(Entry<? extends M, ? extends I> entry);
325 
326     //
327     // WARNING:
328     // Because of the method signature of dateFilter/numberFilter/regexFilter
329     // we can NEVER add a method to RowFilter that returns M,I. If we were
330     // to do so it would be possible to get a ClassCastException during normal
331     // usage.
332     //
333 
334     /**
335      * An <code>Entry</code> object is passed to instances of
336      * <code>RowFilter</code>, allowing the filter to get the value of the
337      * entry's data, and thus to determine whether the entry should be shown.
338      * An <code>Entry</code> object contains information about the model
339      * as well as methods for getting the underlying values from the model.
340      *
341      * @param <M> the type of the model; for example <code>PersonModel</code>
342      * @param <I> the type of the identifier; when using
343      *            <code>TableRowSorter</code> this will be <code>Integer</code>
344      * @see javax.swing.RowFilter
345      * @see javax.swing.DefaultRowSorter#setRowFilter(javax.swing.RowFilter)
346      * @since 1.6
347      */
348     public abstract static class Entry<M, I> {
349         /**
350          * Creates an <code>Entry</code>.
351          */
Entry()352         public Entry() {
353         }
354 
355         /**
356          * Returns the underlying model.
357          *
358          * @return the model containing the data that this entry represents
359          */
getModel()360         public abstract M getModel();
361 
362         /**
363          * Returns the number of values in the entry.  For
364          * example, when used with a table this corresponds to the
365          * number of columns.
366          *
367          * @return number of values in the object being filtered
368          */
getValueCount()369         public abstract int getValueCount();
370 
371         /**
372          * Returns the value at the specified index.  This may return
373          * <code>null</code>.  When used with a table, index
374          * corresponds to the column number in the model.
375          *
376          * @param index the index of the value to get
377          * @return value at the specified index
378          * @throws IndexOutOfBoundsException if index &lt; 0 or
379          *         &gt;= getValueCount
380          */
getValue(int index)381         public abstract Object getValue(int index);
382 
383         /**
384          * Returns the string value at the specified index.  If
385          * filtering is being done based on <code>String</code> values
386          * this method is preferred to that of <code>getValue</code>
387          * as <code>getValue(index).toString()</code> may return a
388          * different result than <code>getStringValue(index)</code>.
389          * <p>
390          * This implementation calls <code>getValue(index).toString()</code>
391          * after checking for <code>null</code>.  Subclasses that provide
392          * different string conversion should override this method if
393          * necessary.
394          *
395          * @param index the index of the value to get
396          * @return {@code non-null} string at the specified index
397          * @throws IndexOutOfBoundsException if index &lt; 0 ||
398          *         &gt;= getValueCount
399          */
getStringValue(int index)400         public String getStringValue(int index) {
401             Object value = getValue(index);
402             return (value == null) ? "" : value.toString();
403         }
404 
405         /**
406          * Returns the identifer (in the model) of the entry.
407          * For a table this corresponds to the index of the row in the model,
408          * expressed as an <code>Integer</code>.
409          *
410          * @return a model-based (not view-based) identifier for
411          *         this entry
412          */
getIdentifier()413         public abstract I getIdentifier();
414     }
415 
416 
417     private abstract static class GeneralFilter<M, I> extends RowFilter<M, I> {
418         private int[] columns;
419 
GeneralFilter(int[] columns)420         GeneralFilter(int[] columns) {
421             checkIndices(columns);
422             this.columns = columns;
423         }
424 
425         @Override
include(Entry<? extends M, ? extends I> value)426         public boolean include(Entry<? extends M, ? extends I> value){
427             int count = value.getValueCount();
428             if (columns.length > 0) {
429                 for (int i = columns.length - 1; i >= 0; i--) {
430                     int index = columns[i];
431                     if (index < count) {
432                         if (include(value, index)) {
433                             return true;
434                         }
435                     }
436                 }
437             } else {
438                 while (--count >= 0) {
439                     if (include(value, count)) {
440                         return true;
441                     }
442                 }
443             }
444             return false;
445         }
446 
include( Entry<? extends M, ? extends I> value, int index)447         protected abstract boolean include(
448               Entry<? extends M, ? extends I> value, int index);
449     }
450 
451 
452     private static class RegexFilter<M, I> extends GeneralFilter<M, I> {
453         private Matcher matcher;
454 
RegexFilter(Pattern regex, int[] columns)455         RegexFilter(Pattern regex, int[] columns) {
456             super(columns);
457             if (regex == null) {
458                 throw new IllegalArgumentException("Pattern must be non-null");
459             }
460             matcher = regex.matcher("");
461         }
462 
463         @Override
include( Entry<? extends M, ? extends I> value, int index)464         protected boolean include(
465                 Entry<? extends M, ? extends I> value, int index) {
466             matcher.reset(value.getStringValue(index));
467             return matcher.find();
468         }
469     }
470 
471 
472     private static class DateFilter<M, I> extends GeneralFilter<M, I> {
473         private long date;
474         private ComparisonType type;
475 
DateFilter(ComparisonType type, long date, int[] columns)476         DateFilter(ComparisonType type, long date, int[] columns) {
477             super(columns);
478             if (type == null) {
479                 throw new IllegalArgumentException("type must be non-null");
480             }
481             this.type = type;
482             this.date = date;
483         }
484 
485         @Override
include( Entry<? extends M, ? extends I> value, int index)486         protected boolean include(
487                 Entry<? extends M, ? extends I> value, int index) {
488             Object v = value.getValue(index);
489 
490             if (v instanceof Date) {
491                 long vDate = ((Date)v).getTime();
492                 switch(type) {
493                 case BEFORE:
494                     return (vDate < date);
495                 case AFTER:
496                     return (vDate > date);
497                 case EQUAL:
498                     return (vDate == date);
499                 case NOT_EQUAL:
500                     return (vDate != date);
501                 default:
502                     break;
503                 }
504             }
505             return false;
506         }
507     }
508 
509     private static class NumberFilter<M, I> extends GeneralFilter<M, I> {
510         private boolean isComparable;
511         private Number number;
512         private ComparisonType type;
513 
NumberFilter(ComparisonType type, Number number, int[] columns)514         NumberFilter(ComparisonType type, Number number, int[] columns) {
515             super(columns);
516             if (type == null || number == null) {
517                 throw new IllegalArgumentException(
518                     "type and number must be non-null");
519             }
520             this.type = type;
521             this.number = number;
522             isComparable = (number instanceof Comparable);
523         }
524 
525         @Override
526         @SuppressWarnings("unchecked")
include( Entry<? extends M, ? extends I> value, int index)527         protected boolean include(
528                 Entry<? extends M, ? extends I> value, int index) {
529             Object v = value.getValue(index);
530 
531             if (v instanceof Number) {
532                 boolean compared = true;
533                 int compareResult;
534                 Class<?> vClass = v.getClass();
535                 if (number.getClass() == vClass && isComparable) {
536                     compareResult = ((Comparable)number).compareTo(v);
537                 }
538                 else {
539                     compareResult = longCompare((Number)v);
540                 }
541                 switch(type) {
542                 case BEFORE:
543                     return (compareResult > 0);
544                 case AFTER:
545                     return (compareResult < 0);
546                 case EQUAL:
547                     return (compareResult == 0);
548                 case NOT_EQUAL:
549                     return (compareResult != 0);
550                 default:
551                     break;
552                 }
553             }
554             return false;
555         }
556 
longCompare(Number o)557         private int longCompare(Number o) {
558             long diff = number.longValue() - o.longValue();
559 
560             if (diff < 0) {
561                 return -1;
562             }
563             else if (diff > 0) {
564                 return 1;
565             }
566             return 0;
567         }
568     }
569 
570 
571     private static class OrFilter<M,I> extends RowFilter<M,I> {
572         List<RowFilter<? super M,? super I>> filters;
573 
OrFilter(Iterable<? extends RowFilter<? super M, ? super I>> filters)574         OrFilter(Iterable<? extends RowFilter<? super M, ? super I>> filters) {
575             this.filters = new ArrayList<RowFilter<? super M,? super I>>();
576             for (RowFilter<? super M, ? super I> filter : filters) {
577                 if (filter == null) {
578                     throw new IllegalArgumentException(
579                         "Filter must be non-null");
580                 }
581                 this.filters.add(filter);
582             }
583         }
584 
include(Entry<? extends M, ? extends I> value)585         public boolean include(Entry<? extends M, ? extends I> value) {
586             for (RowFilter<? super M,? super I> filter : filters) {
587                 if (filter.include(value)) {
588                     return true;
589                 }
590             }
591             return false;
592         }
593     }
594 
595 
596     private static class AndFilter<M,I> extends OrFilter<M,I> {
AndFilter(Iterable<? extends RowFilter<? super M,? super I>> filters)597         AndFilter(Iterable<? extends RowFilter<? super M,? super I>> filters) {
598             super(filters);
599         }
600 
include(Entry<? extends M, ? extends I> value)601         public boolean include(Entry<? extends M, ? extends I> value) {
602             for (RowFilter<? super M,? super I> filter : filters) {
603                 if (!filter.include(value)) {
604                     return false;
605                 }
606             }
607             return true;
608         }
609     }
610 
611 
612     private static class NotFilter<M,I> extends RowFilter<M,I> {
613         private RowFilter<M,I> filter;
614 
NotFilter(RowFilter<M,I> filter)615         NotFilter(RowFilter<M,I> filter) {
616             if (filter == null) {
617                 throw new IllegalArgumentException(
618                     "filter must be non-null");
619             }
620             this.filter = filter;
621         }
622 
include(Entry<? extends M, ? extends I> value)623         public boolean include(Entry<? extends M, ? extends I> value) {
624             return !filter.include(value);
625         }
626     }
627 }
628