001 package railo.commons.io.res.util; 002 003 import java.util.ArrayList; 004 import java.util.List; 005 import java.util.StringTokenizer; 006 007 008 /** 009 * a WildcardPattern that accepts a comma- (or semi-colon-) separated value of patterns, e.g. "*.gif, *.jpg, *.jpeg, *.png" 010 * and an optional isExclude boolean value which negates the results of the default implementation 011 * 012 * also, lines 31 - 35 allow to set isExclude to true by passing a pattern whose first character is an exclamation point '!' 013 * 014 * @author Igal 015 */ 016 public class WildcardPattern { 017 018 private final String pattern; 019 private final boolean isInclude; 020 021 private final List<ParsedPattern> patterns; 022 023 /** 024 * 025 * @param pattern - the wildcard pattern, or a comma/semi-colon separated value of wildcard patterns 026 * @param isCaseSensitive - if true, does a case-sensitive matching 027 * @param isExclude - if true, the filter becomes an Exclude filter so that only items that do not match the pattern are accepted 028 */ 029 public WildcardPattern( String pattern, boolean isCaseSensitive, boolean isExclude ) { 030 031 if ( pattern.charAt( 0 ) == '!' ) { // set isExclude to true if the first char of pattern is an exclamation point '!' 032 033 pattern = pattern.substring( 1 ); 034 isExclude = true; 035 } 036 037 this.pattern = pattern; 038 this.isInclude = !isExclude; 039 040 StringTokenizer tokenizer = new StringTokenizer( pattern, ",;" ); 041 042 patterns = new ArrayList<ParsedPattern>(); 043 044 while ( tokenizer.hasMoreTokens() ) { 045 046 String token = tokenizer.nextToken().trim(); 047 048 if ( !token.isEmpty() ) 049 patterns.add( new ParsedPattern( token, isCaseSensitive ) ); 050 } 051 } 052 053 054 /** calls this( pattern, isCaseSensitive, false ); */ 055 public WildcardPattern( String pattern, boolean isCaseSensitive ) { 056 057 this( pattern, isCaseSensitive, false ); 058 } 059 060 061 public boolean isMatch( String input ) { 062 063 for ( ParsedPattern pp : this.patterns ) { 064 065 boolean match = pp.isMatch( input ); 066 067 if ( match ) 068 return isInclude; 069 } 070 071 return !isInclude; 072 } 073 074 075 public String toString() { 076 077 return "WildcardPattern: " + pattern; 078 } 079 080 public static class ParsedPattern { 081 082 public final static String MATCH_ANY = "*"; 083 public final static String MATCH_ONE = "?"; 084 085 private List<String> parts; 086 private final boolean isCaseSensitive; 087 088 private final boolean isLastPartMatchAny; 089 090 091 public ParsedPattern( String pattern, boolean isCaseSensitive ) { 092 093 this.isCaseSensitive = isCaseSensitive; 094 095 if ( !isCaseSensitive ) 096 pattern = pattern.toLowerCase(); 097 098 parts = new ArrayList<String>(); 099 100 int len = pattern.length(); 101 102 int subStart = 0; 103 104 for ( int i=subStart; i<len; i++ ) { 105 106 char c = pattern.charAt( i ); 107 108 if ( c == '*' || c == '?' ) { 109 110 if ( i > subStart ) 111 parts.add( pattern.substring( subStart, i ) ); 112 113 parts.add( c == '*' ? MATCH_ANY : MATCH_ONE ); 114 115 subStart = i + 1; 116 } 117 } 118 119 if ( len > subStart ) { 120 121 parts.add( pattern.substring( subStart ) ); 122 } 123 124 isLastPartMatchAny = ( parts.get( parts.size() - 1 ) == MATCH_ANY ); 125 } 126 127 128 /** calls this( pattern, false, false ); */ 129 public ParsedPattern( String pattern ) { 130 131 this( pattern, false ); 132 } 133 134 135 /** tests if the input string matches the pattern */ 136 public boolean isMatch( String input ) { 137 138 if ( !isCaseSensitive ) 139 input = input.toLowerCase(); 140 141 int pos = 0; 142 int len = input.length(); 143 144 boolean doMatchAny = false; 145 146 for ( String part : parts ) { 147 148 if ( part == MATCH_ANY ) { 149 150 doMatchAny = true; 151 continue; 152 } 153 154 if ( part == MATCH_ONE ) { 155 156 doMatchAny = false; 157 pos++; 158 continue; 159 } 160 161 int ix = input.indexOf( part, pos ); 162 163 if ( ix == -1 ) 164 return false; 165 166 if ( !doMatchAny && ix != pos ) 167 return false; 168 169 pos = ix + part.length(); 170 doMatchAny = false; 171 } 172 173 if ( !isLastPartMatchAny && ( len != pos ) ) // if pattern doesn't end with * then we shouldn't have any more characters in input 174 return false; 175 176 return true; 177 } 178 179 180 @Override 181 public String toString() { 182 183 StringBuilder sb = new StringBuilder(); 184 185 for ( String s : parts ) { 186 187 sb.append( s ); 188 sb.append( ':' ); 189 } 190 191 if ( sb.length() > 0 ) 192 sb.setLength( sb.length() - 1 ); 193 194 return "[" + sb.toString() + "]"; 195 } 196 } 197 } 198