alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*******************************************************************************
 * Copyright (c) 2000, 2008 IBM Corporation and others.
 * 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.dialogs;

import org.eclipse.ui.internal.misc.StringMatcher;

/**
 * A search pattern defines how search results are found.
 * 
 * <p>
 * This class is intended to be subclassed by clients. A default behavior is
 * provided for each of the methods above, that clients can override if they
 * wish.
 * </p>
 * 
 * @since 3.3
 */
public class SearchPattern {

	// Rules for pattern matching: (exact, prefix, pattern) [ | case sensitive]
	/**
	 * Match rule: The search pattern matches exactly the search result, that
	 * is, the source of the search result equals the search pattern. Search pattern
	 * should start from lowerCase char.
	 */
	public static final int RULE_EXACT_MATCH = 0;

	/**
	 * Match rule: The search pattern is a prefix of the search result.
	 */
	public static final int RULE_PREFIX_MATCH = 0x0001;

	/**
	 * Match rule: The search pattern contains one or more wild cards ('*' or
	 * '?'). A '*' wild-card can replace 0 or more characters in the search
	 * result. A '?' wild-card replaces exactly 1 character in the search
	 * result.
	 */
	public static final int RULE_PATTERN_MATCH = 0x0002;

	/**
	 * Match rule: The search pattern matches the search result only if cases
	 * are the same. Can be combined to previous rules, e.g.
	 * {@link #RULE_EXACT_MATCH} | {@link #RULE_CASE_SENSITIVE}
	 */
	public static final int RULE_CASE_SENSITIVE = 0x0008;

	/**
	 * Match rule: The search pattern is blank.
	 */
	public static final int RULE_BLANK_MATCH = 0x0020;

