View Javadoc
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 }