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