001: /*
002: * Copyright 2003 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package velosurf.validation;
018:
019: import java.util.Locale;
020: import java.util.Date;
021: import java.util.regex.Pattern;
022: import java.util.regex.Matcher;
023: import java.text.DateFormat;
024: import java.text.ParseException;
025: import java.text.SimpleDateFormat;
026:
027: import velosurf.util.Logger;
028:
029: /**
030: * <p>A type and range constraint on dates. Syntax is:</p>
031: *
032: * <pre><code>
033: * <<i>column</i> [type="date"] [after="<i>date-after</i>"] [before="<i>date-before</i>"] />
034: * </code></pre>
035: *<p>The <code>type="date"</code> parameter is implied when <code>after</code> or <code>before</code> is found.<br><br>Or:</p>
036: * <pre>
037: * <<i>column</i>>
038: * <date [after="<i>afer-date</i>"] [before="<i>before-date</i>"] [format="<i>{@link SimpleDateFormat} format</i>"] [message="<i>error-message</i>"] />
039: * </<i>column</i>>
040: * </pre>
041: * <br><p>The format used to specify after and before dates is always <code>yyyyMMdd</code>. The format used to parse the input
042: * is by default the short local date format (which depends upon the user locale) but can be configured using the <i>format</i> attribute.</p>
043: *
044: * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
045: */
046:
047: public class DateRange extends FieldConstraint {
048: /** before date */
049: private Date before = null;
050: /** afer date */
051: private Date after = null;
052: /** date format */
053: private SimpleDateFormat dateFormat = null;
054:
055: /**
056: * Constructor.
057: */
058: public DateRange() {
059: setMessage("field {0}: '{1}' is not a valid date or is outside range");
060: }
061:
062: /**
063: * Before date constraint setter.
064: * @param before before date
065: */
066: public void setBeforeDate(Date before) {
067: this .before = before;
068: }
069:
070: /**
071: * After date constraint setter.
072: * @param after afet date
073: */
074: public void setAfterDate(Date after) {
075: this .after = after;
076: }
077:
078: /**
079: * Validate data against this constraint.
080: * @param data data to validate
081: * @param locale locale to use
082: * @return whether data is valid
083: */
084: public boolean validate(Object data, Locale locale) {
085: SimpleDateFormat format = dateFormat;
086: try {
087: if (data == null || data.toString().length() == 0)
088: return true;
089: if (locale == null) {
090: Logger.error("date range validation: locale is null!");
091: return true;
092: }
093: if (format == null) {
094: format = (SimpleDateFormat) SimpleDateFormat
095: .getDateInstance(DateFormat.SHORT, locale);
096: }
097: String reformatted = reformat(format.toPattern(), data
098: .toString());
099: Date date = format.parse(reformatted);
100: if (after != null && date.before(after)) {
101: return false;
102: }
103: if (before != null && date.after(before)) {
104: return false;
105: }
106: return true;
107: } catch (ParseException pe) {
108: Logger.warn("date validation: could not parse date '"
109: + data.toString() + "' with format: "
110: + format.toPattern());
111: // Logger.log(pe);
112: return false;
113: }
114: }
115:
116: /** 'YYYY' date format. */
117: private static final Pattern y4 = Pattern.compile("\\d{4}");
118:
119: /**
120: * tries to reformat the date to match pattern conventions
121: * @param pattern date pattern
122: * @param date date
123: * @return reformatted date
124: */
125: private static String reformat(String pattern, String date) {
126: int patternLength = pattern.length();
127: int dateLength = date.length();
128: char patternSep, dateSep;
129: boolean warn = false;
130: patternSep = pattern.indexOf('/') != -1 ? '/' : pattern
131: .indexOf('-') != -1 ? '-' : '?';
132: dateSep = date.indexOf('/') != -1 ? '/'
133: : date.indexOf('-') != -1 ? '-' : '?';
134: if (patternSep == '?') {
135: if (dateSep == '?') {
136: warn = (patternLength != dateLength);
137: } else {
138: date = date.replace("" + dateSep, "");
139: warn = (patternLength != date.length());
140: }
141: } else {
142: if (dateSep == '?') {
143: if (dateLength == 6) {
144: /* not ABSOLUTELY sure that six chars without - or / are one of ddMMyy, MMddyy, yyMMdd and the like... but quite. */
145: date = date.substring(0, 2) + patternSep
146: + date.substring(2, 4) + patternSep
147: + date.substring(4, 6);
148: warn = (patternLength != 8);
149: } else {
150: /* too complex */
151: warn = true;
152: }
153: } else {
154: if (patternSep != dateSep) {
155: date = date.replace(dateSep, patternSep);
156: }
157: if (patternLength <= dateLength - 2) {
158: Matcher m = y4.matcher(date);
159: if (m.find()) {
160: date = date.substring(0, m.start())
161: + date.substring(m.start() + 2);
162: } else {
163: warn = true;
164: }
165: }
166: }
167: }
168: if (warn) {
169: Logger.warn("date range validation: could not match date '"
170: + date + "' with format '" + pattern + "'");
171: }
172: return date;
173: }
174:
175: /** yyyyMMdd date format. */
176: private static DateFormat ymd = new SimpleDateFormat("yyyyMMdd");
177:
178: /**
179: * return a string representation for this constraint.
180: * @return string
181: */
182: public String toString() {
183: String ret = "type date";
184: if (after != null) {
185: ret += ", after " + ymd.format(after);
186: }
187: if (before != null) {
188: ret += ", before " + ymd.format(before);
189: }
190: return ret;
191: }
192:
193: /** date format setter.
194: *
195: * @param dateFormat date format
196: */
197: public void setDateFormat(SimpleDateFormat dateFormat) {
198: this.dateFormat = dateFormat;
199: }
200: }
|