001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.commons.io.res.util; 020 021import java.util.ArrayList; 022import java.util.List; 023import java.util.StringTokenizer; 024 025import lucee.commons.lang.StringUtil; 026 027 028/** 029 * a WildcardPattern that accepts a comma- (or semi-colon-) separated value of patterns, e.g. "*.gif, *.jpg, *.jpeg, *.png" 030 * and an optional isExclude boolean value which negates the results of the default implementation 031 * 032 * also, lines 31 - 35 allow to set isExclude to true by passing a pattern whose first character is an exclamation point '!' 033 * 034 * @author Igal 035 */ 036public class WildcardPattern { 037 038 private final String pattern; 039 private final boolean isInclude; 040 041 private final List<ParsedPattern> patterns; 042 043 /** 044 * 045 * @param pattern - the wildcard pattern, or a comma/semi-colon separated value of wildcard patterns 046 * @param isCaseSensitive - if true, does a case-sensitive matching 047 * @param isExclude - if true, the filter becomes an Exclude filter so that only items that do not match the pattern are accepted 048 */ 049 public WildcardPattern( String pattern, boolean isCaseSensitive, boolean isExclude, String delimiters ) { 050 051 if ( pattern.charAt( 0 ) == '!' ) { // set isExclude to true if the first char of pattern is an exclamation point '!' 052 053 pattern = pattern.substring( 1 ); 054 isExclude = true; 055 } 056 057 this.pattern = pattern; 058 this.isInclude = !isExclude; 059 060 StringTokenizer tokenizer = new StringTokenizer( pattern, !StringUtil.isEmpty(delimiters, true) ? delimiters : "|" ); 061 062 patterns = new ArrayList<ParsedPattern>(); 063 064 while ( tokenizer.hasMoreTokens() ) { 065 066 String token = tokenizer.nextToken().trim(); 067 068 if ( !token.isEmpty() ) 069 patterns.add( new ParsedPattern( token, isCaseSensitive ) ); 070 } 071 } 072 073 074 /** calls this( pattern, isCaseSensitive, false, delimiters ); */ 075 public WildcardPattern( String pattern, boolean isCaseSensitive, String delimiters ) { 076 077 this( pattern, isCaseSensitive, false, delimiters ); 078 } 079 080 081 public boolean isMatch( String input ) { 082 083 for ( ParsedPattern pp : this.patterns ) { 084 085 if (pp.isMatch(input)) 086 return isInclude; 087 } 088 089 return !isInclude; 090 } 091 092 093 public String toString() { 094 095 return "WildcardPattern: " + pattern; 096 } 097 098 public static class ParsedPattern { 099 100 public final static String MATCH_ANY = "*"; 101 public final static String MATCH_ONE = "?"; 102 103 private String[] parts; 104 private final boolean isCaseSensitive; 105 106 107 public ParsedPattern( String pattern, boolean isCaseSensitive ) { 108 109 this.isCaseSensitive = isCaseSensitive; 110 111 if (!isCaseSensitive) 112 pattern = pattern.toLowerCase(); 113 114 List<String> lsp = new ArrayList<String>(); 115 116 int len = pattern.length(); 117 int subStart = 0; 118 119 for (int i=subStart; i<len; i++) { 120 121 char c = pattern.charAt( i ); 122 123 if (c == '*' || c == '?') { 124 125 if (i > subStart) 126 lsp.add( pattern.substring( subStart, i ) ); 127 128 lsp.add(c == '*' ? MATCH_ANY : MATCH_ONE); 129 subStart = i + 1; 130 } 131 } 132 133 if (len > subStart) 134 lsp.add(pattern.substring(subStart)); 135 136 this.parts = lsp.toArray(new String[ lsp.size()] ); 137 } 138 139 140 /** calls this( pattern, false, false ); */ 141 public ParsedPattern( String pattern ) { 142 143 this( pattern, false ); 144 } 145 146 147 /** tests if the input string matches the pattern */ 148 public boolean isMatch( String input ) { 149 150 if ( !isCaseSensitive ) 151 input = input.toLowerCase(); 152 153 if (parts.length == 1) 154 return ( parts[0] == MATCH_ANY || parts[0].equals(input) ); 155 156 if (parts.length == 2) { 157 158 if (parts[0] == MATCH_ANY) 159 return input.endsWith( parts[1] ); 160 161 if (parts[ parts.length - 1 ] == MATCH_ANY) 162 return input.startsWith( parts[0] ); 163 } 164 165 int pos = 0; 166 int len = input.length(); 167 168 boolean doMatchAny = false; 169 170 for (String part : parts) { 171 172 if ( part == MATCH_ANY ) { 173 174 doMatchAny = true; 175 continue; 176 } 177 178 if ( part == MATCH_ONE ) { 179 180 doMatchAny = false; 181 pos++; 182 continue; 183 } 184 185 int ix = input.indexOf( part, pos ); 186 187 if ( ix == -1 ) 188 return false; 189 190 if ( !doMatchAny && ix != pos ) 191 return false; 192 193 pos = ix + part.length(); 194 doMatchAny = false; 195 } 196 197 if ( (parts[ parts.length - 1 ] != MATCH_ANY) && (len != pos) ) // if pattern doesn't end with * then we shouldn't have any more characters in input 198 return false; 199 200 return true; 201 } 202 203 204 @Override 205 public String toString() { 206 207 StringBuilder sb = new StringBuilder(); 208 209 for (String s : parts) 210 sb.append( s ); 211 212 return sb.toString(); 213 } 214 } 215} 216