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.sql.*;
013
014import fc.jdbc.*;
015import fc.io.*;
016import fc.util.*;
017
018/**
019Represents an HTML form. It is possible to instantiate a different form
020object per user and store each form in that users session scope. However,
021more typically, instances of this object are instantiated once per unique
022form and stored in web application scope.
023<p>
024Method in this class are thread safe since form processing is done
025entirely within various methods and the form processing results in the
026creation of seperate {@link FormData} objects for each request.
027<p>
028Various {@link Field Fields} can be added to the form at form creation
029time. Some fields (like {@link Hidden} can also be added per user/request, 
030dynamically, to the {@link FormData} object. This is useful while saving
031some user or request specific information.
032<p>
033A form collects data from the front-end/web-client and often saves it to a
034back end database entity. Form data validation can be done in 2 ways:
035<ol>
036  <li>at the <i>form</i> level where the form collects data and validates
037  it via validators associated with the form object.
038  <li>at the <i>field</i> level where the form itself does no higher
039  level validation but populates a field and then asks that field to
040  validate itself.
041</ol>
042In both cases, any errors associated with the form can be shown back to the
043user. This framework allows both types of validation methods to be used.
044Using validation and/or showing validation errors is optional. This framework
045can simply be used to maintain form state and validation can be done via
046other ad-hoc code (outside of this framework) as needed.
047<p>
048An HTML form presented to the user can contain fields from more than 1
049database table. For example, there could to 2 database tables, say
050"person" and "office" both with a "other" column. Since <u>each field
051contained in the form object must have a unique name</u>, there will be a
052name clash between the fields/attributes of the entities when 2 fields
053with the name "other" are added to the form.
054<p>
055There are 2 general approaches to solving this problem. 
056<ol>
057<li>Seperate database tables should be represented by seperate Form objects
058(all of which can be rendered in the same HTML document shown to the user).
059Different instances of form objects (say 1 per table) act as seperate
060name-spaces and fields having the same name can exist in these seperate
061instances. However, the dynamic server page where the form object is
062rendered/submitted has to obtain these seperate instances from the application
063object (or wherever all forms are stored) -- which adds some complexity.
064<li>Create 1 instance of a form object containing fields from more than one
065table. In this case, some or all field names can be prepended (or suffixed)
066with the name of the database table. This removes any name clashes if done
067only for clashing fields; if done for every field may help in documenting
068which table that field belongs to. There is more typing involved with this
069approach.
070</ol>
071Either of the above approaches is valid. 
072<p>
073Sometimes, other methods need to obtain a field (for a particular table) by
074name. For example, the managers generated by {@link fc.jdbc.dbo.Generate}
075have a <tt>addValidators(form)</tt> method that extract the fields for a
076particular table and add validators to that field. This method in
077particular need to be passed either the appropriate form object in which
078that field exists (if there are multiple form objects) or in the case of
079only 1 form object, passed the prefix for that particular field, if that
080field was added with a prefix to it's name.
081
082@author hursh jain
083**/
084public class Form 
085{
086//useful for method that return empty lists/maps in lieu of nulls
087static final public List empty_list = 
088  Collections.unmodifiableList(new ArrayList());
089
090static final public Map empty_map = 
091  Collections.unmodifiableMap(new HashMap());
092
093protected   Log       log;
094//fields have field-level validators  
095protected   LinkedHashMap   fields;
096//collections of fields -- t  hese fields are directly added
097//to the fields map also  
098protected LinkedHashMap   fieldRef; 
099protected   LinkedHashMap     formValidators; //form-level validators
100protected List        dependencies;
101protected   String        name;     //the form name
102protected String        method;     
103protected String        actionURL;
104protected String        displayURL;
105//protected String        fieldNamePrefix;
106protected   java.util.Timer   timer;
107protected SubmitHackedHandler hackHandler;
108/** 
109Creates a new form with the specified name, method (get/post) and URL
110**/
111public Form(String name, String method, String actionURL, Log log, 
112      SubmitHackedHandler handler) 
113  { 
114  this.name = name; 
115  this.method = method;
116  this.actionURL = actionURL;
117  fields = new LinkedHashMap();
118  fieldRef = new LinkedHashMap();
119  formValidators = new LinkedHashMap();
120  dependencies = new ArrayList();
121  this.log = log;
122
123  this.hackHandler = handler;
124  if (handler == null) {
125    hackHandler = new SubmitHackedHandler(this);
126    }
127  }
128
129/*
130Creates a new form with the specified name, method (get/post) and URL
131and the default {@link SubmitHackedHandler} 
132**/
133public Form(String name, String method, String actionURL, Log log)     
134  {
135  this(name, method, actionURL, log, null);
136  }
137
138/** 
139Creates a new form with logging to the default log retreived by
140{@link Log#getDefault} and the default {@link SubmitHackedHandler}
141**/
142public Form(String name, String method, String actionURL) 
143  { 
144  this(name, method, actionURL, Log.getDefault(), null);
145  }
146
147/** 
148Creates a new form with the specified name, method=POST and no action URL
149**/
150public Form(String name) 
151  { 
152  this(name, "post", null);
153  }
154
155/**
156This method should be called when the form is removed from session or
157application scope. It is very important to do this if there are background
158update tasks because this method stops such background refresh timer thread
159(which would otherwise continue to run).
160*/
161public void destroy()
162  {
163  timer.cancel();
164  fields.clear();
165  }
166
167/** 
168Removes the specified field from the form. See {@link
169java.util.Map#remove(Object)} for the general contract.
170
171@param  field the field to remove from the form.
172**/
173public Object remove(Field field) { 
174  return fields.remove(field.getName());
175  }
176
177/** 
178Adds the specified element to the form. The added field can
179later be retrieved via it's name. The field iteration order
180will be the same order in which fields are added via this
181method.
182<p>
183Note, field names have to be unique. "Grouped" fields (such
184as HTML form checkboxes or radio buttons) which require the
185same field name are encapsulated by a single appropriate
186grouped field object such as {@link RadioGroup} and {@link
187CheckboxGroup}. This field object itself should have a
188unique name.
189
190@return  A reference to this form as a convenience for method chaining
191@throws  IllegalArgumentException if an element with the same name
192                  already exists in this form.
193**/
194public Form add(Field field) 
195  { 
196  String name = field.getName();
197  
198  //not needed,  prefix each field manually if needed.
199  /*
200  if (fieldNamePrefix != null) {
201    name = fieldNamePrefix + name;
202    }
203  */
204  if (fields.containsKey(name)) {
205    throw new IllegalArgumentException("A form element by that name [" + name + "] already exists");
206    }
207  fields.put(name, field); 
208  field.setParentForm(this);
209
210  return this;
211  }
212
213/**
214Adds the specified dependency to the form.
215*/
216public Form add(Dependency d) 
217  { 
218  dependencies.add(d); 
219
220  return this;
221  }
222
223
224/**
225Adds a {@link FieldRef} to this Form. This is useful when
226to track a set of related fields in a form. (Note: those fields
227<b>must</b> also directly be added to the form).
228*/
229public Form add(FieldRef ref) 
230  {
231  String name = ref.getName();
232  if (fieldRef.containsKey(name)) {
233    throw new IllegalArgumentException("A fieldRef by that name [" + name+ "] already exists");
234    }
235  fieldRef.put(name, ref);
236  return this;
237  }
238
239/**
240Adds a <b>form level</b> validation error message. This method is needed when
241form level validation is done via classes that are <b>not</b> subclasses
242of {@link FormValidator} (these classes are used outside of the
243validation framework and their error message still needs to be added to
244the form data).
245<p> 
246<b> This is a very useful method for adding the results of arbitrary code
247to this form. </b>
248
249@param  fd      form data
250@param  errorKey  an arbitrary/optional validation key which allows 
251          to later retrieve and display a particular 
252          form error. Specify <tt>null</tt> for no
253          key.
254@param  errorMessage the form error message
255
256@see Field#addError
257**/
258public void addError(FormData fd, String errorKey, String errorMessage) 
259  {
260  fd.addFormError(errorKey, errorMessage);
261  }
262
263/**
264Convenience method that invokes {@link addError(FormData, String, String) 
265addError} with an empty validation <tt>errorKey</tt>. 
266*/
267public void addError(FormData fd, String errorMessage) 
268  {
269  addError(fd, null, errorMessage);
270  }
271
272/**
273Adds a {@link Hidden} field to this form. This field exists only for the
274duration of this form data object and is useful for request specific 
275data.
276*/
277public void addDynamicField(FormData fd, Hidden f) {
278  fd.addDynamicField(f);
279  }
280
281/** 
282Adds a form level validator to this form. Note, validator
283names have to be unique and not clash with other form level
284validator names.
285
286@throws  IllegalArgumentException if a validator with the same name
287                  already exists in this form.
288**/
289public void addValidator(FormValidator validator) {
290  String name = validator.getName();
291  if (formValidators.containsKey(name)) {
292    throw new IllegalArgumentException("A validator by that name [" + name + "] already exists in this form");
293    }
294  formValidators.put(name, validator);
295  }
296
297/**
298Adds an arbitrary message for this request. Messages can include arbitrary
299warnings, usage help etc. 
300*/
301public void addMessage(FormData fd, String key, String value) 
302  {
303  fd.addMessage(key, value);
304  }
305
306/**
307Returns true if the form contains the specified field.
308*/
309public boolean containsField(String fieldName) 
310  {
311  return fields.containsKey(fieldName); 
312  }
313
314/**
315Retrieves a previously added message or <tt>null</tt> if the speccified
316form data was <tt>null</tt> or if the message was not found.
317*/
318public String getMessage(FormData fd, String key) 
319  {
320  if (fd == null)
321    return null;
322    
323  return fd.getMessage(key);
324  }
325        
326public void addSubmitHackedHandler(SubmitHackedHandler handler)
327  {
328  Argcheck.notnull(handler, "handler param was null");
329  this.hackHandler = handler;
330  }
331
332protected void handleSubmitHacked(HttpServletRequest req, String msg)
333throws SubmitHackedException
334  {
335  hackHandler.handle(req, msg);
336  }
337        
338/** 
339Processes the submitted form. <b>This method must be called after each form
340submission by the servlet or page code that handles form submits</b>. This
341method does the following:
342<ol>
343<li>populates the fields of the form from the submitted request.
344<li>if the form has any dependencies and if the dependencies need updating,
345then those dependencies are updated. Validation is not carried out in this
346case since the form has changed and the user needs to typically carry out
347some other action before submitting the form. 
348<li>if there are no updated dependencies, validates the form via any
349validators attached to the form and it's fields.
350</ol>
351Note: To see if there were any validation errors, invoke the {@link
352hasError(FormData)} method with the formdata returned by this method.
353Seeing validation errors is optional of course, and so is using validators
354in the first place. 
355
356@param    req   the HttpServletRequest corresponding to the request
357          being processed by this form.
358
359@return   a {@link FormData} object containing populated 
360      field data and the results of the validation.
361**/ 
362public FormData handleSubmit(HttpServletRequest req)  
363throws ServletException, IOException
364  {
365  return handleSubmit(req, null);
366  }
367
368/**
369This method is similar to {@link handleSubmit(HttpServletRequest)} except
370it stores the specified connection in the form data. This connection can
371be used by any attached dependency classes for database queries when 
372updating form depedencies.  
373<p>
374Note, we can set arbitrary objects in the {@link HttpServletRequest} via
375it's {@link HttpServletRequest#setattribute setAttribute} method.
376However, specifying and storing the connection in the FormData is easier
377to remember and use.
378<p>
379There are 2 cases:
380<ol>
381<li>The dependent fields (if any) will internally use {@link WebApp#getConnection}
382to obtain a connection and no connection has to be set in the form data.</li>
383The {@link handleSubmit(HttpServletRequest)} can then be used. 
384<li>The dependent fields (if any) classes will expect a connection in the form data. In
385that case, this method should be invoked.</li>
386</ol>
387If there are no dependent fields, then the {@link handleSubmit(HttpServletRequest)} 
388is sufficient. 
389
390@param    req   the HttpServletRequest corresponding to the request
391          being processed by this form.
392@param    con   a connection object. The connection is not closed and
393          it is the callers responsiblity to do so.
394*/ 
395public FormData handleSubmit(HttpServletRequest req, Connection con)  
396throws ServletException, IOException
397  {
398  FormData fd = populate(req);  //each field sets it's submitted value
399  if (con != null)
400    fd.setConnection(con);
401  
402  updateDependencies(fd, req);  //this can throw an exception.
403  
404  if (! fd.dependencyUpdated) 
405    validate(fd, req);
406  
407  return fd;
408  }
409
410
411public FormData refresh(HttpServletRequest req) 
412throws ServletException, IOException
413  {
414  return refresh(req, null);
415  }
416
417/**
418Refreshes the form for this request by running all attached refreshers
419and dependencies. Maintains form state. Does not run any validatators.
420Useful for re-displaying a form when lookup fields have changed in the
421database.
422<p>
423<u>To refresh the form for all users/requests from this point onwards,
424create a new instance of the form and replace the existing instance
425of the form with the newly created instance.</u>
426*/
427public FormData refresh(HttpServletRequest req, Connection con) 
428throws ServletException, IOException
429  {
430  FormData fd = populate(req);  //each field sets it's submitted value
431  if (con != null)
432    fd.setConnection(con);
433  
434  updateDependencies(fd, req);  //this can throw an exception.
435  
436  return fd;
437  }
438  
439/**
440Populates field's based on the parameters submitted via the specified
441request and stores it into the specified FormData object. 
442**/
443protected FormData populate(HttpServletRequest req) 
444throws SubmitHackedException
445  {
446  FormData fd = new FormData();
447  Iterator it = fields.entrySet().iterator();
448  while (it.hasNext()) {
449    Map.Entry entry = (Map.Entry) it.next();
450    Field f = (Field) entry.getValue();
451    f.setValueFromSubmit(fd, req);    
452    }
453  return fd;
454  }
455
456protected void updateDependencies(FormData fd, HttpServletRequest req)
457throws DependencyException
458  {
459  for (int i = 0; i < dependencies.size(); i++)
460    {
461    Dependency d = (Dependency) dependencies.get(i);
462    d.updateValues(fd, req);
463    }
464  }
465  
466/** 
467Validates each field and also calls each form level validator.
468<p>
469Validation is a 2-step process. First, only enabled fields are validated.
470Some validators may enable or disable fields as part of their validation
471strategy (for example, if the user chooses a radio button marked 'detail',
472then fields corresponding to the detail section need to be enabled). (other
473fields might be disabled). Fields that are re-enabled in this fashion are
474validated by this method.
475<p>
476After running through all the validators, returns <tt>false</tt> is there
477is a validation error, <tt>true</tt> otherwise. The field and form
478validation errors can then be retrieved via the {@link
479FormData#getFieldErrors} and {@link FormData#getFormErrorsCollection},
480{@link FormData#getFormErrorsMap} methods.
481**/
482protected boolean validate(FormData fd, HttpServletRequest req) 
483  {
484  boolean result = true;
485  Map errors = fd.getFieldErrors();
486  Field f = null;
487  Iterator it = null;
488  //List checkAgainList = new ArrayList();
489  
490  //for all form validators
491  it = formValidators.values().iterator();
492  while (it.hasNext()) {
493    FormValidator v = (FormValidator) it.next();
494    if (! v.validate(fd, req)) {
495      result = false; //latch
496      String tmp = v.getErrorMessage();
497      String vname = v.getName();
498      log.bug("Group validate error: ", vname, tmp);
499      fd.addFormError(vname, tmp);      
500      }
501    }
502  
503  //for all field validators, (some could actually be
504  //disabled by the form validators above).
505  it  = fields.values().iterator(); 
506  while (it.hasNext()) 
507    {
508    f = (Field) it.next();
509    
510    if (f.isEnabled(fd)) 
511      { 
512      if (! f.validate(fd, req)) {
513        result = false; //latch
514        }
515      } 
516    } //!while
517  
518  return result;  
519  }
520
521/**
522Returns <tt>true</tt> if validation did not succeed.
523*/
524public static boolean hasError(FormData fd) 
525  {
526  if (fd == null)
527    return false; //no data so could not validate
528  
529  //FieldErrors = map: fieldname->list of errors
530  //entries in the map (via field.validate) should
531  //only be created if there is a validation error
532  //an empty list of errors should not be created
533  //otherwise the size() below will not work
534  if (fd.getFormErrorsCollection().size() > 0 ||
535    fd.getFieldErrors().size() > 0) 
536    {
537    return true;
538    }
539  
540  return false;
541  }
542
543/**
544Convenience method to render <i>form-level</i> validation errors. This method
545writes validation error message for the specified form level validator.
546The error messages are encapulated in a html &lt;div&gt; tag and the class
547of this tag is set to <tt>form-errmsg</tt>. Each error message within this
548div is assigned it's own div with class set to <tt>form-errmsg-item</tt>
549
550@param  writer      the Writer to render errors to
551**/
552public void renderError(FormData fd, Writer writer) 
553throws IOException 
554  {
555  if (fd == null)
556    return;
557  
558  Collection errors = fd.getFormErrorsCollection();
559
560  int size = errors.size();
561  if (size == 0)
562    return;
563  
564  writer.write("<div class='form-errmsg'>\n");
565  Iterator it = errors.iterator();
566  while (it.hasNext())
567    {
568    String msg = (String) it.next();
569    if (msg == null)
570      continue;
571    writer.write("<div class='form-errmsg-item'>");
572    writer.write(msg);
573    writer.write("</div>\n");
574    }
575  writer.write("</div>\n");
576  }
577
578
579/**
580Convenience method to render a validation error for a particluar form level
581validator. The error messages are encapulated in a html &lt;div&gt; tag and
582the class of this tag is set to <tt>form-errmsg</tt>.
583
584@param  writer        the Writer to render errors to
585@param  formValidatorName the name of a form level validator
586**/
587public void renderErrorFor(
588  FormData fd, Writer writer, String formValidatorName) 
589throws IOException 
590  {
591  if (fd == null)
592    return;
593  
594  List errors = (List) getFormErrorsFor(fd, formValidatorName);
595  
596  int size = errors.size();
597  if (size == 0)
598    return;
599  
600  writer.write("<div class='form-errmsg'>\n");
601  Iterator it = errors.iterator();
602  while (it.hasNext())
603    {
604    String msg = (String) it.next();
605    if (msg == null)
606      continue;
607    writer.write("<div class='form-errmsg-item'>");
608    writer.write(msg);
609    writer.write("</div>\n");
610    }
611  writer.write("</div>\n");
612  }
613
614/** 
615@param   fd a non-null form data object
616@return   a map containing field names as keys and the 
617      field's validation error as the value. The data
618      type of each value is that returned by the {@link
619      Field#getValidateErrors} method
620**/
621public Map getFieldErrors(FormData fd) {
622  return fd.getFieldErrors();
623  }
624
625/** 
626@param   fd a non-null form data object
627@return   a list containing form level errors. The data type
628      of each value is that returned by the {@link
629      FormValidator#getErrorMessage()} method
630**/
631public Collection getFormErrorsCollection(FormData fd) {
632  return fd.getFormErrorsCollection();
633  }
634
635/**
636Returns a list containing all form-level errors for the specified
637validator. If there are no errors for the specified validator, returns an
638empty list.
639
640@param   fd         a non-null form data object
641@param   formValidatorName  a form validator name
642*/
643public List getFormErrorsFor(FormData fd, String formValidatorName) 
644  {
645  Map map = fd.getFormErrorsMap();
646  List list = (List) map.get(formValidatorName);
647  if (list == null)
648    return empty_list;
649    
650  return list;
651  }
652  
653/**
654Renders <b>all</b> dynamically added hidden fields of the form. Dynamic
655fields are created/ added per user per submit to the {@link FormData}
656if/as needed.
657<p>
658<b>Note</b>: Dynamic fields are <b>not</b> part of a form's fields and
659are not available via 
660*/
661public void renderDynamicFields(FormData fd, Writer out) 
662throws SQLException, IOException
663  {
664  if (fd == null || fd.dynamicFields == null)
665    return;
666
667  Iterator it = fd.dynamicFields.values().iterator();
668  while (it.hasNext()) {
669    Hidden h = (Hidden) it.next();
670    h.render(out);
671    }
672  }
673
674/** Returns the name of this form **/
675public String getName() { 
676  return name; 
677  }
678
679/** 
680Sets an arbitrary application specific string that can 
681later be retrieved. Some applications may find 
682this method useful.
683**/
684public void setActionURL(String url) {
685  this.actionURL = url;
686  }
687
688/** 
689Returns the action URL string. Returns <tt>null</tt>
690if no url has been set.
691**/ 
692public String getActionURL() {
693  return actionURL;
694  }
695
696
697/** 
698Sets an arbitrary application specific string. Intended
699to store the URL to the jsp or front end page that will
700render this form for the user.
701**/
702public void setDisplayURL(String url) {
703  this.displayURL = url;
704  }
705
706/** 
707Returns the display URL string. Returns <tt>null</tt>
708if no display url has been set.
709**/ 
710public String getDisplayURL() {
711  return displayURL;
712  }
713
714
715/** 
716Returns a collection view of all the fields contained in this
717form.
718**/
719public Collection getAllFields() { 
720  return fields.values(); 
721  }
722
723
724/*
725*This is overkill, manually prefix fields as needed
726*
727*Automatically appends the specified prefix to the names of all fields
728*added after this method is called. Suppose there are 2 tables contained in
729*1 form and both tables contain a field called <tt>id</tt>. Then, since
730*field names in the form must be unique, one can either add those fields
731*under different names (say, "table1.id", and "table2.id") or can do the
732*following:
733*<blockquote>
734*form.setFieldNamePrefix("table1");
735*form.add(id);
736*form.setFieldNamePrefix("table2");
737*form.add(id);
738*</blockquote>
739*This latter way is useful when many field names clash.
740*
741*@param prefix  the prefix string. Specify <tt>null</tt>
742*       or the empty string to have no prefix.
743*       (<tt>null</tt> is more efficient)
744*
745*public void setFieldNamePrefix(String prefix) {
746* if ( (prefix == null) || prefix.equals("") ) 
747*   {
748*   this.fieldNamePrefix = null;
749*   return;
750*   } 
751* this.fieldNamePrefix = prefix + "_";
752* }
753*/
754
755//get various Field types
756/**
757Returns the form field with the specified name.
758
759@param    name  the name of the field
760
761@return   the field or <tt>null</tt> if no element with that name 
762      was found in this form. 
763**/
764public Field get(String name) 
765  {
766  Field result = (Field) fields.get(name);  
767  
768  if (result == null) 
769    log.warn("Tried to obtain a non-existent field from the form. Did you add a field with this name to the form ? Fieldname: [", name, "]");
770    
771  return result;
772  }
773
774/**
775Gets a {@link Hidden} field added to the form data object. Returns
776<tt>null</tt> if the form data object is null or if the specified
777field does not exist. <u>Dyanamic fields returned by this method are only 
778available for the duration of the form data object and <u>can not
779obtain a field from a submitted form.</u> (use {@link getDynamicField(FormData, HttpServletRequest)}
780for that purpose.
781*/
782public Hidden getDynamicField(FormData fd, String fieldName) 
783  {
784  if (fd == null)
785    return null;
786
787  return fd.getDynamicField(fieldName);
788  }
789
790
791/**
792Once the form is displayed to the user (with, say, hidden fields that
793were added dynamically for that request), then when that form is
794submitted, those dynamic fields can be retrieved via any of: 
795<ul> 
796<li> the {@link HttpServletRequest#getParameter} method (often the easiest
797way)
798<li> the {@link #getSubmittedField(HttpServletRequest)} method (which will
799attempt to read the request parameter with the specified name and return
800is as a Hidden field. (this is slightly more convenient if there is more
801than 1 hidden field with the same name, since all hidden fields with the
802same name are encapsulated by one object of type {@link Hidden}).
803</ul>
804*/
805public Hidden getSubmittedField(HttpServletRequest req, String name) 
806  {
807  String str = req.getParameter(name);
808
809  if (str == null)
810    str = "";
811
812  Hidden hidden = new Hidden(name, str);
813  hidden.dynamicallyAdded = true;
814  return hidden;
815  }
816
817/**
818Returns all the fields that <b>contains</b> the specified name. This is
819useful when a bunch of fields are created via database lookup tables (and
820each such field is prefixed/suffixed by say <tt>lookup_name + some
821number</tt>).
822
823@param    name  the name of the field
824
825@return   a List containing fields with matching names or or an empty list
826      if no elements containing that name were found in this form.
827**/
828public List getContaining(String name) 
829  {
830  final List list = new ArrayList();
831  final Iterator it = fields.values().iterator();
832  while (it.hasNext())
833    {
834    final Field f = (Field) it.next();
835    if ( f.getName().indexOf(name) >= 0 )
836      list.add(f);
837    }
838  return list;
839  }
840
841/**
842Returns the {@link FieldRef} with the specified key. This is
843used to retrieve an arbitrary object added the via {@link
844#addObject} method. Returns <tt>null</tt> if no object is
845found for the specified key.
846*/
847public FieldRef getFieldRef(String key) 
848  {
849  return (FieldRef) fieldRef.get(key); 
850  }
851
852public java.util.Timer getTimer() 
853  {
854  synchronized (this) {
855    if (timer == null) {
856      timer = new java.util.Timer();
857      }
858    }
859    
860  return timer;
861  }
862
863/** 
864Returns the field with the specific name as a {@link Select}.
865
866@throws IllegalArgumentException if a field with the specified name does not exist or
867                 if the field exists but is not of type Select
868**/
869public Select getSelect(String name)
870  {
871  return (Select) get(name);
872  }
873
874/** 
875Returns the field with the specific name as a {@link RefreshableSelect}.
876
877@throws IllegalArgumentException if a field with the specified name does not exist or
878                 if the field exists but is not of type RefreshableSelect
879**/
880public RefreshableSelect getUpdatableSelect(String name)
881  {
882  return (RefreshableSelect) get(name);
883  }
884
885
886
887/** 
888Returns the field with the specific name as a {@link Checkbox}.
889
890@throws IllegalArgumentException if a field with the specified name does not exist or
891                 if the field exists but is not of type Checkbox
892**/
893public Checkbox getCheckbox(String name)
894  {
895  return (Checkbox) get(name);
896  }
897
898/** 
899Returns the field with the specific name as a {@link
900Checkbox}. 
901
902@throws IllegalArgumentException if a field with the specified name does not exist or
903                 if the field exists but is not of type Checkbox
904**/
905public CheckboxGroup getCheckboxGroup(String name)
906  {
907  return (CheckboxGroup) get(name);
908  }
909
910/** 
911Returns the field with the specific name as a {@link
912Hidden}.
913
914@throws IllegalArgumentException if a field with the specified name does not exist or
915                 if the field exists but is not of type Hidden
916**/
917public Hidden getHidden(String name)
918  {
919  return (Hidden) get(name);
920  }
921
922/** 
923Returns the field with the specific name as a {@link Text}.
924
925@throws IllegalArgumentException if a field with the specified name does not exist or
926                 if the field exists but is not of type Text
927**/
928public Text getText(String name)
929  {
930  return (Text) get(name);
931  }
932
933/** 
934Returns the field with the specific name as a {@link
935TextArea}.
936
937@throws IllegalArgumentException if a field with the specified name does not exist or
938                 if the field exists but is not of type TextArea
939**/
940public TextArea getTextArea(String name)
941  {
942  return (TextArea) get(name);
943  }
944
945/** 
946Returns the field with the specific name as a {@link
947Password}.
948
949@throws IllegalArgumentException if a field with the specified name does not exist or
950                 if the field exists but is not of type Password
951**/
952public Password getPassword(String name)
953  {
954  return (Password) get(name);
955  }
956
957/** 
958Returns the field with the specific name as a {@link Radio}.
959
960@throws IllegalArgumentException if a field with the specified name does not exist or
961                 if the field exists but is not of type Radio
962**/
963public Radio getRadio(String name)
964  {
965  return (Radio) get(name);
966  }
967
968/** 
969Returns the field with the specific name as a {@link
970Checkbox}.
971
972@throws IllegalArgumentException if a field with the specified name does not exist or
973                 if the field exists but is not of type Checkbox
974**/
975public RadioGroup getRadioGroup(String name)
976  {
977  return (RadioGroup) get(name);
978  }
979  
980public String toString() 
981  { 
982  final int border_len = 70;
983  final String nl = fc.io.IOUtil.LINE_SEP;
984
985  final int formval_size = formValidators.size();
986  final int fieldref_size = fieldRef.size();
987  StringBuffer buf = new StringBuffer(256);
988  StringBuffer tmp = null;
989  
990  buf.append(nl);
991  buf.append(StringUtil.repeat('-', border_len));
992  buf .append(nl)
993    .append("Form Name: ")
994    .append(name)
995    .append(" [")
996    .append(fields.size())
997    .append(" Fields]")
998    .append(" [")
999    .append(formval_size)
1000    .append(" FormValidators]")
1001    .append(" [")
1002    .append(fieldref_size)
1003    .append(" FieldRefs]");
1004
1005     if (timer != null) {
1006      buf.append(" [Attached Timer: ")
1007         .append(timer)
1008         .append(" ]");
1009      }
1010      
1011    buf.append(nl);
1012    
1013    
1014  ByteArrayOutputStream out = null;
1015  TablePrinter.PrintConfig config = null;
1016  TablePrinter p = null;
1017  
1018  
1019  out = new ByteArrayOutputStream(2048);
1020  config = new TablePrinter.PrintConfig();
1021  config.setPrintBorders(false);
1022  config.setCellSpacing(1);
1023  config.setCellPadding(0);
1024  config.setAutoFit(true);
1025  p = new TablePrinter(3, new PrintStream(out), config);
1026  p.startTable();
1027
1028  p.startRow();
1029  p.printCell("FIELD");
1030  p.printCell("TYPE");
1031  p.printCell("FIELD_VALIDATORS");
1032  p.endRow();
1033
1034  Iterator it = fields.values().iterator();
1035  while(it.hasNext()) 
1036    {
1037    Field f = (Field) it.next();
1038    p.startRow();
1039    p.printCell(f.getName());
1040    p.printCell(f.getType().toString());
1041    List list = f.getValidators();
1042    int size = list.size();
1043    if (size == 0) {
1044      p.printCell("-");
1045      }
1046    else{
1047      tmp = new StringBuffer(256);
1048      for (int n = 0; n < size; n++) 
1049        {
1050        if (n != 0)
1051          tmp.append(", ");
1052        FieldValidator fv = (FieldValidator) list.get(n);
1053        if (fv instanceof VText) {
1054          tmp.append(fv); //prints a bit more info.
1055          }
1056        else{
1057          String s = fv.getClass().getName();
1058          tmp.append(s.substring((s.lastIndexOf(".") + 1), s.length()));
1059          }
1060        }
1061      p.printCell(tmp.toString());
1062      } 
1063    p.endRow();
1064    }
1065  
1066  p.endTable();
1067  buf.append(out.toString());
1068
1069  if (formval_size > 0) 
1070    {
1071    buf.append(nl); 
1072    
1073    out.reset();
1074    config = new TablePrinter.PrintConfig();
1075    config.setPrintBorders(false);
1076    config.setCellSpacing(1);
1077    config.setCellPadding(0);
1078    config.setAutoFit(true);
1079    p = new TablePrinter(1, new PrintStream(out), config);
1080    p.startTable();
1081
1082    p.startRow();
1083    p.printCell("Form-Validators....");
1084    p.endRow();
1085    
1086    it = formValidators.entrySet().iterator();
1087    while (it.hasNext())
1088      {
1089      p.startRow();
1090      Map.Entry me = (Map.Entry) it.next();
1091      String key = (String) me.getKey();
1092      tmp = new StringBuffer(tmp.length() + 2);
1093      tmp.append("\t");
1094      tmp.append(key);
1095      p.printCell(tmp.toString());
1096      p.endRow();
1097      }
1098    
1099    p.endTable();
1100    buf.append(out.toString());
1101    }
1102
1103  if (fieldref_size > 0) 
1104    {
1105    buf.append(nl); 
1106    
1107    out.reset();
1108    config = new TablePrinter.PrintConfig();
1109    config.setPrintBorders(false);
1110    config.setCellSpacing(1);
1111    config.setCellPadding(0);
1112    config.setAutoFit(true);
1113    p = new TablePrinter(2, new PrintStream(out), config);
1114    p.startTable();
1115
1116    p.startRow();
1117    p.printCell("Field References....");
1118    p.printCell("");
1119    p.endRow();
1120    
1121    it = fieldRef.values().iterator();
1122    while (it.hasNext())
1123      {
1124      p.startRow();
1125      FieldRef fr = (FieldRef) it.next();
1126      tmp = new StringBuffer(tmp.length() + 2);
1127      tmp.append("\t");
1128      tmp.append(fr.getName());
1129      p.printCell(tmp.toString());
1130      tmp = new StringBuffer(128);
1131      if (fr.getList() != null) {
1132        tmp.append(fr.getList().size());
1133        tmp.append(" fields");
1134        }
1135      else if (fr.getMap() != null) {
1136        tmp.append(fr.getMap().size());
1137        tmp.append(" fields");
1138        }
1139      else {
1140        tmp.append(fr.getObject());
1141        }
1142      p.printCell(tmp.toString());
1143      p.endRow();
1144      }
1145    
1146    p.endTable();
1147    buf.append(out.toString());
1148    }
1149
1150  buf.append(StringUtil.repeat('-', border_len));
1151  return buf.toString();
1152  }
1153
1154}          //~class Form