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  */
  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 <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 }