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 }