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 <div> 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 <div> 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