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 */ 25package javax.swing; 26 27import java.util.ArrayList; 28import java.math.BigDecimal; 29import java.math.BigInteger; 30import java.util.Date; 31import java.util.List; 32import java.util.regex.Matcher; 33import java.util.regex.Pattern; 34import 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 */ 97public 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 < 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 < 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 < 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<RowFilter<Object,Object>> filters = new ArrayList<RowFilter<Object,Object>>(2); 245 * filters.add(RowFilter.regexFilter("foo")); 246 * filters.add(RowFilter.regexFilter("bar")); 247 * RowFilter<Object,Object> 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<RowFilter<Object,Object>> filters = new ArrayList<RowFilter<Object,Object>>(2); 273 * filters.add(RowFilter.regexFilter("foo")); 274 * filters.add(RowFilter.regexFilter("bar")); 275 * RowFilter<Object,Object> 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 < 0 or 374 * >= 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 < 0 || 393 * >= 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} 623