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.sql.*;
013
014 import fc.jdbc.*;
015 import fc.io.*;
016 import fc.util.*;
017
018 /**
019 Represents an HTML form. It is possible to instantiate a different form
020 object per user and store each form in that users session scope. However,
021 more typically, instances of this object are instantiated once per unique
022 form and stored in web application scope.
023 <p>
024 Method in this class are thread safe since form processing is done
025 entirely within various methods and the form processing results in the
026 creation of seperate {@link FormData} objects for each request.
027 <p>
028 Various {@link Field Fields} can be added to the form at form creation
029 time. Some fields (like {@link Hidden} can also be added per user/request,
030 dynamically, to the {@link FormData} object. This is useful while saving
031 some user or request specific information.
032 <p>
033 A form collects data from the front-end/web-client and often saves it to a
034 back 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>
042 In both cases, any errors associated with the form can be shown back to the
043 user. This framework allows both types of validation methods to be used.
044 Using validation and/or showing validation errors is optional. This framework
045 can simply be used to maintain form state and validation can be done via
046 other ad-hoc code (outside of this framework) as needed.
047 <p>
048 An HTML form presented to the user can contain fields from more than 1
049 database table. For example, there could to 2 database tables, say
050 "person" and "office" both with a "other" column. Since <u>each field
051 contained in the form object must have a unique name</u>, there will be a
052 name clash between the fields/attributes of the entities when 2 fields
053 with the name "other" are added to the form.
054 <p>
055 There 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).
059 Different instances of form objects (say 1 per table) act as seperate
060 name-spaces and fields having the same name can exist in these seperate
061 instances. However, the dynamic server page where the form object is
062 rendered/submitted has to obtain these seperate instances from the application
063 object (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
065 table. In this case, some or all field names can be prepended (or suffixed)
066 with the name of the database table. This removes any name clashes if done
067 only for clashing fields; if done for every field may help in documenting
068 which table that field belongs to. There is more typing involved with this
069 approach.
070 </ol>
071 Either of the above approaches is valid.
072 <p>
073 Sometimes, other methods need to obtain a field (for a particular table) by
074 name. For example, the managers generated by {@link fc.jdbc.dbo.Generate}
075 have a <tt>addValidators(form)</tt> method that extract the fields for a
076 particular table and add validators to that field. This method in
077 particular need to be passed either the appropriate form object in which
078 that field exists (if there are multiple form objects) or in the case of
079 only 1 form object, passed the prefix for that particular field, if that
080 field was added with a prefix to it's name.
081
082 @author hursh jain
083 **/
084 public class Form
085 {
086 //useful for method that return empty lists/maps in lieu of nulls
087 static final public List empty_list =
088 Collections.unmodifiableList(new ArrayList());
089
090 static final public Map empty_map =
091 Collections.unmodifiableMap(new HashMap());
092
093 protected Log log;
094 //fields have field-level validators
095 protected LinkedHashMap fields;
096 //collections of fields -- t hese fields are directly added
097 //to the fields map also
098 protected LinkedHashMap fieldRef;
099 protected LinkedHashMap formValidators; //form-level validators
100 protected List dependencies;
101 protected String name; //the form name
102 protected String method;
103 protected String actionURL;
104 protected String displayURL;
105 //protected String fieldNamePrefix;
106 protected java.util.Timer timer;
107 protected SubmitHackedHandler hackHandler;
108 /**
109 Creates a new form with the specified name, method (get/post) and URL
110 **/
111 public 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 /*
130 Creates a new form with the specified name, method (get/post) and URL
131 and the default {@link SubmitHackedHandler}
132 **/
133 public Form(String name, String method, String actionURL, Log log)
134 {
135 this(name, method, actionURL, log, null);
136 }
137
138 /**
139 Creates a new form with logging to the default log retreived by
140 {@link Log#getDefault} and the default {@link SubmitHackedHandler}
141 **/
142 public Form(String name, String method, String actionURL)
143 {
144 this(name, method, actionURL, Log.getDefault(), null);
145 }
146
147 /**
148 Creates a new form with the specified name, method=POST and no action URL
149 **/
150 public Form(String name)
151 {
152 this(name, "post", null);
153 }
154
155 /**
156 This method should be called when the form is removed from session or
157 application scope. It is very important to do this if there are background
158 update tasks because this method stops such background refresh timer thread
159 (which would otherwise continue to run).
160 */
161 public void destroy()
162 {
163 timer.cancel();
164 fields.clear();
165 }
166
167 /**
168 Removes the specified field from the form. See {@link
169 java.util.Map#remove(Object)} for the general contract.
170
171 @param field the field to remove from the form.
172 **/
173 public Object remove(Field field) {
174 return fields.remove(field.getName());
175 }
176
177 /**
178 Adds the specified element to the form. The added field can
179 later be retrieved via it's name. The field iteration order
180 will be the same order in which fields are added via this
181 method.
182 <p>
183 Note, field names have to be unique. "Grouped" fields (such
184 as HTML form checkboxes or radio buttons) which require the
185 same field name are encapsulated by a single appropriate
186 grouped field object such as {@link RadioGroup} and {@link
187 CheckboxGroup}. This field object itself should have a
188 unique 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 **/
194 public 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 /**
214 Adds the specified dependency to the form.
215 */
216 public Form add(Dependency d)
217 {
218 dependencies.add(d);
219
220 return this;
221 }
222
223
224 /**
225 Adds a {@link FieldRef} to this Form. This is useful when
226 to track a set of related fields in a form. (Note: those fields
227 <b>must</b> also directly be added to the form).
228 */
229 public 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 /**
240 Adds a <b>form level</b> validation error message. This method is needed when
241 form level validation is done via classes that are <b>not</b> subclasses
242 of {@link FormValidator} (these classes are used outside of the
243 validation framework and their error message still needs to be added to
244 the form data).
245 <p>
246 <b> This is a very useful method for adding the results of arbitrary code
247 to 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 **/
258 public void addError(FormData fd, String errorKey, String errorMessage)
259 {
260 fd.addFormError(errorKey, errorMessage);
261 }
262
263 /**
264 Convenience method that invokes {@link addError(FormData, String, String)
265 addError} with an empty validation <tt>errorKey</tt>.
266 */
267 public void addError(FormData fd, String errorMessage)
268 {
269 addError(fd, null, errorMessage);
270 }
271
272 /**
273 Adds a {@link Hidden} field to this form. This field exists only for the
274 duration of this form data object and is useful for request specific
275 data.
276 */
277 public void addDynamicField(FormData fd, Hidden f) {
278 fd.addDynamicField(f);
279 }
280
281 /**
282 Adds a form level validator to this form. Note, validator
283 names have to be unique and not clash with other form level
284 validator names.
285
286 @throws IllegalArgumentException if a validator with the same name
287 already exists in this form.
288 **/
289 public 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 /**
298 Adds an arbitrary message for this request. Messages can include arbitrary
299 warnings, usage help etc.
300 */
301 public void addMessage(FormData fd, String key, String value)
302 {
303 fd.addMessage(key, value);
304 }
305
306 /**
307 Returns true if the form contains the specified field.
308 */
309 public boolean containsField(String fieldName)
310 {
311 return fields.containsKey(fieldName);
312 }
313
314 /**
315 Retrieves a previously added message or <tt>null</tt> if the speccified
316 form data was <tt>null</tt> or if the message was not found.
317 */
318 public String getMessage(FormData fd, String key)
319 {
320 if (fd == null)
321 return null;
322
323 return fd.getMessage(key);
324 }
325
326 public void addSubmitHackedHandler(SubmitHackedHandler handler)
327 {
328 Argcheck.notnull(handler, "handler param was null");
329 this.hackHandler = handler;
330 }
331
332 protected void handleSubmitHacked(HttpServletRequest req, String msg)
333 throws SubmitHackedException
334 {
335 hackHandler.handle(req, msg);
336 }
337
338 /**
339 Processes the submitted form. <b>This method must be called after each form
340 submission by the servlet or page code that handles form submits</b>. This
341 method 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,
345 then those dependencies are updated. Validation is not carried out in this
346 case since the form has changed and the user needs to typically carry out
347 some other action before submitting the form.
348 <li>if there are no updated dependencies, validates the form via any
349 validators attached to the form and it's fields.
350 </ol>
351 Note: To see if there were any validation errors, invoke the {@link
352 hasError(FormData)} method with the formdata returned by this method.
353 Seeing validation errors is optional of course, and so is using validators
354 in 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 **/
362 public FormData handleSubmit(HttpServletRequest req)
363 throws ServletException, IOException
364 {
365 return handleSubmit(req, null);
366 }
367
368 /**
369 This method is similar to {@link handleSubmit(HttpServletRequest)} except
370 it stores the specified connection in the form data. This connection can
371 be used by any attached dependency classes for database queries when
372 updating form depedencies.
373 <p>
374 Note, we can set arbitrary objects in the {@link HttpServletRequest} via
375 it's {@link HttpServletRequest#setattribute setAttribute} method.
376 However, specifying and storing the connection in the FormData is easier
377 to remember and use.
378 <p>
379 There are 2 cases:
380 <ol>
381 <li>The dependent fields (if any) will internally use {@link WebApp#getConnection}
382 to obtain a connection and no connection has to be set in the form data.</li>
383 The {@link handleSubmit(HttpServletRequest)} can then be used.
384 <li>The dependent fields (if any) classes will expect a connection in the form data. In
385 that case, this method should be invoked.</li>
386 </ol>
387 If there are no dependent fields, then the {@link handleSubmit(HttpServletRequest)}
388 is 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 */
395 public FormData handleSubmit(HttpServletRequest req, Connection con)
396 throws 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
411 public FormData refresh(HttpServletRequest req)
412 throws ServletException, IOException
413 {
414 return refresh(req, null);
415 }
416
417 /**
418 Refreshes the form for this request by running all attached refreshers
419 and dependencies. Maintains form state. Does not run any validatators.
420 Useful for re-displaying a form when lookup fields have changed in the
421 database.
422 <p>
423 <u>To refresh the form for all users/requests from this point onwards,
424 create a new instance of the form and replace the existing instance
425 of the form with the newly created instance.</u>
426 */
427 public FormData refresh(HttpServletRequest req, Connection con)
428 throws 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 /**
440 Populates field's based on the parameters submitted via the specified
441 request and stores it into the specified FormData object.
442 **/
443 protected FormData populate(HttpServletRequest req)
444 throws 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
456 protected void updateDependencies(FormData fd, HttpServletRequest req)
457 throws 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 /**
467 Validates each field and also calls each form level validator.
468 <p>
469 Validation is a 2-step process. First, only enabled fields are validated.
470 Some validators may enable or disable fields as part of their validation
471 strategy (for example, if the user chooses a radio button marked 'detail',
472 then fields corresponding to the detail section need to be enabled). (other
473 fields might be disabled). Fields that are re-enabled in this fashion are
474 validated by this method.
475 <p>
476 After running through all the validators, returns <tt>false</tt> is there
477 is a validation error, <tt>true</tt> otherwise. The field and form
478 validation errors can then be retrieved via the {@link
479 FormData#getFieldErrors} and {@link FormData#getFormErrorsCollection},
480 {@link FormData#getFormErrorsMap} methods.
481 **/
482 protected 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 /**
522 Returns <tt>true</tt> if validation did not succeed.
523 */
524 public 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 /**
544 Convenience method to render <i>form-level</i> validation errors. This method
545 writes validation error message for the specified form level validator.
546 The error messages are encapulated in a html <div> tag and the class
547 of this tag is set to <tt>form-errmsg</tt>. Each error message within this
548 div 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 **/
552 public void renderError(FormData fd, Writer writer)
553 throws 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 /**
580 Convenience method to render a validation error for a particluar form level
581 validator. The error messages are encapulated in a html <div> tag and
582 the 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 **/
587 public void renderErrorFor(
588 FormData fd, Writer writer, String formValidatorName)
589 throws 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 **/
621 public 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 **/
631 public Collection getFormErrorsCollection(FormData fd) {
632 return fd.getFormErrorsCollection();
633 }
634
635 /**
636 Returns a list containing all form-level errors for the specified
637 validator. If there are no errors for the specified validator, returns an
638 empty list.
639
640 @param fd a non-null form data object
641 @param formValidatorName a form validator name
642 */
643 public 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 /**
654 Renders <b>all</b> dynamically added hidden fields of the form. Dynamic
655 fields are created/ added per user per submit to the {@link FormData}
656 if/as needed.
657 <p>
658 <b>Note</b>: Dynamic fields are <b>not</b> part of a form's fields and
659 are not available via
660 */
661 public void renderDynamicFields(FormData fd, Writer out)
662 throws 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 **/
675 public String getName() {
676 return name;
677 }
678
679 /**
680 Sets an arbitrary application specific string that can
681 later be retrieved. Some applications may find
682 this method useful.
683 **/
684 public void setActionURL(String url) {
685 this.actionURL = url;
686 }
687
688 /**
689 Returns the action URL string. Returns <tt>null</tt>
690 if no url has been set.
691 **/
692 public String getActionURL() {
693 return actionURL;
694 }
695
696
697 /**
698 Sets an arbitrary application specific string. Intended
699 to store the URL to the jsp or front end page that will
700 render this form for the user.
701 **/
702 public void setDisplayURL(String url) {
703 this.displayURL = url;
704 }
705
706 /**
707 Returns the display URL string. Returns <tt>null</tt>
708 if no display url has been set.
709 **/
710 public String getDisplayURL() {
711 return displayURL;
712 }
713
714
715 /**
716 Returns a collection view of all the fields contained in this
717 form.
718 **/
719 public 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 /**
757 Returns 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 **/
764 public 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 /**
775 Gets 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
777 field does not exist. <u>Dyanamic fields returned by this method are only
778 available for the duration of the form data object and <u>can not
779 obtain a field from a submitted form.</u> (use {@link getDynamicField(FormData, HttpServletRequest)}
780 for that purpose.
781 */
782 public Hidden getDynamicField(FormData fd, String fieldName)
783 {
784 if (fd == null)
785 return null;
786
787 return fd.getDynamicField(fieldName);
788 }
789
790
791 /**
792 Once the form is displayed to the user (with, say, hidden fields that
793 were added dynamically for that request), then when that form is
794 submitted, those dynamic fields can be retrieved via any of:
795 <ul>
796 <li> the {@link HttpServletRequest#getParameter} method (often the easiest
797 way)
798 <li> the {@link #getSubmittedField(HttpServletRequest)} method (which will
799 attempt to read the request parameter with the specified name and return
800 is as a Hidden field. (this is slightly more convenient if there is more
801 than 1 hidden field with the same name, since all hidden fields with the
802 same name are encapsulated by one object of type {@link Hidden}).
803 </ul>
804 */
805 public 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 /**
818 Returns all the fields that <b>contains</b> the specified name. This is
819 useful when a bunch of fields are created via database lookup tables (and
820 each such field is prefixed/suffixed by say <tt>lookup_name + some
821 number</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 **/
828 public 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 /**
842 Returns the {@link FieldRef} with the specified key. This is
843 used to retrieve an arbitrary object added the via {@link
844 #addObject} method. Returns <tt>null</tt> if no object is
845 found for the specified key.
846 */
847 public FieldRef getFieldRef(String key)
848 {
849 return (FieldRef) fieldRef.get(key);
850 }
851
852 public 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 /**
864 Returns 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 **/
869 public Select getSelect(String name)
870 {
871 return (Select) get(name);
872 }
873
874 /**
875 Returns 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 **/
880 public RefreshableSelect getUpdatableSelect(String name)
881 {
882 return (RefreshableSelect) get(name);
883 }
884
885
886
887 /**
888 Returns 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 **/
893 public Checkbox getCheckbox(String name)
894 {
895 return (Checkbox) get(name);
896 }
897
898 /**
899 Returns the field with the specific name as a {@link
900 Checkbox}.
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 **/
905 public CheckboxGroup getCheckboxGroup(String name)
906 {
907 return (CheckboxGroup) get(name);
908 }
909
910 /**
911 Returns the field with the specific name as a {@link
912 Hidden}.
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 **/
917 public Hidden getHidden(String name)
918 {
919 return (Hidden) get(name);
920 }
921
922 /**
923 Returns 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 **/
928 public Text getText(String name)
929 {
930 return (Text) get(name);
931 }
932
933 /**
934 Returns the field with the specific name as a {@link
935 TextArea}.
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 **/
940 public TextArea getTextArea(String name)
941 {
942 return (TextArea) get(name);
943 }
944
945 /**
946 Returns the field with the specific name as a {@link
947 Password}.
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 **/
952 public Password getPassword(String name)
953 {
954 return (Password) get(name);
955 }
956
957 /**
958 Returns 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 **/
963 public Radio getRadio(String name)
964 {
965 return (Radio) get(name);
966 }
967
968 /**
969 Returns the field with the specific name as a {@link
970 Checkbox}.
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 **/
975 public RadioGroup getRadioGroup(String name)
976 {
977 return (RadioGroup) get(name);
978 }
979
980 public 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