// =========================================================================== // CONTENT : CLASS StringPattern // AUTHOR : Manfred Duchrow // VERSION : 1.7 - 13/02/2003 // HISTORY : // 24/01/2000 duma CREATED // 08/01/2002 duma bugfix -> Handle *xxx (equal characters after star) correctly // 16/01/2002 duma changed -> Implements Serializable // 06/07/2002 duma bugfix -> Couldn't match "London" on "L*n" // 19/09/2002 duma bugfix -> Couldn't match "MA_DR_HRBLUB" on "*_HR*" // 19/09/2002 duma changed -> Using now StringExaminer instead of CharacterIterator // 29/09/2002 duma changed -> Refactored: Using StringExaminer instead of StringScanner // 26/12/2002 duma changed -> Comment of matches() was wrong / new hasWildcard() // 13/02/2003 duma added -> setDigitWildcardChar() // // Copyright (c) 2000-2003, by Manfred Duchrow. All rights reserved. // =========================================================================== package org.pf.text; // =========================================================================== // IMPORTS // =========================================================================== import java.io.Serializable ; /** * This class provides services for checking strings against string-patterns. * Currently it supports the wildcards<br> * '*' for any number of any character and <br> * '?' for any one character. * * The API is very simple:<br> * <br> * There are only the two class methods <i>match()</i> and <i>matchIgnoreCase()</i>. * <br> * Example: * <br> * StringPattern.match( 'Hello World", "H* W*" ) ; --> evaluates to true <br> * StringPattern.matchIgnoreCase( 'StringPattern", "str???pat*" ) ; --> evaluates to true <br> * * * @author Manfred Duchrow * @version 1.7 */ public class StringPattern implements Serializable { // ========================================================================= // CONSTANTS // ========================================================================= protected final static String MULTI_WILDCARD = "*" ; protected final static char MULTICHAR_WILDCARD = '*' ; protected final static char SINGLECHAR_WILDCARD = '?' ; // ========================================================================= // INSTANCE VARIABLES // ========================================================================= private boolean ignoreCase = false ; /** * Returns whether or not the pattern matching ignores upper and lower case */ public boolean getIgnoreCase() { return ignoreCase ; } /** * Sets whether the pattern matching should ignore case or not */ public void setIgnoreCase( boolean newValue ) { ignoreCase = newValue ; } private String pattern = null ; /** * Returns the pattern as string. */ public String getPattern() { return pattern ; } /** * Sets the pattern to a new value */ public void setPattern( String newValue ) { pattern = newValue ; } // ------------------------------------------------------------------------- private Character digitWildcard = null ; protected Character digitWildcard() { return digitWildcard ; } protected void digitWildcard( Character newValue ) { digitWildcard = newValue ; } // ========================================================================= // CLASS METHODS // ========================================================================= /** * Returns true, if the given probe string matches the given pattern. <br> * The character comparison is done case sensitive. * * @param probe The string to check against the pattern. * @param pattern The patter, that probably contains wildcards ( '*' or '?' ) */ public static boolean match( String probe, String pattern ) { StringPattern stringPattern = new StringPattern( pattern, false ) ; return ( stringPattern.matches( probe ) ) ; } // match() // ------------------------------------------------------------------------- /** * Returns true, if the given probe string matches the given pattern. <br> * The character comparison is done ignoring upper/lower-case. * * @param probe The string to check against the pattern. * @param pattern The patter, that probably contains wildcards ( '*' or '?' ) */ public static boolean matchIgnoreCase( String probe, String pattern ) { StringPattern stringPattern = new StringPattern( pattern, true ) ; return ( stringPattern.matches( probe ) ) ; } // matchIgnoreCase() // ------------------------------------------------------------------------- // ========================================================================= // CONSTRUCTORS // ========================================================================= /** * Initializes the new instance with the string pattern and the selecteion, * if case should be ignored when comparing characters. * * @param pattern The pattern to check against ( May contain '*' and '?' wildcards ) * @param ignoreCase Definition, if case sensitive character comparison or not. */ public StringPattern( String pattern, boolean ignoreCase ) { this.setPattern( pattern ) ; this.setIgnoreCase( ignoreCase ) ; } // StringPattern() // ------------------------------------------------------------------------- /** * Initializes the new instance with the string pattern. * The default is case sensitive checking. * * @param pattern The pattern to check against ( May contain '*' and '?' wildcards ) */ public StringPattern( String pattern ) { this( pattern, false) ; } // StringPattern() // ------------------------------------------------------------------------- /** * Initializes the new instance with the string pattern and a digit wildcard * character. * The default is case sensitive checking. * * @param pattern The pattern to check against ( May contain '*', '?' wildcards and the digit wildcard ) * @param digitWildcard A wildcard character that stands as placeholder for digits */ public StringPattern( String pattern, char digitWildcard ) { this( pattern, false, digitWildcard ) ; } // StringPattern() // ------------------------------------------------------------------------- /** * Initializes the new instance with the string pattern and the selecteion, * if case should be ignored when comparing characters plus a wildcard * character for digits. * * @param pattern The pattern to check against ( May contain '*' and '?' wildcards ) * @param ignoreCase Definition, if case sensitive character comparison or not. * @param digitWildcard A wildcard character that stands as placeholder for digits */ public StringPattern( String pattern, boolean ignoreCase, char digitWildcard ) { this.setPattern( pattern ) ; this.setIgnoreCase( ignoreCase ) ; this.setDigitWildcardChar( digitWildcard ) ; } // StringPattern() // ------------------------------------------------------------------------- // ========================================================================= // PUBLIC INSTANCE METHODS // ========================================================================= /** * Tests if a specified string matches the pattern. * * @param probe The string to compare to the pattern * @return true if and only if the probe matches the pattern, false otherwise. */ public boolean matches( String probe ) { StringExaminer patternIterator = null ; StringExaminer probeIterator = null ; char patternCh = '-' ; char probeCh = '-' ; String newPattern = null ; String subPattern = null ; int charIndex = 0 ; if ( probe == null ) return false ; if ( probe.length() == 0 ) return false ; patternIterator = this.newExaminer( this.getPattern() ) ; probeIterator = this.newExaminer( probe ) ; probeCh = probeIterator.nextChar() ; patternCh = this.getPatternChar( patternIterator, probeCh ) ; while ( ( this.endNotReached( patternCh ) ) && ( this.endNotReached( probeCh ) ) ) { if ( patternCh == MULTICHAR_WILDCARD ) { patternCh = this.skipWildcards( patternIterator ) ; if ( this.endReached( patternCh ) ) { return true ; // No more characters after multi wildcard - So everything matches } else { patternIterator.skip(-1) ; newPattern = this.upToEnd( patternIterator ) ; charIndex = newPattern.indexOf( MULTICHAR_WILDCARD ) ; if ( charIndex >= 0 ) { subPattern = newPattern.substring( 0, charIndex ) ; if ( this.skipAfter( probeIterator, subPattern ) ) { patternIterator = this.newExaminer( newPattern.substring( charIndex ) ) ; patternCh = probeCh ; } else { return false ; } } else { probeIterator.skip(-1) ; return this.matchReverse( newPattern, probeIterator ) ; } } } if ( this.charsAreEqual( probeCh, patternCh ) ) { if ( this.endNotReached(patternCh) ) { probeCh = probeIterator.nextChar() ; patternCh = this.getPatternChar( patternIterator, probeCh ) ; } } else { if ( patternCh != MULTICHAR_WILDCARD ) return false ; // character is not matching - return immediately } } // while() return ( ( this.endReached( patternCh ) ) && ( this.endReached( probeCh ) ) ) ; } // matches() // ------------------------------------------------------------------------- /** * Returns the pattern string. * * @see java.lang.Object#toString() */ public String toString() { if ( this.getPattern() == null ) return super.toString() ; else return this.getPattern() ; } // toString() // ------------------------------------------------------------------------- /** * Returns true if the pattern contains any '*' or '?' wildcard character. */ public boolean hasWildcard() { if ( this.getPattern() == null ) return false ; if ( this.hasDigitWildcard() ) { if ( this.getPattern().indexOf( this.digitWildcardChar() ) >= 0 ) return true ; } return ( this.getPattern().indexOf( MULTI_WILDCARD ) >= 0 ) || ( this.getPattern().indexOf( SINGLECHAR_WILDCARD ) >= 0 ) ; } // hasWildcard() // ------------------------------------------------------------------------- /** * Sets the given character as a wildcard character in this pattern to * match only digits ('0'-'9'). <br> * * @param digitWildcard The placeholder character for digits */ public void setDigitWildcardChar( char digitWildcard ) { if ( digitWildcard <= 0 ) { this.digitWildcard( null ) ; } else { this.digitWildcard( new Character( digitWildcard ) ) ; } } // setDigitWildcardChar() // ------------------------------------------------------------------------- // ========================================================================= // PROTECTED INSTANCE METHODS // ========================================================================= protected boolean hasDigitWildcard() { return this.digitWildcard() != null ; } // hasDigitWildcard() // ------------------------------------------------------------------------- protected char digitWildcardChar() { if ( this.hasDigitWildcard() ) return this.digitWildcard().charValue() ; else return '\0' ; } // digitWildcardChar() // ------------------------------------------------------------------------- /** * Moves the iterator position to the next character that is no wildcard. * Doesn't skip digit wildcards ! */ protected char skipWildcards( StringExaminer iterator ) { char result = '-' ; do { result = iterator.nextChar() ; } while ( ( result == MULTICHAR_WILDCARD ) || ( result == SINGLECHAR_WILDCARD ) ) ; return result ; } // skipWildcards() // ------------------------------------------------------------------------- /** * Increments the given iterator up to the last character that matched * the character sequence in the given matchString. * Returns true, if the matchString was found, otherwise false. * * @param matchString The string to be found (must not contain *) */ protected boolean skipAfter( StringExaminer examiner, String matchString ) { // Do not use the method of StringExaminer anymore, because digit wildcard // support is in the charsAreEqual() method which is unknown to the examiner. // return examiner.skipAfter( matchString ) ; char ch = '-' ; char matchChar = ' ' ; boolean found = false ; int index = 0 ; if ( ( matchString == null ) || ( matchString.length() == 0 ) ) return false ; ch = examiner.nextChar() ; while ( ( examiner.endNotReached( ch ) ) && ( ! found ) ) { matchChar = matchString.charAt( index ) ; if ( this.charsAreEqual( ch, matchChar ) ) { index++ ; if ( index >= matchString.length() ) // whole matchString checked ? { found = true ; } else { ch = examiner.nextChar() ; } } else { if ( index == 0 ) { ch = examiner.nextChar() ; } else { index = 0 ; } } } return found ; } // skipAfter() // ------------------------------------------------------------------------- protected String upToEnd( StringExaminer iterator ) { return iterator.upToEnd() ; } // upToEnd() // ------------------------------------------------------------------------- protected boolean matchReverse( String pattern, StringExaminer probeIterator ) { String newPattern ; String newProbe ; StringPattern newMatcher ; newPattern = MULTI_WILDCARD + pattern ; newProbe = this.upToEnd( probeIterator ) ; newPattern = this.strUtil().reverse( newPattern ) ; newProbe = this.strUtil().reverse( newProbe ) ; newMatcher = new StringPattern( newPattern, this.getIgnoreCase() ) ; if ( this.hasDigitWildcard() ) newMatcher.setDigitWildcardChar( this.digitWildcardChar() ) ; return newMatcher.matches( newProbe ) ; } // matchReverse() // ------------------------------------------------------------------------- protected boolean charsAreEqual( char probeChar, char patternChar ) { if ( this.hasDigitWildcard() ) { if ( patternChar == this.digitWildcardChar() ) { return Character.isDigit( probeChar ) ; } } if ( this.getIgnoreCase() ) { return ( Character.toUpperCase(probeChar) == Character.toUpperCase( patternChar ) ) ; } else { return ( probeChar == patternChar ) ; } } // charsAreEqual() // ------------------------------------------------------------------------- protected boolean endReached( char character ) { return ( character == StringExaminer.END_REACHED ) ; } // endReached() // ------------------------------------------------------------------------- protected boolean endNotReached( char character ) { return ( ! endReached( character ) ) ; } // endNotReached() // ------------------------------------------------------------------------- protected char getPatternChar( StringExaminer patternIterator , char probeCh ) { char patternCh ; patternCh = patternIterator.nextChar() ; return ( ( patternCh == SINGLECHAR_WILDCARD ) ? probeCh : patternCh ) ; } // getPatternChar() // ------------------------------------------------------------------------- protected StringExaminer newExaminer( String str ) { return new StringExaminer( str, this.getIgnoreCase() ) ; } // newExaminer() // ------------------------------------------------------------------------- protected StringUtil strUtil() { return StringUtil.current() ; } // strUtil() // ------------------------------------------------------------------------- } // class StringPattern