Skip to main content
aboutsummaryrefslogblamecommitdiffstats
blob: 5f85f7895c45e061e067af329424381ac27933db (plain) (tree)


































































































































































































































































                                                                                                                       
/*******************************************************************************
 * Copyright (c) 2007, 2009 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.common.utility.internal;

import java.io.Serializable;
import java.util.regex.Pattern;
import org.eclipse.jpt.common.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("*"); //$NON-NLS-1$
	}

	/**
	 * 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 ""; //$NON-NLS-1$
	}

	/**
	 * 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 "*"; //$NON-NLS-1$
	}

	/**
	 * 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(".*"); //$NON-NLS-1$
				continue;
			}
			if (c == '?') {
				sb.append('.');
				continue;
			}
			// escape regex meta-chars
			if (ArrayTools.contains(REG_EX_META_CHARS, c)) {
				sb.append('\\');
			}
			sb.append(c);
		}
	}

}

Back to the top