001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.validator.routines; 018 019import java.text.DecimalFormat; 020import java.text.DecimalFormatSymbols; 021import java.text.Format; 022import java.text.NumberFormat; 023import java.util.Locale; 024 025import org.apache.commons.validator.GenericValidator; 026 027/** 028 * <p>Abstract class for Number Validation.</p> 029 * 030 * <p>This is a <em>base</em> class for building Number 031 * Validators using format parsing.</p> 032 * 033 * @since 1.3.0 034 */ 035public abstract class AbstractNumberValidator extends AbstractFormatValidator { 036 037 private static final long serialVersionUID = -3088817875906765463L; 038 039 /** Standard {@code NumberFormat} type */ 040 public static final int STANDARD_FORMAT = 0; 041 042 /** Currency {@code NumberFormat} type */ 043 public static final int CURRENCY_FORMAT = 1; 044 045 /** Percent {@code NumberFormat} type */ 046 public static final int PERCENT_FORMAT = 2; 047 048 /** 049 * {@code true} if fractions are allowed or {@code false} if integers only. 050 */ 051 private final boolean allowFractions; 052 053 /** 054 * The {@code NumberFormat} type to create for validation, default is STANDARD_FORMAT. 055 */ 056 private final int formatType; 057 058 /** 059 * Constructs an instance with specified <em>strict</em> 060 * and <em>decimal</em> parameters. 061 * 062 * @param strict {@code true} if strict 063 * {@code Format} parsing should be used. 064 * @param formatType The {@code NumberFormat} type to 065 * create for validation, default is STANDARD_FORMAT. 066 * @param allowFractions {@code true} if fractions are 067 * allowed or {@code false} if integers only. 068 */ 069 public AbstractNumberValidator(final boolean strict, final int formatType, final boolean allowFractions) { 070 super(strict); 071 this.allowFractions = allowFractions; 072 this.formatType = formatType; 073 } 074 075 /** 076 * <p>Returns the <em>multiplier</em> of the {@code NumberFormat}.</p> 077 * 078 * @param format The {@code NumberFormat} to determine the 079 * multiplier of. 080 * @return The multiplying factor for the format. 081 */ 082 protected int determineScale(final NumberFormat format) { 083 if (!isStrict()) { 084 return -1; 085 } 086 if (!isAllowFractions() || format.isParseIntegerOnly()) { 087 return 0; 088 } 089 final int minimumFraction = format.getMinimumFractionDigits(); 090 final int maximumFraction = format.getMaximumFractionDigits(); 091 if (minimumFraction != maximumFraction) { 092 return -1; 093 } 094 int scale = minimumFraction; 095 if (format instanceof DecimalFormat) { 096 final int multiplier = ((DecimalFormat) format).getMultiplier(); 097 if (multiplier == 100) { // CHECKSTYLE IGNORE MagicNumber 098 scale += 2; // CHECKSTYLE IGNORE MagicNumber 099 } 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}