package railo.commons.io.res.util; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; /** * a WildcardPattern that accepts a comma- (or semi-colon-) separated value of patterns, e.g. "*.gif, *.jpg, *.jpeg, *.png" * and an optional isExclude boolean value which negates the results of the default implementation * * also, lines 31 - 35 allow to set isExclude to true by passing a pattern whose first character is an exclamation point '!' * * @author Igal */ public class WildcardPattern { private final String pattern; private final boolean isInclude; private final List<ParsedPattern> patterns; /** * * @param pattern - the wildcard pattern, or a comma/semi-colon separated value of wildcard patterns * @param isCaseSensitive - if true, does a case-sensitive matching * @param isExclude - if true, the filter becomes an Exclude filter so that only items that do not match the pattern are accepted */ public WildcardPattern( String pattern, boolean isCaseSensitive, boolean isExclude ) { if ( pattern.charAt( 0 ) == '!' ) { // set isExclude to true if the first char of pattern is an exclamation point '!' pattern = pattern.substring( 1 ); isExclude = true; } this.pattern = pattern; this.isInclude = !isExclude; StringTokenizer tokenizer = new StringTokenizer( pattern, ",;|" ); patterns = new ArrayList<ParsedPattern>(); while ( tokenizer.hasMoreTokens() ) { String token = tokenizer.nextToken().trim(); if ( !token.isEmpty() ) patterns.add( new ParsedPattern( token, isCaseSensitive ) ); } } /** calls this( pattern, isCaseSensitive, false ); */ public WildcardPattern( String pattern, boolean isCaseSensitive ) { this( pattern, isCaseSensitive, false ); } public boolean isMatch( String input ) { for ( ParsedPattern pp : this.patterns ) { boolean match = pp.isMatch( input ); if ( match ) return isInclude; } return !isInclude; } public String toString() { return "WildcardPattern: " + pattern; } public static class ParsedPattern { public final static String MATCH_ANY = "*"; public final static String MATCH_ONE = "?"; private List<String> parts; private final boolean isCaseSensitive; private final boolean isLastPartMatchAny; public ParsedPattern( String pattern, boolean isCaseSensitive ) { this.isCaseSensitive = isCaseSensitive; if ( !isCaseSensitive ) pattern = pattern.toLowerCase(); parts = new ArrayList<String>(); int len = pattern.length(); int subStart = 0; for ( int i=subStart; i<len; i++ ) { char c = pattern.charAt( i ); if ( c == '*' || c == '?' ) { if ( i > subStart ) parts.add( pattern.substring( subStart, i ) ); parts.add( c == '*' ? MATCH_ANY : MATCH_ONE ); subStart = i + 1; } } if ( len > subStart ) { parts.add( pattern.substring( subStart ) ); } isLastPartMatchAny = ( parts.get( parts.size() - 1 ) == MATCH_ANY ); } /** calls this( pattern, false, false ); */ public ParsedPattern( String pattern ) { this( pattern, false ); } /** tests if the input string matches the pattern */ public boolean isMatch( String input ) { if ( !isCaseSensitive ) input = input.toLowerCase(); int pos = 0; int len = input.length(); boolean doMatchAny = false; for ( String part : parts ) { if ( part == MATCH_ANY ) { doMatchAny = true; continue; } if ( part == MATCH_ONE ) { doMatchAny = false; pos++; continue; } int ix = input.indexOf( part, pos ); if ( ix == -1 ) return false; if ( !doMatchAny && ix != pos ) return false; pos = ix + part.length(); doMatchAny = false; } if ( !isLastPartMatchAny && ( len != pos ) ) // if pattern doesn't end with * then we shouldn't have any more characters in input return false; return true; } @Override public String toString() { StringBuilder sb = new StringBuilder(); for ( String s : parts ) { sb.append( s ); sb.append( ':' ); } if ( sb.length() > 0 ) sb.setLength( sb.length() - 1 ); return "[" + sb.toString() + "]"; } } }