001// Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 002// The Molly framework is freely distributable under the terms of an 003// MIT-style license. For details, see the molly pages web site at: 004// http://www.mollypages.org/. Use, modify, have fun ! 005 006package fc.web.forms; 007 008import javax.servlet.*; 009import javax.servlet.http.*; 010import java.io.*; 011import java.util.*; 012import java.util.regex.*; 013 014import fc.jdbc.*; 015import fc.io.*; 016import fc.util.*; 017 018/** 019Allows for various types of text validation. This class is meant to be 020used with text based input types such as subclasses of {@link 021AbstractText} 022**/ 023public final class VText extends FieldValidator 024{ 025final static boolean dbg = false; //internal debugging 026 027boolean checkLength = false; 028boolean checkUnallowedChars = false; 029boolean checkAllowedChars = false; 030boolean checkAllowedPat = false; 031boolean allowEmpty = true; 032boolean trim = false; 033//for a better toString() 034boolean intOnly = false; 035boolean floatOnly = false; 036// 037 038int minlength; 039int maxlength; 040String unallowedChars; 041String allowedChars; 042Pattern allowedPat; 043 044/** 045Creates a new validator that by default only fails validation if the 046field's value is empty, that is to say, it's not filled by the user or is 047filled only with spaces (since spaces are removed before validation). This 048can be changed via the {@link #trimSpaces} method. 049<p> 050Other methods in this class can be invoked for further kinds of 051validation. 052**/ 053public VText(AbstractText field, String errorMessage) 054 { 055 super(field, errorMessage); 056 } 057 058/** 059If set to <tt>true</tt>, trims the string entered by the user before 060attempting to run further validation on it. Defaults to <tt>false</tt>. 061[Note: by default, AbstractText fields trim their values anyway so this 062method is kinda redundant] 063*/ 064public void trimSpaces(boolean val) 065 { 066 trim = val; 067 } 068 069/** 070Checks to see if the field is required to be filled by the user. If the 071field is <b>allowed</b> to be empty (not filled) and <b>is</b> empty, then 072validation succeeds and no further validation checks are done. 073 074<p>This is useful, for example, if a field is required to be either 075totally empty or filled with some sort of pattern as specified by {@link 076#setAllowedPattern}. Note, however, that this method can get confusing if 077a {@link VFilled} validator is also attached to this field (which can be 078done automatically by database objects generated by {@link 079fc.jdbc.dbo.Generate}. In that case, 2 error messages will be shown to the 080user if the field is left blank - one for the attached VFilled validator 081and one for this one. 082 083@param allow <tt>true</tt> to allow for an empty field 084 <tt>false</tt> otherwise (defaults to <tt> 085 true</tt>). 086 087@return this object for method chaining convenience 088**/ 089public VText allowEmpty(boolean allow) 090 { 091 allowEmpty = allow; 092 return this; 093 } 094 095private static final String intOnlyRE = "\\s*\\d*\\s*"; 096private static final String floatOnlyRE = "\\s*-?\\d*\\.?\\d*\\s*"; 097 098/** 099Ensures that the string is composed of only integer characters, with 100optional leading/trailing blanks. This is a convenience method that sets 101the pattern to be <tt>"\s*\d*\s*"</tt> 102*/ 103public VText allowIntegersOnly() { 104 setAllowedPattern(Pattern.compile(intOnlyRE)); 105 intOnly = true; 106 return this; 107 } 108 109 110/** 111Ensures that the string is composed of only floating point number 112characters with optional leading/trailing blanks. This is a convenience 113method that sets the pattern to be <tt>"\s*-?\d*\.?\d*\s*"</tt> 114*/ 115public VText allowFloatingOnly() { 116 setAllowedPattern(Pattern.compile(floatOnlyRE)); 117 floatOnly = true; 118 return this; 119 } 120 121/** 122Checks to see if the number of chars in the field are between the minimum 123and maximum amount (both inclusive). If the minimum and maximum amounts 124are the same (including 0), then the field has to be exactly that length. 125(empty fields are also allowed if set via {@link #allowEmpty} method. 126 127@return this object for method chaining convenience 128**/ 129public VText setLengthRange(int minlength, int maxlength) 130 { 131 this.minlength = minlength; 132 this.maxlength = maxlength; 133 checkLength = true; 134 return this; 135 } 136 137/** 138Checks to see if the number of chars in the field are between 0 and the 139specified maximum amount (inclusive). [this method calls {@link 140setLengthRange(0, maxlength)}] 141 142@return this object for method chaining convenience 143**/ 144public VText setMaxSize(int maxlength) 145 { 146 return setLengthRange(0, maxlength); 147 } 148 149/** 150Checks to see if the number of chars in the field are at least 151the minimum number (inclusive) specified by this method. 152 153@return this object for method chaining convenience 154**/ 155public VText setMinSize(int minlength) 156 { 157 return setLengthRange(minlength, Integer.MAX_VALUE); 158 } 159 160 161/** 162Sets the characters <b>not</b> allowed in this field. If some character is 163marked as both allowed (via the {@link setAllowedChars} method) and 164unallowed (via this method), then unallowed characters have precedence and 165if found in the input, the field will not be validated. 166<p> 167The same functionality can be achieved via regular expressions and negated 168character classes. It's a matter of preference. 169<p> 170After this method is called, the pattern previously set (if any) via the 171{@link #setAllowedPattern} method will be ignored for validation. 172 173@param chars the unallowed chars. This parameter must not be null. 174@return this object for method chaining convenience 175**/ 176public VText setUnallowedChars(String chars) 177 { 178 Argcheck.notnull(chars, "specified param 'chars' was null"); 179 unallowedChars = chars; 180 checkUnallowedChars = true; 181 checkAllowedPat = false; 182 return this; 183 } 184 185/** 186Sets the characters allowed in this field. All characters in the specified 187string will be allowed, all else disallowed. An empty string (no 188characters at all) is allowed at validation time if set via {@link 189#allowEmpty} method. 190<p> 191After this method is called, the pattern previously set (if any) via the 192{@link #setAllowedPattern} method will be ignored for validation. 193 194@param chars the allowed chars. This parameter must not be null. 195@return this object for method chaining convenience 196**/ 197public VText setAllowedChars(String chars) 198 { 199 Argcheck.notnull(chars, "specified param 'chars' was null"); 200 allowedChars = chars; 201 checkAllowedChars = true; 202 checkAllowedPat = false; 203 return this; 204 } 205 206/** 207Sets the regular expression representing the allowed input. The pattern 208will be matched with the entire value of the field. 209<p> 210After this method is called, the string previously set (if any) via the 211{@link #setAllowedChars} method will be ignored for validation. 212 213@param pat the allowed pattern. Must not be null. 214@return this object for method chaining convenience 215**/ 216public VText setAllowedPattern(Pattern pat) 217 { 218 Argcheck.notnull(pat, "specified param 'pat' was null"); 219 allowedPat = pat; 220 checkAllowedPat = true; 221 checkAllowedChars = false; 222 223 //callers should set these back to true 224 intOnly = false; 225 floatOnly = false; 226 227 return this; 228 } 229 230public String toString() 231 { 232 StringBuffer buf = new StringBuffer(512); 233 buf.append("VText("); 234 if (checkLength) { 235 buf.append("Len=["); 236 buf.append(minlength); 237 buf.append("-"); 238 buf.append(maxlength); 239 buf.append("]"); 240 buf.append(", "); 241 } 242 if (checkUnallowedChars) { 243 buf.append("Unallowed="); 244 buf.append(unallowedChars); 245 buf.append(", "); 246 } 247 if (checkAllowedChars) { 248 buf.append("Allowed="); 249 buf.append(allowedChars); 250 buf.append(", "); 251 } 252 if (checkAllowedPat) 253 { 254 if (intOnly) { 255 buf.append("ints only"); 256 } 257 else if (floatOnly) { 258 buf.append("floats only"); 259 } 260 else { 261 buf.append("Pattern="); 262 buf.append(StringUtil.viewableAscii(allowedPat.pattern())); 263 } 264 buf.append(", "); 265 } 266 if (trim) { 267 buf.append("Trim=true"); 268 buf.append(", "); 269 } 270 271 buf.deleteCharAt(buf.length() -1); 272 buf.deleteCharAt(buf.length() -1); 273 buf.append(")"); 274 275 return buf.toString(); 276 } 277 278 279public boolean validate(FormData fd, HttpServletRequest req) 280 { 281 String val = ((AbstractText)field).getValue(fd); 282 283 /* 284 can be null if not submitted, not filled by user or even 285 initially if some field was constructed with a default 286 value of null 287 */ 288 if (val == null) 289 { 290 if (dbg) System.out.println(">>>>>>>>>> val == null, returning false"); 291 292 if (allowEmpty) 293 return true; 294 else 295 return false; 296 } 297 298 if (trim) 299 val = val.trim(); 300 301 //check for empty again after trimming. 302 if (allowEmpty && isEmpty(val)) { 303 if (dbg) System.out.println("RETURNING TRUE"); 304 return true; 305 } 306 307 if ( ! allowEmpty && isEmpty(val)) { 308 if (dbg) System.out.println("RETURNING FALSE"); 309 return false; 310 } 311 312 boolean result = true; 313 if (dbg) System.out.println(">>>>>>>>>>> ALLOW_EMPTY=" + allowEmpty + "; isEmpty()=" + isEmpty(val) + "; VALUE=[" + val + "]"); 314 315 // keep going thru all other checks to see if there is an error 316 317 if (checkLength) { //checking for length ? 318 if (result) { //latch, test further since result currently true 319 result = checkLength(val); 320 } 321 } 322 323 if (checkUnallowedChars) { //checking for unallowed chars ? 324 if (result) { //latch, test further since result currently true 325 result = checkUnallowedChars(val); 326 } 327 } 328 329 if (checkAllowedChars) { //checking for allowed chars ? 330 if (result) { //latch, test further since result currently true 331 result = checkAllowedChars(val); 332 } 333 } 334 335 if (checkAllowedPat) { //checking for regex pattern ? 336 if (result) { //latch, test further since result currently true 337 result = checkAllowedPat(val); 338 } 339 } 340 341 return result; 342 } 343 344final boolean isEmpty(String str) 345 { 346 if (str.length() == 0) { 347 return true; 348 } 349 350 return false; 351 } 352 353final boolean checkLength(String str) 354 { 355 if (str == null) { 356 return false; 357 } 358 359 int len = str.length(); 360 if (len < minlength || len > maxlength) { 361 return false; 362 } 363 364 return true; 365 } 366 367final boolean checkAllowedChars(String str) 368 { 369 if (str == null) 370 return false; 371 372 boolean found = false; 373 for (int n = 0; n < str.length(); n++) 374 { 375 char c = str.charAt(n); 376 for (int k = 0; k < allowedChars.length(); k++) 377 { 378 char mchar = allowedChars.charAt(k); 379 if (c == mchar) { 380 found = true; 381 break; 382 } 383 } 384 } 385 return found; 386 } 387 388 389final boolean checkUnallowedChars(String str) 390 { 391 if (str == null) 392 return false; 393 394 boolean found = false; 395 for (int n = 0; n < str.length(); n++) 396 { 397 char c = str.charAt(n); 398 for (int k = 0; k < unallowedChars.length(); k++) 399 { 400 char mchar = unallowedChars.charAt(k); 401 if (c == mchar) { 402 found = true; 403 break; 404 } 405 } 406 } 407 408 if (found) { //found an unallowed char 409 return false; 410 } 411 else 412 return true; 413 } 414 415final boolean checkAllowedPat(String str) 416 { 417 if (str == null) 418 return false; 419 420 Matcher matcher = allowedPat.matcher(str); 421 422 return matcher.matches(); 423 } 424 425} //~class VTextLength 426