extensions/net.sf.basedb.reggie/trunk/src/net/sf/basedb/reggie/plugins/cmd/DateValidator.java

Code
Comments
Other
Rev Date Author Line
6200 08 Apr 21 nicklas 1 package net.sf.basedb.reggie.plugins.cmd;
6200 08 Apr 21 nicklas 2
6200 08 Apr 21 nicklas 3 import java.text.DateFormat;
6200 08 Apr 21 nicklas 4 import java.text.SimpleDateFormat;
6200 08 Apr 21 nicklas 5 import java.util.Date;
6200 08 Apr 21 nicklas 6
6201 09 Apr 21 nicklas 7 import net.sf.basedb.core.DbControl;
6200 08 Apr 21 nicklas 8
6201 09 Apr 21 nicklas 9
6200 08 Apr 21 nicklas 10 /**
6200 08 Apr 21 nicklas 11   Validator for Date values. Date values are represented
6200 08 Apr 21 nicklas 12   as strings in the JSON file. A date format string can be
6200 08 Apr 21 nicklas 13   specified for converting the string to a Date instance.
7026 08 Feb 23 nicklas 14   
7026 08 Feb 23 nicklas 15   Note that we still use the SimpleDateFormat class in this
7026 08 Feb 23 nicklas 16   implementation since we need the non-lenient support. Multi-threading
7026 08 Feb 23 nicklas 17   issues (see ticket #1454) are solved by synchronized blocks.
7026 08 Feb 23 nicklas 18   
6200 08 Apr 21 nicklas 19   @since 4.32
6200 08 Apr 21 nicklas 20 */
6200 08 Apr 21 nicklas 21 public class DateValidator
6206 12 Apr 21 nicklas 22   implements ValueValidator<String, Date>, Cloneable
6200 08 Apr 21 nicklas 23 {
6200 08 Apr 21 nicklas 24   /**
6200 08 Apr 21 nicklas 25     Date validator for dates in 'yyyy-MM-dd' format.
6200 08 Apr 21 nicklas 26   */
6200 08 Apr 21 nicklas 27   public static final DateValidator YYYY_MM_DD = new DateValidator("yyyy-MM-dd");
6200 08 Apr 21 nicklas 28
6200 08 Apr 21 nicklas 29   /**
6217 19 Apr 21 nicklas 30     Date validator for dates in 'yyyy-MM-dd HH:mm' format.
6217 19 Apr 21 nicklas 31   */
6217 19 Apr 21 nicklas 32   public static final DateValidator YYYY_MM_DD_HH_MM = new DateValidator("yyyy-MM-dd HH:mm");
6217 19 Apr 21 nicklas 33   
6217 19 Apr 21 nicklas 34   /**
6704 26 Apr 22 nicklas 35     Date validator for dates in 'yyyy-MM-dd [HH:mm]' format (time is optional).
6704 26 Apr 22 nicklas 36     @since 4.39
6704 26 Apr 22 nicklas 37   */
6704 26 Apr 22 nicklas 38   public static final DateValidator YYYY_MM_DD_OPT_HH_MM = new DateValidator("yyyy-MM-dd HH:mm", "yyyy-MM-dd");
6704 26 Apr 22 nicklas 39
6704 26 Apr 22 nicklas 40   /**
6200 08 Apr 21 nicklas 41     Date validator for dates in 'yyMMdd' format.
6200 08 Apr 21 nicklas 42   */
6200 08 Apr 21 nicklas 43   public static final DateValidator YYMMDD = new DateValidator("yyMMdd");
6200 08 Apr 21 nicklas 44
6201 09 Apr 21 nicklas 45   /**
6201 09 Apr 21 nicklas 46     Date+time validator for timestamps in 'yyMMdd HH:mm:ss' format.
6201 09 Apr 21 nicklas 47   */
6201 09 Apr 21 nicklas 48   public static final DateValidator YYMMDD_HH_MM_SS = new DateValidator("yyMMdd HH:mm:ss");
6201 09 Apr 21 nicklas 49
6201 09 Apr 21 nicklas 50   
6200 08 Apr 21 nicklas 51   private final String format;
6200 08 Apr 21 nicklas 52   private final DateFormat dateFormat;
6704 26 Apr 22 nicklas 53   private final DateFormat alternateDateFormat;
6200 08 Apr 21 nicklas 54   
6206 12 Apr 21 nicklas 55   private Date notAfter;
6206 12 Apr 21 nicklas 56   private Date notBefore;
6206 12 Apr 21 nicklas 57   
6200 08 Apr 21 nicklas 58   public DateValidator(String format)
6200 08 Apr 21 nicklas 59   {
6704 26 Apr 22 nicklas 60     this(format, null);
6704 26 Apr 22 nicklas 61   }
6704 26 Apr 22 nicklas 62   
6704 26 Apr 22 nicklas 63   public DateValidator(String format, String alternateFormat)
6704 26 Apr 22 nicklas 64   {
6704 26 Apr 22 nicklas 65     this.format = format+(alternateFormat == null ? "" : " or "+alternateFormat);
6200 08 Apr 21 nicklas 66     this.dateFormat = new SimpleDateFormat(format);
6906 30 Nov 22 nicklas 67     this.dateFormat.setLenient(false);
6704 26 Apr 22 nicklas 68     this.alternateDateFormat = alternateFormat == null ? null : new SimpleDateFormat(alternateFormat);
6906 30 Nov 22 nicklas 69     if (this.alternateDateFormat != null) this.alternateDateFormat.setLenient(false);
6200 08 Apr 21 nicklas 70   }
6200 08 Apr 21 nicklas 71   
6217 19 Apr 21 nicklas 72   @Override
6217 19 Apr 21 nicklas 73   protected DateValidator clone() 
6217 19 Apr 21 nicklas 74   {
6217 19 Apr 21 nicklas 75     try
6217 19 Apr 21 nicklas 76     {
6217 19 Apr 21 nicklas 77       return (DateValidator)super.clone();
6217 19 Apr 21 nicklas 78     }
6217 19 Apr 21 nicklas 79     catch (CloneNotSupportedException e) 
6217 19 Apr 21 nicklas 80     {
6217 19 Apr 21 nicklas 81       // Should never happen
6217 19 Apr 21 nicklas 82       throw new UnsupportedOperationException("clone()");
6217 19 Apr 21 nicklas 83     }
6217 19 Apr 21 nicklas 84   }
6217 19 Apr 21 nicklas 85
6206 12 Apr 21 nicklas 86   /**
6206 12 Apr 21 nicklas 87     Wrap this date validator with a validator that issue a warning
6206 12 Apr 21 nicklas 88     if the date is in the future.
6206 12 Apr 21 nicklas 89   */
6206 12 Apr 21 nicklas 90   public DateValidator warnIfFuture()
6206 12 Apr 21 nicklas 91   {
6217 19 Apr 21 nicklas 92     DateValidator wrap = clone();
6919 01 Dec 22 nicklas 93     wrap.notAfter = alignToFormat(new Date());
6206 12 Apr 21 nicklas 94     return wrap;
6206 12 Apr 21 nicklas 95   }
6206 12 Apr 21 nicklas 96   
6206 12 Apr 21 nicklas 97   /**
6206 12 Apr 21 nicklas 98     Wrap this date validator with a validator that issue a warning
6206 12 Apr 21 nicklas 99     if the date is in the future or older than the specified number of
6206 12 Apr 21 nicklas 100     days.
6206 12 Apr 21 nicklas 101   */
6206 12 Apr 21 nicklas 102   public DateValidator warnIfFutureOrOlder(int numDays)
6206 12 Apr 21 nicklas 103   {
6206 12 Apr 21 nicklas 104     DateValidator wrap = warnIfFuture();
6919 01 Dec 22 nicklas 105     wrap.notBefore = alignToFormat(new Date(wrap.notAfter.getTime()-numDays*86400l*1000l));
6206 12 Apr 21 nicklas 106     return wrap;
6206 12 Apr 21 nicklas 107   }
6206 12 Apr 21 nicklas 108   
6206 12 Apr 21 nicklas 109   /**
6206 12 Apr 21 nicklas 110     Wrap this date validator with a validator that issue a warning
6206 12 Apr 21 nicklas 111     if the date is in the future or older than the specified date.
6893 25 Nov 22 nicklas 112     If multiple dates are given, the first non-null date is used.
6206 12 Apr 21 nicklas 113   */
6893 25 Nov 22 nicklas 114   public DateValidator warnIfFutureOrOlder(Date... oldDates)
6206 12 Apr 21 nicklas 115   {
6206 12 Apr 21 nicklas 116     DateValidator wrap = warnIfFuture();
6893 25 Nov 22 nicklas 117     for (Date d : oldDates)
6893 25 Nov 22 nicklas 118     {
6893 25 Nov 22 nicklas 119       if (d != null)
6893 25 Nov 22 nicklas 120       {
6919 01 Dec 22 nicklas 121         wrap.notBefore = alignToFormat(d);
6893 25 Nov 22 nicklas 122         break;
6893 25 Nov 22 nicklas 123       }
6893 25 Nov 22 nicklas 124     }
6206 12 Apr 21 nicklas 125     return wrap;
6206 12 Apr 21 nicklas 126   }
6206 12 Apr 21 nicklas 127   
6200 08 Apr 21 nicklas 128   @Override
6201 09 Apr 21 nicklas 129   public Date isValid(DbControl dc, String value, JsonSection section, String entryKey) 
6200 08 Apr 21 nicklas 130   {
6206 12 Apr 21 nicklas 131     Date result = null;
6510 03 Dec 21 nicklas 132     if (value != null)
6200 08 Apr 21 nicklas 133     {
6704 26 Apr 22 nicklas 134       DateFormat df = dateFormat;
6704 26 Apr 22 nicklas 135       result = parseWith(dateFormat, value);
6704 26 Apr 22 nicklas 136       if (result == null && alternateDateFormat != null)
6206 12 Apr 21 nicklas 137       {
6704 26 Apr 22 nicklas 138         df = alternateDateFormat;
6704 26 Apr 22 nicklas 139         result = parseWith(alternateDateFormat, value);
6206 12 Apr 21 nicklas 140       }
6704 26 Apr 22 nicklas 141       if (result == null)
6206 12 Apr 21 nicklas 142       {
6510 03 Dec 21 nicklas 143         section.addErrorMessage("Invalid date in JSON: "+entryKey+"="+value+" (expected format: "+format+")");
6206 12 Apr 21 nicklas 144       }
6704 26 Apr 22 nicklas 145       else
6510 03 Dec 21 nicklas 146       {
6907 30 Nov 22 nicklas 147         // Checks if the parsed date is the string-wise equal to the value from JSON
7026 08 Feb 23 nicklas 148         synchronized (df)
6510 03 Dec 21 nicklas 149         {
7026 08 Feb 23 nicklas 150           String parsedAs = df.format(result);
7026 08 Feb 23 nicklas 151           boolean sameResult = parsedAs.equals(value);
7026 08 Feb 23 nicklas 152           if (notAfter != null && result.after(notAfter))
7026 08 Feb 23 nicklas 153           {
7026 08 Feb 23 nicklas 154             section.addWarningMessage("Future date in JSON: "+entryKey+"="+value+(sameResult?"":" (parsed as '"+parsedAs+"')"));
7026 08 Feb 23 nicklas 155           }
7026 08 Feb 23 nicklas 156           if (notBefore != null && result.before(notBefore))
7026 08 Feb 23 nicklas 157           {
7026 08 Feb 23 nicklas 158             section.addWarningMessage("Old date in JSON: "+entryKey+"="+value+
7026 08 Feb 23 nicklas 159               " ("+(sameResult?"":"parsed as '"+parsedAs+"'; ")+"expected after "+df.format(notBefore)+")");
7026 08 Feb 23 nicklas 160           }
6510 03 Dec 21 nicklas 161         }
6510 03 Dec 21 nicklas 162       }
6206 12 Apr 21 nicklas 163     }
6206 12 Apr 21 nicklas 164     return result;
6200 08 Apr 21 nicklas 165   }
6200 08 Apr 21 nicklas 166   
6704 26 Apr 22 nicklas 167   private Date parseWith(DateFormat df, String value)
6704 26 Apr 22 nicklas 168   {
6704 26 Apr 22 nicklas 169     try
6704 26 Apr 22 nicklas 170     {
7026 08 Feb 23 nicklas 171       synchronized (df)
7026 08 Feb 23 nicklas 172       {
7026 08 Feb 23 nicklas 173         return df.parse(value);
7026 08 Feb 23 nicklas 174       }
6704 26 Apr 22 nicklas 175     }
6704 26 Apr 22 nicklas 176     catch (Exception ex)
6704 26 Apr 22 nicklas 177     {}
6704 26 Apr 22 nicklas 178     return null;
6704 26 Apr 22 nicklas 179   }
6704 26 Apr 22 nicklas 180   
6200 08 Apr 21 nicklas 181   @Override
6200 08 Apr 21 nicklas 182   public Class<String> getExpectedClass() 
6200 08 Apr 21 nicklas 183   {
6200 08 Apr 21 nicklas 184     return String.class;
6200 08 Apr 21 nicklas 185   }
6919 01 Dec 22 nicklas 186
6919 01 Dec 22 nicklas 187   /**
6919 01 Dec 22 nicklas 188     Align the given date to the precision used by the format. This will avoid situations
6919 01 Dec 22 nicklas 189     where one date is considered older than another. For example 2022-12-01 is "older than"
6919 01 Dec 22 nicklas 190     2022-12-01 14:47 but not really if we only consider the date part.
6919 01 Dec 22 nicklas 191   */
6919 01 Dec 22 nicklas 192   private Date alignToFormat(Date d)
6919 01 Dec 22 nicklas 193   {
6919 01 Dec 22 nicklas 194     try
6919 01 Dec 22 nicklas 195     {
7026 08 Feb 23 nicklas 196       synchronized (dateFormat)
7026 08 Feb 23 nicklas 197       {
7026 08 Feb 23 nicklas 198         d = dateFormat.parse(dateFormat.format(d));
7026 08 Feb 23 nicklas 199       }
6919 01 Dec 22 nicklas 200     }
6919 01 Dec 22 nicklas 201     catch (Exception ex)
6919 01 Dec 22 nicklas 202     {}
6919 01 Dec 22 nicklas 203     return d;
6919 01 Dec 22 nicklas 204     
6919 01 Dec 22 nicklas 205   }
6200 08 Apr 21 nicklas 206   
6200 08 Apr 21 nicklas 207 }