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.transform;
17  
18  import java.lang.reflect.ParameterizedType;
19  import java.text.NumberFormat;
20  import java.text.ParseException;
21  import java.text.ParsePosition;
22  import java.util.Locale;
23  
24  import org.apache.commons.configuration.Configuration;
25  import org.apache.commons.configuration.MapConfiguration;
26  
27  /**
28   * <p>
29   * An abstract base class for transformers and validators for numbers.
30   * </p>
31   * <p>
32   * This base class already provides the major part of functionality for
33   * validating numeric input and transforming strings to number objects. Concrete
34   * sub classes are responsible for the creation of a
35   * <code>java.text.NumberFormat</code> object that is used for parsing the
36   * user input. The class also supports certain semantic checks, especially
37   * whether an entered number lies in a specified interval.
38   * </p>
39   * <p>
40   * This class makes use of Java generics to be independent on a concrete number
41   * type. The returned (transformed) object will be of this type, and also the
42   * specified minimum or maximum values must use this type.
43   * </p>
44   * <p>
45   * The following properties are supported by this class:
46   * </p>
47   * <table border="1">
48   * <tr>
49   * <th>Property</th>
50   * <th>Description</th>
51   * <th>Default</th>
52   * </tr>
53   * <tr>
54   * <td valign="top">minimum</td>
55   * <td>Here the minimum value can be defined. Entered numbers are checked to be
56   * greater or equal than this number. If this property is undefined, no minimum
57   * checks will be performed.</td>
58   * <td valign="top">undefined</td>
59   * </tr>
60   * <tr>
61   * <td valign="top">maximum</td>
62   * <td>Here the maximum value can be defined. Entered numbers are checked to be
63   * less or equal than this number. If this property is undefined, no maximum
64   * checks will be performed.</td>
65   * <td valign="top">undefined</td>
66   * </tr>
67   * </table>
68   * </p>
69   * <p>
70   * Validation of user input can fail for multiple reasons. The following table
71   * lists the possible error messages: <table border="1">
72   * <tr>
73   * <th>Message key</th>
74   * <th>Description</th>
75   * <th>Parameters</th>
76   * </tr>
77   * <tr>
78   * <td valign="top"><code>{@value ValidationMessageConstants#ERR_INVALID_NUMBER}</code></td>
79   * <td>The passed in string cannot be parsed to a number object.</td>
80   * <td valign="top">{0} = the input string</td>
81   * </tr>
82   * <tr>
83   * <td valign="top"><code>{@value ValidationMessageConstants#ERR_NUMBER_TOO_SMALL}</code></td>
84   * <td>The entered number is too small. This error message is returned if the
85   * number is less than the specified minimum number and no maximum number was
86   * specified. (If both a minimum and a maximum number are specified, the error
87   * code <code>{@value ValidationMessageConstants#ERR_NUMBER_INTERVAL}</code>
88   * is used.)</td>
89   * <td valign="top">{0} = the minimum number</td>
90   * </tr>
91   * <tr>
92   * <td valign="top"><code>{@value ValidationMessageConstants#ERR_NUMBER_TOO_BIG}</code></td>
93   * <td>The entered number is too big. This error message is returned if the
94   * number is greater than the specified maximum number and no minimum number was
95   * specified. (If both a minimum and a maximum number are specified, the error
96   * code <code>{@value ValidationMessageConstants#ERR_NUMBER_INTERVAL}</code>
97   * is used.)</td>
98   * <td valign="top">{0} = the maximum number</td>
99   * </tr>
100  * <tr>
101  * <td valign="top"><code>{@value ValidationMessageConstants#ERR_NUMBER_INTERVAL}</code></td>
102  * <td>The entered number is not in the interval spanned by the minimum and the
103  * maximum value. If both a minimum and a maximum are specified and the entered
104  * number does not meet these constraints, this error message is produced rather
105  * than one of
106  * <code>{@value ValidationMessageConstants#ERR_NUMBER_TOO_SMALL}</code> or
107  * <code>{@value ValidationMessageConstants#ERR_NUMBER_TOO_BIG}</code>.</td>
108  * <td valign="top">{0} = the minimum value, {1} = the maximum value</td>
109  * </tr>
110  * </table>
111  * </p>
112  * <p>
113  * The class implements both the <code>{@link Transformer}</code> and
114  * {@link Validator} interfaces. It is safe to use an instance
115  * concurrently as transformer and validator for the same or multiple input
116  * fields.
117  * </p>
118  *
119  * @author Oliver Heger
120  * @version $Id: NumberTransformerBase.java 205 2012-01-29 18:29:57Z oheger $
121  * @param <T> the type handled by this transformer
122  */
123 public abstract class NumberTransformerBase<T extends Number> implements
124         Transformer, Validator
125 {
126     /** Constant for the minimum property. */
127     protected static final String PROP_MINIMUM = "minimum";
128 
129     /** Constant for the maximum property. */
130     protected static final String PROP_MAXIMUM = "maximum";
131 
132     /** Stores the minimum allowed value. */
133     private T minimum;
134 
135     /** Stores the maximum allowed value. */
136     private T maximum;
137 
138     /**
139      * Returns the minimum value.
140      *
141      * @return the minimum value (can be <b>null</b>)
142      */
143     public T getMinimum()
144     {
145         return minimum;
146     }
147 
148     /**
149      * Sets the minimum value. If a minimum value is specified, the validator
150      * implementation will check whether an entered number is not less than this
151      * minimum value.
152      *
153      * @param minimum the minimum value
154      */
155     public void setMinimum(T minimum)
156     {
157         this.minimum = minimum;
158     }
159 
160     /**
161      * Returns the maximum value.
162      *
163      * @return the maximum value (can be <b>null</b>)
164      */
165     public T getMaximum()
166     {
167         return maximum;
168     }
169 
170     /**
171      * Sets the maximum value. If a maximum value is specified, the validator
172      * implementation will check whether an entered number is not greater than
173      * this maximum value.
174      *
175      * @param maximum the maximum value
176      */
177     public void setMaximum(T maximum)
178     {
179         this.maximum = maximum;
180     }
181 
182     /**
183      * Transforms the specified object to the target format. This implementation
184      * tries to convert the passed in object to a <code>Number</code> of the
185      * type specified by the generics parameter for this class. This is done by
186      * using a <code>java.text.NumberFormat</code> object. If the passed in
187      * object is <b>null</b>, <b>null</b> will also be returned.
188      *
189      * @param o the object to be transformed
190      * @param ctx the transformer context
191      * @return the transformed object
192      * @throws Exception if conversion fails
193      */
194     public Object transform(Object o, TransformerContext ctx) throws Exception
195     {
196         return transformToNumber(o, ctx, createFormat(ctx.getLocale()));
197     }
198 
199     /**
200      * Validates the specified object. This implementation checks whether the
201      * object can be transformed to a number. If this is the case,
202      * <code>isNumberValid()</code> will be called to check whether the number
203      * lies in a valid range. Depending on these checks a validation result
204      * object is returned. A <b>null</b> object or an empty string are
205      * considered valid.
206      *
207      * @param o the object to be validated
208      * @param ctx the transformer context
209      * @return an object with the results of the validation
210      */
211     public ValidationResult isValid(Object o, TransformerContext ctx)
212     {
213         NumberFormat fmt = createFormat(ctx.getLocale());
214         try
215         {
216             T number = transformToNumber(o, ctx, fmt);
217             if (number == null)
218             {
219                 return DefaultValidationResult.VALID;
220             }
221             else
222             {
223                 Configuration config = new MapConfiguration(ctx.properties());
224                 return isNumberValid(number, fmt, ctx, fetchProperty(config,
225                         PROP_MINIMUM, getMinimum()), fetchProperty(config,
226                         PROP_MAXIMUM, getMaximum()));
227             }
228         }
229         catch (ParseException pex)
230         {
231             // the string could not be parsed to a number
232             return errorResult(ValidationMessageConstants.ERR_INVALID_NUMBER,
233                     ctx, o);
234         }
235         catch (IllegalArgumentException iex)
236         {
237             // the number does not fit into the allowed value range
238             return errorResult(
239                     ValidationMessageConstants.ERR_NUMBER_OUT_OF_RANGE, ctx, o);
240         }
241     }
242 
243     /**
244      * Transforms the given object into a number. This method is called by both
245      * <code>transform()</code> and <code>isValid()</code>. It performs the
246      * actual transformation. The passed in object may be <b>null</b> or empty,
247      * in which case <b>null</b> is returned.
248      *
249      * @param o the object to be transformed
250      * @param ctx the transformer context
251      * @param fmt the format object to be used
252      * @return the converted number
253      * @throws ParseException if transformation fails
254      */
255     protected T transformToNumber(Object o, TransformerContext ctx,
256             NumberFormat fmt) throws ParseException
257     {
258         if (o == null)
259         {
260             return null;
261         }
262 
263         String n = String.valueOf(o);
264         if (n.length() < 1)
265         {
266             return null;
267         }
268 
269         ParsePosition ppos = new ParsePosition(0);
270         Number num = fmt.parse(n, ppos);
271         if (ppos.getErrorIndex() >= 0 || ppos.getIndex() < n.length())
272         {
273             throw new ParseException("Invalid number: " + n, ppos.getIndex());
274         }
275 
276         return convertToTarget(num);
277     }
278 
279     /**
280      * Validates an entered number. This method is called by
281      * <code>isValid()</code> if the passed in object can be successfully
282      * converted into a number. It checks this number against the minimum and
283      * maximum values (if defined).
284      *
285      * @param n the number to check
286      * @param fmt the format object (used for formatting the minimum and/or
287      *        maximum values in error messages)
288      * @param ctx the transformation context
289      * @param min the minimum value (can be <b>null</b>)
290      * @param max the maximum value (can be <b>null</b>)
291      * @return an object with the results of the validation
292      */
293     @SuppressWarnings("unchecked")
294     protected ValidationResult isNumberValid(T n, NumberFormat fmt,
295             TransformerContext ctx, T min, T max)
296     {
297         Comparable<T> comp = (Comparable<T>) n;
298 
299         if (min != null && comp.compareTo(min) < 0)
300         {
301             return rangeErrorResult(true, ctx, min, max, fmt);
302         }
303 
304         if (max != null && comp.compareTo(max) > 0)
305         {
306             return rangeErrorResult(false, ctx, min, max, fmt);
307         }
308 
309         return DefaultValidationResult.VALID;
310     }
311 
312     /**
313      * Creates a validation result if an error occurred.
314      *
315      * @param errorKey the key of the error message
316      * @param ctx the transformer context
317      * @param params optional parameters for the error message
318      * @return the validation result with this error
319      */
320     protected ValidationResult errorResult(String errorKey,
321             TransformerContext ctx, Object... params)
322     {
323         DefaultValidationResult vr = new DefaultValidationResult.Builder()
324                 .addValidationMessage(
325                         ctx.getValidationMessageHandler().getValidationMessage(
326                                 ctx, errorKey, params)).build();
327         return vr;
328     }
329 
330     /**
331      * Converts the specified number to the target type supported by this
332      * transformer. This method is called after the object to be transformed was
333      * parsed by a <code>NumberFormat</code> object. There is no guarantee
334      * that the result of this parsing process matches the desired type. If this
335      * is not the case, this method will be called.
336      *
337      * @param n the number to be converted
338      * @return the converted number
339      * @throws IllegalArgumentException if the number cannot be converted to the
340      *         target type (e.g. because it does not fit into the supported
341      *         range)
342      */
343     protected abstract T convert(Number n);
344 
345     /**
346      * Creates the format object for parsing a number. This method is called
347      * whenever a string (entered by the user) has to be converted into a
348      * number.
349      *
350      * @param locale the locale to use
351      * @return the format object for parsing the number
352      */
353     protected abstract NumberFormat createFormat(Locale locale);
354 
355     /**
356      * Fetches a property of the supported type from the specified configuration
357      * object. This method is called for determining the current values of
358      * type-related properties (e.g. the minimum and maximum values). An
359      * implementation has to invoke the appropriate methods on the passed in
360      * <code>Configuration</code> object.
361      *
362      * @param config the configuration object
363      * @param property the name of the property to be obtained
364      * @param defaultValue the default value for this property
365      * @return the value of this property
366      */
367     protected abstract T fetchProperty(Configuration config, String property,
368             T defaultValue);
369 
370     /**
371      * Converts the passed in number to the target type if necessary. If the
372      * number is already of the target type, nothing is done.
373      *
374      * @param n the number to convert
375      * @return the converted number
376      */
377     @SuppressWarnings("unchecked")
378     private T convertToTarget(Number n)
379     {
380         Class<?> targetClass = (Class<?>) ((ParameterizedType) getClass()
381                 .getGenericSuperclass()).getActualTypeArguments()[0];
382         if (targetClass.equals(n.getClass()))
383         {
384             return (T) n;
385         }
386         else
387         {
388             return convert(n);
389         }
390     }
391 
392     /**
393      * Creates a validation result object when the number is either too small or
394      * too big. The error message depends on the fact whether both the minimum
395      * and the maximum value are defined.
396      *
397      * @param tooSmall <b>true</b> if the entered number is too small, <b>false</b>
398      *        otherwise
399      * @param ctx the transformer context
400      * @param min the minimum value
401      * @param max the maximum value
402      * @param fmt the format object
403      * @return the validation result
404      */
405     private ValidationResult rangeErrorResult(boolean tooSmall,
406             TransformerContext ctx, T min, T max, NumberFormat fmt)
407     {
408         boolean interval = tooSmall ? max != null : min != null;
409         if (interval)
410         {
411             return errorResult(ValidationMessageConstants.ERR_NUMBER_INTERVAL,
412                     ctx, fmt.format(min), fmt.format(max));
413         }
414         else
415         {
416             if (tooSmall)
417             {
418                 return errorResult(
419                         ValidationMessageConstants.ERR_NUMBER_TOO_SMALL, ctx,
420                         fmt.format(min));
421             }
422             else
423             {
424                 return errorResult(
425                         ValidationMessageConstants.ERR_NUMBER_TOO_BIG, ctx, fmt
426                                 .format(max));
427             }
428         }
429     }
430 }