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    An object that contains form data submitted by a user. This object
020    represents the data submitted by a browser upon each time the form is
021    submitted. This object can then be passed along to various other methods
022    in the form framework as needed. This object can also be created manually
023    before the form is shown if default/initial values must differ per user
024    (say, when user must initially see their last filled value which is
025    retreived from a database).
026    
027    @see {@link Form#handleSubmit handleSubmit}. 
028    @author hursh jain
029    */
030    public final class FormData 
031    {
032    //The following 2 store the same info, one all the
033    //error messages, the other validator-->error message
034    //
035    //list of of form errors
036    List      formValidateErrorsList;
037    //form-validator-->error message
038    Map       formValidateErrorsMap;
039    //
040    //fieldname->list of errors 
041    Map       fieldValidateErrors; 
042    //submitted value of each field, fieldname->value
043    Map       fieldValues; 
044    //
045    Set       disabledFields;
046    //dynamically added fields
047    Map       dynamicFields;
048    //
049    boolean     dependencyUpdated = false;
050    //
051    Log       log;
052    //
053    Connection    connection; /*remains null typically until set*/
054    //
055    Map       messages; //usually null
056    //
057    Map       validatedData; //mostly null
058    
059    public FormData(Log log) {
060      this.log = log;
061      fieldValues = new HashMap();
062      formValidateErrorsList = new ArrayList();
063      formValidateErrorsMap = new HashMap();
064      fieldValidateErrors = new HashMap();
065      disabledFields = new HashSet();
066      }
067    
068    public FormData() {
069      this(Log.getDefault());
070      }
071      
072    /* 
073    Adds a new validation error for the specified field 
074    */
075    List createFieldErrorList(Field field) 
076      {
077      String name = field.name;
078      List list = (List) fieldValidateErrors.get(name);
079      if (list == null) {
080        list = new ArrayList();
081        fieldValidateErrors.put(name, list);
082        }
083      return list;
084      }
085    
086    /**
087    Returns a map containing all validation errors for all
088    fields [map: field->List_of_errors]
089    */
090    Map getFieldErrors() {
091      return fieldValidateErrors;
092      }
093    
094    /**
095    Returns a list containing all validation errors for the
096    specified field. If  
097    */
098    List getFieldErrors(Field field) {
099      return (List) fieldValidateErrors.get(field.name);
100      }
101    
102    void addFormError(String validatorName, String errmsg)
103      {
104      formValidateErrorsList.add(errmsg);
105      
106      if (validatorName != null)
107        {
108        List list = (List) formValidateErrorsMap.get(validatorName);
109        if (list == null) {
110          list = new ArrayList();
111          formValidateErrorsMap.put(validatorName, list);   
112          } 
113        list.add(errmsg);
114        }
115      }
116    
117    /**
118    Returns a list containing all form level validation errors
119    */
120    Collection getFormErrorsCollection() {
121      return formValidateErrorsList;
122      }
123    
124    /**
125    Returns a map containing all form level validation errors keyed by the
126    validator name. The value in the map is a List containing all errors for
127    that validator. If the error was added using Form.addError(fd, null, "foo"),
128    then the MAP won't have any data (since the key was null) but the form error
129    LIST will have the error.
130    */
131    Map getFormErrorsMap() {
132      return formValidateErrorsMap;
133      }
134    
135    void putData(String fieldname, Object obj) {
136      fieldValues.put(fieldname, obj);
137      }
138    
139    //set since i keep forgetting whether it's set or put
140    void setData(String fieldname, Object obj) {
141      fieldValues.put(fieldname, obj);
142      }
143        
144    Object getData(String fieldname) {
145      return fieldValues.get(fieldname);
146      }
147    
148    void enableField(String fieldname, boolean enable)  
149      {
150      if (enable) {
151        if (disabledFields.remove(fieldname));
152        }
153      else {
154        disabledFields.add(fieldname);
155        }
156      }
157    
158    boolean isEnabled(String fieldname)
159      {
160      return ! disabledFields.contains(fieldname);
161      }
162    
163    
164    void addMessage(String key, String value) 
165      {
166      //no need to synchronize this, form processing is sequential/per-thread
167      //for each fd.
168      if (messages == null)
169        messages = new HashMap();
170      
171      messages.put(key, value);
172      }
173    
174    String getMessage(String key) 
175      {
176      if (messages == null || ! messages.containsKey(key))
177        return "";
178      
179      return (String) messages.get(key);
180      }
181    
182    //================== Public methods =============================
183    //For convenience. These can go in the form itself if need be and made 
184    //private here.
185    
186    /**
187    Useful if strings entered by the user in the browser form are converted
188    to dates/times etc as part of the validation. Validators can then put
189    those date/time/whatever objects here and then those same conversions
190    don't have to be repeated at a later stage while saving the form out to 
191    a database.
192    */
193    public void putValidatedData(String fieldname, Object obj)
194      {
195      if (validatedData == null)
196        validatedData = new HashMap();
197      
198      validatedData.put(fieldname, obj);
199      }
200    
201    public Object getValidatedData(String fieldname)
202      {
203      return validatedData.get(fieldname);
204      }
205    
206    
207    
208    /**
209    Sets a connection to be used during this request. Meant primarily for 
210    refresher/dependent fields. 
211    */
212    public void setConnection(Connection con) {
213      this.connection = con;
214      }
215    
216    public Connection getConnection() {
217      return connection;
218      }
219      
220    /**
221    Adds a {@link Hidden} field to this form. This field exists only for the
222    duration of this form data object. This field can be rendered.
223    */
224    public void addDynamicField(Hidden hidden) {
225      //no need to synchronize this, form processing is sequential & mem-vis
226      //not a concern... i guess 
227      if (dynamicFields == null)
228        dynamicFields = new HashMap();
229      
230      hidden.dynamicallyAdded = true;
231      dynamicFields.put(hidden.getName(), hidden);  
232      }
233    
234    /**
235    Gets a {@link Hidden} field added to this formdata object. Returns
236    <tt>null</tt> if that field does not exist.
237    */
238    public Hidden getDynamicField(String fieldName) 
239      {
240      if (dynamicFields == null) {
241        log.warn("Cannot obtain field:", fieldName, " from this formdata. This field is only available after being added to the formdata and is lost when the formdata object is lost, typically after the form is rendered.");   
242        return null;
243        }
244      
245      return (Hidden) dynamicFields.get(fieldName); 
246      }
247    
248    /**
249    Dependencies should call this method to set the dependencyUpdated flag to
250    true anytime there is a dependency change.
251    */
252    public void setDependencyUpdated()
253      {
254      dependencyUpdated = true;
255      }
256    
257    public String toString()
258      {
259      return new ToString(this, ToString.Style.VisibleLevel.PRIVATE)
260        .reflect().render();  
261        }
262     
263    }