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