// ===========================================================================
// 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