    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.routines;
    018
    019import java.text.DateFormat;
    020import java.text.DateFormatSymbols;
    021import java.text.Format;
    022import java.text.SimpleDateFormat;
    023import java.util.Calendar;
    024import java.util.Locale;
    025import java.util.TimeZone;
    026
    027import org.apache.commons.validator.GenericValidator;
    028
    029/**
    030 * <p>Abstract class for Date/Time/Calendar validation.</p>
    031 *
    032 * <p>This is a <i>base</i> class for building Date / Time
    033 *    Validators using format parsing.</p>
    034 *
    035 * @since 1.3.0
    036 */
    037public abstract class AbstractCalendarValidator extends AbstractFormatValidator {
    038
    039    private static final long serialVersionUID = -1410008585975827379L;
    040
    041    /**
    042     * The date style to use for Locale validation.
    043     */
    044    private final int dateStyle;
    045
    046    /**
    047     * The time style to use for Locale validation.
    048     */
    049    private final int timeStyle;
    050
    051    /**
    052     * Constructs an instance with the specified <i>strict</i>,
    053     * <i>time</i> and <i>date</i> style parameters.
    054     *
    055     * @param strict {@code true} if strict
    056     *        <code>Format</code> parsing should be used.
    057     * @param dateStyle the date style to use for Locale validation.
    058     * @param timeStyle the time style to use for Locale validation.
    059     */
    060    public AbstractCalendarValidator(final boolean strict, final int dateStyle, final int timeStyle) {
    061        super(strict);
    062        this.dateStyle = dateStyle;
    063        this.timeStyle = timeStyle;
    064    }
    065
    066    /**
    067     * <p>Compares the field from two calendars indicating whether the field for the
    068     *    first calendar is equal to, less than or greater than the field from the
    069     *    second calendar.
    070     *
    071     * @param value The Calendar value.
    072     * @param compare The <code>Calendar</code> to check the value against.
    073     * @param field The field to compare for the calendars.
    074     * @return Zero if the first calendar's field is equal to the seconds, -1
    075     *         if it is less than the seconds or +1 if it is greater than the seconds.
    076     */
    077    private int calculateCompareResult(final Calendar value, final Calendar compare, final int field) {
    078        final int difference = value.get(field) - compare.get(field);
    079        if (difference < 0) {
    080            return -1;
    081        }
    082        if (difference > 0) {
    083            return 1;
    084        }
    085        return 0;
    086    }
    087
    088    /**
    089     * <p>Calculate the quarter for the specified Calendar.</p>
    090     *
    091     * @param calendar The Calendar value.
    092     * @param monthOfFirstQuarter The  month that the first quarter starts.
    093     * @return The calculated quarter.
    094     */
    095    private int calculateQuarter(final Calendar calendar, final int monthOfFirstQuarter) {
    096        // Add Year
    097        int year = calendar.get(Calendar.YEAR);
    098
    099        final int month = calendar.get(Calendar.MONTH) + 1;
    100        final int relativeMonth = month >= monthOfFirstQuarter
    101                          ? month - monthOfFirstQuarter
    102                          : month + 12 - monthOfFirstQuarter; // CHECKSTYLE IGNORE MagicNumber
    103        final int quarter = relativeMonth / 3 + 1; // CHECKSTYLE IGNORE MagicNumber
    104        // adjust the year if the quarter doesn't start in January
    105        if (month < monthOfFirstQuarter) {
    106            --year;
    107        }
    108        return year * 10 + quarter; // CHECKSTYLE IGNORE MagicNumber
    109    }
    110
    111    /**
    112     * <p>Compares a calendar value to another, indicating whether it is
    113     *    equal, less then or more than at a specified level.</p>
    114     *
    115     * @param value The Calendar value.
    116     * @param compare The <code>Calendar</code> to check the value against.
    117     * @param field The field <i>level</i> to compare to - e.g. specifying
    118     *        <code>Calendar.MONTH</code> will compare the year and month
    119     *        portions of the calendar.
    120     * @return Zero if the first value is equal to the second, -1
    121     *         if it is less than the second or +1 if it is greater than the second.
    122     */
    123    protected int compare(final Calendar value, final Calendar compare, final int field) {
    124
    125        int result;
    126
    127        // Compare Year
    128        result = calculateCompareResult(value, compare, Calendar.YEAR);
    129        if (result != 0 || field == Calendar.YEAR) {
    130            return result;
    131        }
    132
    133        // Compare Week of Year
    134        if (field == Calendar.WEEK_OF_YEAR) {
    135            return calculateCompareResult(value, compare, Calendar.WEEK_OF_YEAR);
    136        }
    137
    138        // Compare Day of the Year
    139        if (field == Calendar.DAY_OF_YEAR) {
    140            return calculateCompareResult(value, compare, Calendar.DAY_OF_YEAR);
    141        }
    142
    143        // Compare Month
    144        result = calculateCompareResult(value, compare, Calendar.MONTH);
    145        if (result != 0 || field == Calendar.MONTH) {
    146            return result;
    147        }
    148
    149        // Compare Week of Month
    150        if (field == Calendar.WEEK_OF_MONTH) {
    151            return calculateCompareResult(value, compare, Calendar.WEEK_OF_MONTH);
    152        }
    153
    154        // Compare Date
    155        result = calculateCompareResult(value, compare, Calendar.DATE);
    156        if (result != 0 || field == Calendar.DATE ||
    157                          field == Calendar.DAY_OF_WEEK ||
    158                          field == Calendar.DAY_OF_WEEK_IN_MONTH) {
    159            return result;
    160        }
    161
    162        // Compare Time fields
    163        return compareTime(value, compare, field);
    164
    165    }
    166
    167    /**
    168     * <p>Compares a calendar's quarter value to another, indicating whether it is
    169     *    equal, less then or more than the specified quarter.</p>
    170     *
    171     * @param value The Calendar value.
    172     * @param compare The <code>Calendar</code> to check the value against.
    173     * @param monthOfFirstQuarter The  month that the first quarter starts.
    174     * @return Zero if the first quarter is equal to the second, -1
    175     *         if it is less than the second or +1 if it is greater than the second.
    176     */
    177    protected int compareQuarters(final Calendar value, final Calendar compare, final int monthOfFirstQuarter) {
    178        final int valueQuarter = calculateQuarter(value, monthOfFirstQuarter);
    179        final int compareQuarter = calculateQuarter(compare, monthOfFirstQuarter);
    180        if (valueQuarter < compareQuarter) {
    181            return -1;
    182        }
    183        if (valueQuarter > compareQuarter) {
    184            return 1;
    185        }
    186        return 0;
    187    }
    188
    189    /**
    190     * <p>Compares a calendar time value to another, indicating whether it is
    191     *    equal, less then or more than at a specified level.</p>
    192     *
    193     * @param value The Calendar value.
    194     * @param compare The <code>Calendar</code> to check the value against.
    195     * @param field The field <i>level</i> to compare to - e.g. specifying
    196     *        <code>Calendar.MINUTE</code> will compare the hours and minutes
    197     *        portions of the calendar.
    198     * @return Zero if the first value is equal to the second, -1
    199     *         if it is less than the second or +1 if it is greater than the second.
    200     */
    201    protected int compareTime(final Calendar value, final Calendar compare, final int field) {
    202
    203        int result;
    204
    205        // Compare Hour
    206        result = calculateCompareResult(value, compare, Calendar.HOUR_OF_DAY);
    207        if (result != 0 || field == Calendar.HOUR || field == Calendar.HOUR_OF_DAY) {
    208            return result;
    209        }
    210
    211        // Compare Minute
    212        result = calculateCompareResult(value, compare, Calendar.MINUTE);
    213        if (result != 0 || field == Calendar.MINUTE) {
    214            return result;
    215        }
    216
    217        // Compare Second
    218        result = calculateCompareResult(value, compare, Calendar.SECOND);
    219        if (result != 0 || field == Calendar.SECOND) {
    220            return result;
    221        }
    222
    223        // Compare Milliseconds
    224        if (field == Calendar.MILLISECOND) {
    225            return calculateCompareResult(value, compare, Calendar.MILLISECOND);
    226        }
    227
    228        throw new IllegalArgumentException("Invalid field: " + field);
    229
    230    }
    231
    232    /**
    233     * <p>Format a value with the specified <code>DateFormat</code>.</p>
    234     *
    235     * @param value The value to be formatted.
    236     * @param formatter The Format to use.
    237     * @return The formatted value.
    238     */
    239    @Override
    240    protected String format(Object value, final Format formatter) {
    241        if (value == null) {
    242            return null;
    243        }
    244        if (value instanceof Calendar) {
    245            value = ((Calendar) value).getTime();
    246        }
    247        return formatter.format(value);
    248    }
    249
    250    /**
    251     * <p>Format an object into a <code>String</code> using
    252     * the specified Locale.</p>
    253     *
    254     * @param value The value validation is being performed on.
    255     * @param locale The locale to use for the Format.
    256     * @param timeZone The Time Zone used to format the date,
    257     *  system default if null (unless value is a <code>Calendar</code>.
    258     * @return The value formatted as a <code>String</code>.
    259     */
    260    public String format(final Object value, final Locale locale, final TimeZone timeZone) {
    261        return format(value, (String) null, locale, timeZone);
    262    }
    263
    264    /**
    265     * <p>Format an object using the specified pattern and/or
    266     *    <code>Locale</code>.
    267     *
    268     * @param value The value validation is being performed on.
    269     * @param pattern The pattern used to format the value.
    270     * @param locale The locale to use for the Format.
    271     * @return The value formatted as a <code>String</code>.
    272     */
    273    @Override
    274    public String format(final Object value, final String pattern, final Locale locale) {
    275        return format(value, pattern, locale, (TimeZone) null);
    276    }
    277
    278    /**
    279     * <p>Format an object using the specified pattern and/or
    280     *    <code>Locale</code>.
    281     *
    282     * @param value The value validation is being performed on.
    283     * @param pattern The pattern used to format the value.
    284     * @param locale The locale to use for the Format.
    285     * @param timeZone The Time Zone used to format the date,
    286     *  system default if null (unless value is a <code>Calendar</code>.
    287     * @return The value formatted as a <code>String</code>.
    288     */
    289    public String format(final Object value, final String pattern, final Locale locale, final TimeZone timeZone) {
    290        final DateFormat formatter = (DateFormat) getFormat(pattern, locale);
    291        if (timeZone != null) {
    292            formatter.setTimeZone(timeZone);
    293        } else if (value instanceof Calendar) {
    294            formatter.setTimeZone(((Calendar) value).getTimeZone());
    295        }
    296        return format(value, formatter);
    297    }
    298
    299    /**
    300     * <p>Format an object into a <code>String</code> using
    301     * the specified pattern.</p>
    302     *
    303     * @param value The value validation is being performed on.
    304     * @param pattern The pattern used to format the value.
    305     * @param timeZone The Time Zone used to format the date,
    306     *  system default if null (unless value is a <code>Calendar</code>.
    307     * @return The value formatted as a <code>String</code>.
    308     */
    309    public String format(final Object value, final String pattern, final TimeZone timeZone) {
    310        return format(value, pattern, (Locale) null, timeZone);
    311    }
    312
    313    /**
    314     * <p>Format an object into a <code>String</code> using
    315     * the default Locale.</p>
    316     *
    317     * @param value The value validation is being performed on.
    318     * @param timeZone The Time Zone used to format the date,
    319     *  system default if null (unless value is a <code>Calendar</code>.
    320     * @return The value formatted as a <code>String</code>.
    321     */
    322    public String format(final Object value, final TimeZone timeZone) {
    323        return format(value, (String) null, (Locale) null, timeZone);
    324    }
    325
    326    /**
    327     * <p>Returns a <code>DateFormat</code> for the specified Locale.</p>
    328     *
    329     * @param locale The locale a <code>DateFormat</code> is required for,
    330     *        system default if null.
    331     * @return The <code>DateFormat</code> to created.
    332     */
    333    protected Format getFormat(final Locale locale) {
    334        DateFormat formatter;
    335        if (dateStyle >= 0 && timeStyle >= 0) {
    336            if (locale == null) {
    337                formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle);
    338            } else {
    339                formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
    340            }
    341        } else if (timeStyle >= 0) {
    342            if (locale == null) {
    343                formatter = DateFormat.getTimeInstance(timeStyle);
    344            } else {
    345                formatter = DateFormat.getTimeInstance(timeStyle, locale);
    346            }
    347        } else {
    348            final int useDateStyle = dateStyle >= 0 ? dateStyle : DateFormat.SHORT;
    349            if (locale == null) {
    350                formatter = DateFormat.getDateInstance(useDateStyle);
    351            } else {
    352                formatter = DateFormat.getDateInstance(useDateStyle, locale);
    353            }
    354        }
    355        formatter.setLenient(false);
    356        return formatter;
    357
    358    }
    359
    360    /**
    361     * <p>Returns a <code>DateFormat</code> for the specified <i>pattern</i>
    362     *    and/or <code>Locale</code>.</p>
    363     *
    364     * @param pattern The pattern used to validate the value against or
    365     *        {@code null} to use the default for the <code>Locale</code>.
    366     * @param locale The locale to use for the currency format, system default if null.
    367     * @return The <code>DateFormat</code> to created.
    368     */
    369    @Override
    370    protected Format getFormat(final String pattern, final Locale locale) {
    371        DateFormat formatter;
    372        final boolean usePattern = !GenericValidator.isBlankOrNull(pattern);
    373        if (!usePattern) {
    374            formatter = (DateFormat) getFormat(locale);
    375        } else if (locale == null) {
    376            formatter = new SimpleDateFormat(pattern);
    377        } else {
    378            final DateFormatSymbols symbols = new DateFormatSymbols(locale);
    379            formatter = new SimpleDateFormat(pattern, symbols);
    380        }
    381        formatter.setLenient(false);
    382        return formatter;
    383    }
    384
    385    /**
    386     * <p>Validate using the specified <code>Locale</code>.
    387     *
    388     * @param value The value validation is being performed on.
    389     * @param pattern The pattern used to format the value.
    390     * @param locale The locale to use for the Format, defaults to the default
    391     * @return {@code true} if the value is valid.
    392     */
    393    @Override
    394    public boolean isValid(final String value, final String pattern, final Locale locale) {
    395        return parse(value, pattern, locale, (TimeZone) null) != null;
    396    }
    397
    398    /**
    399     * <p>Checks if the value is valid against a specified pattern.</p>
    400     *
    401     * @param value The value validation is being performed on.
    402     * @param pattern The pattern used to validate the value against, or the
    403     *        default for the <code>Locale</code> if {@code null}.
    404     * @param locale The locale to use for the date format, system default if null.
    405     * @param timeZone The Time Zone used to parse the date, system default if null.
    406     * @return The parsed value if valid or {@code null} if invalid.
    407     */
    408    protected Object parse(String value, final String pattern, final Locale locale, final TimeZone timeZone) {
    409        value = value == null ? null : value.trim();
    410        final String value1 = value;
    411        if (GenericValidator.isBlankOrNull(value1)) {
    412            return null;
    413        }
    414        final DateFormat formatter = (DateFormat) getFormat(pattern, locale);
    415        if (timeZone != null) {
    416            formatter.setTimeZone(timeZone);
    417        }
    418        return parse(value, formatter);
    419
    420    }
    421
    422    /**
    423     * <p>Process the parsed value, performing any further validation
    424     *    and type conversion required.</p>
    425     *
    426     * @param value The parsed object created.
    427     * @param formatter The Format used to parse the value with.
    428     * @return The parsed value converted to the appropriate type
    429     *         if valid or {@code null} if invalid.
    430     */
    431    @Override
    432    protected abstract Object processParsedValue(Object value, Format formatter);
    433}