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
006 package fc.web.forms;
007
008 import javax.servlet.*;
009 import javax.servlet.http.*;
010 import java.io.*;
011 import java.util.*;
012 import java.util.regex.*;
013
014 import fc.jdbc.*;
015 import fc.io.*;
016 import fc.util.*;
017
018 /**
019 Allows for various types of text validation. This class is meant to be
020 used with text based input types such as subclasses of {@link
021 AbstractText}
022 **/
023 public final class VText extends FieldValidator
024 {
025 final static boolean dbg = false; //internal debugging
026
027 boolean checkLength = false;
028 boolean checkUnallowedChars = false;
029 boolean checkAllowedChars = false;
030 boolean checkAllowedPat = false;
031 boolean allowEmpty = true;
032 boolean trim = false;
033 //for a better toString()
034 boolean intOnly = false;
035 boolean floatOnly = false;
036 //
037
038 int minlength;
039 int maxlength;
040 String unallowedChars;
041 String allowedChars;
042 Pattern allowedPat;
043
044 /**
045 Creates a new validator that by default only fails validation if the
046 field's value is empty, that is to say, it's not filled by the user or is
047 filled only with spaces (since spaces are removed before validation). This
048 can be changed via the {@link #trimSpaces} method.
049 <p>
050 Other methods in this class can be invoked for further kinds of
051 validation.
052 **/
053 public VText(AbstractText field, String errorMessage)
054 {
055 super(field, errorMessage);
056 }
057
058 /**
059 If set to <tt>true</tt>, trims the string entered by the user before
060 attempting to run further validation on it. Defaults to <tt>false</tt>.
061 [Note: by default, AbstractText fields trim their values anyway so this
062 method is kinda redundant]
063 */
064 public void trimSpaces(boolean val)
065 {
066 trim = val;
067 }
068
069 /**
070 Checks to see if the field is required to be filled by the user. If the
071 field is <b>allowed</b> to be empty (not filled) and <b>is</b> empty, then
072 validation succeeds and no further validation checks are done.
073
074 <p>This is useful, for example, if a field is required to be either
075 totally empty or filled with some sort of pattern as specified by {@link
076 #setAllowedPattern}. Note, however, that this method can get confusing if
077 a {@link VFilled} validator is also attached to this field (which can be
078 done automatically by database objects generated by {@link
079 fc.jdbc.dbo.Generate}. In that case, 2 error messages will be shown to the
080 user if the field is left blank - one for the attached VFilled validator
081 and 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 **/
089 public VText allowEmpty(boolean allow)
090 {
091 allowEmpty = allow;
092 return this;
093 }
094
095 private static final String intOnlyRE = "\\s*\\d*\\s*";
096 private static final String floatOnlyRE = "\\s*-?\\d*\\.?\\d*\\s*";
097
098 /**
099 Ensures that the string is composed of only integer characters, with
100 optional leading/trailing blanks. This is a convenience method that sets
101 the pattern to be <tt>"\s*\d*\s*"</tt>
102 */
103 public VText allowIntegersOnly() {
104 setAllowedPattern(Pattern.compile(intOnlyRE));
105 intOnly = true;
106 return this;
107 }
108
109
110 /**
111 Ensures that the string is composed of only floating point number
112 characters with optional leading/trailing blanks. This is a convenience
113 method that sets the pattern to be <tt>"\s*-?\d*\.?\d*\s*"</tt>
114 */
115 public VText allowFloatingOnly() {
116 setAllowedPattern(Pattern.compile(floatOnlyRE));
117 floatOnly = true;
118 return this;
119 }
120
121 /**
122 Checks to see if the number of chars in the field are between the minimum
123 and maximum amount (both inclusive). If the minimum and maximum amounts
124 are 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 **/
129 public VText setLengthRange(int minlength, int maxlength)
130 {
131 this.minlength = minlength;
132 this.maxlength = maxlength;
133 checkLength = true;
134 return this;
135 }
136
137 /**
138 Checks to see if the number of chars in the field are between 0 and the
139 specified maximum amount (inclusive). [this method calls {@link
140 setLengthRange(0, maxlength)}]
141
142 @return this object for method chaining convenience
143 **/
144 public VText setMaxSize(int maxlength)
145 {
146 return setLengthRange(0, maxlength);
147 }
148
149 /**
150 Checks to see if the number of chars in the field are at least
151 the minimum number (inclusive) specified by this method.
152
153 @return this object for method chaining convenience
154 **/
155 public VText setMinSize(int minlength)
156 {
157 return setLengthRange(minlength, Integer.MAX_VALUE);
158 }
159
160
161 /**
162 Sets the characters <b>not</b> allowed in this field. If some character is
163 marked as both allowed (via the {@link setAllowedChars} method) and
164 unallowed (via this method), then unallowed characters have precedence and
165 if found in the input, the field will not be validated.
166 <p>
167 The same functionality can be achieved via regular expressions and negated
168 character classes. It's a matter of preference.
169 <p>
170 After 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 **/
176 public 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 /**
186 Sets the characters allowed in this field. All characters in the specified
187 string will be allowed, all else disallowed. An empty string (no
188 characters at all) is allowed at validation time if set via {@link
189 #allowEmpty} method.
190 <p>
191 After 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 **/
197 public 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 /**
207 Sets the regular expression representing the allowed input. The pattern
208 will be matched with the entire value of the field.
209 <p>
210 After 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 **/
216 public 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
230 public 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
279 public 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
344 final boolean isEmpty(String str)
345 {
346 if (str.length() == 0) {
347 return true;
348 }
349
350 return false;
351 }
352
353 final 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
367 final 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
389 final 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
415 final 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