001    package org.apache.fulcrum.intake.validator;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import java.text.DateFormat;
023    import java.text.ParseException;
024    import java.text.SimpleDateFormat;
025    
026    import java.util.ArrayList;
027    import java.util.Date;
028    import java.util.List;
029    import java.util.Map;
030    
031    import org.apache.commons.lang.StringUtils;
032    
033    import org.apache.fulcrum.intake.IntakeException;
034    
035    /**
036     * Validates numbers with the following constraints in addition to those
037     * listed in DefaultValidator.
038     *
039     * <table>
040     * <tr><th>Name</th><th>Valid Values</th><th>Default Value</th></tr>
041     * <tr><td>format</td><td>see SimpleDateFormat javadoc</td>
042     * <td>&nbsp;</td></tr>
043     * <tr><td>formatx</td><td>see SimpleDateFormat javadoc</td>
044     * <td>&nbsp;</td></tr>
045     * <tr><td colspan=3>where x is &gt;= 1 to specify multiple date
046     *         formats.  Only one format rule should have a message</td></tr>
047     * <tr><td>flexible</td><td>true, as long as DateFormat can parse the date,
048     *                            allow it, and false</td>
049     * <td>false</td></tr>
050     * </table>
051     *
052     * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
053     * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
054     * @author <a href="mailto:Colin.Chalmers@maxware.nl">Colin Chalmers</a>
055     * @author <a href="mailto:jh@byteaction.de">J&uuml;rgen Hoffmann</a>
056     * @author <a href="mailto:seade@backstagetech.com.au">Scott Eade</a>
057     * @version $Id: DateStringValidator.java 535465 2007-05-05 06:58:06Z tv $
058     */
059    public class DateStringValidator
060            extends DefaultValidator
061    {
062        private static final String DEFAULT_DATE_MESSAGE =
063                "Date could not be parsed";
064    
065        /**  */
066        private List dateFormats = null;
067    
068        /**  */
069        private String dateFormatMessage = null;
070    
071        /**  */
072        private boolean flexible = false;
073    
074        /**  */
075        private DateFormat df = null;
076    
077        /**  */
078        private SimpleDateFormat sdf = null;
079    
080        public DateStringValidator(Map paramMap)
081                throws IntakeException
082        {
083            init(paramMap);
084        }
085    
086        /**
087         * Default Constructor
088         */
089        public DateStringValidator()
090        {
091            dateFormats = new ArrayList(5);
092        }
093    
094        /**
095         * Constructor to use when initialising Object
096         *
097         * @param paramMap
098         * @throws InvalidMaskException
099         */
100        public void init(Map paramMap)
101                throws InvalidMaskException
102        {
103            super.init(paramMap);
104    
105            Constraint constraint = (Constraint) paramMap.get(FORMAT_RULE_NAME);
106    
107            if (constraint != null)
108            {
109                dateFormats.add(constraint.getValue());
110                setDateFormatMessage(constraint.getMessage());
111            }
112    
113            for(int i = 1 ;; i++)
114            {
115                constraint = (Constraint) paramMap.get(FORMAT_RULE_NAME + i);
116    
117                if (constraint == null)
118                {
119                    break; // for
120                }
121    
122                dateFormats.add(constraint.getValue());
123                setDateFormatMessage(constraint.getMessage());
124            }
125    
126            if (StringUtils.isEmpty(dateFormatMessage))
127            {
128                dateFormatMessage = DEFAULT_DATE_MESSAGE;
129            }
130    
131            constraint = (Constraint) paramMap.get(FLEXIBLE_RULE_NAME);
132    
133            if (constraint != null)
134            {
135                flexible = Boolean.valueOf(constraint.getValue()).booleanValue();
136            }
137    
138            if (dateFormats.size() == 0)
139            {
140                df = DateFormat.getInstance();
141                df.setLenient(flexible);
142            }
143            else
144            {
145                sdf = new SimpleDateFormat();
146                sdf.setLenient(flexible);
147            }
148        }
149    
150        /**
151         * Determine whether a testValue meets the criteria specified
152         * in the constraints defined for this validator
153         *
154         * @param testValue a <code>String</code> to be tested
155         * @exception ValidationException containing an error message if the
156         * testValue did not pass the validation tests.
157         */
158        public void assertValidity(String testValue)
159                throws ValidationException
160        {
161            super.assertValidity(testValue);
162    
163            if (required || StringUtils.isNotEmpty(testValue))
164            {
165                try
166                {
167                    parse(testValue);
168                }
169                catch (ParseException e)
170                {
171                    errorMessage = dateFormatMessage;
172                    throw new ValidationException(dateFormatMessage);
173                }
174            }
175        }
176    
177        /**
178         * Parses the String s according to the rules/formats for this validator.
179         * The formats provided by the "formatx" rules (where x is &gt;= 1) are
180         * used <strong>before</strong> the "format" rules to allow for a display
181         * format that includes a 4 digit year, but that will parse the date using
182         * a format that accepts 2 digit years.
183         *
184         * @throws ParseException indicates that the string could not be
185         * parsed into a date.
186         */
187        public Date parse(String s)
188                throws ParseException
189        {
190            Date date = null;
191    
192            if (s == null)
193            {
194                throw new ParseException("Input string was null", -1);
195            }
196    
197            for (int i = 1; i < dateFormats.size() && date == null; i++)
198            {
199                sdf.applyPattern((String) dateFormats.get(i));
200    
201                try
202                {
203                    date = sdf.parse(s);
204                }
205                catch (ParseException e)
206                {
207                    // ignore
208                }
209            }
210    
211            if (date == null)
212            {
213                sdf.applyPattern((String) dateFormats.get(0));
214    
215                try
216                {
217                    date = sdf.parse(s);
218                }
219                catch (ParseException e)
220                {
221                    // ignore
222                }
223            }
224    
225            if (date == null && df != null)
226            {
227                date = df.parse(s);
228            }
229    
230            // if the date still has not been parsed at this point, throw
231            // a ParseException.
232            if (date == null)
233            {
234                throw new ParseException("Could not parse the date", 0);
235            }
236    
237            return date;
238        }
239    
240        /**
241         * Formats a date into a String.  The format used is from
242         * the first format rule found for the field.
243         *
244         * @param date the Date object to convert into a string.
245         * @return formatted date
246         */
247        public String format(Date date)
248        {
249            String s = null;
250            if (date != null)
251            {
252                sdf.applyPattern((String) dateFormats.get(0));
253                s = sdf.format(date);
254            }
255            return s;
256        }
257    
258    
259        // ************************************************************
260        // **                Bean accessor methods                   **
261        // ************************************************************
262    
263        /**
264         * Get the value of minLengthMessage.
265         *
266         * @return value of minLengthMessage.
267         */
268        public String getDateFormatMessage()
269        {
270            return dateFormatMessage;
271        }
272    
273        /**
274         * Only sets the message if the new message has some information.
275         * So the last setMessage call with valid data wins.  But later calls
276         * with null or empty string will not affect a previous valid setting.
277         *
278         * @param message  Value to assign to minLengthMessage.
279         */
280        public void setDateFormatMessage(String message)
281        {
282            if (StringUtils.isNotEmpty(message))
283            {
284                dateFormatMessage = message;
285            }
286        }
287    
288        /**
289         * Get the value of dateFormats.
290         *
291         * @return value of dateFormats.
292         */
293        public List getDateFormats()
294        {
295            return dateFormats;
296        }
297    
298        /**
299         * Set the value of dateFormats.
300         *
301         * @param formats  Value to assign to dateFormats.
302         */
303        public void setDateFormats(List formats)
304        {
305            this.dateFormats = formats;
306        }
307    
308        /**
309         * Get the value of flexible.
310         *
311         * @return value of flexible.
312         */
313        public boolean isFlexible()
314        {
315            return flexible;
316        }
317    
318        /**
319         * Set the value of flexible.
320         *
321         * @param flexible  Value to assign to flexible.
322         */
323        public void setFlexible(boolean flexible)
324        {
325            this.flexible = flexible;
326        }
327    }