View Javadoc

1   /*
2    * Copyright 2006-2016 The JGUIraffe Team.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.jguiraffe.gui.forms;
17  
18  import java.util.HashMap;
19  import java.util.Map;
20  
21  import net.sf.jguiraffe.transform.ValidationMessage;
22  import net.sf.jguiraffe.transform.ValidationMessageLevel;
23  import net.sf.jguiraffe.transform.ValidationResult;
24  
25  import org.apache.commons.lang.text.StrSubstitutor;
26  
27  /**
28   * <p>
29   * A class for converting a {@link FormValidatorResults} object to text.
30   * </p>
31   * <p>
32   * If validation of a form fails, usually the corresponding error messages have
33   * to be displayed somehow to the user. This class takes a
34   * <code>FormValidationResults</code> object as input, iterates over the
35   * contained validation error messages and converts them to text. The result is
36   * a string that can be displayed in a message box for instance.
37   * </p>
38   * <p>
39   * The class can be configured with a number of template strings for specifying
40   * the desired output. There are four template strings that are evaluated:
41   * <ol>
42   * <li>The class iterates over all fields in the passed in {@code
43   * FormValidatorResults} object, for which error messages are found. Whenever a
44   * new field starts the {@code fieldHeaderTemplate} is issued.</li>
45   * <li>Then the single validation messages available for this field are
46   * processed. To each error message the {@code fieldErrorTemplate} template is
47   * applied.</li>
48   * <li>After the error messages the warning messages (if available) are
49   * processed. To each warning message the {@code fieldWarningTemplate} template
50   * is applied. If the {@code fieldWarningTemplate} is not defined, the {@code
51   * fieldErrorTemplate} is used instead. The output of warning messages can be
52   * suppressed at all by setting the {@code suppressWarnings} property to
53   * <b>true</b>.</li>
54   * <li>Finally, the {@code fieldFooterTemplate} template is output.</li>
55   * </ol>
56   * </p>
57   * <p>
58   * Templates are strings that can contain variables for the name of the current
59   * field and the current error message. When processing the templates the
60   * variables are replaced with their current values resulting in the text to be
61   * displayed. For variables the syntax <code>${...}</code> is used, which should
62   * be familiar from Ant. The following table lists the supported variables:
63   * <table border="1">
64   * <tr>
65   * <th>Variable</th>
66   * <th>Description</th>
67   * </tr>
68   * <tr>
69   * <td valign="top">field</td>
70   * <td>Will be replaced by the name of the current field. This variable is
71   * allowed in all templates.</td>
72   * </tr>
73   * <tr>
74   * <td valign="top">msgCount</td>
75   * <td>Will be replaced by the number of error messages for the current field.
76   * This could for instance be used in the header template to give an overview
77   * over the number of errors detected for the current input field.</td>
78   * </tr>
79   * <tr>
80   * <td valign="top">msg</td>
81   * <td>Will be replaced by the current error message. This variable is only
82   * supported by the <code>fieldErrorTemplate</code> template.</td>
83   * </tr>
84   * <tr>
85   * <td valign="top">msgIndex</td>
86   * <td>Will be replaced by the index of the current error message. The messages
87   * of a field are numbered from 1 up to <code>${msgCount}</code>. With this
88   * variable the index can be added to the resulting text.</td>
89   * </tr>
90   * </table>
91   * </p>
92   * <p>
93   * The default initialization leaves the header and footer template empty and
94   * sets the following error template: <code>${field}: ${msg}\n</code>. This
95   * results in output like
96   *
97   * <pre>
98   * Field1: Message1 for Field1
99   * Field2: Message1 for Field2
100  * Field2: Message2 for Field2
101  * Field3: Message1 for Field3
102  * </pre>
103  *
104  * By carefully designing the templates, more complex output can be generated as
105  * in the following example:
106  * <ul>
107  * <li>fieldHeaderTemplate = <code>${field} ${msgCount} error(s):\n</code></li>
108  * <li>fieldErrorTemplate = <code>- ${msg}</code></li>
109  * <li>fieldFooterTemplate = <code>\n</code></li>
110  * </ul>
111  * In this example the output will look like the following:
112  *
113  * <pre>
114  * Field1 1 error(s):
115  * - Message1 for Field1
116  * Field2 2 error(s):
117  * - Message1 for Field2
118  * - Message2 for Field2
119  * </pre>
120  *
121  * Templates that are <b>null</b> will be ignored.
122  * </p>
123  * <p>
124  * Implementation note: This class has a mutable state and thus is not
125  * thread-safe. However if it is initialized once and the templates are not
126  * changed later, it can be shared between multiple threads.
127  * </p>
128  *
129  * @author Oliver Heger
130  * @version $Id: FormValidationMessageFormat.java 205 2012-01-29 18:29:57Z oheger $
131  */
132 public class FormValidationMessageFormat
133 {
134     /** Constant for the default field errors template. */
135     public static final String DEF_ERRORS_TEMPLATE = "${field}: ${msg}\n";
136 
137     /** Constant for the field variable. */
138     public static final String VAR_FIELD = "field";
139 
140     /** Constant for the msg variable. */
141     public static final String VAR_MSG = "msg";
142 
143     /** Constant for the msgCount variable. */
144     public static final String VAR_MSG_COUNT = "msgCount";
145 
146     /** Constant for the msgIndex variable. */
147     public static final String VAR_MSG_INDEX = "msgIndex";
148 
149     /** Constant for the initial buffer size. */
150     private static final int BUF_SIZE = 256;
151 
152     /** Stores the template for the header of a field. */
153     private String fieldHeaderTemplate;
154 
155     /** Stores the template for the footer of a field. */
156     private String fieldFooterTemplate;
157 
158     /** Stores the template for the error messages of a field. */
159     private String fieldErrorTemplate;
160 
161     /** Stores the template for the warning messages of a field. */
162     private String fieldWarningTemplate;
163 
164     /** A flag whether warning messages should be suppressed. */
165     private boolean suppressWarnings;
166 
167     /**
168      * Creates a new instance of <code>FormValidationMessageFormat</code>.
169      */
170     public FormValidationMessageFormat()
171     {
172         setFieldErrorTemplate(DEF_ERRORS_TEMPLATE);
173     }
174 
175     /**
176      * Returns the template for the header of a field.
177      *
178      * @return the field header template
179      */
180     public String getFieldHeaderTemplate()
181     {
182         return fieldHeaderTemplate;
183     }
184 
185     /**
186      * Sets the template for the header of a field. This template will be
187      * processed at the beginning of a new input field with validation error
188      * messages.
189      *
190      * @param fieldHeaderTemplate the template for the header of a field
191      */
192     public void setFieldHeaderTemplate(String fieldHeaderTemplate)
193     {
194         this.fieldHeaderTemplate = fieldHeaderTemplate;
195     }
196 
197     /**
198      * Returns the template for the footer of a field.
199      *
200      * @return the field footer template
201      */
202     public String getFieldFooterTemplate()
203     {
204         return fieldFooterTemplate;
205     }
206 
207     /**
208      * Sets the template for the footer of a field. This template will be
209      * processed after the error messages of an input field have been output.
210      *
211      * @param fieldFooterTemplate the template for the footer of a field
212      */
213     public void setFieldFooterTemplate(String fieldFooterTemplate)
214     {
215         this.fieldFooterTemplate = fieldFooterTemplate;
216     }
217 
218     /**
219      * Returns the template for the error messages of an input field.
220      *
221      * @return the error messages template
222      */
223     public String getFieldErrorTemplate()
224     {
225         return fieldErrorTemplate;
226     }
227 
228     /**
229      * Sets the template for the error messages of an input field. For each
230      * validation error message associated with an input field this template
231      * will be processed.
232      *
233      * @param fieldErrorTemplate the field error template
234      */
235     public void setFieldErrorTemplate(String fieldErrorTemplate)
236     {
237         this.fieldErrorTemplate = fieldErrorTemplate;
238     }
239 
240     /**
241      * Returns the template for the warning messages of an input field.
242      *
243      * @return the warning messages template
244      */
245     public String getFieldWarningTemplate()
246     {
247         return fieldWarningTemplate;
248     }
249 
250     /**
251      * Sets the template for the warning messages of an input field. For each
252      * validation warning message associated with an input field this template
253      * will be processed. Warning messages are only processed if the {@code
254      * suppressWarnings} property is not set. If {@code suppressWarnings} is
255      * <b>false</b> and no specific template for warning messages is set, the
256      * error template is used.
257      *
258      * @param fieldWarningTemplate the field warnings template
259      */
260     public void setFieldWarningTemplate(String fieldWarningTemplate)
261     {
262         this.fieldWarningTemplate = fieldWarningTemplate;
263     }
264 
265     /**
266      * Returns a flag whether warning messages should be suppressed.
267      *
268      * @return the suppress warnings flag
269      */
270     public boolean isSuppressWarnings()
271     {
272         return suppressWarnings;
273     }
274 
275     /**
276      * Sets a flag whether warning messages should be suppressed. If this
277      * message is called with the parameter <b>true</b>, the output generated by
278      * this object contains only error messages.
279      *
280      * @param suppressWarnings the suppress warnings flag
281      */
282     public void setSuppressWarnings(boolean suppressWarnings)
283     {
284         this.suppressWarnings = suppressWarnings;
285     }
286 
287     /**
288      * The main formatting method. Transforms the passed in validation result
289      * object into a text according to the values of the current templates.
290      *
291      * @param res the object with the validation results (can be <b>null</b>,
292      *        then the result of this method is <b>null</b>)
293      * @param form the current <code>Form</code> object; this object is used for
294      *        obtaining the display names of the error fields; it can be
295      *        <b>null</b>, then the field names are used
296      * @return the corresponding text
297      */
298     public String format(FormValidatorResults res, Form form)
299     {
300         if (res == null)
301         {
302             return null;
303         }
304 
305         StringBuilder buf = new StringBuilder(BUF_SIZE);
306         for (String field : res.getErrorFieldNames())
307         {
308             processField(buf, res, form, field);
309         }
310 
311         return buf.toString();
312     }
313 
314     /**
315      * Transforms all validation messages found in the passed {@code
316      * FormValidatorResults} object for the given field name into a text
317      * according to the values of the current templates. This method can be
318      * called to process the messages of a single field only.
319      *
320      * @param res the object with the validation results (can be <b>null</b>,
321      *        then the result of this method is <b>null</b>)
322      * @param form the current <code>Form</code> object; this object is used for
323      *        obtaining the display names of the error fields; it can be
324      *        <b>null</b>, then the field names are used
325      * @param field the name of the field in question (if this field cannot be
326      *        found, result is an empty string)
327      * @return the corresponding text
328      */
329     public String formatField(FormValidatorResults res, Form form, String field)
330     {
331         if (res == null)
332         {
333             return null;
334         }
335 
336         StringBuilder buf = new StringBuilder(BUF_SIZE);
337         processField(buf, res, form, field);
338         return buf.toString();
339     }
340 
341     /**
342      * Initializes the variables for the specified field of the validation
343      * results object. This method is invoked at the beginning of the processing
344      * of a new field.
345      *
346      * @param vars the map with the variables
347      * @param res the results object
348      * @param form the form object (may be <b>null</b>)
349      * @param field the name of the current field
350      */
351     protected void setUpVariablesForField(Map<String, String> vars,
352             FormValidatorResults res, Form form, String field)
353     {
354         vars.put(VAR_FIELD, fetchDisplayName(form, field));
355         vars.put(VAR_MSG_COUNT, String.valueOf(res.getResultsFor(field)
356                 .getValidationMessages().size()));
357     }
358 
359     /**
360      * Initializes the variables for an error message. This method is invoked
361      * for each error message of a field. The passed in parameters represent the
362      * information available for the current error message. The variables for
363      * the field have already been initialized (
364      * <code>setUpVariablesForField()</code> has already been called).
365      *
366      * @param vars the map with the variables
367      * @param res the results object
368      * @param form the form object (may be <b>null</b>)
369      * @param field the name of the current field
370      * @param msg the current validation error message
371      * @param index the index of this message
372      */
373     protected void setUpVariablesForMessage(Map<String, String> vars,
374             FormValidatorResults res, Form form, String field, String msg,
375             int index)
376     {
377         vars.put(VAR_MSG, msg);
378         vars.put(VAR_MSG_INDEX, String.valueOf(index));
379     }
380 
381     /**
382      * Applies the template for the error messages to all messages available for
383      * the current field.
384      *
385      * @param buf the target buffer
386      * @param subst the substitutor
387      * @param res the validation results
388      * @param form the form
389      * @param field the current field
390      * @param variables the map with the variables
391      */
392     protected void processMessages(StringBuilder buf, StrSubstitutor subst,
393             FormValidatorResults res, Form form, String field,
394             Map<String, String> variables)
395     {
396         int index = 1;
397         int count = processMessagesOfLevel(buf, subst, res, form, field,
398                 variables, ValidationMessageLevel.ERROR,
399                 getFieldErrorTemplate(), index);
400 
401         if (!isSuppressWarnings())
402         {
403             index += count;
404             String template = (getFieldWarningTemplate() != null) ? getFieldWarningTemplate()
405                     : getFieldErrorTemplate();
406             processMessagesOfLevel(buf, subst, res, form, field, variables,
407                     ValidationMessageLevel.WARNING, template, index);
408         }
409     }
410 
411     /**
412      * Determines the display name of the given field. If a <code>Form</code>
413      * object is provided, it is used for resolving the display name. Otherwise
414      * the field name is returned.
415      *
416      * @param form the form
417      * @param field the field name
418      * @return the display name
419      */
420     protected String fetchDisplayName(Form form, String field)
421     {
422         return (form != null) ? form.getDisplayName(field) : field;
423     }
424 
425     /**
426      * Helper method for generating output for all messages for the given field
427      * with the specified validation level. This method is called for each field
428      * to process for the validation levels to take into account.
429      *
430      * @param buf the target buffer
431      * @param subst the substitutor
432      * @param res the validation results
433      * @param form the form
434      * @param field the current field
435      * @param variables the map with the variables
436      * @param level the validation message level
437      * @param template the template to use
438      * @param index the index of the first field
439      * @return the number of fields processed
440      */
441     private int processMessagesOfLevel(StringBuilder buf, StrSubstitutor subst,
442             FormValidatorResults res, Form form, String field,
443             Map<String, String> variables, ValidationMessageLevel level,
444             String template, int index)
445     {
446         ValidationResult vres = res.getResultsFor(field);
447         int count = 0;
448 
449         if (vres != null)
450         {
451             for (ValidationMessage msg : vres.getValidationMessages(level))
452             {
453                 setUpVariablesForMessage(variables, res, form, field, msg
454                         .getMessage(), count + index);
455                 buf.append(subst.replace(template));
456                 count++;
457             }
458         }
459 
460         return count;
461     }
462 
463     /**
464      * Produces output for the specified field. This helper method applies all
465      * templates to the messages of the specified field.
466      *
467      * @param buf the target buffer
468      * @param res the results object
469      * @param form the form
470      * @param field the field to be processed
471      */
472     private void processField(StringBuilder buf, FormValidatorResults res,
473             Form form, String field)
474     {
475         if (!res.getFieldNames().contains(field))
476         {
477             return;
478         }
479 
480         Map<String, String> variables = new HashMap<String, String>();
481         setUpVariablesForField(variables, res, form, field);
482         StrSubstitutor subst = new StrSubstitutor(variables);
483 
484         if (getFieldHeaderTemplate() != null)
485         {
486             buf.append(subst.replace(getFieldHeaderTemplate()));
487         }
488 
489         if (getFieldErrorTemplate() != null)
490         {
491             processMessages(buf, subst, res, form, field, variables);
492         }
493 
494         if (getFieldFooterTemplate() != null)
495         {
496             buf.append(subst.replace(getFieldFooterTemplate()));
497         }
498     }
499 }