1 /* 2 * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing; 26 27 import java.util.ArrayList; 28 import java.math.BigDecimal; 29 import java.math.BigInteger; 30 import java.util.Date; 31 import java.util.List; 32 import java.util.regex.Matcher; 33 import java.util.regex.Pattern; 34 import java.util.regex.PatternSyntaxException; 35 36 /** 37 * <code>RowFilter</code> is used to filter out entries from the 38 * model so that they are not shown in the view. For example, a 39 * <code>RowFilter</code> associated with a <code>JTable</code> might 40 * only allow rows that contain a column with a specific string. The 41 * meaning of <em>entry</em> depends on the component type. 42 * For example, when a filter is 43 * associated with a <code>JTable</code>, an entry corresponds to a 44 * row; when associated with a <code>JTree</code>, an entry corresponds 45 * to a node. 46 * <p> 47 * Subclasses must override the <code>include</code> method to 48 * indicate whether the entry should be shown in the 49 * view. The <code>Entry</code> argument can be used to obtain the values in 50 * each of the columns in that entry. The following example shows an 51 * <code>include</code> method that allows only entries containing one or 52 * more values starting with the string "a": 53 * <pre> 54 * RowFilter<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 * Throws an IllegalArgumentException if any of the values in 133 * columns are {@literal <} 0. 134 */ 135 private static void checkIndices(int[] columns) { 136 for (int i = columns.length - 1; i >= 0; i--) { 137 if (columns[i] < 0) { 138 throw new IllegalArgumentException("Index must be >= 0"); 139 } 140 } 141 } 142 143 /** 144 * Returns a <code>RowFilter</code> that uses a regular 145 * expression to determine which entries to include. Only entries 146 * with at least one matching value are included. For 147 * example, the following creates a <code>RowFilter</code> that 148 * includes entries with at least one value starting with 149 * "a": 150 * <pre> 151 * RowFilter.regexFilter("^a"); 152 * </pre> 153 * <p> 154 * The returned filter uses {@link java.util.regex.Matcher#find} 155 * to test for inclusion. To test for exact matches use the 156 * characters '^' and '$' to match the beginning and end of the 157 * string respectively. For example, "^foo$" includes only rows whose 158 * string is exactly "foo" and not, for example, "food". See 159 * {@link java.util.regex.Pattern} for a complete description of 160 * the supported regular-expression constructs. 161 * 162 * @param regex the regular expression to filter on 163 * @param indices the indices of the values to check. If not supplied all 164 * values are evaluated 165 * @return a <code>RowFilter</code> implementing the specified criteria 166 * @throws NullPointerException if <code>regex</code> is 167 * <code>null</code> 168 * @throws IllegalArgumentException if any of the <code>indices</code> 169 * are < 0 170 * @throws PatternSyntaxException if <code>regex</code> is 171 * not a valid regular expression. 172 * @see java.util.regex.Pattern 173 */ 174 public static <M,I> RowFilter<M,I> regexFilter(String regex, 175 int... indices) { 176 return new RegexFilter<M, I>(Pattern.compile(regex), indices); 177 } 178 179 /** 180 * Returns a <code>RowFilter</code> that includes entries that 181 * have at least one <code>Date</code> value meeting the specified 182 * criteria. For example, the following <code>RowFilter</code> includes 183 * only entries with at least one date value after the current date: 184 * <pre> 185 * RowFilter.dateFilter(ComparisonType.AFTER, new Date()); 186 * </pre> 187 * 188 * @param type the type of comparison to perform 189 * @param date the date to compare against 190 * @param indices the indices of the values to check. If not supplied all 191 * values are evaluated 192 * @return a <code>RowFilter</code> implementing the specified criteria 193 * @throws NullPointerException if <code>date</code> is 194 * <code>null</code> 195 * @throws IllegalArgumentException if any of the <code>indices</code> 196 * are < 0 or <code>type</code> is 197 * <code>null</code> 198 * @see java.util.Calendar 199 * @see java.util.Date 200 */ 201 public static <M,I> RowFilter<M,I> dateFilter(ComparisonType type, 202 Date date, int... indices) { 203 return new DateFilter<M, I>(type, date.getTime(), indices); 204 } 205 206 /** 207 * Returns a <code>RowFilter</code> that includes entries that 208 * have at least one <code>Number</code> value meeting the 209 * specified criteria. For example, the following 210 * filter will only include entries with at 211 * least one number value equal to 10: 212 * <pre> 213 * RowFilter.numberFilter(ComparisonType.EQUAL, 10); 214 * </pre> 215 * 216 * @param type the type of comparison to perform 217 * @param indices the indices of the values to check. If not supplied all 218 * values are evaluated 219 * @return a <code>RowFilter</code> implementing the specified criteria 220 * @throws IllegalArgumentException if any of the <code>indices</code> 221 * are < 0, <code>type</code> is <code>null</code> 222 * or <code>number</code> is <code>null</code> 223 */ 224 public static <M,I> RowFilter<M,I> numberFilter(ComparisonType type, 225 Number number, int... indices) { 226 return new NumberFilter<M, I>(type, number, indices); 227 } 228 229 /** 230 * Returns a <code>RowFilter</code> that includes entries if any 231 * of the supplied filters includes the entry. 232 * <p> 233 * The following example creates a <code>RowFilter</code> that will 234 * include any entries containing the string "foo" or the string 235 * "bar": 236 * <pre> 237 * List<RowFilter<Object,Object>> filters = new ArrayList<RowFilter<Object,Object>>(2); 238 * filters.add(RowFilter.regexFilter("foo")); 239 * filters.add(RowFilter.regexFilter("bar")); 240 * RowFilter<Object,Object> 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<RowFilter<Object,Object>> filters = new ArrayList<RowFilter<Object,Object>>(2); 264 * filters.add(RowFilter.regexFilter("foo")); 265 * filters.add(RowFilter.regexFilter("bar")); 266 * RowFilter<Object,Object> fooBarFilter = RowFilter.andFilter(filters); 267 * </pre> 268 * 269 * @param filters the <code>RowFilter</code>s to test 270 * @return a <code>RowFilter</code> implementing the specified criteria 271 * @throws IllegalArgumentException if any of the filters 272 * are <code>null</code> 273 * @throws NullPointerException if <code>filters</code> is null 274 * @see java.util.Arrays#asList 275 */ 276 public static <M,I> RowFilter<M,I> andFilter( 277 Iterable<? extends RowFilter<? super M, ? super I>> filters) { 278 return new AndFilter<M,I>(filters); 279 } 280 281 /** 282 * Returns a <code>RowFilter</code> that includes entries if the 283 * supplied filter does not include the entry. 284 * 285 * @param filter the <code>RowFilter</code> to negate 286 * @return a <code>RowFilter</code> implementing the specified criteria 287 * @throws IllegalArgumentException if <code>filter</code> is 288 * <code>null</code> 289 */ 290 public static <M,I> RowFilter<M,I> notFilter(RowFilter<M,I> filter) { 291 return new NotFilter<M,I>(filter); 292 } 293 294 /** 295 * Returns true if the specified entry should be shown; 296 * returns false if the entry should be hidden. 297 * <p> 298 * The <code>entry</code> argument is valid only for the duration of 299 * the invocation. Using <code>entry</code> after the call returns 300 * results in undefined behavior. 301 * 302 * @param entry a non-<code>null</code> object that wraps the underlying 303 * object from the model 304 * @return true if the entry should be shown 305 */ 306 public abstract boolean include(Entry<? extends M, ? extends I> entry); 307 308 // 309 // WARNING: 310 // Because of the method signature of dateFilter/numberFilter/regexFilter 311 // we can NEVER add a method to RowFilter that returns M,I. If we were 312 // to do so it would be possible to get a ClassCastException during normal 313 // usage. 314 // 315 316 /** 317 * An <code>Entry</code> object is passed to instances of 318 * <code>RowFilter</code>, allowing the filter to get the value of the 319 * entry's data, and thus to determine whether the entry should be shown. 320 * An <code>Entry</code> object contains information about the model 321 * as well as methods for getting the underlying values from the model. 322 * 323 * @param <M> the type of the model; for example <code>PersonModel</code> 324 * @param <I> the type of the identifier; when using 325 * <code>TableRowSorter</code> this will be <code>Integer</code> 326 * @see javax.swing.RowFilter 327 * @see javax.swing.DefaultRowSorter#setRowFilter(javax.swing.RowFilter) 328 * @since 1.6 329 */ 330 public static abstract class Entry<M, I> { 331 /** 332 * Creates an <code>Entry</code>. 333 */ 334 public Entry() { 335 } 336 337 /** 338 * Returns the underlying model. 339 * 340 * @return the model containing the data that this entry represents 341 */ 342 public abstract M getModel(); 343 344 /** 345 * Returns the number of values in the entry. For 346 * example, when used with a table this corresponds to the 347 * number of columns. 348 * 349 * @return number of values in the object being filtered 350 */ 351 public abstract int getValueCount(); 352 353 /** 354 * Returns the value at the specified index. This may return 355 * <code>null</code>. When used with a table, index 356 * corresponds to the column number in the model. 357 * 358 * @param index the index of the value to get 359 * @return value at the specified index 360 * @throws IndexOutOfBoundsException if index < 0 or 361 * >= getValueCount 362 */ 363 public abstract Object getValue(int index); 364 365 /** 366 * Returns the string value at the specified index. If 367 * filtering is being done based on <code>String</code> values 368 * this method is preferred to that of <code>getValue</code> 369 * as <code>getValue(index).toString()</code> may return a 370 * different result than <code>getStringValue(index)</code>. 371 * <p> 372 * This implementation calls <code>getValue(index).toString()</code> 373 * after checking for <code>null</code>. Subclasses that provide 374 * different string conversion should override this method if 375 * necessary. 376 * 377 * @param index the index of the value to get 378 * @return {@code non-null} string at the specified index 379 * @throws IndexOutOfBoundsException if index < 0 || 380 * >= getValueCount 381 */ 382 public String getStringValue(int index) { 383 Object value = getValue(index); 384 return (value == null) ? "" : value.toString(); 385 } 386 387 /** 388 * Returns the identifer (in the model) of the entry. 389 * For a table this corresponds to the index of the row in the model, 390 * expressed as an <code>Integer</code>. 391 * 392 * @return a model-based (not view-based) identifier for 393 * this entry 394 */ 395 public abstract I getIdentifier(); 396 } 397 398 399 private static abstract class GeneralFilter<M, I> extends RowFilter<M, I> { 400 private int[] columns; 401 402 GeneralFilter(int[] columns) { 403 checkIndices(columns); 404 this.columns = columns; 405 } 406 407 @Override 408 public boolean include(Entry<? extends M, ? extends I> value){ 409 int count = value.getValueCount(); 410 if (columns.length > 0) { 411 for (int i = columns.length - 1; i >= 0; i--) { 412 int index = columns[i]; 413 if (index < count) { 414 if (include(value, index)) { 415 return true; 416 } 417 } 418 } 419 } else { 420 while (--count >= 0) { 421 if (include(value, count)) { 422 return true; 423 } 424 } 425 } 426 return false; 427 } 428 429 protected abstract boolean include( 430 Entry<? extends M, ? extends I> value, int index); 431 } 432 433 434 private static class RegexFilter<M, I> extends GeneralFilter<M, I> { 435 private Matcher matcher; 436 437 RegexFilter(Pattern regex, int[] columns) { 438 super(columns); 439 if (regex == null) { 440 throw new IllegalArgumentException("Pattern must be non-null"); 441 } 442 matcher = regex.matcher(""); 443 } 444 445 @Override 446 protected boolean include( 447 Entry<? extends M, ? extends I> value, int index) { 448 matcher.reset(value.getStringValue(index)); 449 return matcher.find(); 450 } 451 } 452 453 454 private static class DateFilter<M, I> extends GeneralFilter<M, I> { 455 private long date; 456 private ComparisonType type; 457 458 DateFilter(ComparisonType type, long date, int[] columns) { 459 super(columns); 460 if (type == null) { 461 throw new IllegalArgumentException("type must be non-null"); 462 } 463 this.type = type; 464 this.date = date; 465 } 466 467 @Override 468 protected boolean include( 469 Entry<? extends M, ? extends I> value, int index) { 470 Object v = value.getValue(index); 471 472 if (v instanceof Date) { 473 long vDate = ((Date)v).getTime(); 474 switch(type) { 475 case BEFORE: 476 return (vDate < date); 477 case AFTER: 478 return (vDate > date); 479 case EQUAL: 480 return (vDate == date); 481 case NOT_EQUAL: 482 return (vDate != date); 483 default: 484 break; 485 } 486 } 487 return false; 488 } 489 } 490 491 private static class NumberFilter<M, I> extends GeneralFilter<M, I> { 492 private boolean isComparable; 493 private Number number; 494 private ComparisonType type; 495 496 NumberFilter(ComparisonType type, Number number, int[] columns) { 497 super(columns); 498 if (type == null || number == null) { 499 throw new IllegalArgumentException( 500 "type and number must be non-null"); 501 } 502 this.type = type; 503 this.number = number; 504 isComparable = (number instanceof Comparable); 505 } 506 507 @Override 508 @SuppressWarnings("unchecked") 509 protected boolean include( 510 Entry<? extends M, ? extends I> value, int index) { 511 Object v = value.getValue(index); 512 513 if (v instanceof Number) { 514 boolean compared = true; 515 int compareResult; 516 Class<?> vClass = v.getClass(); 517 if (number.getClass() == vClass && isComparable) { 518 compareResult = ((Comparable)number).compareTo(v); 519 } 520 else { 521 compareResult = longCompare((Number)v); 522 } 523 switch(type) { 524 case BEFORE: 525 return (compareResult > 0); 526 case AFTER: 527 return (compareResult < 0); 528 case EQUAL: 529 return (compareResult == 0); 530 case NOT_EQUAL: 531 return (compareResult != 0); 532 default: 533 break; 534 } 535 } 536 return false; 537 } 538 539 private int longCompare(Number o) { 540 long diff = number.longValue() - o.longValue(); 541 542 if (diff < 0) { 543 return -1; 544 } 545 else if (diff > 0) { 546 return 1; 547 } 548 return 0; 549 } 550 } 551 552 553 private static class OrFilter<M,I> extends RowFilter<M,I> { 554 List<RowFilter<? super M,? super I>> filters; 555 556 OrFilter(Iterable<? extends RowFilter<? super M, ? super I>> filters) { 557 this.filters = new ArrayList<RowFilter<? super M,? super I>>(); 558 for (RowFilter<? super M, ? super I> filter : filters) { 559 if (filter == null) { 560 throw new IllegalArgumentException( 561 "Filter must be non-null"); 562 } 563 this.filters.add(filter); 564 } 565 } 566 567 public boolean include(Entry<? extends M, ? extends I> value) { 568 for (RowFilter<? super M,? super I> filter : filters) { 569 if (filter.include(value)) { 570 return true; 571 } 572 } 573 return false; 574 } 575 } 576 577 578 private static class AndFilter<M,I> extends OrFilter<M,I> { 579 AndFilter(Iterable<? extends RowFilter<? super M,? super I>> filters) { 580 super(filters); 581 } 582 583 public boolean include(Entry<? extends M, ? extends I> value) { 584 for (RowFilter<? super M,? super I> filter : filters) { 585 if (!filter.include(value)) { 586 return false; 587 } 588 } 589 return true; 590 } 591 } 592 593 594 private static class NotFilter<M,I> extends RowFilter<M,I> { 595 private RowFilter<M,I> filter; 596 597 NotFilter(RowFilter<M,I> filter) { 598 if (filter == null) { 599 throw new IllegalArgumentException( 600 "filter must be non-null"); 601 } 602 this.filter = filter; 603 } 604 605 public boolean include(Entry<? extends M, ? extends I> value) { 606 return !filter.include(value); 607 } 608 } 609 }