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