	/**
	 * Match rule: The search pattern contains a Camel Case expression. <br>
	 * Examples:
	 * <ul>
	 * <li>NPE type string pattern will match
	 * <code>NullPointerException and
	 * <code>NpPermissionException types,
	 * <li>NuPoEx type string pattern will only match
	 * <code>NullPointerException type.
	 * </ul>
	 * 
	 * 
	 * <br>
	 * Can be combined to {@link #RULE_PREFIX_MATCH} match rule. For example,
	 * when prefix match rule is combined with Camel Case match rule,
	 * <code>"nPE" pattern will match nPException. 
* Match rule {@link #RULE_PATTERN_MATCH} may also be combined but both * rules will not be used simultaneously as they are mutually exclusive. * Used match rule depends on whether string pattern contains specific * pattern characters (e.g. '*' or '?') or not. If it does, then only * Pattern match rule will be used, otherwise only Camel Case match will be * used. For example, with <code>"NPE" string pattern, search will * only use Camel Case match rule, but with <code>N*P*E* string * pattern, it will use only Pattern match rule. * */ public static final int RULE_CAMELCASE_MATCH = 0x0080; private int matchRule; private String stringPattern; private String initialPattern; private StringMatcher stringMatcher; private static final char END_SYMBOL = '<'; private static final char ANY_STRING = '*'; private static final char BLANK = ' '; private int allowedRules; /** * Creates new instance of SearchPattern Default allowedRules for it is * result of belong logic operation: ( RULE_EXACT_MATCH | RULE_PREFIX_MATCH | * RULE_PATTERN_MATCH | RULE_CAMELCASE_MATCH ) * */ public SearchPattern() { this(RULE_EXACT_MATCH | RULE_PREFIX_MATCH | RULE_PATTERN_MATCH | RULE_CAMELCASE_MATCH | RULE_BLANK_MATCH); } /** * Creates a search pattern with the rule to apply for matching index keys. * It can be exact match, prefix match, pattern match or camelCase match. * Rule can also be combined with a case sensitivity flag. * * @param allowedRules * one of {@link #RULE_EXACT_MATCH}, {@link #RULE_PREFIX_MATCH}, * {@link #RULE_PATTERN_MATCH}, {@link #RULE_CASE_SENSITIVE}, * {@link #RULE_CAMELCASE_MATCH} combined with one of following * values: {@link #RULE_EXACT_MATCH}, {@link #RULE_PREFIX_MATCH}, * {@link #RULE_PATTERN_MATCH} or {@link #RULE_CAMELCASE_MATCH}. * e.g. {@link #RULE_EXACT_MATCH} | {@link #RULE_CASE_SENSITIVE} * if an exact and case sensitive match is requested, * {@link #RULE_PREFIX_MATCH} if a prefix non case sensitive * match is requested or {@link #RULE_EXACT_MATCH} if a non case * sensitive and erasure match is requested.<br> * Note also that default behavior for generic types/methods * search is to find exact matches. */ public SearchPattern(int allowedRules) { this.allowedRules = allowedRules; } /** * Gets string pattern used by matcher * * @return pattern */ public String getPattern() { return this.stringPattern; } /** * @param stringPattern * The stringPattern to set. */ public void setPattern(String stringPattern) { this.initialPattern = stringPattern; this.stringPattern = stringPattern; initializePatternAndMatchRule(stringPattern); matchRule = matchRule & this.allowedRules; if (matchRule == RULE_PATTERN_MATCH) { stringMatcher = new StringMatcher(this.stringPattern, true, false); } } /** * Matches text with pattern. matching is determine by matchKind. * * @param text * @return true if search pattern was matched with text false in other way */ public boolean matches(String text) { switch (matchRule) { case RULE_BLANK_MATCH: return true; case RULE_PATTERN_MATCH: return stringMatcher.match(text); case RULE_EXACT_MATCH: return stringPattern.equalsIgnoreCase(text); case RULE_CAMELCASE_MATCH: if (camelCaseMatch(stringPattern, text)) { return true; } default: return startsWithIgnoreCase(text, stringPattern); } } private void initializePatternAndMatchRule(String pattern) { int length = pattern.length(); if (length == 0) { matchRule = RULE_BLANK_MATCH; stringPattern = pattern; return; } char last = pattern.charAt(length - 1); if (pattern.indexOf('*') != -1 || pattern.indexOf('?') != -1) { matchRule = RULE_PATTERN_MATCH; switch (last) { case END_SYMBOL: case BLANK: stringPattern = pattern.substring(0, length - 1); break; case ANY_STRING: stringPattern = pattern; break; default: stringPattern = pattern + ANY_STRING; } return; } if (validateMatchRule(pattern, RULE_CAMELCASE_MATCH) == RULE_CAMELCASE_MATCH) { matchRule = RULE_CAMELCASE_MATCH; stringPattern = pattern; return; } if (last == END_SYMBOL || last == BLANK) { matchRule = RULE_EXACT_MATCH; stringPattern = pattern.substring(0, length - 1); return; } matchRule = RULE_PREFIX_MATCH; stringPattern = pattern; } /** * @param text * @param prefix * @return true if text starts with given prefix, ignoring case false in * other way */ private boolean startsWithIgnoreCase(String text, String prefix) { int textLength = text.length(); int prefixLength = prefix.length(); if (textLength < prefixLength) return false; for (int i = prefixLength - 1; i >= 0; i--) { if (Character.toLowerCase(prefix.charAt(i)) != Character .toLowerCase(text.charAt(i))) return false; } return true; } /** * Answers true if the pattern matches the given name using CamelCase rules, * or false otherwise. CamelCase matching does NOT accept explicit * wild-cards '*' and '?' and is inherently case sensitive. <br> * CamelCase denotes the convention of writing compound names without * spaces, and capitalizing every term. This function recognizes both upper * and lower CamelCase, depending whether the leading character is * capitalized or not. The leading part of an upper CamelCase pattern is * assumed to contain a sequence of capitals which are appearing in the * matching name; e.g. 'NPE' will match 'NullPointerException', but not * 'NewPerfData'. A lower CamelCase pattern uses a lowercase first * character. In Java, type names follow the upper CamelCase convention, * whereas method or field names follow the lower CamelCase convention. <br> * The pattern may contain lowercase characters, which will be match in a * case sensitive way. These characters must appear in sequence in the name. * For instance, 'NPExcep' will match 'NullPointerException', but not * 'NullPointerExCEPTION' or 'NuPoEx' will match 'NullPointerException', but * not 'NoPointerException'. <br> * <br> * Examples: * <ol> * <li> * * <pre> * pattern = "NPE" * name = NullPointerException / NoPermissionException * result => true * </pre> * * </li> * <li> * * <pre> * pattern = "NuPoEx" * name = NullPointerException * result => true * </pre> * * </li> * <li> * * <pre> * pattern = "npe" * name = NullPointerException * result => false * </pre> * * </li> * </ol> * * @param pattern * the given pattern * @param name * the given name * @return true if the pattern matches the given name, false otherwise * */ private boolean camelCaseMatch(String pattern, String name) { if (pattern == null) return true; // null pattern is equivalent to '*' if (name == null) return false; // null name cannot match return camelCaseMatch(pattern, 0, pattern.length(), name, 0, name .length()); } /** * Answers true if a sub-pattern matches the subpart of the given name using * CamelCase rules, or false otherwise. CamelCase matching does NOT accept * explicit wild-cards '*' and '?' and is inherently case sensitive. Can * match only subset of name/pattern, considering end positions as * non-inclusive. The subpattern is defined by the patternStart and * patternEnd positions. <br> * CamelCase denotes the convention of writing compound names without * spaces, and capitalizing every term. This function recognizes both upper * and lower CamelCase, depending whether the leading character is * capitalized or not. The leading part of an upper CamelCase pattern is * assumed to contain a sequence of capitals which are appearing in the * matching name; e.g. 'NPE' will match 'NullPointerException', but not * 'NewPerfData'. A lower CamelCase pattern uses a lowercase first * character. In Java, type names follow the upper CamelCase convention, * whereas method or field names follow the lower CamelCase convention. <br> * The pattern may contain lowercase characters, which will be match in a * case sensitive way. These characters must appear in sequence in the name. * For instance, 'NPExcep' will match 'NullPointerException', but not * 'NullPointerExCEPTION' or 'NuPoEx' will match 'NullPointerException', but * not 'NoPointerException'. <br> * <br> * Examples: * <ol> * <li> * * <pre> * pattern = "NPE" * patternStart = 0 * patternEnd = 3 * name = NullPointerException * nameStart = 0 * nameEnd = 20 * result => true * </pre> * * </li> * <li> * * <pre> * pattern = "NPE" * patternStart = 0 * patternEnd = 3 * name = NoPermissionException * nameStart = 0 * nameEnd = 21 * result => true * </pre> * * </li> * <li> * * <pre> * pattern = "NuPoEx" * patternStart = 0 * patternEnd = 6 * name = NullPointerException * nameStart = 0 * nameEnd = 20 * result => true * </pre> * * </li> * <li> * * <pre> * pattern = "NuPoEx" * patternStart = 0 * patternEnd = 6 * name = NoPermissionException * nameStart = 0 * nameEnd = 21 * result => false * </pre> * * </li> * <li> * * <pre> * pattern = "npe" * patternStart = 0 * patternEnd = 3 * name = NullPointerException * nameStart = 0 * nameEnd = 20 * result => false * </pre> * * </li> * </ol> * * @param pattern * the given pattern * @param patternStart * the start index of the pattern, inclusive * @param patternEnd * the end index of the pattern, exclusive * @param name * the given name * @param nameStart * the start index of the name, inclusive * @param nameEnd * the end index of the name, exclusive * @return true if a sub-pattern matches the subpart of the given name, * false otherwise */ private boolean camelCaseMatch(String pattern, int patternStart, int patternEnd, String name, int nameStart, int nameEnd) { if (name == null) return false; // null name cannot match if (pattern == null) return true; // null pattern is equivalent to '*' if (patternEnd < 0) patternEnd = pattern.length(); if (nameEnd < 0) nameEnd = name.length(); if (patternEnd <= patternStart) return nameEnd <= nameStart; if (nameEnd <= nameStart) return false; // check first pattern char if (name.charAt(nameStart) != pattern.charAt(patternStart)) { // first char must strictly match (upper/lower) return false; } int patternLength = patternEnd; if (pattern.charAt(patternEnd - 1) == END_SYMBOL || pattern.charAt(patternEnd - 1) == BLANK ) patternLength = patternEnd - 1; char patternChar, nameChar; int iPattern = patternStart; int iName = nameStart; // Main loop is on pattern characters while (true) { iPattern++; iName++; if (iPattern == patternEnd) { // We have exhausted pattern, so it's a match return true; } if (iName == nameEnd) { if (iPattern == patternLength) return true; // We have exhausted name (and not pattern), so it's not a match return false; } // For as long as we're exactly matching, bring it on (even if it's // a lower case character) if ((patternChar = pattern.charAt(iPattern)) == name.charAt(iName)) { continue; } // If characters are not equals, then it's not a match if // patternChar is lowercase if (!isPatternCharAllowed(patternChar)) return false; // patternChar is uppercase, so let's find the next uppercase in // name while (true) { if (iName == nameEnd) { if ((iPattern == patternLength) && (patternChar == END_SYMBOL || patternChar == BLANK)) return true; return false; } nameChar = name.charAt(iName); if ((iPattern == patternLength) && (patternChar == END_SYMBOL || patternChar == BLANK)) { if (isNameCharAllowed(nameChar)) { return false; } iName++; continue; } if (!isNameCharAllowed(nameChar)) { // nameChar is lowercase iName++; // nameChar is uppercase... } else if (patternChar != nameChar) { // .. and it does not match patternChar, so it's not a match return false; } else { // .. and it matched patternChar. Back to the big loop break; } } // At this point, either name has been exhausted, or it is at an // uppercase letter. // Since pattern is also at an uppercase letter } } /** * Checks pattern's character is allowed for specified set. It could be * override if you want change logic of camelCaseMatch methods. * * @param patternChar * @return true if patternChar is in set of allowed characters for pattern */ protected boolean isPatternCharAllowed(char patternChar) { return Character.isUpperCase(patternChar) || patternChar == END_SYMBOL || patternChar == BLANK; } /** * Checks character of element's name is allowed for specified set. It could * be override if you want change logic of camelCaseMatch methods. * * @param nameChar - * name of searched element * @return if nameChar is in set of allowed characters for name of element */ protected boolean isNameCharAllowed(char nameChar) { return Character.isUpperCase(nameChar); } /** * Returns the rule to apply for matching keys. Can be exact match, prefix * match, pattern match or camelcase match. Rule can also be combined with a * case sensitivity flag. * * @return one of RULE_EXACT_MATCH, RULE_PREFIX_MATCH, RULE_PATTERN_MATCH, * RULE_CAMELCASE_MATCH, combined with RULE_CASE_SENSITIVE, e.g. * RULE_EXACT_MATCH | RULE_CASE_SENSITIVE if an exact and case * sensitive match is requested, or RULE_PREFIX_MATCH if a prefix * non case sensitive match is requested. */ public final int getMatchRule() { return this.matchRule; } /** * Validate compatibility between given string pattern and match rule. <br> * Optimized (ie. returned match rule is modified) combinations are: * <ul> * <li>{@link #RULE_PATTERN_MATCH} without any '*' or '?' in string * pattern: pattern match bit is unset, </li> * <li>{@link #RULE_PATTERN_MATCH} and {@link #RULE_PREFIX_MATCH} bits * simultaneously set: prefix match bit is unset, </li> * <li>{@link #RULE_PATTERN_MATCH} and {@link #RULE_CAMELCASE_MATCH} bits * simultaneously set: camel case match bit is unset, </li> * <li>{@link #RULE_CAMELCASE_MATCH} with invalid combination of uppercase * and lowercase characters: camel case match bit is unset and replaced with * prefix match pattern, </li> * <li>{@link #RULE_CAMELCASE_MATCH} combined with * {@link #RULE_PREFIX_MATCH} and {@link #RULE_CASE_SENSITIVE} bits is * reduced to only {@link #RULE_CAMELCASE_MATCH} as Camel Case search is * already prefix and case sensitive, </li> * </ul> * <br> * Rejected (ie. returned match rule -1) combinations are: * <ul> * <li>{@link #RULE_REGEXP_MATCH} with any other match mode bit set, * </ul> * * @param stringPattern * The string pattern * @param matchRule * The match rule * @return Optimized valid match rule or -1 if an incompatibility was * detected. */ private int validateMatchRule(String stringPattern, int matchRule) { // Verify Pattern match rule int starIndex = stringPattern.indexOf('*'); int questionIndex = stringPattern.indexOf('?'); if (starIndex < 0 && questionIndex < 0) { // reset pattern match bit if any matchRule &= ~RULE_PATTERN_MATCH; } else { // force Pattern rule matchRule |= RULE_PATTERN_MATCH; } if ((matchRule & RULE_PATTERN_MATCH) != 0) { // remove Camel Case and Prefix match bits if any matchRule &= ~RULE_CAMELCASE_MATCH; matchRule &= ~RULE_PREFIX_MATCH; } // Verify Camel Case match rule if ((matchRule & RULE_CAMELCASE_MATCH) != 0) { // Verify sting pattern validity int length = stringPattern.length(); boolean validCamelCase = true; for (int i = 0; i < length && validCamelCase; i++) { char ch = stringPattern.charAt(i); validCamelCase = isValidCamelCaseChar(ch); } validCamelCase = validCamelCase && Character.isUpperCase(stringPattern.charAt(0)); // Verify bits compatibility if (validCamelCase) { if ((matchRule & RULE_PREFIX_MATCH) != 0) { if ((matchRule & RULE_CASE_SENSITIVE) != 0) { // This is equivalent to Camel Case match rule matchRule &= ~RULE_PREFIX_MATCH; matchRule &= ~RULE_CASE_SENSITIVE; } } } else { matchRule &= ~RULE_CAMELCASE_MATCH; if ((matchRule & RULE_PREFIX_MATCH) == 0) { matchRule |= RULE_PREFIX_MATCH; matchRule |= RULE_CASE_SENSITIVE; } } } return matchRule; } /** * Check if character is valid camelCase character * * @param ch * character to be validated * @return true if character is valid */ protected boolean isValidCamelCaseChar(char ch) { return true; } /** * Tells whether the given <code>SearchPattern equals this pattern. * * @param pattern * pattern to be checked * @return true if the given pattern equals this search pattern */ public boolean equalsPattern(SearchPattern pattern) { return trimWildcardCharacters(pattern.initialPattern).equals( trimWildcardCharacters(this.initialPattern)); } /** * Tells whether the given <code>SearchPattern is a sub-pattern of * this pattern. * <p> * <i>WARNING: This method is not defined in reading order, i.e. * <code>a.isSubPattern(b) is true iff * <code>b is a sub-pattern of a, and not vice-versa. * </i> * </p> * * @param pattern * pattern to be checked * @return true if the given pattern is a sub pattern of this search pattern */ public boolean isSubPattern(SearchPattern pattern) { return trimWildcardCharacters(pattern.initialPattern).startsWith( trimWildcardCharacters(this.initialPattern)); } /** * Trims sequences of '*' characters * * @param pattern * string to be trimmed * @return trimmed pattern */ private String trimWildcardCharacters(String pattern) { return pattern.replaceAll("\\*+", "\\*"); //$NON-NLS-1$ //$NON-NLS-2$ } } }
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.