/* * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.swing; import java.util.ArrayList; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Date; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; /** * RowFilter is used to filter out entries from the * model so that they are not shown in the view. For example, a * RowFilter associated with a JTable might * only allow rows that contain a column with a specific string. The * meaning of entry depends on the component type. * For example, when a filter is * associated with a JTable, an entry corresponds to a * row; when associated with a JTree, an entry corresponds * to a node. *

* Subclasses must override the include method to * indicate whether the entry should be shown in the * view. The Entry argument can be used to obtain the values in * each of the columns in that entry. The following example shows an * include method that allows only entries containing one or * more values starting with the string "a": *

 * RowFilter<Object,Object> startsWithAFilter = new RowFilter<Object,Object>() {
 *   public boolean include(Entry<? extends Object, ? extends Object> entry) {
 *     for (int i = entry.getValueCount() - 1; i >= 0; i--) {
 *       if (entry.getStringValue(i).startsWith("a")) {
 *         // The value starts with "a", include it
 *         return true;
 *       }
 *     }
 *     // None of the columns start with "a"; return false so that this
 *     // entry is not shown
 *     return false;
 *   }
 * };
 * 
* RowFilter has two formal type parameters that allow * you to create a RowFilter for a specific model. For * example, the following assumes a specific model that is wrapping * objects of type Person. Only Persons * with an age over 20 will be shown: *
 * RowFilter<PersonModel,Integer> ageFilter = new RowFilter<PersonModel,Integer>() {
 *   public boolean include(Entry<? extends PersonModel, ? extends Integer> entry) {
 *     PersonModel personModel = entry.getModel();
 *     Person person = personModel.getPerson(entry.getIdentifier());
 *     if (person.getAge() > 20) {
 *       // Returning true indicates this row should be shown.
 *       return true;
 *     }
 *     // Age is <= 20, don't show it.
 *     return false;
 *   }
 * };
 * PersonModel model = createPersonModel();
 * TableRowSorter<PersonModel> sorter = new TableRowSorter<PersonModel>(model);
 * sorter.setRowFilter(ageFilter);
 * 
* * @param the type of the model; for example PersonModel * @param the type of the identifier; when using * TableRowSorter this will be Integer * @see javax.swing.table.TableRowSorter * @since 1.6 */ public abstract class RowFilter { /** * Enumeration of the possible comparison values supported by * some of the default RowFilters. * * @see RowFilter * @since 1.6 */ public enum ComparisonType { /** * Indicates that entries with a value before the supplied * value should be included. */ BEFORE, /** * Indicates that entries with a value after the supplied * value should be included. */ AFTER, /** * Indicates that entries with a value equal to the supplied * value should be included. */ EQUAL, /** * Indicates that entries with a value not equal to the supplied * value should be included. */ NOT_EQUAL } /** * Throws an IllegalArgumentException if any of the values in * columns are {@literal <} 0. */ private static void checkIndices(int[] columns) { for (int i = columns.length - 1; i >= 0; i--) { if (columns[i] < 0) { throw new IllegalArgumentException("Index must be >= 0"); } } } /** * Returns a RowFilter that uses a regular * expression to determine which entries to include. Only entries * with at least one matching value are included. For * example, the following creates a RowFilter that * includes entries with at least one value starting with * "a": *
     *   RowFilter.regexFilter("^a");
     * 
*

* The returned filter uses {@link java.util.regex.Matcher#find} * to test for inclusion. To test for exact matches use the * characters '^' and '$' to match the beginning and end of the * string respectively. For example, "^foo$" includes only rows whose * string is exactly "foo" and not, for example, "food". See * {@link java.util.regex.Pattern} for a complete description of * the supported regular-expression constructs. * * @param regex the regular expression to filter on * @param indices the indices of the values to check. If not supplied all * values are evaluated * @return a RowFilter implementing the specified criteria * @throws NullPointerException if regex is * null * @throws IllegalArgumentException if any of the indices * are < 0 * @throws PatternSyntaxException if regex is * not a valid regular expression. * @see java.util.regex.Pattern */ public static RowFilter regexFilter(String regex, int... indices) { return new RegexFilter(Pattern.compile(regex), indices); } /** * Returns a RowFilter that includes entries that * have at least one Date value meeting the specified * criteria. For example, the following RowFilter includes * only entries with at least one date value after the current date: *

     *   RowFilter.dateFilter(ComparisonType.AFTER, new Date());
     * 
* * @param type the type of comparison to perform * @param date the date to compare against * @param indices the indices of the values to check. If not supplied all * values are evaluated * @return a RowFilter implementing the specified criteria * @throws NullPointerException if date is * null * @throws IllegalArgumentException if any of the indices * are < 0 or type is * null * @see java.util.Calendar * @see java.util.Date */ public static RowFilter dateFilter(ComparisonType type, Date date, int... indices) { return new DateFilter(type, date.getTime(), indices); } /** * Returns a RowFilter that includes entries that * have at least one Number value meeting the * specified criteria. For example, the following * filter will only include entries with at * least one number value equal to 10: *
     *   RowFilter.numberFilter(ComparisonType.EQUAL, 10);
     * 
* * @param type the type of comparison to perform * @param indices the indices of the values to check. If not supplied all * values are evaluated * @return a RowFilter implementing the specified criteria * @throws IllegalArgumentException if any of the indices * are < 0, type is null * or number is null */ public static RowFilter numberFilter(ComparisonType type, Number number, int... indices) { return new NumberFilter(type, number, indices); } /** * Returns a RowFilter that includes entries if any * of the supplied filters includes the entry. *

* The following example creates a RowFilter that will * include any entries containing the string "foo" or the string * "bar": *

     *   List<RowFilter<Object,Object>> filters = new ArrayList<RowFilter<Object,Object>>(2);
     *   filters.add(RowFilter.regexFilter("foo"));
     *   filters.add(RowFilter.regexFilter("bar"));
     *   RowFilter<Object,Object> fooBarFilter = RowFilter.orFilter(filters);
     * 
* * @param filters the RowFilters to test * @throws IllegalArgumentException if any of the filters * are null * @throws NullPointerException if filters is null * @return a RowFilter implementing the specified criteria * @see java.util.Arrays#asList */ public static RowFilter orFilter( Iterable> filters) { return new OrFilter(filters); } /** * Returns a RowFilter that includes entries if all * of the supplied filters include the entry. *

* The following example creates a RowFilter that will * include any entries containing the string "foo" and the string * "bar": *

     *   List<RowFilter<Object,Object>> filters = new ArrayList<RowFilter<Object,Object>>(2);
     *   filters.add(RowFilter.regexFilter("foo"));
     *   filters.add(RowFilter.regexFilter("bar"));
     *   RowFilter<Object,Object> fooBarFilter = RowFilter.andFilter(filters);
     * 
* * @param filters the RowFilters to test * @return a RowFilter implementing the specified criteria * @throws IllegalArgumentException if any of the filters * are null * @throws NullPointerException if filters is null * @see java.util.Arrays#asList */ public static RowFilter andFilter( Iterable> filters) { return new AndFilter(filters); } /** * Returns a RowFilter that includes entries if the * supplied filter does not include the entry. * * @param filter the RowFilter to negate * @return a RowFilter implementing the specified criteria * @throws IllegalArgumentException if filter is * null */ public static RowFilter notFilter(RowFilter filter) { return new NotFilter(filter); } /** * Returns true if the specified entry should be shown; * returns false if the entry should be hidden. *

* The entry argument is valid only for the duration of * the invocation. Using entry after the call returns * results in undefined behavior. * * @param entry a non-null object that wraps the underlying * object from the model * @return true if the entry should be shown */ public abstract boolean include(Entry entry); // // WARNING: // Because of the method signature of dateFilter/numberFilter/regexFilter // we can NEVER add a method to RowFilter that returns M,I. If we were // to do so it would be possible to get a ClassCastException during normal // usage. // /** * An Entry object is passed to instances of * RowFilter, allowing the filter to get the value of the * entry's data, and thus to determine whether the entry should be shown. * An Entry object contains information about the model * as well as methods for getting the underlying values from the model. * * @param the type of the model; for example PersonModel * @param the type of the identifier; when using * TableRowSorter this will be Integer * @see javax.swing.RowFilter * @see javax.swing.DefaultRowSorter#setRowFilter(javax.swing.RowFilter) * @since 1.6 */ public static abstract class Entry { /** * Creates an Entry. */ public Entry() { } /** * Returns the underlying model. * * @return the model containing the data that this entry represents */ public abstract M getModel(); /** * Returns the number of values in the entry. For * example, when used with a table this corresponds to the * number of columns. * * @return number of values in the object being filtered */ public abstract int getValueCount(); /** * Returns the value at the specified index. This may return * null. When used with a table, index * corresponds to the column number in the model. * * @param index the index of the value to get * @return value at the specified index * @throws IndexOutOfBoundsException if index < 0 or * >= getValueCount */ public abstract Object getValue(int index); /** * Returns the string value at the specified index. If * filtering is being done based on String values * this method is preferred to that of getValue * as getValue(index).toString() may return a * different result than getStringValue(index). *

* This implementation calls getValue(index).toString() * after checking for null. Subclasses that provide * different string conversion should override this method if * necessary. * * @param index the index of the value to get * @return {@code non-null} string at the specified index * @throws IndexOutOfBoundsException if index < 0 || * >= getValueCount */ public String getStringValue(int index) { Object value = getValue(index); return (value == null) ? "" : value.toString(); } /** * Returns the identifer (in the model) of the entry. * For a table this corresponds to the index of the row in the model, * expressed as an Integer. * * @return a model-based (not view-based) identifier for * this entry */ public abstract I getIdentifier(); } private static abstract class GeneralFilter extends RowFilter { private int[] columns; GeneralFilter(int[] columns) { checkIndices(columns); this.columns = columns; } @Override public boolean include(Entry value){ int count = value.getValueCount(); if (columns.length > 0) { for (int i = columns.length - 1; i >= 0; i--) { int index = columns[i]; if (index < count) { if (include(value, index)) { return true; } } } } else { while (--count >= 0) { if (include(value, count)) { return true; } } } return false; } protected abstract boolean include( Entry value, int index); } private static class RegexFilter extends GeneralFilter { private Matcher matcher; RegexFilter(Pattern regex, int[] columns) { super(columns); if (regex == null) { throw new IllegalArgumentException("Pattern must be non-null"); } matcher = regex.matcher(""); } @Override protected boolean include( Entry value, int index) { matcher.reset(value.getStringValue(index)); return matcher.find(); } } private static class DateFilter extends GeneralFilter { private long date; private ComparisonType type; DateFilter(ComparisonType type, long date, int[] columns) { super(columns); if (type == null) { throw new IllegalArgumentException("type must be non-null"); } this.type = type; this.date = date; } @Override protected boolean include( Entry value, int index) { Object v = value.getValue(index); if (v instanceof Date) { long vDate = ((Date)v).getTime(); switch(type) { case BEFORE: return (vDate < date); case AFTER: return (vDate > date); case EQUAL: return (vDate == date); case NOT_EQUAL: return (vDate != date); default: break; } } return false; } } private static class NumberFilter extends GeneralFilter { private boolean isComparable; private Number number; private ComparisonType type; NumberFilter(ComparisonType type, Number number, int[] columns) { super(columns); if (type == null || number == null) { throw new IllegalArgumentException( "type and number must be non-null"); } this.type = type; this.number = number; isComparable = (number instanceof Comparable); } @Override @SuppressWarnings("unchecked") protected boolean include( Entry value, int index) { Object v = value.getValue(index); if (v instanceof Number) { boolean compared = true; int compareResult; Class vClass = v.getClass(); if (number.getClass() == vClass && isComparable) { compareResult = ((Comparable)number).compareTo(v); } else { compareResult = longCompare((Number)v); } switch(type) { case BEFORE: return (compareResult > 0); case AFTER: return (compareResult < 0); case EQUAL: return (compareResult == 0); case NOT_EQUAL: return (compareResult != 0); default: break; } } return false; } private int longCompare(Number o) { long diff = number.longValue() - o.longValue(); if (diff < 0) { return -1; } else if (diff > 0) { return 1; } return 0; } } private static class OrFilter extends RowFilter { List> filters; OrFilter(Iterable> filters) { this.filters = new ArrayList>(); for (RowFilter filter : filters) { if (filter == null) { throw new IllegalArgumentException( "Filter must be non-null"); } this.filters.add(filter); } } public boolean include(Entry value) { for (RowFilter filter : filters) { if (filter.include(value)) { return true; } } return false; } } private static class AndFilter extends OrFilter { AndFilter(Iterable> filters) { super(filters); } public boolean include(Entry value) { for (RowFilter filter : filters) { if (!filter.include(value)) { return false; } } return true; } } private static class NotFilter extends RowFilter { private RowFilter filter; NotFilter(RowFilter filter) { if (filter == null) { throw new IllegalArgumentException( "filter must be non-null"); } this.filter = filter; } public boolean include(Entry value) { return !filter.include(value); } } }