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 &lt; 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 &lt; 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}