package com.onaro.util.jfc.tables.filter.matcher; import java.util.*; public class StringFilterMatcher implements FilterMatcher { private static final char EXACT_CHAR = '#'; /** * Keeps a "lower case" version of the pattern. Used to speed up the pattern matching. */ private char patternChars[]; /** * Tells if the search will be for a substring or for exact match. */ private boolean exactMatch = false; public StringFilterMatcher(String patternStr) { //remove the exact phrase char and mark as exact if (patternStr.length() > 1 && patternStr.charAt(0) == EXACT_CHAR && patternStr.charAt(patternStr.length() - 1) == EXACT_CHAR) { exactMatch = true; patternStr = patternStr.substring(1, patternStr.length() - 1); }else if (patternStr.length() == 0){ //in case of "" - treat as matching only empty strings exactMatch = true; } patternChars = new char[patternStr.length()]; patternStr.toLowerCase().getChars(0, patternChars.length, patternChars, 0); } /** * Tests if a value contains the "filter by" expression (regardless of character case). * * @param value the value to test * @return true if the value matches this filter */ public boolean isAccepted(Object value) { if (value != null) { if (exactMatch) { return equalsIgnoreCase(value.toString(), patternChars); } else { return indexOfIgnoreCase(value.toString(), patternChars) >= 0; } } else { //null values should be displayed if "" is used return exactMatch && patternChars.length == 0; } } /** * A case insensitve exact match search. Used to avoid the much slower {@link String#equalsIgnoreCase(String)}. * * @param str the string to search in * @param pattern the string to search for * @return true if the two strings are the same */ public static boolean equalsIgnoreCase(String str, char pattern[]) { if (str.length() != pattern.length) return false; int patternLength = pattern.length; for (int i = 0; i < patternLength; i++) { char stringCchar = str.charAt(i); char patternChar = pattern[i]; if ((stringCchar != patternChar) && (Character.toLowerCase(stringCchar) != patternChar)) return false; } return true; } /** * A case insensitve search for a substring. Used to avoid the much slower {@link String#toLowerCase()}. * * @param str the string to search in * @param pattern the substring to search for * @return the index where the substring begins within the string, -1 if not found */ public static int indexOfIgnoreCase(String str, char pattern[]) { return indexOfIgnoreCase(str, 0, pattern); } public static int indexOfIgnoreCase(String str, int startFrom,char pattern[]) { int patternLength = pattern.length; int max = str.length() - patternLength; test: for (int i = startFrom; i <= max; i++) { int k = 0, j = i, n = patternLength; char stringCchar, patternChar; while (n-- > 0) { stringCchar = str.charAt(j++); patternChar = pattern[k++]; if ((stringCchar != patternChar) && (Character.toLowerCase(stringCchar) != patternChar)) continue test; } return i; } return -1; } /** * Enable renders to highlight parts of the the filtered value * If the pattern appear more than once, the ranges of all those places will be returned * @param value the filtered value * @return ranges that mathes the filter pattern in the value */ public List getAcceptedRange(Object value) { ArrayList ranges = new ArrayList(); if (value != null) { String valueStr = value.toString(); if (exactMatch) { if(equalsIgnoreCase(valueStr, patternChars)) { ranges.add(new MatchRange(0,valueStr.length())); } } else { int rangeStart; for (int startFrom = 0; (rangeStart = indexOfIgnoreCase(valueStr, startFrom, patternChars)) != -1;) { int rangeEnd = rangeStart + patternChars.length; ranges.add(new MatchRange(rangeStart, rangeEnd)); startFrom = rangeEnd; } } } return ranges; } }