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> </td></tr>
41 * <tr><td>maxValue</td><td>less than BigDecimal.MAX_VALUE</td>
42 * <td> </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 <rule> 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 }