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}