1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.validator.routines;
18
19 import java.text.DecimalFormat;
20 import java.text.DecimalFormatSymbols;
21 import java.text.Format;
22 import java.text.NumberFormat;
23 import java.util.Locale;
24
25 import org.apache.commons.validator.GenericValidator;
26
27 /**
28 * <p>Abstract class for Number Validation.</p>
29 *
30 * <p>This is a <em>base</em> class for building Number
31 * Validators using format parsing.</p>
32 *
33 * @since 1.3.0
34 */
35 public abstract class AbstractNumberValidator extends AbstractFormatValidator {
36
37 private static final long serialVersionUID = -3088817875906765463L;
38
39 /** Standard {@code NumberFormat} type */
40 public static final int STANDARD_FORMAT = 0;
41
42 /** Currency {@code NumberFormat} type */
43 public static final int CURRENCY_FORMAT = 1;
44
45 /** Percent {@code NumberFormat} type */
46 public static final int PERCENT_FORMAT = 2;
47
48 /**
49 * {@code true} if fractions are allowed or {@code false} if integers only.
50 */
51 private final boolean allowFractions;
52
53 /**
54 * The {@code NumberFormat} type to create for validation, default is STANDARD_FORMAT.
55 */
56 private final int formatType;
57
58 /**
59 * Constructs an instance with specified <em>strict</em>
60 * and <em>decimal</em> parameters.
61 *
62 * @param strict {@code true} if strict
63 * {@code Format} parsing should be used.
64 * @param formatType The {@code NumberFormat} type to
65 * create for validation, default is STANDARD_FORMAT.
66 * @param allowFractions {@code true} if fractions are
67 * allowed or {@code false} if integers only.
68 */
69 public AbstractNumberValidator(final boolean strict, final int formatType, final boolean allowFractions) {
70 super(strict);
71 this.allowFractions = allowFractions;
72 this.formatType = formatType;
73 }
74
75 /**
76 * <p>Returns the <em>multiplier</em> of the {@code NumberFormat}.</p>
77 *
78 * @param format The {@code NumberFormat} to determine the
79 * multiplier of.
80 * @return The multiplying factor for the format.
81 */
82 protected int determineScale(final NumberFormat format) {
83 if (!isStrict()) {
84 return -1;
85 }
86 if (!isAllowFractions() || format.isParseIntegerOnly()) {
87 return 0;
88 }
89 final int minimumFraction = format.getMinimumFractionDigits();
90 final int maximumFraction = format.getMaximumFractionDigits();
91 if (minimumFraction != maximumFraction) {
92 return -1;
93 }
94 int scale = minimumFraction;
95 if (format instanceof DecimalFormat) {
96 final int multiplier = ((DecimalFormat) format).getMultiplier();
97 if (multiplier == 100) { // CHECKSTYLE IGNORE MagicNumber
98 scale += 2; // CHECKSTYLE IGNORE MagicNumber
99 } else if (multiplier == 1000) { // CHECKSTYLE IGNORE MagicNumber
100 scale += 3; // CHECKSTYLE IGNORE MagicNumber
101 }
102 } else if (formatType == PERCENT_FORMAT) {
103 scale += 2; // CHECKSTYLE IGNORE MagicNumber
104 }
105 return scale;
106 }
107
108 /**
109 * <p>Returns a {@code NumberFormat} for the specified Locale.</p>
110 *
111 * @param locale The locale a {@code NumberFormat} is required for,
112 * system default if null.
113 * @return The {@code NumberFormat} to created.
114 */
115 protected Format getFormat(final Locale locale) {
116 final NumberFormat formatter;
117 switch (formatType) {
118 case CURRENCY_FORMAT:
119 if (locale == null) {
120 formatter = NumberFormat.getCurrencyInstance();
121 } else {
122 formatter = NumberFormat.getCurrencyInstance(locale);
123 }
124 break;
125 case PERCENT_FORMAT:
126 if (locale == null) {
127 formatter = NumberFormat.getPercentInstance();
128 } else {
129 formatter = NumberFormat.getPercentInstance(locale);
130 }
131 break;
132 default:
133 if (locale == null) {
134 formatter = NumberFormat.getInstance();
135 } else {
136 formatter = NumberFormat.getInstance(locale);
137 }
138 if (!isAllowFractions()) {
139 formatter.setParseIntegerOnly(true);
140 }
141 break;
142 }
143 return formatter;
144 }
145
146 /**
147 * <p>Returns a {@code NumberFormat} for the specified <em>pattern</em>
148 * and/or {@link Locale}.</p>
149 *
150 * @param pattern The pattern used to validate the value against or
151 * {@code null} to use the default for the {@link Locale}.
152 * @param locale The locale to use for the currency format, system default if null.
153 * @return The {@code NumberFormat} to created.
154 */
155 @Override
156 protected Format getFormat(final String pattern, final Locale locale) {
157 final NumberFormat formatter;
158 final boolean usePattern = !GenericValidator.isBlankOrNull(pattern);
159 if (!usePattern) {
160 formatter = (NumberFormat) getFormat(locale);
161 } else if (locale == null) {
162 formatter = new DecimalFormat(pattern);
163 } else {
164 final DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
165 formatter = new DecimalFormat(pattern, symbols);
166 }
167 if (!isAllowFractions()) {
168 formatter.setParseIntegerOnly(true);
169 }
170 return formatter;
171 }
172
173 /**
174 * <p>Indicates the type of {@code NumberFormat} created
175 * by this validator instance.</p>
176 *
177 * @return the format type created.
178 */
179 public int getFormatType() {
180 return formatType;
181 }
182
183 /**
184 * <p>Indicates whether the number being validated is
185 * a decimal or integer.</p>
186 *
187 * @return {@code true} if decimals are allowed
188 * or {@code false} if the number is an integer.
189 */
190 public boolean isAllowFractions() {
191 return allowFractions;
192 }
193
194 /**
195 * Check if the value is within a specified range.
196 *
197 * @param value The value validation is being performed on.
198 * @param min The minimum value of the range.
199 * @param max The maximum value of the range.
200 * @return {@code true} if the value is within the
201 * specified range.
202 */
203 public boolean isInRange(final Number value, final Number min, final Number max) {
204 return minValue(value, min) && maxValue(value, max);
205 }
206
207 /**
208 * <p>Validate using the specified {@link Locale}.</p>
209 *
210 * @param value The value validation is being performed on.
211 * @param pattern The pattern used to validate the value against, or the
212 * default for the {@link Locale} if {@code null}.
213 * @param locale The locale to use for the date format, system default if null.
214 * @return {@code true} if the value is valid.
215 */
216 @Override
217 public boolean isValid(final String value, final String pattern, final Locale locale) {
218 return parse(value, pattern, locale) != null;
219 }
220
221 /**
222 * Check if the value is less than or equal to a maximum.
223 *
224 * @param value The value validation is being performed on.
225 * @param max The maximum value.
226 * @return {@code true} if the value is less than
227 * or equal to the maximum.
228 */
229 public boolean maxValue(final Number value, final Number max) {
230 if (isAllowFractions()) {
231 return value.doubleValue() <= max.doubleValue();
232 }
233 return value.longValue() <= max.longValue();
234 }
235
236 /**
237 * Check if the value is greater than or equal to a minimum.
238 *
239 * @param value The value validation is being performed on.
240 * @param min The minimum value.
241 * @return {@code true} if the value is greater than
242 * or equal to the minimum.
243 */
244 public boolean minValue(final Number value, final Number min) {
245 if (isAllowFractions()) {
246 return value.doubleValue() >= min.doubleValue();
247 }
248 return value.longValue() >= min.longValue();
249 }
250
251 /**
252 * <p>Parse the value using the specified pattern.</p>
253 *
254 * @param value The value validation is being performed on.
255 * @param pattern The pattern used to validate the value against, or the
256 * default for the {@link Locale} if {@code null}.
257 * @param locale The locale to use for the date format, system default if null.
258 * @return The parsed value if valid or {@code null} if invalid.
259 */
260 protected Object parse(String value, final String pattern, final Locale locale) {
261 value = value == null ? null : value.trim();
262 final String value1 = value;
263 if (GenericValidator.isBlankOrNull(value1)) {
264 return null;
265 }
266 final Format formatter = getFormat(pattern, locale);
267 return parse(value, formatter);
268
269 }
270
271 /**
272 * <p>Process the parsed value, performing any further validation
273 * and type conversion required.</p>
274 *
275 * @param value The parsed object created.
276 * @param formatter The Format used to parse the value with.
277 * @return The parsed value converted to the appropriate type
278 * if valid or {@code null} if invalid.
279 */
280 @Override
281 protected abstract Object processParsedValue(Object value, Format formatter);
282 }