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 */ 017 018package org.apache.commons.validator; 019 020import java.io.Serializable; 021import java.text.DateFormat; 022import java.text.NumberFormat; 023import java.text.ParseException; 024import java.text.ParsePosition; 025import java.text.SimpleDateFormat; 026import java.util.Date; 027import java.util.Locale; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031 032/** 033 * This class contains basic methods for performing validations that return the correctly typed class based on the validation performed. 034 */ 035public class GenericTypeValidator implements Serializable { 036 037 private static final Log LOG = LogFactory.getLog(GenericTypeValidator.class); 038 private static final long serialVersionUID = 5487162314134261703L; 039 040 /** 041 * Checks if the value can safely be converted to a byte primitive. 042 * 043 * @param value The value validation is being performed on. 044 * @return The converted Byte value. 045 */ 046 public static Byte formatByte(final String value) { 047 if (value == null) { 048 return null; 049 } 050 try { 051 return Byte.valueOf(value); 052 } catch (final NumberFormatException e) { 053 return null; 054 } 055 } 056 057 /** 058 * Checks if the value can safely be converted to a byte primitive. 059 * 060 * @param value The value validation is being performed on. 061 * @param locale The locale to use to parse the number (system default if null) 062 * @return The converted Byte value. 063 */ 064 public static Byte formatByte(final String value, final Locale locale) { 065 Byte result = null; 066 if (value != null) { 067 final ParsePosition pos = new ParsePosition(0); 068 final Number num = getIntegerOnlyFormat(locale).parse(value, pos); 069 // parseIntegerOnly only stops at the decimal separator, so a fractional value written with a negative 070 // exponent and no decimal point (for example "15E-1" for 1.5) is consumed in full and returned as a 071 // Double; byteValue() would floor it. NumberFormat returns a Long only for a non-fractional value in 072 // long range, so guard on that before the byte range check, matching formatLong. 073 if (pos.getIndex() == value.length() && num instanceof Long) { 074 final long longValue = (Long) num; 075 if (longValue >= Byte.MIN_VALUE && longValue <= Byte.MAX_VALUE) { 076 result = Byte.valueOf((byte) longValue); 077 } 078 } 079 } 080 return result; 081 } 082 083 /** 084 * Checks if the field is a valid credit card number. 085 * 086 * <p> 087 * Reference Sean M. Burke's <a href="https://www.ling.nwu.edu/~sburke/pub/luhn_lib.pl"> script</a>. 088 * </p> 089 * 090 * @param value The value validation is being performed on. 091 * @return The converted Credit Card number. 092 */ 093 public static Long formatCreditCard(final String value) { 094 return GenericValidator.isCreditCard(value) ? Long.valueOf(value) : null; 095 } 096 097 /** 098 * Checks if the field is a valid date. 099 * 100 * <p> 101 * The {@link Locale} is used with {@link DateFormat}. The {@link java.text.DateFormat#setLenient(boolean)} method is set to {@code false} for 102 * all. 103 * </p> 104 * 105 * @param value The value validation is being performed on. 106 * @param locale The Locale to use to parse the date (system default if null) 107 * @return The converted Date value. 108 */ 109 public static Date formatDate(final String value, final Locale locale) { 110 Date date = null; 111 if (value == null) { 112 return null; 113 } 114 try { 115 // Get the formatters to check against 116 final DateFormat formatterShort = DateFormat.getDateInstance(DateFormat.SHORT, Validator.toLocale(locale)); 117 final DateFormat formatterDefault = DateFormat.getDateInstance(DateFormat.DEFAULT, Validator.toLocale(locale)); 118 // Turn off lenient parsing 119 formatterShort.setLenient(false); 120 formatterDefault.setLenient(false); 121 // Firstly, try with the short form 122 try { 123 date = formatterShort.parse(value); 124 } catch (final ParseException e) { 125 // Fall back on the default one 126 date = formatterDefault.parse(value); 127 } 128 } catch (final ParseException e) { 129 // Bad date, so LOG and return null 130 if (LOG.isDebugEnabled()) { 131 LOG.debug("Date parse failed value=[" + value + "], " + "locale=[" + locale + "] " + e); 132 } 133 } 134 return date; 135 } 136 137 /** 138 * Checks if the field is a valid date. 139 * 140 * <p> 141 * The pattern is used with {@link SimpleDateFormat}. If strict is true, then the length will be checked so '2/12/1999' will not pass validation 142 * with the format 'MM/dd/yyyy' because the month isn't two digits. The {@link java.text.SimpleDateFormat#setLenient(boolean)} method is set to 143 * {@code false} for all. 144 * </p> 145 * 146 * @param value The value validation is being performed on. 147 * @param datePattern The pattern passed to {@link SimpleDateFormat}. 148 * @param strict Whether or not to have an exact match of the datePattern. 149 * @return The converted Date value. 150 */ 151 public static Date formatDate(final String value, final String datePattern, final boolean strict) { 152 if (value == null || datePattern == null || datePattern.isEmpty()) { 153 return null; 154 } 155 final SimpleDateFormat formatter = new SimpleDateFormat(datePattern); 156 formatter.setLenient(false); 157 final ParsePosition pos = new ParsePosition(0); 158 Date date = formatter.parse(value, pos); 159 // parse(String, ParsePosition) stops at the first unparsable character instead of failing, so a strict 160 // match must also confirm the whole value was consumed; otherwise trailing text such as the 'f' in 161 // "11/11/199f" is dropped and the truncated date validates. 162 if (date != null && strict && (pos.getIndex() < value.length() || datePattern.length() != value.length())) { 163 date = null; 164 } 165 return date; 166 } 167 168 /** 169 * Checks if the value can safely be converted to a double primitive. 170 * 171 * @param value The value validation is being performed on. 172 * @return The converted Double value. 173 */ 174 public static Double formatDouble(final String value) { 175 if (value == null) { 176 return null; 177 } 178 try { 179 return Double.valueOf(value); 180 } catch (final NumberFormatException e) { 181 return null; 182 } 183 } 184 185 /** 186 * Checks if the value can safely be converted to a double primitive. 187 * 188 * @param value The value validation is being performed on. 189 * @param locale The locale to use to parse the number (system default if null) 190 * @return The converted Double value. 191 */ 192 public static Double formatDouble(final String value, final Locale locale) { 193 Double result = null; 194 if (value != null) { 195 final ParsePosition pos = new ParsePosition(0); 196 final Number num = getNumberFormat(locale).parse(value, pos); 197 // If there was no error and we used the whole string 198 if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() && num.doubleValue() >= Double.MAX_VALUE * -1 199 && num.doubleValue() <= Double.MAX_VALUE) { 200 result = Double.valueOf(num.doubleValue()); 201 } 202 } 203 return result; 204 } 205 206 /** 207 * Checks if the value can safely be converted to a float primitive. 208 * 209 * @param value The value validation is being performed on. 210 * @return The converted Float value. 211 */ 212 public static Float formatFloat(final String value) { 213 if (value == null) { 214 return null; 215 } 216 try { 217 return Float.valueOf(value); 218 } catch (final NumberFormatException e) { 219 return null; 220 } 221 } 222 223 /** 224 * Checks if the value can safely be converted to a float primitive. 225 * 226 * @param value The value validation is being performed on. 227 * @param locale The locale to use to parse the number (system default if null) 228 * @return The converted Float value. 229 */ 230 public static Float formatFloat(final String value, final Locale locale) { 231 Float result = null; 232 if (value != null) { 233 final ParsePosition pos = new ParsePosition(0); 234 final Number num = getNumberFormat(locale).parse(value, pos); 235 // If there was no error and we used the whole string 236 if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() && num.doubleValue() >= Float.MAX_VALUE * -1 237 && num.doubleValue() <= Float.MAX_VALUE) { 238 result = Float.valueOf(num.floatValue()); 239 } 240 } 241 return result; 242 } 243 244 /** 245 * Checks if the value can safely be converted to an int primitive. 246 * 247 * @param value The value validation is being performed on. 248 * @return The converted Integer value. 249 */ 250 public static Integer formatInt(final String value) { 251 if (value == null) { 252 return null; 253 } 254 try { 255 return Integer.valueOf(value); 256 } catch (final NumberFormatException e) { 257 return null; 258 } 259 } 260 261 /** 262 * Checks if the value can safely be converted to an int primitive. 263 * 264 * @param value The value validation is being performed on. 265 * @param locale The locale to use to parse the number (system default if null) 266 * @return The converted Integer value. 267 */ 268 public static Integer formatInt(final String value, final Locale locale) { 269 Integer result = null; 270 if (value != null) { 271 final NumberFormat formatter = getIntegerOnlyFormat(locale); 272 final ParsePosition pos = new ParsePosition(0); 273 final Number num = formatter.parse(value, pos); 274 // parseIntegerOnly only stops at the decimal separator, so a fractional value written with a negative 275 // exponent and no decimal point (for example "15E-1" for 1.5) is consumed in full and returned as a 276 // Double; intValue() would floor it. NumberFormat returns a Long only for a non-fractional value in 277 // long range, so guard on that before the int range check, matching formatLong. 278 if (pos.getIndex() == value.length() && num instanceof Long) { 279 final long longValue = (Long) num; 280 if (longValue >= Integer.MIN_VALUE && longValue <= Integer.MAX_VALUE) { 281 result = Integer.valueOf((int) longValue); 282 } 283 } 284 } 285 return result; 286 } 287 288 /** 289 * Checks if the value can safely be converted to a long primitive. 290 * 291 * @param value The value validation is being performed on. 292 * @return The converted Long value. 293 */ 294 public static Long formatLong(final String value) { 295 if (value == null) { 296 return null; 297 } 298 try { 299 return Long.valueOf(value); 300 } catch (final NumberFormatException e) { 301 return null; 302 } 303 } 304 305 /** 306 * Checks if the value can safely be converted to a long primitive. 307 * 308 * @param value The value validation is being performed on. 309 * @param locale The locale to use to parse the number (system default if null) 310 * @return The converted Long value. 311 */ 312 public static Long formatLong(final String value, final Locale locale) { 313 Long result = null; 314 if (value != null) { 315 final NumberFormat formatter = getIntegerOnlyFormat(locale); 316 final ParsePosition pos = new ParsePosition(0); 317 final Number num = formatter.parse(value, pos); 318 // If we used the whole string and the value fits in a long. 319 // NumberFormat returns a Long only when the value fits in a long; 320 // out-of-range input is returned as a Double, so a doubleValue() 321 // range check cannot be used here (Long.MAX_VALUE is not exactly 322 // representable as a double and would let 2^63 through). A failed 323 // parse returns null, so the instanceof check also covers it and 324 // the pos.getErrorIndex() test is no longer needed. 325 if (pos.getIndex() == value.length() && num instanceof Long) { 326 result = (Long) num; 327 } 328 } 329 return result; 330 } 331 332 /** 333 * Checks if the value can safely be converted to a short primitive. 334 * 335 * @param value The value validation is being performed on. 336 * @return The converted Short value. 337 */ 338 public static Short formatShort(final String value) { 339 if (value == null) { 340 return null; 341 } 342 try { 343 return Short.valueOf(value); 344 } catch (final NumberFormatException e) { 345 return null; 346 } 347 } 348 349 /** 350 * Checks if the value can safely be converted to a short primitive. 351 * 352 * @param value The value validation is being performed on. 353 * @param locale The locale to use to parse the number (system default if null) 354 * @return The converted Short value. 355 */ 356 public static Short formatShort(final String value, final Locale locale) { 357 Short result = null; 358 if (value != null) { 359 final NumberFormat formatter = getIntegerOnlyFormat(locale); 360 final ParsePosition pos = new ParsePosition(0); 361 final Number num = formatter.parse(value, pos); 362 // parseIntegerOnly only stops at the decimal separator, so a fractional value written with a negative 363 // exponent and no decimal point (for example "15E-1" for 1.5) is consumed in full and returned as a 364 // Double; shortValue() would floor it. NumberFormat returns a Long only for a non-fractional value in 365 // long range, so guard on that before the short range check, matching formatLong. 366 if (pos.getIndex() == value.length() && num instanceof Long) { 367 final long longValue = (Long) num; 368 if (longValue >= Short.MIN_VALUE && longValue <= Short.MAX_VALUE) { 369 result = Short.valueOf((short) longValue); 370 } 371 } 372 } 373 return result; 374 } 375 376 private static NumberFormat getIntegerOnlyFormat(final Locale locale) { 377 final NumberFormat formatter = NumberFormat.getNumberInstance(Validator.toLocale(locale)); 378 formatter.setParseIntegerOnly(true); 379 return formatter; 380 } 381 382 private static NumberFormat getNumberFormat(final Locale locale) { 383 return NumberFormat.getInstance(Validator.toLocale(locale)); 384 } 385 386 /** 387 * Constructs a new instance. 388 * 389 * @deprecated Will be private in the next major version. 390 */ 391 @Deprecated 392 public GenericTypeValidator() { 393 // empty 394 } 395}