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/**
019An object that contains form data submitted by a user. This object
020represents the data submitted by a browser upon each time the form is
021submitted. This object can then be passed along to various other methods
022in the form framework as needed. This object can also be created manually
023before 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
025retreived from a database).
026
027@see {@link Form#handleSubmit handleSubmit}. 
028@author hursh jain
029*/
030public 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
036List      formValidateErrorsList;
037//form-validator-->error message
038Map       formValidateErrorsMap;
039//
040//fieldname->list of errors 
041Map       fieldValidateErrors; 
042//submitted value of each field, fieldname->value
043Map       fieldValues; 
044//
045Set       disabledFields;
046//dynamically added fields
047Map       dynamicFields;
048//
049boolean     dependencyUpdated = false;
050//
051Log       log;
052//
053Connection    connection; /*remains null typically until set*/
054//
055Map       messages; //usually null
056//
057Map       validatedData; //mostly null
058
059public 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
068public FormData() {
069  this(Log.getDefault());
070  }
071  
072/* 
073Adds a new validation error for the specified field 
074*/
075List 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/**
087Returns a map containing all validation errors for all
088fields [map: field->List_of_errors]
089*/
090Map getFieldErrors() {
091  return fieldValidateErrors;
092  }
093
094/**
095Returns a list containing all validation errors for the
096specified field. If  
097*/
098List getFieldErrors(Field field) {
099  return (List) fieldValidateErrors.get(field.name);
100  }
101
102void 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/**
118Returns a list containing all form level validation errors
119*/
120Collection getFormErrorsCollection() {
121  return formValidateErrorsList;
122  }
123
124/**
125Returns a map containing all form level validation errors keyed by the
126validator name. The value in the map is a List containing all errors for
127that validator. If the error was added using Form.addError(fd, null, "foo"),
128then the MAP won't have any data (since the key was null) but the form error
129LIST will have the error.
130*/
131Map getFormErrorsMap() {
132  return formValidateErrorsMap;
133  }
134
135void putData(String fieldname, Object obj) {
136  fieldValues.put(fieldname, obj);
137  }
138
139//set since i keep forgetting whether it's set or put
140void setData(String fieldname, Object obj) {
141  fieldValues.put(fieldname, obj);
142  }
143    
144Object getData(String fieldname) {
145  return fieldValues.get(fieldname);
146  }
147
148void enableField(String fieldname, boolean enable)  
149  {
150  if (enable) {
151    if (disabledFields.remove(fieldname));
152    }
153  else {
154    disabledFields.add(fieldname);
155    }
156  }
157
158boolean isEnabled(String fieldname)
159  {
160  return ! disabledFields.contains(fieldname);
161  }
162
163
164void 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
174String 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/**
187Useful if strings entered by the user in the browser form are converted
188to dates/times etc as part of the validation. Validators can then put
189those date/time/whatever objects here and then those same conversions
190don't have to be repeated at a later stage while saving the form out to 
191a database.
192*/
193public void putValidatedData(String fieldname, Object obj)
194  {
195  if (validatedData == null)
196    validatedData = new HashMap();
197  
198  validatedData.put(fieldname, obj);
199  }
200
201public Object getValidatedData(String fieldname)
202  {
203  return validatedData.get(fieldname);
204  }
205
206
207
208/**
209Sets a connection to be used during this request. Meant primarily for 
210refresher/dependent fields. 
211*/
212public void setConnection(Connection con) {
213  this.connection = con;
214  }
215
216public Connection getConnection() {
217  return connection;
218  }
219  
220/**
221Adds a {@link Hidden} field to this form. This field exists only for the
222duration of this form data object. This field can be rendered.
223*/
224public 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/**
235Gets a {@link Hidden} field added to this formdata object. Returns
236<tt>null</tt> if that field does not exist.
237*/
238public 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/**
249Dependencies should call this method to set the dependencyUpdated flag to
250true anytime there is a dependency change.
251*/
252public void setDependencyUpdated()
253  {
254  dependencyUpdated = true;
255  }
256
257public String toString()
258  {
259  return new ToString(this, ToString.Style.VisibleLevel.PRIVATE)
260    .reflect().render();  
261    }
262 
263}