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