blob: 5631b623c8b1fb2bf3c400b489af3dc271ebea04 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2008 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0, which accompanies this distribution
* and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.utility.internal;
import java.io.Serializable;
import java.util.regex.Pattern;
import org.eclipse.jpt.utility.Filter;
// TODO the regex code is not very fast - we could probably do better,
// hand-coding the matching algorithm (eclipse StringMatcher?)
/**
* This class implements a simple string-matching algorithm that is a little
* more user-friendly than standard regular expressions. Instantiate a
* string matcher with a filter pattern and then you can use the matcher
* to determine whether another string (or object) matches the pattern.
* You can also specify whether the matching should be case-sensitive.
*
* The pattern can contain two "meta-characters":
* '*' will match any set of zero or more characters
* '?' will match any single character
*
* Subclasses can override #prefix() and/or #suffix() to change what
* strings are prepended or appended to the original pattern string.
* This can offer a slight performance improvement over concatenating
* strings before calling #setPatternString(String).
* By default, a '*' is appended to every string.
*
* This class also uses the string-matching algorithm to "filter" objects
* (and, as a result, also implements the Filter interface).
* A string converter is used to determine what string aspect of the
* object is compared to the pattern. By default the string returned
* by the object's #toString() method is passed to the pattern matcher.
*/
public class SimpleStringMatcher<T>
implements StringMatcher, Filter<T>, Serializable
{
/** An adapter that converts the objects into strings to be matched with the pattern. */
private StringConverter<T> stringConverter;
/** The string used to construct the regular expression pattern. */
private String patternString;
/** Whether the matcher ignores case - the default is true. */
private boolean ignoresCase;
/** The regular expression pattern built from the pattern string. */
private Pattern pattern;
/** A list of the meta-characters we need to escape if found in the pattern string. */
public static final char[] REG_EX_META_CHARS = { '(', '[', '{', '\\', '^', '$', '|', ')', '?', '*', '+', '.' };
private static final long serialVersionUID = 1L;
// ********** constructors **********
/**
* Construct a string matcher with an pattern that will match
* any string and ignore case.
*/
public SimpleStringMatcher() {
this("*");
}
/**
* Construct a string matcher with the specified pattern
* that will ignore case.
*/
public SimpleStringMatcher(String patternString) {
this(patternString, true);
}
/**
* Construct a string matcher with the specified pattern that will
* ignore case as specified.
*/
public SimpleStringMatcher(String patternString, boolean ignoresCase) {
super();
this.patternString = patternString;
this.ignoresCase = ignoresCase;
this.initialize();
}
// ********** initialization **********
protected void initialize() {
this.stringConverter = StringConverter.Default.instance();
this.rebuildPattern();
}
/**
* Given the current pattern string and case-sensitivity setting,
* re-build the regular expression pattern.
*/
protected synchronized void rebuildPattern() {
this.pattern = this.buildPattern();
}
/**
* Given the current pattern string and case-sensitivity setting,
* build and return a regular expression pattern that can be used
* to match strings.
*/
protected Pattern buildPattern() {
int patternFlags = 0x0;
if (this.ignoresCase) {
patternFlags = Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE;
}
return Pattern.compile(this.convertToRegEx(this.patternString), patternFlags);
}
// ********** StringMatcher implementation **********
public synchronized void setPatternString(String patternString) {
this.patternString = patternString;
this.rebuildPattern();
}
/**
* Return whether the specified string matches the pattern.
*/
public synchronized boolean matches(String string) {
return this.pattern.matcher(string).matches();
}
// ********** Filter implementation **********
public synchronized boolean accept(T o) {
return this.matches(this.stringConverter.convertToString(o));
}
// ********** accessors **********
/**
* Return the string converter used to convert the objects
* passed to the matcher into strings.
*/
public synchronized StringConverter<T> stringConverter() {
return this.stringConverter;
}
/**
* Set the string converter used to convert the objects
* passed to the matcher into strings.
*/
public synchronized void setStringConverter(StringConverter<T> stringConverter) {
this.stringConverter = stringConverter;
}
/**
* Return the original pattern string.
*/
public synchronized String patternString() {
return this.patternString;
}
/**
* Return whether the matcher ignores case.
*/
public synchronized boolean ignoresCase() {
return this.ignoresCase;
}
/**
* Set whether the matcher ignores case.
*/
public synchronized void setIgnoresCase(boolean ignoresCase) {
this.ignoresCase = ignoresCase;
this.rebuildPattern();
}
/**
* Return the regular expression pattern.
*/
public synchronized Pattern pattern() {
return this.pattern;
}
// ********** other public API **********
/**
* Return the regular expression corresponding to
* the original pattern string.
*/
public synchronized String regularExpression() {
return this.convertToRegEx(this.patternString);
}
// ********** converting **********
/**
* Convert the specified string to a regular expression.
*/
protected String convertToRegEx(String string) {
StringBuffer sb = new StringBuffer(string.length() + 10);
this.convertToRegExOn(this.prefix(), sb);
this.convertToRegExOn(string, sb);
this.convertToRegExOn(this.suffix(), sb);
return sb.toString();
}
/**
* Return any prefix that should be prepended to the original
* string. By default, there is no prefix.
*/
protected String prefix() {
return "";
}
/**
* Return any suffix that should be appended to the original
* string. Since this class is typically used in UI situation where
* the user is typing in a pattern used to filter a list, the default
* suffix is a wildcard character.
*/
protected String suffix() {
return "*";
}
/**
* Convert the specified string to a regular expression.
*/
protected void convertToRegExOn(String string, StringBuffer sb) {
char[] charArray = string.toCharArray();
int length = charArray.length;
for (int i = 0; i < length; i++) {
char c = charArray[i];
// convert user-friendly meta-chars into regex meta-chars
if (c == '*') {
sb.append(".*");
continue;
}
if (c == '?') {
sb.append('.');
continue;
}
// escape regex meta-chars
if (CollectionTools.contains(REG_EX_META_CHARS, c)) {
sb.append('\\');
}
sb.append(c);
}
}
}