001/*
002 * $Id: SearchPredicate.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 for searching. Highlights
028 * the current adapter cell if the value matches the pattern. 
029 * The highlight scope can be limited to a certain column and
030 * row. <p>
031 * 
032 * Note: this differs from PatternPredicate in that it is focused
033 * on the current cell (highlight coordinates == test coordinates)
034 * while the PatternPredicate can have separate test and highlight
035 * coordinates. <p>
036 * 
037 * 
038 * @author Jeanette Winzenburg
039 */
040public class SearchPredicate implements HighlightPredicate {
041    public static final int ALL = -1;
042    public static final String MATCH_ALL = ".*";
043    private int highlightColumn;
044    private int highlightRow; // in view coordinates?
045    private Pattern pattern;
046
047    /**
048     * Instantiates a Predicate with the given Pattern. 
049     * All matching cells are highlighted. 
050     * 
051     * 
052     * @param pattern the Pattern to test the cell value against
053     */
054    public SearchPredicate(Pattern pattern) {
055        this(pattern, ALL, ALL);
056    }
057
058    /**
059     * Instantiates a Predicate with the given Pattern. Highlighting
060     * is limited to matching cells in the given column.
061     * 
062     * @param pattern the Pattern to test the cell value against
063     * @param column the column to limit the highlight to
064     */
065    public SearchPredicate(Pattern pattern, int column) {
066        this(pattern, ALL, column);
067    }
068
069    /**
070     * Instantiates a Predicate with the given Pattern. Highlighting
071     * is limited to matching cells in the given column and row. A
072     * value of -1 indicates all rows/columns. <p>
073     * 
074     * Note: the coordinates are asymmetric - rows are in view- and
075     * column in model-coordinates - due to corresponding methods in
076     * ComponentAdapter. Hmm... no need to? This happens on the
077     * current adapter state which is view always, so could use view
078     * only? 
079     * 
080     * @param pattern the Pattern to test the cell value against
081     * @param row the row index in view coordinates to limit the 
082     *    highlight.
083     * @param column the column in model coordinates 
084     *    to limit the highlight to
085     */
086    public SearchPredicate(Pattern pattern, int row, int column) {
087        this.pattern = pattern;
088        this.highlightColumn = column;
089        this.highlightRow = row;
090    }
091
092    /**
093     * Instantiates a Predicate with a Pattern compiled from the given
094     * regular expression. 
095     * All matching cells are highlighted.
096     * 
097     * @param regex the regular expression to test the cell value against
098     */
099    public SearchPredicate(String regex) {
100        this(regex, ALL, ALL);
101        
102    }
103
104    /**
105     * Instantiates a Predicate with a Pattern compiled from the given
106     * regular expression. Highlighting
107     * is applied to matching cells in all rows, but only in the given column. A
108     * value of ALL indicates all columns. <p>
109     * 
110     * @param regex the regular expression to test the cell value against
111     * @param column the column index in model coordinates to limit the highlight to
112     */
113    public SearchPredicate(String regex, int column) {
114        this(regex, ALL, column);
115    }
116
117    /**
118     * Instantiates a Predicate with a Pattern compiled from the given
119     * regular expression. Highlighting
120     * is limited to matching cells in the given column and row. A
121     * value of ALL indicates all rows/columns. <p>
122     * 
123     * Note: the coordinates are asymmetric - rows are in view- and
124     * column in model-coordinates - due to corresponding methods in
125     * ComponentAdapter. Hmm... no need to? This happens on the
126     * current adapter state which is view always, so could use view
127     * only? 
128     * 
129     * @param regex the Pattern to test the cell value against
130     * @param row the row index in view coordinates to limit the 
131     *    highlight.
132     * @param column the column in model coordinates 
133     *    to limit the highlight to
134     */
135    public SearchPredicate(String regex, int row, int column) {
136        // test against empty string
137        this((regex != null) && (regex.length() > 0) ? 
138                Pattern.compile(regex) : null, row, column);
139    }
140
141    /**
142     * 
143     * @return returns the column index to decorate (in model coordinates)
144     */
145    public int getHighlightColumn() {
146        return highlightColumn;
147    }
148
149    /**
150     * 
151     * @return returns the column index to decorate (in model coordinates)
152     */
153    public int getHighlightRow() {
154        return highlightRow;
155    }
156    
157    /**
158     * 
159     * @return returns the Pattern to test the cell value against
160     */
161    public Pattern getPattern() {
162        return pattern;
163    }
164
165
166    /**
167     * {@inheritDoc}
168     */
169    @Override
170    public boolean isHighlighted(Component renderer, ComponentAdapter adapter) {
171        if (isHighlightCandidate(renderer, adapter)) {
172            return test(renderer, adapter);
173        }
174        return false;
175    }
176
177    /**
178     * Test the value. This is called only if the 
179     * pre-check returned true, because accessing the 
180     * value might be potentially costly
181     * @param renderer
182     * @param adapter
183     * @return
184     */
185    private boolean test(Component renderer, ComponentAdapter adapter) {
186        // PENDING JW: why convert here? we are focused on the adapter's cell
187        // looks like an oversight as of ol' days ;-)
188         int  columnToTest = adapter.convertColumnIndexToModel(adapter.column);
189         String value = adapter.getString(columnToTest);
190         
191         if ((value == null) || (value.length() == 0)) {
192             return false;
193         }
194         return pattern.matcher(value).find();
195     }
196
197    /**
198     * A quick pre-check.
199     * 
200     * @param renderer
201     * @param adapter
202     * @return
203     */
204    private boolean isHighlightCandidate(Component renderer, ComponentAdapter adapter) {
205        if (!isEnabled()) return false;
206        if (highlightRow >= 0 && (adapter.row != highlightRow)) {
207            return false;
208        }
209        return 
210            ((highlightColumn < 0) ||
211               (highlightColumn == adapter.convertColumnIndexToModel(adapter.column)));
212    }
213
214    private boolean isEnabled() {
215        Pattern pattern = getPattern();
216        if (pattern == null || MATCH_ALL.equals(pattern.pattern())) {
217            return false;
218        }
219        return true;
220    }
221
222
223}