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 * http://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 /** 26 * <p>Abstract class for Number Validation.</p> 27 * 28 * <p>This is a <i>base</i> class for building Number 29 * Validators using format parsing.</p> 30 * 31 * @since 1.3.0 32 */ 33 public abstract class AbstractNumberValidator extends AbstractFormatValidator { 34 35 private static final long serialVersionUID = -3088817875906765463L; 36 37 /** Standard <code>NumberFormat</code> type */ 38 public static final int STANDARD_FORMAT = 0; 39 40 /** Currency <code>NumberFormat</code> type */ 41 public static final int CURRENCY_FORMAT = 1; 42 43 /** Percent <code>NumberFormat</code> type */ 44 public static final int PERCENT_FORMAT = 2; 45 46 /** 47 * {@code true} if fractions are allowed or {@code false} if integers only. 48 */ 49 private final boolean allowFractions; 50 51 /** 52 * The <code>NumberFormat</code> type to create for validation, default is STANDARD_FORMAT. 53 */ 54 private final int formatType; 55 56 /** 57 * Constructs an instance with specified <i>strict</i> 58 * and <i>decimal</i> parameters. 59 * 60 * @param strict {@code true} if strict 61 * <code>Format</code> parsing should be used. 62 * @param formatType The <code>NumberFormat</code> type to 63 * create for validation, default is STANDARD_FORMAT. 64 * @param allowFractions {@code true} if fractions are 65 * allowed or {@code false} if integers only. 66 */ 67 public AbstractNumberValidator(final boolean strict, final int formatType, final boolean allowFractions) { 68 super(strict); 69 this.allowFractions = allowFractions; 70 this.formatType = formatType; 71 } 72 73 /** 74 * <p>Returns the <i>multiplier</i> of the <code>NumberFormat</code>.</p> 75 * 76 * @param format The <code>NumberFormat</code> to determine the 77 * multiplier of. 78 * @return The multiplying factor for the format.. 79 */ 80 protected int determineScale(final NumberFormat format) { 81 if (!isStrict()) { 82 return -1; 83 } 84 if (!isAllowFractions() || format.isParseIntegerOnly()) { 85 return 0; 86 } 87 final int minimumFraction = format.getMinimumFractionDigits(); 88 final int maximumFraction = format.getMaximumFractionDigits(); 89 if (minimumFraction != maximumFraction) { 90 return -1; 91 } 92 int scale = minimumFraction; 93 if (format instanceof DecimalFormat) { 94 final int multiplier = ((DecimalFormat) format).getMultiplier(); 95 if (multiplier == 100) { // CHECKSTYLE IGNORE MagicNumber 96 scale += 2; // CHECKSTYLE IGNORE MagicNumber 97 } else if (multiplier == 1000) { // CHECKSTYLE IGNORE MagicNumber 98 scale += 3; // CHECKSTYLE IGNORE MagicNumber 99 } 100 } else if (formatType == PERCENT_FORMAT) { 101 scale += 2; // CHECKSTYLE IGNORE MagicNumber 102 } 103 return scale; 104 } 105 106 /** 107 * <p>Returns a <code>NumberFormat</code> for the specified Locale.</p> 108 * 109 * @param locale The locale a <code>NumberFormat</code> is required for, 110 * system default if null. 111 * @return The <code>NumberFormat</code> to created. 112 */ 113 protected Format getFormat(final Locale locale) { 114 NumberFormat formatter; 115 switch (formatType) { 116 case CURRENCY_FORMAT: 117 if (locale == null) { 118 formatter = NumberFormat.getCurrencyInstance(); 119 } else { 120 formatter = NumberFormat.getCurrencyInstance(locale); 121 } 122 break; 123 case PERCENT_FORMAT: 124 if (locale == null) { 125 formatter = NumberFormat.getPercentInstance(); 126 } else { 127 formatter = NumberFormat.getPercentInstance(locale); 128 } 129 break; 130 default: 131 if (locale == null) { 132 formatter = NumberFormat.getInstance(); 133 } else { 134 formatter = NumberFormat.getInstance(locale); 135 } 136 if (!isAllowFractions()) { 137 formatter.setParseIntegerOnly(true); 138 } 139 break; 140 } 141 return formatter; 142 } 143 144 /** 145 * <p>Returns a <code>NumberFormat</code> for the specified <i>pattern</i> 146 * and/or <code>Locale</code>.</p> 147 * 148 * @param pattern The pattern used to validate the value against or 149 * <code>null</code> to use the default for the <code>Locale</code>. 150 * @param locale The locale to use for the currency format, system default if null. 151 * @return The <code>NumberFormat</code> to created. 152 */ 153 @Override 154 protected Format getFormat(final String pattern, final Locale locale) { 155 156 NumberFormat formatter; 157 final boolean usePattern = pattern != null && !pattern.isEmpty(); 158 if (!usePattern) { 159 formatter = (NumberFormat) getFormat(locale); 160 } else if (locale == null) { 161 formatter = new DecimalFormat(pattern); 162 } else { 163 final DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale); 164 formatter = new DecimalFormat(pattern, symbols); 165 } 166 167 if (!isAllowFractions()) { 168 formatter.setParseIntegerOnly(true); 169 } 170 return formatter; 171 } 172 173 /** 174 * <p>Indicates the type of <code>NumberFormat</code> 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 <code>Locale</code>.</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 <code>Locale</code> if <code>null</code>. 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 final Object parsedValue = parse(value, pattern, locale); 219 return parsedValue == null ? false : true; 220 } 221 222 /** 223 * Check if the value is less than or equal to a maximum. 224 * 225 * @param value The value validation is being performed on. 226 * @param max The maximum value. 227 * @return {@code true} if the value is less than 228 * or equal to the maximum. 229 */ 230 public boolean maxValue(final Number value, final Number max) { 231 if (isAllowFractions()) { 232 return value.doubleValue() <= max.doubleValue(); 233 } 234 return value.longValue() <= max.longValue(); 235 } 236 237 /** 238 * Check if the value is greater than or equal to a minimum. 239 * 240 * @param value The value validation is being performed on. 241 * @param min The minimum value. 242 * @return {@code true} if the value is greater than 243 * or equal to the minimum. 244 */ 245 public boolean minValue(final Number value, final Number min) { 246 if (isAllowFractions()) { 247 return value.doubleValue() >= min.doubleValue(); 248 } 249 return value.longValue() >= min.longValue(); 250 } 251 252 /** 253 * <p>Parse the value using the specified pattern.</p> 254 * 255 * @param value The value validation is being performed on. 256 * @param pattern The pattern used to validate the value against, or the 257 * default for the <code>Locale</code> if <code>null</code>. 258 * @param locale The locale to use for the date format, system default if null. 259 * @return The parsed value if valid or <code>null</code> if invalid. 260 */ 261 protected Object parse(String value, final String pattern, final Locale locale) { 262 value = value == null ? null : value.trim(); 263 if (value == null || value.isEmpty()) { 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</code> if invalid. 279 */ 280 @Override 281 protected abstract Object processParsedValue(Object value, Format formatter); 282 }