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 */
25package javax.swing;
26
27import java.util.ArrayList;
28import java.math.BigDecimal;
29import java.math.BigInteger;
30import java.util.Date;
31import java.util.List;
32import java.util.regex.Matcher;
33import java.util.regex.Pattern;
34import 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 */
97public 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     * Throws an IllegalArgumentException if any of the values in
133     * columns are {@literal <} 0.
134     */
135    private static void checkIndices(int[] columns) {
136        for (int i = columns.length - 1; i >= 0; i--) {
137            if (columns[i] < 0) {
138                throw new IllegalArgumentException("Index must be >= 0");
139            }
140        }
141    }
142
143    /**
144     * Returns a <code>RowFilter</code> that uses a regular
145     * expression to determine which entries to include.  Only entries
146     * with at least one matching value are included.  For
147     * example, the following creates a <code>RowFilter</code> that
148     * includes entries with at least one value starting with
149     * "a":
150     * <pre>
151     *   RowFilter.regexFilter("^a");
152     * </pre>
153     * <p>
154     * The returned filter uses {@link java.util.regex.Matcher#find}
155     * to test for inclusion.  To test for exact matches use the
156     * characters '^' and '$' to match the beginning and end of the
157     * string respectively.  For example, "^foo$" includes only rows whose
158     * string is exactly "foo" and not, for example, "food".  See
159     * {@link java.util.regex.Pattern} for a complete description of
160     * the supported regular-expression constructs.
161     *
162     * @param <M> the type of the model to which the {@code RowFilter} applies
163     * @param <I> the type of the identifier passed to the {@code RowFilter}
164     * @param regex the regular expression to filter on
165     * @param indices the indices of the values to check.  If not supplied all
166     *               values are evaluated
167     * @return a <code>RowFilter</code> implementing the specified criteria
168     * @throws NullPointerException if <code>regex</code> is
169     *         <code>null</code>
170     * @throws IllegalArgumentException if any of the <code>indices</code>
171     *         are &lt; 0
172     * @throws PatternSyntaxException if <code>regex</code> is
173     *         not a valid regular expression.
174     * @see java.util.regex.Pattern
175     */
176    public static <M,I> RowFilter<M,I> regexFilter(String regex,
177                                                       int... indices) {
178        return new RegexFilter<M, I>(Pattern.compile(regex), indices);
179    }
180
181    /**
182     * Returns a <code>RowFilter</code> that includes entries that
183     * have at least one <code>Date</code> value meeting the specified
184     * criteria.  For example, the following <code>RowFilter</code> includes
185     * only entries with at least one date value after the current date:
186     * <pre>
187     *   RowFilter.dateFilter(ComparisonType.AFTER, new Date());
188     * </pre>
189     *
190     * @param <M> the type of the model to which the {@code RowFilter} applies
191     * @param <I> the type of the identifier passed to the {@code RowFilter}
192     * @param type the type of comparison to perform
193     * @param date the date to compare against
194     * @param indices the indices of the values to check.  If not supplied all
195     *               values are evaluated
196     * @return a <code>RowFilter</code> implementing the specified criteria
197     * @throws NullPointerException if <code>date</code> is
198     *          <code>null</code>
199     * @throws IllegalArgumentException if any of the <code>indices</code>
200     *         are &lt; 0 or <code>type</code> is
201     *         <code>null</code>
202     * @see java.util.Calendar
203     * @see java.util.Date
204     */
205    public static <M,I> RowFilter<M,I> dateFilter(ComparisonType type,
206                                            Date date, int... indices) {
207        return new DateFilter<M, I>(type, date.getTime(), indices);
208    }
209
210    /**
211     * Returns a <code>RowFilter</code> that includes entries that
212     * have at least one <code>Number</code> value meeting the
213     * specified criteria.  For example, the following
214     * filter will only include entries with at
215     * least one number value equal to 10:
216     * <pre>
217     *   RowFilter.numberFilter(ComparisonType.EQUAL, 10);
218     * </pre>
219     *
220     * @param <M> the type of the model to which the {@code RowFilter} applies
221     * @param <I> the type of the identifier passed to the {@code RowFilter}
222     * @param type the type of comparison to perform
223     * @param number a {@code Number} value to compare against
224     * @param indices the indices of the values to check.  If not supplied all
225     *               values are evaluated
226     * @return a <code>RowFilter</code> implementing the specified criteria
227     * @throws IllegalArgumentException if any of the <code>indices</code>
228     *         are &lt; 0, <code>type</code> is <code>null</code>
229     *         or <code>number</code> is <code>null</code>
230     */
231    public static <M,I> RowFilter<M,I> numberFilter(ComparisonType type,
232                                            Number number, int... indices) {
233        return new NumberFilter<M, I>(type, number, indices);
234    }
235
236    /**
237     * Returns a <code>RowFilter</code> that includes entries if any
238     * of the supplied filters includes the entry.
239     * <p>
240     * The following example creates a <code>RowFilter</code> that will
241     * include any entries containing the string "foo" or the string
242     * "bar":
243     * <pre>
244     *   List&lt;RowFilter&lt;Object,Object&gt;&gt; filters = new ArrayList&lt;RowFilter&lt;Object,Object&gt;&gt;(2);
245     *   filters.add(RowFilter.regexFilter("foo"));
246     *   filters.add(RowFilter.regexFilter("bar"));
247     *   RowFilter&lt;Object,Object&gt; fooBarFilter = RowFilter.orFilter(filters);
248     * </pre>
249     *
250     * @param <M> the type of the model to which the {@code RowFilter} applies
251     * @param <I> the type of the identifier passed to the {@code RowFilter}
252     * @param filters the <code>RowFilter</code>s to test
253     * @throws IllegalArgumentException if any of the filters
254     *         are <code>null</code>
255     * @throws NullPointerException if <code>filters</code> is null
256     * @return a <code>RowFilter</code> implementing the specified criteria
257     * @see java.util.Arrays#asList
258     */
259    public static <M,I> RowFilter<M,I> orFilter(
260            Iterable<? extends RowFilter<? super M, ? super I>> filters) {
261        return new OrFilter<M,I>(filters);
262    }
263
264    /**
265     * Returns a <code>RowFilter</code> that includes entries if all
266     * of the supplied filters include the entry.
267     * <p>
268     * The following example creates a <code>RowFilter</code> that will
269     * include any entries containing the string "foo" and the string
270     * "bar":
271     * <pre>
272     *   List&lt;RowFilter&lt;Object,Object&gt;&gt; filters = new ArrayList&lt;RowFilter&lt;Object,Object&gt;&gt;(2);
273     *   filters.add(RowFilter.regexFilter("foo"));
274     *   filters.add(RowFilter.regexFilter("bar"));
275     *   RowFilter&lt;Object,Object&gt; fooBarFilter = RowFilter.andFilter(filters);
276     * </pre>
277     *
278     * @param <M> the type of the model the {@code RowFilter} applies to
279     * @param <I> the type of the identifier passed to the {@code RowFilter}
280     * @param filters the <code>RowFilter</code>s to test
281     * @return a <code>RowFilter</code> implementing the specified criteria
282     * @throws IllegalArgumentException if any of the filters
283     *         are <code>null</code>
284     * @throws NullPointerException if <code>filters</code> is null
285     * @see java.util.Arrays#asList
286     */
287    public static <M,I> RowFilter<M,I> andFilter(
288            Iterable<? extends RowFilter<? super M, ? super I>> filters) {
289        return new AndFilter<M,I>(filters);
290    }
291
292    /**
293     * Returns a <code>RowFilter</code> that includes entries if the
294     * supplied filter does not include the entry.
295     *
296     * @param <M> the type of the model to which the {@code RowFilter} applies
297     * @param <I> the type of the identifier passed to the {@code RowFilter}
298     * @param filter the <code>RowFilter</code> to negate
299     * @return a <code>RowFilter</code> implementing the specified criteria
300     * @throws IllegalArgumentException if <code>filter</code> is
301     *         <code>null</code>
302     */
303    public static <M,I> RowFilter<M,I> notFilter(RowFilter<M,I> filter) {
304        return new NotFilter<M,I>(filter);
305    }
306
307    /**
308     * Returns true if the specified entry should be shown;
309     * returns false if the entry should be hidden.
310     * <p>
311     * The <code>entry</code> argument is valid only for the duration of
312     * the invocation.  Using <code>entry</code> after the call returns
313     * results in undefined behavior.
314     *
315     * @param entry a non-<code>null</code> object that wraps the underlying
316     *              object from the model
317     * @return true if the entry should be shown
318     */
319    public abstract boolean include(Entry<? extends M, ? extends I> entry);
320
321    //
322    // WARNING:
323    // Because of the method signature of dateFilter/numberFilter/regexFilter
324    // we can NEVER add a method to RowFilter that returns M,I. If we were
325    // to do so it would be possible to get a ClassCastException during normal
326    // usage.
327    //
328
329    /**
330     * An <code>Entry</code> object is passed to instances of
331     * <code>RowFilter</code>, allowing the filter to get the value of the
332     * entry's data, and thus to determine whether the entry should be shown.
333     * An <code>Entry</code> object contains information about the model
334     * as well as methods for getting the underlying values from the model.
335     *
336     * @param <M> the type of the model; for example <code>PersonModel</code>
337     * @param <I> the type of the identifier; when using
338     *            <code>TableRowSorter</code> this will be <code>Integer</code>
339     * @see javax.swing.RowFilter
340     * @see javax.swing.DefaultRowSorter#setRowFilter(javax.swing.RowFilter)
341     * @since 1.6
342     */
343    public abstract static class Entry<M, I> {
344        /**
345         * Creates an <code>Entry</code>.
346         */
347        public Entry() {
348        }
349
350        /**
351         * Returns the underlying model.
352         *
353         * @return the model containing the data that this entry represents
354         */
355        public abstract M getModel();
356
357        /**
358         * Returns the number of values in the entry.  For
359         * example, when used with a table this corresponds to the
360         * number of columns.
361         *
362         * @return number of values in the object being filtered
363         */
364        public abstract int getValueCount();
365
366        /**
367         * Returns the value at the specified index.  This may return
368         * <code>null</code>.  When used with a table, index
369         * corresponds to the column number in the model.
370         *
371         * @param index the index of the value to get
372         * @return value at the specified index
373         * @throws IndexOutOfBoundsException if index &lt; 0 or
374         *         &gt;= getValueCount
375         */
376        public abstract Object getValue(int index);
377
378        /**
379         * Returns the string value at the specified index.  If
380         * filtering is being done based on <code>String</code> values
381         * this method is preferred to that of <code>getValue</code>
382         * as <code>getValue(index).toString()</code> may return a
383         * different result than <code>getStringValue(index)</code>.
384         * <p>
385         * This implementation calls <code>getValue(index).toString()</code>
386         * after checking for <code>null</code>.  Subclasses that provide
387         * different string conversion should override this method if
388         * necessary.
389         *
390         * @param index the index of the value to get
391         * @return {@code non-null} string at the specified index
392         * @throws IndexOutOfBoundsException if index &lt; 0 ||
393         *         &gt;= getValueCount
394         */
395        public String getStringValue(int index) {
396            Object value = getValue(index);
397            return (value == null) ? "" : value.toString();
398        }
399
400        /**
401         * Returns the identifer (in the model) of the entry.
402         * For a table this corresponds to the index of the row in the model,
403         * expressed as an <code>Integer</code>.
404         *
405         * @return a model-based (not view-based) identifier for
406         *         this entry
407         */
408        public abstract I getIdentifier();
409    }
410
411
412    private abstract static class GeneralFilter<M, I> extends RowFilter<M, I> {
413        private int[] columns;
414
415        GeneralFilter(int[] columns) {
416            checkIndices(columns);
417            this.columns = columns;
418        }
419
420        @Override
421        public boolean include(Entry<? extends M, ? extends I> value){
422            int count = value.getValueCount();
423            if (columns.length > 0) {
424                for (int i = columns.length - 1; i >= 0; i--) {
425                    int index = columns[i];
426                    if (index < count) {
427                        if (include(value, index)) {
428                            return true;
429                        }
430                    }
431                }
432            } else {
433                while (--count >= 0) {
434                    if (include(value, count)) {
435                        return true;
436                    }
437                }
438            }
439            return false;
440        }
441
442        protected abstract boolean include(
443              Entry<? extends M, ? extends I> value, int index);
444    }
445
446
447    private static class RegexFilter<M, I> extends GeneralFilter<M, I> {
448        private Matcher matcher;
449
450        RegexFilter(Pattern regex, int[] columns) {
451            super(columns);
452            if (regex == null) {
453                throw new IllegalArgumentException("Pattern must be non-null");
454            }
455            matcher = regex.matcher("");
456        }
457
458        @Override
459        protected boolean include(
460                Entry<? extends M, ? extends I> value, int index) {
461            matcher.reset(value.getStringValue(index));
462            return matcher.find();
463        }
464    }
465
466
467    private static class DateFilter<M, I> extends GeneralFilter<M, I> {
468        private long date;
469        private ComparisonType type;
470
471        DateFilter(ComparisonType type, long date, int[] columns) {
472            super(columns);
473            if (type == null) {
474                throw new IllegalArgumentException("type must be non-null");
475            }
476            this.type = type;
477            this.date = date;
478        }
479
480        @Override
481        protected boolean include(
482                Entry<? extends M, ? extends I> value, int index) {
483            Object v = value.getValue(index);
484
485            if (v instanceof Date) {
486                long vDate = ((Date)v).getTime();
487                switch(type) {
488                case BEFORE:
489                    return (vDate < date);
490                case AFTER:
491                    return (vDate > date);
492                case EQUAL:
493                    return (vDate == date);
494                case NOT_EQUAL:
495                    return (vDate != date);
496                default:
497                    break;
498                }
499            }
500            return false;
501        }
502    }
503
504    private static class NumberFilter<M, I> extends GeneralFilter<M, I> {
505        private boolean isComparable;
506        private Number number;
507        private ComparisonType type;
508
509        NumberFilter(ComparisonType type, Number number, int[] columns) {
510            super(columns);
511            if (type == null || number == null) {
512                throw new IllegalArgumentException(
513                    "type and number must be non-null");
514            }
515            this.type = type;
516            this.number = number;
517            isComparable = (number instanceof Comparable);
518        }
519
520        @Override
521        @SuppressWarnings("unchecked")
522        protected boolean include(
523                Entry<? extends M, ? extends I> value, int index) {
524            Object v = value.getValue(index);
525
526            if (v instanceof Number) {
527                boolean compared = true;
528                int compareResult;
529                Class<?> vClass = v.getClass();
530                if (number.getClass() == vClass && isComparable) {
531                    compareResult = ((Comparable)number).compareTo(v);
532                }
533                else {
534                    compareResult = longCompare((Number)v);
535                }
536                switch(type) {
537                case BEFORE:
538                    return (compareResult > 0);
539                case AFTER:
540                    return (compareResult < 0);
541                case EQUAL:
542                    return (compareResult == 0);
543                case NOT_EQUAL:
544                    return (compareResult != 0);
545                default:
546                    break;
547                }
548            }
549            return false;
550        }
551
552        private int longCompare(Number o) {
553            long diff = number.longValue() - o.longValue();
554
555            if (diff < 0) {
556                return -1;
557            }
558            else if (diff > 0) {
559                return 1;
560            }
561            return 0;
562        }
563    }
564
565
566    private static class OrFilter<M,I> extends RowFilter<M,I> {
567        List<RowFilter<? super M,? super I>> filters;
568
569        OrFilter(Iterable<? extends RowFilter<? super M, ? super I>> filters) {
570            this.filters = new ArrayList<RowFilter<? super M,? super I>>();
571            for (RowFilter<? super M, ? super I> filter : filters) {
572                if (filter == null) {
573                    throw new IllegalArgumentException(
574                        "Filter must be non-null");
575                }
576                this.filters.add(filter);
577            }
578        }
579
580        public boolean include(Entry<? extends M, ? extends I> value) {
581            for (RowFilter<? super M,? super I> filter : filters) {
582                if (filter.include(value)) {
583                    return true;
584                }
585            }
586            return false;
587        }
588    }
589
590
591    private static class AndFilter<M,I> extends OrFilter<M,I> {
592        AndFilter(Iterable<? extends RowFilter<? super M,? super I>> filters) {
593            super(filters);
594        }
595
596        public boolean include(Entry<? extends M, ? extends I> value) {
597            for (RowFilter<? super M,? super I> filter : filters) {
598                if (!filter.include(value)) {
599                    return false;
600                }
601            }
602            return true;
603        }
604    }
605
606
607    private static class NotFilter<M,I> extends RowFilter<M,I> {
608        private RowFilter<M,I> filter;
609
610        NotFilter(RowFilter<M,I> filter) {
611            if (filter == null) {
612                throw new IllegalArgumentException(
613                    "filter must be non-null");
614            }
615            this.filter = filter;
616        }
617
618        public boolean include(Entry<? extends M, ? extends I> value) {
619            return !filter.include(value);
620        }
621    }
622}
623