View Javadoc
1   package org.apache.fulcrum.intake.validator;
2   
3   import java.text.NumberFormat;
4   import java.text.ParseException;
5   import java.text.ParsePosition;
6   
7   /*
8    * Licensed to the Apache Software Foundation (ASF) under one
9    * or more contributor license agreements.  See the NOTICE file
10   * distributed with this work for additional information
11   * regarding copyright ownership.  The ASF licenses this file
12   * to you under the Apache License, Version 2.0 (the
13   * "License"); you may not use this file except in compliance
14   * with the License.  You may obtain a copy of the License at
15   *
16   *   http://www.apache.org/licenses/LICENSE-2.0
17   *
18   * Unless required by applicable law or agreed to in writing,
19   * software distributed under the License is distributed on an
20   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21   * KIND, either express or implied.  See the License for the
22   * specific language governing permissions and limitations
23   * under the License.
24   */
25  
26  import java.util.Locale;
27  import java.util.Map;
28  
29  import org.apache.commons.lang3.StringUtils;
30  import org.apache.fulcrum.intake.model.Field;
31  
32  /**
33   * Validates numbers with the following constraints in addition to those
34   * listed in DefaultValidator.
35   *
36   * <table>
37   * <caption>Validation rules</caption>
38   * <tr><th>Name</th><th>Valid Values</th><th>Default Value</th></tr>
39   * <tr><td>minValue</td><td>greater than BigDecimal.MIN_VALUE</td>
40   * <td>&nbsp;</td></tr>
41   * <tr><td>maxValue</td><td>less than BigDecimal.MAX_VALUE</td>
42   * <td>&nbsp;</td></tr>
43   * <tr><td>notANumberMessage</td><td>Some text</td>
44   * <td>Entry was not a valid number</td></tr>
45   * </table>
46   *
47   * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
48   * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
49   * @author <a href="mailto:Colin.Chalmers@maxware.nl">Colin Chalmers</a>
50   * @version $Id$
51   */
52  public abstract class NumberValidator<T extends Number>
53          extends DefaultValidator<T>
54  {
55      /** The message to show if field fails min-value test */
56      String minValueMessage = null;
57  
58      /** The message to show if field fails max-value test */
59      String maxValueMessage = null;
60  
61      /** The message to use for invalid numbers */
62      String invalidNumberMessage = null;
63  
64      private T minValue = null;
65      private T maxValue = null;
66  
67      /**
68       * Default Constructor
69       */
70      public NumberValidator()
71      {
72          super();
73      }
74  
75      /**
76       * Extract the relevant parameters from the constraints listed
77       * in &lt;rule&gt; tags within the intake.xml file.
78       *
79       * @param paramMap a <code>Map</code> of <code>rule</code>'s
80       * containing constraints on the input.
81       * @throws InvalidMaskException an invalid mask was specified
82       */
83      @Override
84  	public void init(Map<String, ? extends Constraint> paramMap)
85              throws InvalidMaskException
86      {
87          super.init(paramMap);
88  
89          Constraint constraint = paramMap.get(INVALID_NUMBER_RULE_NAME);
90  
91          if (constraint != null)
92          {
93              invalidNumberMessage = constraint.getMessage();
94          }
95  
96          constraint = paramMap.get(MIN_VALUE_RULE_NAME);
97          if (constraint != null)
98          {
99              String param = constraint.getValue();
100             try
101             {
102                 minValue = parseNumber(param, Locale.US);
103             }
104             catch (NumberFormatException e)
105             {
106                 throw new InvalidMaskException("Could not parse minimum value " + param, e);
107             }
108             minValueMessage = constraint.getMessage();
109         }
110 
111         constraint = paramMap.get(MAX_VALUE_RULE_NAME);
112         if (constraint != null)
113         {
114             String param = constraint.getValue();
115             try
116             {
117                 maxValue = parseNumber(param, Locale.US);
118             }
119             catch (NumberFormatException e)
120             {
121                 throw new InvalidMaskException("Could not parse minimum value " + param, e);
122             }
123             maxValueMessage = constraint.getMessage();
124         }
125     }
126 
127     /**
128      * Parse the actual value out of a string
129      *
130      * @param stringValue the string value
131      * @param locale the locale to use while parsing
132      *
133      * @return the value
134      *
135      * @throws NumberFormatException if the value could not be parsed
136      */
137     protected abstract T parseNumber(String stringValue, Locale locale) throws NumberFormatException;
138 
139     /**
140      * Helper method to parse a number object out of a string
141      *
142      * @param stringValue the string value
143      * @param locale the locale to use while parsing
144      *
145      * @return the Number
146      *
147      * @throws NumberFormatException if the value could not be parsed
148      */
149     protected Number parseIntoNumber(String stringValue, Locale locale) throws NumberFormatException
150     {
151         NumberFormat nf = NumberFormat.getInstance(locale);
152 
153         try
154         {
155             ParsePosition pos = new ParsePosition(0);
156             Number number = nf.parse(stringValue, pos);
157 
158             if (pos.getIndex() != stringValue.length())
159             {
160                 throw new ParseException("Could not parse string completely", pos.getErrorIndex());
161             }
162 
163             return number;
164         }
165         catch (ParseException e)
166         {
167             throw new NumberFormatException(e.getMessage());
168         }
169     }
170 
171     /**
172      * Determine whether a field meets the criteria specified
173      * in the constraints defined for this validator
174      *
175      * @param field a <code>Field</code> to be tested
176      * @throws ValidationException containing an error message if the
177      * testValue did not pass the validation tests.
178      */
179     @Override
180 	public void assertValidity(Field<T> field) throws ValidationException
181     {
182         Locale locale = field.getLocale();
183 
184         if (field.isMultiValued())
185         {
186             String[] stringValues = (String[])field.getTestValue();
187 
188             for (int i = 0; i < stringValues.length; i++)
189             {
190                 assertValidity(stringValues[i], locale);
191             }
192         }
193         else
194         {
195             assertValidity((String)field.getTestValue(), locale);
196         }
197     }
198 
199     /**
200      * Determine whether a testValue meets the criteria specified
201      * in the constraints defined for this validator
202      *
203      * @param testValue a <code>String</code> to be tested
204      * @param locale the Locale of the associated field
205      * @throws ValidationException containing an error message if the
206      * testValue did not pass the validation tests.
207      */
208     public void assertValidity(String testValue, Locale locale) throws ValidationException
209     {
210         super.assertValidity(testValue);
211 
212         if (required || StringUtils.isNotEmpty(testValue))
213         {
214             T number = null;
215             try
216             {
217                 number = parseNumber(testValue, locale);
218             }
219             catch (NumberFormatException e)
220             {
221                 errorMessage = invalidNumberMessage;
222                 throw new ValidationException(invalidNumberMessage);
223             }
224 
225             if (minValue != null && number.doubleValue() < minValue.doubleValue())
226             {
227                 errorMessage = minValueMessage;
228                 throw new ValidationException(minValueMessage);
229             }
230             if (maxValue != null && number.doubleValue() > maxValue.doubleValue())
231             {
232                 errorMessage = maxValueMessage;
233                 throw new ValidationException(maxValueMessage);
234             }
235         }
236     }
237 
238     // ************************************************************
239     // **                Bean accessor methods                   **
240     // ************************************************************
241 
242     /**
243      * Get the value of minValueMessage.
244      *
245      * @return value of minValueMessage.
246      */
247     public String getMinValueMessage()
248     {
249         return minValueMessage;
250     }
251 
252     /**
253      * Set the value of minValueMessage.
254      *
255      * @param minValueMessage  Value to assign to minValueMessage.
256      */
257     public void setMinValueMessage(String minValueMessage)
258     {
259         this.minValueMessage = minValueMessage;
260     }
261 
262     /**
263      * Get the value of maxValueMessage.
264      *
265      * @return value of maxValueMessage.
266      */
267     public String getMaxValueMessage()
268     {
269         return maxValueMessage;
270     }
271 
272     /**
273      * Set the value of maxValueMessage.
274      *
275      * @param maxValueMessage  Value to assign to maxValueMessage.
276      */
277     public void setMaxValueMessage(String maxValueMessage)
278     {
279         this.maxValueMessage = maxValueMessage;
280     }
281 
282     /**
283      * Get the value of invalidNumberMessage.
284      *
285      * @return value of invalidNumberMessage.
286      */
287     public String getInvalidNumberMessage()
288     {
289         return invalidNumberMessage;
290     }
291 
292     /**
293      *
294      * Set the value of invalidNumberMessage.
295      * @param invalidNumberMessage  Value to assign to invalidNumberMessage.
296      */
297     public void setInvalidNumberMessage(String invalidNumberMessage)
298     {
299         this.invalidNumberMessage = invalidNumberMessage;
300     }
301 
302     /**
303      * Get the value of minValue.
304      *
305      * @return value of minValue.
306      */
307     public T getMinValue()
308     {
309         return minValue;
310     }
311 
312     /**
313      * Set the value of minValue.
314      *
315      * @param minValue  Value to assign to minValue.
316      */
317     public void setMinValue(T minValue)
318     {
319         this.minValue = minValue;
320     }
321 
322     /**
323      * Get the value of maxValue.
324      *
325      * @return value of maxValue.
326      */
327     public T getMaxValue()
328     {
329         return maxValue;
330     }
331 
332     /**
333      * Set the value of maxValue.
334      *
335      * @param maxValue  Value to assign to maxValue.
336      */
337     public void setMaxValue(T maxValue)
338     {
339         this.maxValue = maxValue;
340     }
341 }