001/*
002 * $Id: PatternPredicate.java 3927 2011-02-22 16:34:11Z kleopatra $
003 *
004 * Copyright 2006 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 */
021package org.jdesktop.swingx.decorator;
022
023import java.awt.Component;
024import java.util.regex.Pattern;
025
026/**
027 * Pattern based HighlightPredicate. <p>
028 * 
029 * Turns on the highlight of a single or all columns of the current row if
030 * a match of the String representation of cell content against the given Pattern
031 * is found.<p>
032 * 
033 * The match logic can be configured to either test 
034 * one specific column in the current row or all columns. In the latter case
035 * the logic is the same as in RowFilters.GeneralFilter: the row is included
036 * if any of the cell contents in the row are matches. <p>
037 * 
038 * 
039 * @author Jeanette Winzenburg
040 */
041public class PatternPredicate implements HighlightPredicate {
042    public static final int ALL = -1;
043    
044    private int highlightColumn;
045    private int testColumn;
046    private Pattern pattern;
047    
048    /**
049     * Instantiates a Predicate with the given Pattern and testColumn index
050     * (in model coordinates) highlighting all columns.
051     *  A column index of -1 is interpreted
052     * as "all". 
053     * 
054     * @param pattern the Pattern to test the cell value against
055     * @param testColumn the column index in model coordinates
056     *   of the cell which contains the value to test against the pattern 
057     */
058    public PatternPredicate(Pattern pattern, int testColumn) {
059        this(pattern, testColumn, ALL);
060    }
061    
062    /**
063     * Instantiates a Predicate with the given Pattern testing against
064     * all columns and highlighting all columns.
065     * 
066     * @param pattern the Pattern to test the cell value against
067     */
068    public PatternPredicate(Pattern pattern) {
069        this(pattern, ALL, ALL);
070    }
071
072    /**
073     * Instantiates a Predicate with the given Pattern and test-/decorate
074     * column index in model coordinates. A column index of -1 is interpreted
075     * as "all". 
076     * 
077     * 
078     * @param pattern the Pattern to test the cell value against
079     * @param testColumn the column index in model coordinates 
080     *   of the cell which contains the value
081     *   to test against the pattern 
082     * @param decorateColumn the column index in model coordinates
083     *   of the cell which should be 
084     *   decorated if the test against the value succeeds.
085     */
086    public PatternPredicate(Pattern pattern, int testColumn, int decorateColumn) {
087        this.pattern = pattern;
088        this.testColumn = testColumn;
089        this.highlightColumn = decorateColumn;
090    }
091
092    /**
093     * Instantiates a Predicate with the given Pattern testing against
094     * all columns and highlighting all columns.
095     * 
096     * @param pattern the Pattern to test the cell value against
097     */
098    public PatternPredicate(String pattern) {
099        this(pattern, ALL, ALL);
100    }
101
102    /**
103     * Instantiates a Predicate with the given regex and test
104     * column index in model coordinates. The pattern string is compiled to a 
105     * Pattern with flags 0. A column index of -1 is interpreted
106     * as "all". 
107     * 
108     * @param regex the regex string to test the cell value against
109     * @param testColumn the column index in model coordinates
110     *   of the cell which contains the value
111     *   to test against the pattern 
112     */
113    public PatternPredicate(String regex, int testColumn) {
114        this(regex, testColumn, ALL);
115    }
116
117
118    /**
119     * Instantiates a Predicate with the given regex and test-/decorate
120     * column index in model coordinates. The pattern string is compiled to a 
121     * Pattern with flags 0. A column index of -1 is interpreted
122     * as "all". 
123     * 
124     * @param regex the regex string to test the cell value against
125     * @param testColumn the column index in model coordinates
126     *   of the cell which contains the value
127     *   to test against the pattern 
128     * @param decorateColumn the column index in model coordinates
129     *   of the cell which should be 
130     *   decorated if the test against the value succeeds.
131     */
132    public PatternPredicate(String regex, int testColumn, int decorateColumn) {
133        this(Pattern.compile(regex), testColumn, decorateColumn);
134    }
135
136    /**
137     * 
138     * @inherited <p>
139     * 
140     * Implemented to return true if the match of cell content's String representation
141     * against the Pattern if found and the adapter's view column maps to the 
142     * decorateColumn/s. Otherwise returns false.
143     * 
144     */
145    @Override
146    public boolean isHighlighted(Component renderer, ComponentAdapter adapter) {
147        if (isHighlightCandidate(adapter)) {
148            return test(adapter);
149        }
150        return false;
151    }
152
153    /**
154     * Test the value. This is called only if the 
155     * pre-check returned true, because accessing the 
156     * value might be potentially costly
157     * @param adapter
158     * @return
159     */
160    private boolean test(ComponentAdapter adapter) {
161        // single test column
162        if (testColumn >= 0) return testColumn(adapter, testColumn);
163        // test all
164        for (int column = 0; column < adapter.getColumnCount(); column++) {
165            boolean result = testColumn(adapter, column);
166            if (result) return true;
167        }
168        return false;
169    }
170
171    /**
172     * @param adapter
173     * @param testColumn
174     * @return
175     */
176    private boolean testColumn(ComponentAdapter adapter, int testColumn) {
177        if (!adapter.isTestable(testColumn))
178            return false;
179        String value = adapter.getString(testColumn);
180        
181        if ((value == null) || (value.length() == 0)) {
182            return false;
183        }
184        return pattern.matcher(value).find();
185    }
186
187    /**
188     * A quick pre-check.
189     * @param adapter
190     * 
191     * @return
192     */
193    private boolean isHighlightCandidate(ComponentAdapter adapter) {
194        return (pattern != null) && 
195            ((highlightColumn < 0) ||
196               (highlightColumn == adapter.convertColumnIndexToModel(adapter.column)));
197    }
198
199    /**
200     * 
201     * @return returns the column index to decorate (in model coordinates)
202     */
203    public int getHighlightColumn() {
204        return highlightColumn;
205    }
206
207    /**
208     * 
209     * @return returns the Pattern to test the cell value against
210     */
211    public Pattern getPattern() {
212        return pattern;
213    }
214
215    /**
216     * 
217     * @return the column to use for testing (in model coordinates)
218     */
219    public int getTestColumn() {
220        return testColumn;
221    }
222
223}