001/* 002 * $Id$ 003 * 004 * Copyright 2009 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 * 021 */ 022package org.jdesktop.swingx.sort; 023 024import java.util.regex.Matcher; 025import java.util.regex.Pattern; 026import java.util.regex.PatternSyntaxException; 027 028import javax.swing.RowFilter; 029 030import org.jdesktop.swingx.util.Contract; 031 032/** 033 * Factory of additional <code>RowFilter</code>s. <p> 034 * 035 * Trigger is the missing of Pattern/Regex+matchflags factory method in core. 036 * Can't do much other than c&p core as both abstract base class GeneralFilter and 037 * concrete RowFilter are private. Expose the base as public for custom subclasses 038 * 039 * @author Jeanette Winzenburg 040 */ 041@SuppressWarnings("unchecked") 042public class RowFilters { 043 044 /** 045 * Returns a <code>RowFilter</code> that uses a regular 046 * expression to determine which entries to include. Only entries 047 * with at least one matching value are included. For 048 * example, the following creates a <code>RowFilter</code> that 049 * includes entries with at least one value starting with 050 * "a": 051 * <pre> 052 * RowFilter.regexFilter("^a"); 053 * </pre> 054 * <p> 055 * The returned filter uses {@link java.util.regex.Matcher#find} 056 * to test for inclusion. To test for exact matches use the 057 * characters '^' and '$' to match the beginning and end of the 058 * string respectively. For example, "^foo$" includes only rows whose 059 * string is exactly "foo" and not, for example, "food". See 060 * {@link java.util.regex.Pattern} for a complete description of 061 * the supported regular-expression constructs. 062 * 063 * @param regex the regular expression to filter on 064 * @param indices the indices of the values to check. If not supplied all 065 * values are evaluated 066 * @return a <code>RowFilter</code> implementing the specified criteria 067 * @throws NullPointerException if <code>regex</code> is 068 * <code>null</code> 069 * @throws IllegalArgumentException if any of the <code>indices</code> 070 * are < 0 071 * @throws PatternSyntaxException if <code>regex</code> is 072 * not a valid regular expression. 073 * @see java.util.regex.Pattern 074 */ 075 public static <M,I> RowFilter<M,I> regexFilter(String regex, 076 int... indices) { 077 return regexFilter(0, regex, indices); 078 } 079 080 /** 081 * Returns a <code>RowFilter</code> that uses a regular 082 * expression to determine which entries to include. Only entries 083 * with at least one matching value are included. For 084 * example, the following creates a <code>RowFilter</code> that 085 * includes entries with at least one value starting with 086 * "a" ignoring case: 087 * <pre> 088 * RowFilter.regexFilter(Pattern.CASE_INSENSITIVE, "^a"); 089 * </pre> 090 * <p> 091 * The returned filter uses {@link java.util.regex.Matcher#find} 092 * to test for inclusion. To test for exact matches use the 093 * characters '^' and '$' to match the beginning and end of the 094 * string respectively. For example, "^foo$" includes only rows whose 095 * string is exactly "foo" and not, for example, "food". See 096 * {@link java.util.regex.Pattern} for a complete description of 097 * the supported regular-expression constructs. 098 * 099 * @param matchFlags 100 * Match flags, a bit mask that may include 101 * {@link Pattern#CASE_INSENSITIVE}, {@link Pattern#MULTILINE}, {@link Pattern#DOTALL}, 102 * {@link Pattern#UNICODE_CASE}, {@link Pattern#CANON_EQ}, {@link Pattern#UNIX_LINES}, 103 * {@link Pattern#LITERAL} and {@link Pattern#COMMENTS} 104 * 105 * @param regex the regular expression to filter on 106 * @param indices the indices of the values to check. If not supplied all 107 * values are evaluated 108 * @return a <code>RowFilter</code> implementing the specified criteria 109 * @throws NullPointerException if <code>regex</code> is 110 * <code>null</code> 111 * @throws IllegalArgumentException if any of the <code>indices</code> 112 * are < 0 113 * @throws IllegalArgumentException 114 * If bit values other than those corresponding to the defined 115 * match flags are set in <tt>flags</tt> 116 * @throws PatternSyntaxException if <code>regex</code> is 117 * not a valid regular expression. 118 * @see java.util.regex.Pattern 119 */ 120 public static <M,I> RowFilter<M,I> regexFilter(int matchFlags, String regex, 121 int... indices) { 122 return regexFilter(Pattern.compile(regex, matchFlags), indices); 123 } 124 125 /** 126 * Returns a <code>RowFilter</code> that uses a regular 127 * expression to determine which entries to include. 128 * 129 * @param pattern the Pattern to use for matching 130 * @param indices the indices of the values to check. If not supplied all 131 * values are evaluated 132 * @return a <code>RowFilter</code> implementing the specified criteria 133 * @throws NullPointerException if <code>pattern</code> is 134 * <code>null</code> 135 * @see java.util.regex.Pattern 136 */ 137 public static <M,I> RowFilter<M,I> regexFilter(Pattern pattern, 138 int... indices) { 139 return (RowFilter<M,I>)new RegexFilter(pattern, indices); 140 } 141 142 /** 143 * C&P from core Swing to allow subclassing. 144 */ 145 public static abstract class GeneralFilter extends RowFilter<Object,Object> { 146 private int[] columns; 147 148 protected GeneralFilter(int... columns) { 149 checkIndices(columns); 150 this.columns = columns; 151 } 152 153 @Override 154 public boolean include(Entry<? extends Object,? extends Object> value){ 155 int count = value.getValueCount(); 156 if (columns.length > 0) { 157 for (int i = columns.length - 1; i >= 0; i--) { 158 int index = columns[i]; 159 if (index < count) { 160 if (include(value, index)) { 161 return true; 162 } 163 } 164 } 165 } 166 else { 167 while (--count >= 0) { 168 if (include(value, count)) { 169 return true; 170 } 171 } 172 } 173 return false; 174 } 175 176 protected abstract boolean include( 177 Entry<? extends Object,? extends Object> value, int index); 178 /** 179 * Throws an IllegalArgumentException if any of the values in 180 * columns are < 0. 181 */ 182 protected void checkIndices(int[] columns) { 183 for (int i = columns.length - 1; i >= 0; i--) { 184 if (columns[i] < 0) { 185 throw new IllegalArgumentException("Index must be >= 0"); 186 } 187 } 188 } 189 } 190 191 /** 192 * C&P from core to allow richer factory methods. 193 */ 194 private static class RegexFilter extends GeneralFilter { 195 private Matcher matcher; 196 197 RegexFilter(Pattern regex, int[] columns) { 198 super(columns); 199 if (regex == null) { 200 // JW: Exception type changed to comply with swingx convention 201 Contract.asNotNull(regex, "Pattern must be non-null"); 202// throw new IllegalArgumentException("Pattern must be non-null"); 203 } 204 matcher = regex.matcher(""); 205 } 206 207 @Override 208 protected boolean include( 209 Entry<? extends Object,? extends Object> value, int index) { 210 matcher.reset(value.getStringValue(index)); 211 return matcher.find(); 212 } 213 } 214 215 private RowFilters() {}; 216 217}