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