/* | 
|
 * 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;  | 
|
/** | 
|
 * <code>RowFilter</code> is used to filter out entries from the | 
|
 * model so that they are not shown in the view.  For example, a | 
|
 * <code>RowFilter</code> associated with a <code>JTable</code> might | 
|
 * only allow rows that contain a column with a specific string. The | 
|
 * meaning of <em>entry</em> depends on the component type. | 
|
 * For example, when a filter is | 
|
 * associated with a <code>JTable</code>, an entry corresponds to a | 
|
 * row; when associated with a <code>JTree</code>, an entry corresponds | 
|
 * to a node. | 
|
 * <p> | 
|
 * Subclasses must override the <code>include</code> method to | 
|
 * indicate whether the entry should be shown in the | 
|
 * view.  The <code>Entry</code> argument can be used to obtain the values in | 
|
 * each of the columns in that entry.  The following example shows an | 
|
 * <code>include</code> method that allows only entries containing one or | 
|
 * more values starting with the string "a": | 
|
 * <pre> | 
|
 * 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; | 
|
 *   } | 
|
 * }; | 
|
 * </pre> | 
|
 * <code>RowFilter</code> has two formal type parameters that allow | 
|
 * you to create a <code>RowFilter</code> for a specific model. For | 
|
 * example, the following assumes a specific model that is wrapping | 
|
 * objects of type <code>Person</code>.  Only <code>Person</code>s | 
|
 * with an age over 20 will be shown: | 
|
 * <pre> | 
|
 * 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); | 
|
 * </pre> | 
|
 * | 
|
 * @param <M> the type of the model; for example <code>PersonModel</code> | 
|
 * @param <I> the type of the identifier; when using | 
|
 *            <code>TableRowSorter</code> this will be <code>Integer</code> | 
|
 * @see javax.swing.table.TableRowSorter | 
|
 * @since 1.6 | 
|
*/  | 
|
public abstract class RowFilter<M,I> { | 
|
    /** | 
|
     * Enumeration of the possible comparison values supported by | 
|
     * some of the default <code>RowFilter</code>s. | 
|
     * | 
|
     * @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 <code>RowFilter</code> 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 <code>RowFilter</code> that | 
|
     * includes entries with at least one value starting with | 
|
     * "a": | 
|
     * <pre> | 
|
     *   RowFilter.regexFilter("^a"); | 
|
     * </pre> | 
|
     * <p> | 
|
     * 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 <code>RowFilter</code> implementing the specified criteria | 
|
     * @throws NullPointerException if <code>regex</code> is | 
|
     *         <code>null</code> | 
|
     * @throws IllegalArgumentException if any of the <code>indices</code> | 
|
     *         are < 0 | 
|
     * @throws PatternSyntaxException if <code>regex</code> is | 
|
     *         not a valid regular expression. | 
|
     * @see java.util.regex.Pattern | 
|
*/  | 
|
public static <M,I> RowFilter<M,I> regexFilter(String regex,  | 
|
                                                       int... indices) { | 
|
return (RowFilter<M,I>)new RegexFilter(Pattern.compile(regex),  | 
|
indices);  | 
|
}  | 
|
    /** | 
|
     * Returns a <code>RowFilter</code> that includes entries that | 
|
     * have at least one <code>Date</code> value meeting the specified | 
|
     * criteria.  For example, the following <code>RowFilter</code> includes | 
|
     * only entries with at least one date value after the current date: | 
|
     * <pre> | 
|
     *   RowFilter.dateFilter(ComparisonType.AFTER, new Date()); | 
|
     * </pre> | 
|
     * | 
|
     * @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 <code>RowFilter</code> implementing the specified criteria | 
|
     * @throws NullPointerException if <code>date</code> is | 
|
     *          <code>null</code> | 
|
     * @throws IllegalArgumentException if any of the <code>indices</code> | 
|
     *         are < 0 or <code>type</code> is | 
|
     *         <code>null</code> | 
|
     * @see java.util.Calendar | 
|
     * @see java.util.Date | 
|
*/  | 
|
public static <M,I> RowFilter<M,I> dateFilter(ComparisonType type,  | 
|
Date date, int... indices) {  | 
|
return (RowFilter<M,I>)new DateFilter(type, date.getTime(), indices);  | 
|
}  | 
|
    /** | 
|
     * Returns a <code>RowFilter</code> that includes entries that | 
|
     * have at least one <code>Number</code> value meeting the | 
|
     * specified criteria.  For example, the following | 
|
     * filter will only include entries with at | 
|
     * least one number value equal to 10: | 
|
     * <pre> | 
|
     *   RowFilter.numberFilter(ComparisonType.EQUAL, 10); | 
|
     * </pre> | 
|
     * | 
|
     * @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 <code>RowFilter</code> implementing the specified criteria | 
|
     * @throws IllegalArgumentException if any of the <code>indices</code> | 
|
     *         are < 0, <code>type</code> is <code>null</code> | 
|
     *         or <code>number</code> is <code>null</code> | 
|
*/  | 
|
public static <M,I> RowFilter<M,I> numberFilter(ComparisonType type,  | 
|
Number number, int... indices) {  | 
|
return (RowFilter<M,I>)new NumberFilter(type, number, indices);  | 
|
}  | 
|
    /** | 
|
     * Returns a <code>RowFilter</code> that includes entries if any | 
|
     * of the supplied filters includes the entry. | 
|
     * <p> | 
|
     * The following example creates a <code>RowFilter</code> that will | 
|
     * include any entries containing the string "foo" or the string | 
|
     * "bar": | 
|
     * <pre> | 
|
     *   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); | 
|
     * </pre> | 
|
     * | 
|
     * @param filters the <code>RowFilter</code>s to test | 
|
     * @throws IllegalArgumentException if any of the filters | 
|
     *         are <code>null</code> | 
|
     * @throws NullPointerException if <code>filters</code> is null | 
|
     * @return a <code>RowFilter</code> implementing the specified criteria | 
|
     * @see java.util.Arrays#asList | 
|
*/  | 
|
public static <M,I> RowFilter<M,I> orFilter(  | 
|
Iterable<? extends RowFilter<? super M, ? super I>> filters) {  | 
|
return new OrFilter<M,I>(filters);  | 
|
}  | 
|
    /** | 
|
     * Returns a <code>RowFilter</code> that includes entries if all | 
|
     * of the supplied filters include the entry. | 
|
     * <p> | 
|
     * The following example creates a <code>RowFilter</code> that will | 
|
     * include any entries containing the string "foo" and the string | 
|
     * "bar": | 
|
     * <pre> | 
|
     *   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); | 
|
     * </pre> | 
|
     * | 
|
     * @param filters the <code>RowFilter</code>s to test | 
|
     * @return a <code>RowFilter</code> implementing the specified criteria | 
|
     * @throws IllegalArgumentException if any of the filters | 
|
     *         are <code>null</code> | 
|
     * @throws NullPointerException if <code>filters</code> is null | 
|
     * @see java.util.Arrays#asList | 
|
*/  | 
|
public static <M,I> RowFilter<M,I> andFilter(  | 
|
Iterable<? extends RowFilter<? super M, ? super I>> filters) {  | 
|
return new AndFilter<M,I>(filters);  | 
|
}  | 
|
    /** | 
|
     * Returns a <code>RowFilter</code> that includes entries if the | 
|
     * supplied filter does not include the entry. | 
|
     * | 
|
     * @param filter the <code>RowFilter</code> to negate | 
|
     * @return a <code>RowFilter</code> implementing the specified criteria | 
|
     * @throws IllegalArgumentException if <code>filter</code> is | 
|
     *         <code>null</code> | 
|
*/  | 
|
public static <M,I> RowFilter<M,I> notFilter(RowFilter<M,I> filter) {  | 
|
return new NotFilter<M,I>(filter);  | 
|
}  | 
|
    /** | 
|
     * Returns true if the specified entry should be shown; | 
|
     * returns false if the entry should be hidden. | 
|
     * <p> | 
|
     * The <code>entry</code> argument is valid only for the duration of | 
|
     * the invocation.  Using <code>entry</code> after the call returns | 
|
     * results in undefined behavior. | 
|
     * | 
|
     * @param entry a non-<code>null</code> object that wraps the underlying | 
|
     *              object from the model | 
|
     * @return true if the entry should be shown | 
|
*/  | 
|
public abstract boolean include(Entry<? extends M, ? extends I> 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 <code>Entry</code> object is passed to instances of | 
|
     * <code>RowFilter</code>, allowing the filter to get the value of the | 
|
     * entry's data, and thus to determine whether the entry should be shown. | 
|
     * An <code>Entry</code> object contains information about the model | 
|
     * as well as methods for getting the underlying values from the model. | 
|
     * | 
|
     * @param <M> the type of the model; for example <code>PersonModel</code> | 
|
     * @param <I> the type of the identifier; when using | 
|
     *            <code>TableRowSorter</code> this will be <code>Integer</code> | 
|
     * @see javax.swing.RowFilter | 
|
     * @see javax.swing.DefaultRowSorter#setRowFilter(javax.swing.RowFilter) | 
|
     * @since 1.6 | 
|
*/  | 
|
    public static abstract class Entry<M, I> { | 
|
        /** | 
|
         * Creates an <code>Entry</code>. | 
|
*/  | 
|
        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 | 
|
         * <code>null</code>.  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 <code>String</code> values | 
|
         * this method is preferred to that of <code>getValue</code> | 
|
         * as <code>getValue(index).toString()</code> may return a | 
|
         * different result than <code>getStringValue(index)</code>. | 
|
         * <p> | 
|
         * This implementation calls <code>getValue(index).toString()</code> | 
|
         * after checking for <code>null</code>.  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 <code>Integer</code>. | 
|
         * | 
|
         * @return a model-based (not view-based) identifier for | 
|
         *         this entry | 
|
*/  | 
|
public abstract I getIdentifier();  | 
|
}  | 
|
private static abstract class GeneralFilter extends RowFilter<Object,Object> {  | 
|
private int[] columns;  | 
|
        GeneralFilter(int[] columns) { | 
|
checkIndices(columns);  | 
|
this.columns = columns;  | 
|
}  | 
|
public boolean include(Entry<? extends Object,? extends Object> 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<? extends Object,? extends Object> 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("");  | 
|
}  | 
|
protected boolean include(  | 
|
Entry<? extends Object,? extends Object> 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;  | 
|
}  | 
|
protected boolean include(  | 
|
Entry<? extends Object,? extends Object> 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);  | 
|
}  | 
|
        @SuppressWarnings("unchecked") | 
|
protected boolean include(  | 
|
Entry<? extends Object,? extends Object> 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<M,I> extends RowFilter<M,I> {  | 
|
List<RowFilter<? super M,? super I>> filters;  | 
|
OrFilter(Iterable<? extends RowFilter<? super M, ? super I>> filters) {  | 
|
this.filters = new ArrayList<RowFilter<? super M,? super I>>();  | 
|
for (RowFilter<? super M, ? super I> filter : filters) {  | 
|
if (filter == null) {  | 
|
throw new IllegalArgumentException(  | 
|
                        "Filter must be non-null"); | 
|
}  | 
|
this.filters.add(filter);  | 
|
}  | 
|
}  | 
|
public boolean include(Entry<? extends M, ? extends I> value) {  | 
|
for (RowFilter<? super M,? super I> filter : filters) {  | 
|
if (filter.include(value)) {  | 
|
return true;  | 
|
}  | 
|
}  | 
|
return false;  | 
|
}  | 
|
}  | 
|
    private static class AndFilter<M,I> extends OrFilter<M,I> { | 
|
AndFilter(Iterable<? extends RowFilter<? super M,? super I>> filters) {  | 
|
super(filters);  | 
|
}  | 
|
public boolean include(Entry<? extends M, ? extends I> value) {  | 
|
for (RowFilter<? super M,? super I> filter : filters) {  | 
|
if (!filter.include(value)) {  | 
|
return false;  | 
|
}  | 
|
}  | 
|
return true;  | 
|
}  | 
|
}  | 
|
private static class NotFilter<M,I> extends RowFilter<M,I> {  | 
|
private RowFilter<M,I> filter;  | 
|
NotFilter(RowFilter<M,I> filter) {  | 
|
if (filter == null) {  | 
|
throw new IllegalArgumentException(  | 
|
                    "filter must be non-null"); | 
|
}  | 
|
this.filter = filter;  | 
|
}  | 
|
public boolean include(Entry<? extends M, ? extends I> value) {  | 
|
return !filter.include(value);  | 
|
}  | 
|
}  | 
|
}  |