Home » openjdk-7 » javax » swing » [javadoc | source]

    1   /*
    2    * Copyright (c) 2005, 2006, 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   
   35   /**
   36    * <code>RowFilter</code> is used to filter out entries from the
   37    * model so that they are not shown in the view.  For example, a
   38    * <code>RowFilter</code> associated with a <code>JTable</code> might
   39    * only allow rows that contain a column with a specific string. The
   40    * meaning of <em>entry</em> depends on the component type.
   41    * For example, when a filter is
   42    * associated with a <code>JTable</code>, an entry corresponds to a
   43    * row; when associated with a <code>JTree</code>, an entry corresponds
   44    * to a node.
   45    * <p>
   46    * Subclasses must override the <code>include</code> method to
   47    * indicate whether the entry should be shown in the
   48    * view.  The <code>Entry</code> argument can be used to obtain the values in
   49    * each of the columns in that entry.  The following example shows an
   50    * <code>include</code> method that allows only entries containing one or
   51    * more values starting with the string "a":
   52    * <pre>
   53    * RowFilter&lt;Object,Object&gt; startsWithAFilter = new RowFilter&lt;Object,Object&gt;() {
   54    *   public boolean include(Entry&lt;? extends Object, ? extends Object&gt; entry) {
   55    *     for (int i = entry.getValueCount() - 1; i &gt;= 0; i--) {
   56    *       if (entry.getStringValue(i).startsWith("a")) {
   57    *         // The value starts with "a", include it
   58    *         return true;
   59    *       }
   60    *     }
   61    *     // None of the columns start with "a"; return false so that this
   62    *     // entry is not shown
   63    *     return false;
   64    *   }
   65    * };
   66    * </pre>
   67    * <code>RowFilter</code> has two formal type parameters that allow
   68    * you to create a <code>RowFilter</code> for a specific model. For
   69    * example, the following assumes a specific model that is wrapping
   70    * objects of type <code>Person</code>.  Only <code>Person</code>s
   71    * with an age over 20 will be shown:
   72    * <pre>
   73    * RowFilter&lt;PersonModel,Integer&gt; ageFilter = new RowFilter&lt;PersonModel,Integer&gt;() {
   74    *   public boolean include(Entry&lt;? extends PersonModel, ? extends Integer&gt; entry) {
   75    *     PersonModel personModel = entry.getModel();
   76    *     Person person = personModel.getPerson(entry.getIdentifier());
   77    *     if (person.getAge() &gt; 20) {
   78    *       // Returning true indicates this row should be shown.
   79    *       return true;
   80    *     }
   81    *     // Age is &lt;= 20, don't show it.
   82    *     return false;
   83    *   }
   84    * };
   85    * PersonModel model = createPersonModel();
   86    * TableRowSorter&lt;PersonModel&gt; sorter = new TableRowSorter&lt;PersonModel&gt;(model);
   87    * sorter.setRowFilter(ageFilter);
   88    * </pre>
   89    *
   90    * @param <M> the type of the model; for example <code>PersonModel</code>
   91    * @param <I> the type of the identifier; when using
   92    *            <code>TableRowSorter</code> this will be <code>Integer</code>
   93    * @see javax.swing.table.TableRowSorter
   94    * @since 1.6
   95    */
   96   public abstract class RowFilter<M,I> {
   97       /**
   98        * Enumeration of the possible comparison values supported by
   99        * some of the default <code>RowFilter</code>s.
  100        *
  101        * @see RowFilter
  102        * @since 1.6
  103        */
  104       public enum ComparisonType {
  105           /**
  106            * Indicates that entries with a value before the supplied
  107            * value should be included.
  108            */
  109           BEFORE,
  110   
  111           /**
  112            * Indicates that entries with a value after the supplied
  113            * value should be included.
  114            */
  115           AFTER,
  116   
  117           /**
  118            * Indicates that entries with a value equal to the supplied
  119            * value should be included.
  120            */
  121           EQUAL,
  122   
  123           /**
  124            * Indicates that entries with a value not equal to the supplied
  125            * value should be included.
  126            */
  127           NOT_EQUAL
  128       }
  129   
  130       /**
  131        * Throws an IllegalArgumentException if any of the values in
  132        * columns are < 0.
  133        */
  134       private static void checkIndices(int[] columns) {
  135           for (int i = columns.length - 1; i >= 0; i--) {
  136               if (columns[i] < 0) {
  137                   throw new IllegalArgumentException("Index must be >= 0");
  138               }
  139           }
  140       }
  141   
  142       /**
  143        * Returns a <code>RowFilter</code> that uses a regular
  144        * expression to determine which entries to include.  Only entries
  145        * with at least one matching value are included.  For
  146        * example, the following creates a <code>RowFilter</code> that
  147        * includes entries with at least one value starting with
  148        * "a":
  149        * <pre>
  150        *   RowFilter.regexFilter("^a");
  151        * </pre>
  152        * <p>
  153        * The returned filter uses {@link java.util.regex.Matcher#find}
  154        * to test for inclusion.  To test for exact matches use the
  155        * characters '^' and '$' to match the beginning and end of the
  156        * string respectively.  For example, "^foo$" includes only rows whose
  157        * string is exactly "foo" and not, for example, "food".  See
  158        * {@link java.util.regex.Pattern} for a complete description of
  159        * the supported regular-expression constructs.
  160        *
  161        * @param regex the regular expression to filter on
  162        * @param indices the indices of the values to check.  If not supplied all
  163        *               values are evaluated
  164        * @return a <code>RowFilter</code> implementing the specified criteria
  165        * @throws NullPointerException if <code>regex</code> is
  166        *         <code>null</code>
  167        * @throws IllegalArgumentException if any of the <code>indices</code>
  168        *         are &lt; 0
  169        * @throws PatternSyntaxException if <code>regex</code> is
  170        *         not a valid regular expression.
  171        * @see java.util.regex.Pattern
  172        */
  173       public static <M,I> RowFilter<M,I> regexFilter(String regex,
  174                                                          int... indices) {
  175           return (RowFilter<M,I>)new RegexFilter(Pattern.compile(regex),
  176                                                  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 (RowFilter<M,I>)new DateFilter(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 (RowFilter<M,I>)new NumberFilter(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 <code>IndexOutOfBoundsException</code> 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 <code>IndexOutOfBoundsException</code> 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 extends RowFilter<Object,Object> {
  400           private int[] columns;
  401   
  402           GeneralFilter(int[] columns) {
  403               checkIndices(columns);
  404               this.columns = columns;
  405           }
  406   
  407           public boolean include(Entry<? extends Object,? extends Object> value){
  408               int count = value.getValueCount();
  409               if (columns.length > 0) {
  410                   for (int i = columns.length - 1; i >= 0; i--) {
  411                       int index = columns[i];
  412                       if (index < count) {
  413                           if (include(value, index)) {
  414                               return true;
  415                           }
  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 Object,? extends Object> value, int index);
  431       }
  432   
  433   
  434       private static class RegexFilter extends GeneralFilter {
  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           protected boolean include(
  446                   Entry<? extends Object,? extends Object> value, int index) {
  447               matcher.reset(value.getStringValue(index));
  448               return matcher.find();
  449           }
  450       }
  451   
  452   
  453       private static class DateFilter extends GeneralFilter {
  454           private long date;
  455           private ComparisonType type;
  456   
  457           DateFilter(ComparisonType type, long date, int[] columns) {
  458               super(columns);
  459               if (type == null) {
  460                   throw new IllegalArgumentException("type must be non-null");
  461               }
  462               this.type = type;
  463               this.date = date;
  464           }
  465   
  466           protected boolean include(
  467                   Entry<? extends Object,? extends Object> value, int index) {
  468               Object v = value.getValue(index);
  469   
  470               if (v instanceof Date) {
  471                   long vDate = ((Date)v).getTime();
  472                   switch(type) {
  473                   case BEFORE:
  474                       return (vDate < date);
  475                   case AFTER:
  476                       return (vDate > date);
  477                   case EQUAL:
  478                       return (vDate == date);
  479                   case NOT_EQUAL:
  480                       return (vDate != date);
  481                   default:
  482                       break;
  483                   }
  484               }
  485               return false;
  486           }
  487       }
  488   
  489   
  490   
  491   
  492       private static class NumberFilter extends GeneralFilter {
  493           private boolean isComparable;
  494           private Number number;
  495           private ComparisonType type;
  496   
  497           NumberFilter(ComparisonType type, Number number, int[] columns) {
  498               super(columns);
  499               if (type == null || number == null) {
  500                   throw new IllegalArgumentException(
  501                       "type and number must be non-null");
  502               }
  503               this.type = type;
  504               this.number = number;
  505               isComparable = (number instanceof Comparable);
  506           }
  507   
  508           @SuppressWarnings("unchecked")
  509           protected boolean include(
  510                   Entry<? extends Object,? extends Object> 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   }

Home » openjdk-7 » javax » swing » [javadoc | source]