package com.onaro.util.jfc.search; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.swing.JTable; import javax.swing.table.TableColumnModel; import com.jidesoft.swing.TableSearchable; /** * Searches through tables comparing values of all the columns. * At least one of the values in a row must match the search text in order for a row to be selected. * The matching is case-insensitive and cyclic (will loop to the begining after the first match found) * Wild Char is supported: include ? to replace any char and * for multiple. * Note: this class uses regular expression for implementation only. regexp patterns are not supported */ public class AllColumnsTableSearchable extends TableSearchable { private static String REGEXP_SPECIAL_CHARS = ".+^$\\|[](){}"; //$NON-NLS-1$ /** * ignore case and new lines */ private static final int REGEXP_PATTERN_FLAGS = Pattern.CASE_INSENSITIVE | Pattern.DOTALL; private static final int SEARCH_DELAY = 500; private JTable table; private Pattern lastPattern; private String lastSearchingText; public AllColumnsTableSearchable(JTable table) { super(table); this.table = table; setRepeats(true); setSearchingDelay(SEARCH_DELAY); } protected Object getElementAt(int i) { return Integer.valueOf(i); } /** * Return true when the value in one of the cells matches the search text * @param element * @param searchingText * @return true if the value in one of the cells matches the search text */ protected boolean compare(Object element, String searchingText) { assert element instanceof Integer; int row = ((Integer) element).intValue(); TableColumnModel columnModel = table.getColumnModel(); int numOfColumns = columnModel.getColumnCount(); Pattern pattern = getPattern(searchingText); if (pattern != null) { for (int i = 0; i < numOfColumns; i++) { //search only on visible columns if(columnModel.getColumn(i).getWidth() > 0) { Object valueAt = table.getValueAt(row, i); if (valueAt != null) { String text = String.valueOf(valueAt); if(pattern.matcher(text).matches()){ //if at least one of the cell matches, return true return true; } } } } }else{ //illegal pattern, fail the match } return false; } /** * Returns a regular expression pattern based on the text given by the user * This method converts the ? and * wild char characters to the regexp equivalent, and escapes any other regexp special char (. is the most common) * For optimization purposes the last pattern is cached. * @param searchingText text as written by the user * @return regular expression pattern based on the searchingText */ private Pattern getPattern(String searchingText) { try { //if the pattern is not cached, create a new one if (!searchingText.equals(lastSearchingText)) { lastPattern = Pattern.compile(".*" + encodeSearchString(searchingText) + ".*", REGEXP_PATTERN_FLAGS); //$NON-NLS-1$ //$NON-NLS-2$ lastSearchingText = searchingText; } return lastPattern; } catch (PatternSyntaxException e) { //if the pattern compilation fails, return null and compare() will return false return null; } } /** * Converts the search string given by the user to regular expression by: * converting the ? and * wild char characters to the regexp equivalent(. and .*), and escapes any other regexp special char (. is the most common) * @param searchingText search string given by the user * @return a regular expression string pattern */ private static String encodeSearchString(String searchingText) { int len = searchingText.length(); StringBuilder builder = new StringBuilder(searchingText); for (int i = len - 1; i >= 0; i--) { char c = builder.charAt(i); if(shouldEscape(c)) { builder.insert(i, '\\'); }else if(c == '?') { builder.replace(i, i, "."); //$NON-NLS-1$ }else if(c == '*') { builder.insert(i, '.'); } } // Trim the search string return builder.toString().trim(); } /** * Checks if the char is one of the regexp chars that are not supported in whild chars */ private static boolean shouldEscape(char c) { char[] specials = REGEXP_SPECIAL_CHARS.toCharArray(); for (int i = 0; i < specials.length; i++) { if(specials[i] == c) { return true; } } return false; } }