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 *      http://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;
018
019import java.io.Serializable;
020import java.text.DateFormat;
021import java.text.NumberFormat;
022import java.text.ParseException;
023import java.text.ParsePosition;
024import java.text.SimpleDateFormat;
025import java.util.Date;
026import java.util.Locale;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030
031/**
032 * This class contains basic methods for performing validations that return the
033 * correctly typed class based on the validation performed.
034 *
035 * @version $Revision: 1649191 $
036 */
037public class GenericTypeValidator implements Serializable {
038
039    private static final long serialVersionUID = 5487162314134261703L;
040
041    private static final Log LOG = LogFactory.getLog(GenericTypeValidator.class);
042
043    /**
044     * Checks if the value can safely be converted to a byte primitive.
045     *
046     * @param value The value validation is being performed on.
047     * @return the converted Byte value.
048     */
049    public static Byte formatByte(String value) {
050        if (value == null) {
051            return null;
052        }
053
054        try {
055            return new Byte(value);
056        } catch (NumberFormatException e) {
057            return null;
058        }
059
060    }
061
062    /**
063     * Checks if the value can safely be converted to a byte primitive.
064     *
065     * @param value  The value validation is being performed on.
066     * @param locale The locale to use to parse the number (system default if
067     *               null)
068     * @return the converted Byte value.
069     */
070    public static Byte formatByte(String value, Locale locale) {
071        Byte result = null;
072
073        if (value != null) {
074            NumberFormat formatter = null;
075            if (locale != null) {
076                formatter = NumberFormat.getNumberInstance(locale);
077            } else {
078                formatter = NumberFormat.getNumberInstance(Locale.getDefault());
079            }
080            formatter.setParseIntegerOnly(true);
081            ParsePosition pos = new ParsePosition(0);
082            Number num = formatter.parse(value, pos);
083
084            // If there was no error      and we used the whole string
085            if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() &&
086                    num.doubleValue() >= Byte.MIN_VALUE &&
087                    num.doubleValue() <= Byte.MAX_VALUE) {
088                result = new Byte(num.byteValue());
089            }
090        }
091
092        return result;
093    }
094
095    /**
096     * Checks if the value can safely be converted to a short primitive.
097     *
098     * @param value The value validation is being performed on.
099     * @return the converted Short value.
100     */
101    public static Short formatShort(String value) {
102        if (value == null) {
103            return null;
104        }
105
106        try {
107            return new Short(value);
108        } catch (NumberFormatException e) {
109            return null;
110        }
111
112    }
113
114    /**
115     * Checks if the value can safely be converted to a short primitive.
116     *
117     * @param value  The value validation is being performed on.
118     * @param locale The locale to use to parse the number (system default if
119     *               null)
120     * @return the converted Short value.
121     */
122    public static Short formatShort(String value, Locale locale) {
123        Short result = null;
124
125        if (value != null) {
126            NumberFormat formatter = null;
127            if (locale != null) {
128                formatter = NumberFormat.getNumberInstance(locale);
129            } else {
130                formatter = NumberFormat.getNumberInstance(Locale.getDefault());
131            }
132            formatter.setParseIntegerOnly(true);
133            ParsePosition pos = new ParsePosition(0);
134            Number num = formatter.parse(value, pos);
135
136            // If there was no error      and we used the whole string
137            if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() &&
138                    num.doubleValue() >= Short.MIN_VALUE &&
139                    num.doubleValue() <= Short.MAX_VALUE) {
140                result = new Short(num.shortValue());
141            }
142        }
143
144        return result;
145    }
146
147    /**
148     * Checks if the value can safely be converted to a int primitive.
149     *
150     * @param value The value validation is being performed on.
151     * @return the converted Integer value.
152     */
153    public static Integer formatInt(String value) {
154        if (value == null) {
155            return null;
156        }
157
158        try {
159            return new Integer(value);
160        } catch (NumberFormatException e) {
161            return null;
162        }
163
164    }
165
166    /**
167     * Checks if the value can safely be converted to an int primitive.
168     *
169     * @param value  The value validation is being performed on.
170     * @param locale The locale to use to parse the number (system default if
171     *               null)
172     * @return the converted Integer value.
173     */
174    public static Integer formatInt(String value, Locale locale) {
175        Integer result = null;
176
177        if (value != null) {
178            NumberFormat formatter = null;
179            if (locale != null) {
180                formatter = NumberFormat.getNumberInstance(locale);
181            } else {
182                formatter = NumberFormat.getNumberInstance(Locale.getDefault());
183            }
184            formatter.setParseIntegerOnly(true);
185            ParsePosition pos = new ParsePosition(0);
186            Number num = formatter.parse(value, pos);
187
188            // If there was no error      and we used the whole string
189            if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() &&
190                    num.doubleValue() >= Integer.MIN_VALUE &&
191                    num.doubleValue() <= Integer.MAX_VALUE) {
192                result = new Integer(num.intValue());
193            }
194        }
195
196        return result;
197    }
198
199    /**
200     * Checks if the value can safely be converted to a long primitive.
201     *
202     * @param value The value validation is being performed on.
203     * @return the converted Long value.
204     */
205    public static Long formatLong(String value) {
206        if (value == null) {
207            return null;
208        }
209
210        try {
211            return new Long(value);
212        } catch (NumberFormatException e) {
213            return null;
214        }
215
216    }
217
218    /**
219     * Checks if the value can safely be converted to a long primitive.
220     *
221     * @param value  The value validation is being performed on.
222     * @param locale The locale to use to parse the number (system default if
223     *               null)
224     * @return the converted Long value.
225     */
226    public static Long formatLong(String value, Locale locale) {
227        Long result = null;
228
229        if (value != null) {
230            NumberFormat formatter = null;
231            if (locale != null) {
232                formatter = NumberFormat.getNumberInstance(locale);
233            } else {
234                formatter = NumberFormat.getNumberInstance(Locale.getDefault());
235            }
236            formatter.setParseIntegerOnly(true);
237            ParsePosition pos = new ParsePosition(0);
238            Number num = formatter.parse(value, pos);
239
240            // If there was no error      and we used the whole string
241            if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() &&
242                    num.doubleValue() >= Long.MIN_VALUE &&
243                    num.doubleValue() <= Long.MAX_VALUE) {
244                result = new Long(num.longValue());
245            }
246        }
247
248        return result;
249    }
250
251    /**
252     * Checks if the value can safely be converted to a float primitive.
253     *
254     * @param value The value validation is being performed on.
255     * @return the converted Float value.
256     */
257    public static Float formatFloat(String value) {
258        if (value == null) {
259            return null;
260        }
261
262        try {
263            return new Float(value);
264        } catch (NumberFormatException e) {
265            return null;
266        }
267
268    }
269
270    /**
271     * Checks if the value can safely be converted to a float primitive.
272     *
273     * @param value  The value validation is being performed on.
274     * @param locale The locale to use to parse the number (system default if
275     *               null)
276     * @return the converted Float value.
277     */
278    public static Float formatFloat(String value, Locale locale) {
279        Float result = null;
280
281        if (value != null) {
282            NumberFormat formatter = null;
283            if (locale != null) {
284                formatter = NumberFormat.getInstance(locale);
285            } else {
286                formatter = NumberFormat.getInstance(Locale.getDefault());
287            }
288            ParsePosition pos = new ParsePosition(0);
289            Number num = formatter.parse(value, pos);
290
291            // If there was no error      and we used the whole string
292            if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() &&
293                    num.doubleValue() >= (Float.MAX_VALUE * -1) &&
294                    num.doubleValue() <= Float.MAX_VALUE) {
295                result = new Float(num.floatValue());
296            }
297        }
298
299        return result;
300    }
301
302    /**
303     * Checks if the value can safely be converted to a double primitive.
304     *
305     * @param value The value validation is being performed on.
306     * @return the converted Double value.
307     */
308    public static Double formatDouble(String value) {
309        if (value == null) {
310            return null;
311        }
312
313        try {
314            return new Double(value);
315        } catch (NumberFormatException e) {
316            return null;
317        }
318
319    }
320
321    /**
322     * Checks if the value can safely be converted to a double primitive.
323     *
324     * @param value  The value validation is being performed on.
325     * @param locale The locale to use to parse the number (system default if
326     *               null)
327     * @return the converted Double value.
328     */
329    public static Double formatDouble(String value, Locale locale) {
330        Double result = null;
331
332        if (value != null) {
333            NumberFormat formatter = null;
334            if (locale != null) {
335                formatter = NumberFormat.getInstance(locale);
336            } else {
337                formatter = NumberFormat.getInstance(Locale.getDefault());
338            }
339            ParsePosition pos = new ParsePosition(0);
340            Number num = formatter.parse(value, pos);
341
342            // If there was no error      and we used the whole string
343            if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() &&
344                    num.doubleValue() >= (Double.MAX_VALUE * -1) &&
345                    num.doubleValue() <= Double.MAX_VALUE) {
346                result = new Double(num.doubleValue());
347            }
348        }
349
350        return result;
351    }
352
353    /**
354     * Checks if the field is a valid date.
355     *
356     * <p>The {@code Locale} is used with {@code java.text.DateFormat}. The {@link java.text.DateFormat#setLenient(boolean)}
357     * method is set to {@code false} for all.
358     * </p>
359     *
360     * @param value  The value validation is being performed on.
361     * @param locale The Locale to use to parse the date (system default if null)
362     * @return the converted Date value.
363     */
364    public static Date formatDate(String value, Locale locale) {
365        Date date = null;
366
367        if (value == null) {
368            return null;
369        }
370
371        try {
372            // Get the formatters to check against
373            DateFormat formatterShort = null;
374            DateFormat formatterDefault = null;
375            if (locale != null) {
376                formatterShort =
377                        DateFormat.getDateInstance(DateFormat.SHORT, locale);
378                formatterDefault =
379                        DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
380            } else {
381                formatterShort =
382                        DateFormat.getDateInstance(
383                                DateFormat.SHORT,
384                                Locale.getDefault());
385                formatterDefault =
386                        DateFormat.getDateInstance(
387                                DateFormat.DEFAULT,
388                                Locale.getDefault());
389            }
390
391            // Turn off lenient parsing
392            formatterShort.setLenient(false);
393            formatterDefault.setLenient(false);
394
395            // Firstly, try with the short form
396            try {
397                date = formatterShort.parse(value);
398            } catch (ParseException e) {
399                // Fall back on the default one
400                date = formatterDefault.parse(value);
401            }
402        } catch (ParseException e) {
403            // Bad date, so LOG and return null
404            if (LOG.isDebugEnabled()) {
405                LOG.debug("Date parse failed value=[" + value + "], " +
406                        "locale=[" + locale + "] " + e);
407            }
408        }
409
410        return date;
411    }
412
413    /**
414     * Checks if the field is a valid date.
415     *
416     * <p>The pattern is used with {@code java.text.SimpleDateFormat}.
417     * If strict is true, then the length will be checked so '2/12/1999' will
418     * not pass validation with the format 'MM/dd/yyyy' because the month isn't
419     * two digits. The {@link java.text.SimpleDateFormat#setLenient(boolean)}
420     * method is set to {@code false} for all.
421     * </p>
422     *
423     * @param value       The value validation is being performed on.
424     * @param datePattern The pattern passed to {@code SimpleDateFormat}.
425     * @param strict      Whether or not to have an exact match of the
426     *                    datePattern.
427     * @return the converted Date value.
428     */
429    public static Date formatDate(String value, String datePattern, boolean strict) {
430        Date date = null;
431
432        if (value == null
433                || datePattern == null
434                || datePattern.length() == 0) {
435            return null;
436        }
437
438        try {
439            SimpleDateFormat formatter = new SimpleDateFormat(datePattern);
440            formatter.setLenient(false);
441
442            date = formatter.parse(value);
443
444            if (strict && datePattern.length() != value.length()) {
445                date = null;
446            }
447        } catch (ParseException e) {
448            // Bad date so return null
449            if (LOG.isDebugEnabled()) {
450                LOG.debug("Date parse failed value=[" + value + "], " +
451                        "pattern=[" + datePattern + "], " +
452                        "strict=[" + strict + "] " + e);
453            }
454        }
455
456        return date;
457    }
458
459    /**
460     * Checks if the field is a valid credit card number.
461     *
462     * <p>Reference Sean M. Burke's <a href="http://www.ling.nwu.edu/~sburke/pub/luhn_lib.pl">
463     * script</a>.</p>
464     *
465     * @param value The value validation is being performed on.
466     * @return the converted Credit Card number.
467     */
468    public static Long formatCreditCard(String value) {
469        return GenericValidator.isCreditCard(value) ? new Long(value) : null;
470    }
471
472}
473