You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
611 lines
21 KiB
611 lines
21 KiB
/*
|
|
* Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
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);
|
|
}
|
|
}
|
|
}
|