View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    * 
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   */
19  package org.apache.myfaces.trinidad.convert;
20  
21  import java.text.DateFormat;
22  import java.text.DateFormatSymbols;
23  import java.text.ParseException;
24  import java.text.SimpleDateFormat;
25  
26  import java.util.Calendar;
27  import java.util.Date;
28  import java.util.GregorianCalendar;
29  import java.util.HashSet;
30  import java.util.Locale;
31  import java.util.MissingResourceException;
32  import java.util.ResourceBundle;
33  import java.util.Set;
34  import java.util.TimeZone;
35  
36  import javax.faces.application.FacesMessage;
37  import javax.faces.component.StateHolder;
38  import javax.faces.component.UIComponent;
39  import javax.faces.component.ValueHolder;
40  import javax.faces.context.FacesContext;
41  import javax.faces.convert.Converter;
42  import javax.faces.convert.ConverterException;
43  import javax.faces.el.ValueBinding;
44  
45  import org.apache.myfaces.trinidad.bean.FacesBean;
46  import org.apache.myfaces.trinidad.bean.PropertyKey;
47  import org.apache.myfaces.trinidad.context.RequestContext;
48  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
49  import org.apache.myfaces.trinidad.util.ComponentUtils;
50  import org.apache.myfaces.trinidad.util.MessageFactory;
51  
52  
53  /**
54   * <p>{@link Converter} implementation for <code>java.util.Date</code>
55   * values. Converts an strings to and from java.util.Date objects.</p>
56   *
57   * The converter has additonal features than standard JSF
58   * {@link javax.faces.convert.DateTimeConverter}.
59   *
60   * New dateStyle <code>shortish</code> has been introduced. Shortish is identical
61   * to <code>short</code> but forces the year to be a full four digits.
62   * If dateStyle is not set, then <code>dateStyle</code> defaults to
63   * <code>shortish</code>.
64   *
65   *  <p>Timezone can be set per web-app in trinidad-config.xml configuration file.
66   * If <code>timeZone</code> is not set on the converter, then timezone will be defaulted to the
67   * value set in trinidad-config.xml configuration file. If it is not set in the
68   * configuration file, then it will be defaulted to GMT.</p>
69   *
70   * <p>The converter always allows a level of <code>leniency</code> while converting
71   * user input value to date to the following extent.
72   * <ul>
73   * <li>A converter with associated pattern 'MMM' for month, when attached to any
74   * value holder, will accept values with month specified in the form 'MM' or 'M'
75   * as valid.</li>
76   * <li>Allows use of separators '-' or '.' or '/' irrespective of the separator
77   * specified in the associated pattern.</li>
78   * <li>The leniency is applicable to both 'pattern' and 'secondaryPattern'.</li>
79   * </ul></p>
80   * <p>
81   * For example:</br>
82   * When pattern on the converter is set to "MMM/d/yyyy" the following inputs
83   * are tolerated as valid by the converter.</br>
84   * <dl>
85   * <dt>Jan/4/2004</dt>
86   * <dt>Jan-4-2004</dt>
87   * <dt>Jan.4.2004</dt>
88   * <dt>01/4/2004</dt>
89   * <dt>01-4-2004</dt>
90   * <dt>01.4.2004</dt>
91   * <dt>1/4/2004</dt>
92   * <dt>1-4-2004</dt>
93   * <dt>1.4.2004</dt>
94   * </dl>
95   * </p>
96   *
97   * The detail part of faces message,for conversion errors can be customized
98   * by overriding the message associated with each CONVERT_[TYPE]_MESSAGE_ID.
99   *
100  * <p>The methods used for customizing the detail message associated with each id
101  * is given below:</p>
102  * <ol>
103  * <li>{@link #CONVERT_DATE_MESSAGE_ID} - {@link #setMessageDetailConvertDate(String)}</li>
104  * <li>{@link #CONVERT_TIME_MESSAGE_ID} - {@link #setMessageDetailConvertTime(String) }</li>
105  * <li>{@link #CONVERT_BOTH_MESSAGE_ID} - {@link #setMessageDetailConvertBoth(String) }</li>
106  * </ol> The custom messages can contain placeholders, which will be replaced with
107  * values as specified in its corresponding message id.
108  *
109  * <p>The <code>getAsObject()</code> method parses a String into a
110  * <code>java.util.Date</code>, according to the following algorithm:</p>
111  * <ul>
112  * <li>If the specified String is null, return
113  *     a <code>null</code>.  Otherwise, trim leading and trailing
114  *     whitespace before proceeding.</li>
115  * <li>If the specified String - after trimming - has a zero length,
116  *     return <code>null</code>.</li>
117  * <li>If the <code>locale</code> property is not null,
118  *     use that <code>Locale</code> for managing parsing.  Otherwise, use the
119  *     <code>Locale</code> from the <code>UIViewRoot</code>.</li>
120  * <li>If a <code>pattern</code> has been specified, its syntax must conform
121  *     the rules specified by <code>java.text.SimpleDateFormat</code>.  Such
122  *     a pattern will be used to parse, and the <code>type</code>,
123  *     <code>dateStyle</code>, and <code>timeStyle</code> properties
124  *     will be ignored.</li>
125  * <li>If a <code>pattern</code> has not been specified, parsing will be based
126  *     on the <code>type</code> property, which expects a date value, a time
127  *     value, or both.  Any date and time values included will be parsed in
128  *     accordance to the styles specified by <code>dateStyle</code> and
129  *     <code>timeStyle</code>, respectively.</li>
130  * <li>If conversion fails with <code>pattern</code> or <code>style</code>
131  *     and if <code> secondaryPattern</code> is set, re parsers based on the
132  *     <code>secondaryPattern</code>. Syntax for <code>secondaryPattern</code>
133  *     must conform to the rules specified by
134  *     <code>java.text.SimpleDateFormat</code>.</li>
135  * <li>Parsing is lenient as outlined earlier and is not the same as setting
136  *     leniency on <code>java.text.DateFormat</code>
137  * </ul>
138  *
139  * <p>The <code>getAsString()</code> method expects a value of type
140  * <code>java.util.Date</code> (or a subclass), and creates a formatted
141  * String according to the following algorithm:</p>
142  * <ul>
143  * <li>If the specified value is null, return a zero-length String.</li>
144  * <li>If the specified value is a String, return it unmodified.</li>
145  * <li>If the <code>locale</code> property is not null,
146  *     use that <code>Locale</code> for managing formatting.  Otherwise, use the
147  *     <code>Locale</code> from the <code>UIViewRoot</code>.</li>
148  * <li>If a <code>pattern</code> has been specified, its syntax must conform
149  *     the rules specified by <code>java.text.SimpleDateFormat</code>. Such
150  *     a pattern will be used to format, and the <code>type</code>,
151  *     <code>dateStyle</code>, and <code>timeStyle</code> properties
152  *     will be ignored.</li>
153  * <li>If a <code>pattern</code> has not been specified, formatting will be
154  *     based on the <code>type</code> property, which includes a date value,
155  *     a time value, or both into the formatted String.  Any date and time
156  *     values included will be formatted in accordance to the styles specified
157  *     by <code>dateStyle</code> and <code>timeStyle</code>, respectively.</li>
158  * <li><code>secondaryPattern</code> even if set is never used for formatting
159  *     to a String</li>
160  * </ul>
161  *
162  * @see #CONVERT_DATE_MESSAGE_ID
163  * @see #CONVERT_TIME_MESSAGE_ID
164  * @see #CONVERT_BOTH_MESSAGE_ID
165  * @see java.text.DateFormat
166  * @see java.text.SimpleDateFormat
167  *
168  * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-api/src/main/java/oracle/adf/view/faces/convert/DateTimeConverter.java#0 $) $Date: 10-nov-2005.19:09:11 $
169  */
170 public class DateTimeConverter extends javax.faces.convert.DateTimeConverter
171                                implements Converter, StateHolder
172 
173 {
174 
175   /**
176    * <p>Standard converter id for this converter.</p>
177    */
178   public static final String CONVERTER_ID = "org.apache.myfaces.trinidad.DateTime";
179 
180   /**
181    * <p>The message identifier of the FacesMessage to be created if
182    * the value cannot be converted to a date, when <code>pattern</code>
183    * is null or not set and <code>type</code> is set to <code>'date'</code>.
184    * Or when failures occurs when value cannot be converted to a date
185    * based on the pattern set. The message format string for this message
186    * may optionally include <code>{0}</code>, <code>{1}</code>, <code>{4}</code>
187    * placeholdes, which will be replaced  by input value, component label
188    * and example date based on the <code>pattern</code> or based on the
189    * <code>style</code> when <code>type</code> is set to <code>'date'</code>.</p>
190    */
191   public static final String CONVERT_DATE_MESSAGE_ID =
192       "org.apache.myfaces.trinidad.convert.DateTimeConverter.CONVERT_DATE";
193 
194   /**
195    * <p>The message identifier of the FacesMessage to be created if
196    * the value cannot be converted date time object, when <code>type</code>
197    * is set to <code>'time'</code> and pattern is null or not set.
198    * The message format string for this message may optionally include
199    * <code>{0}</code>, <code>{1}</code>, <code>{4}</code>
200    * placeholdes, which will be replaced  by input value, component label
201    * and a time example, based on the <code>timeStyle</code>
202    * set in the converter.</p>
203    */
204   public static final String CONVERT_TIME_MESSAGE_ID =
205       "org.apache.myfaces.trinidad.convert.DateTimeConverter.CONVERT_TIME";
206 
207   /**
208    * <p>The message identifier of the FacesMessage to be created if
209    * the value cannot be converted to a date when <code>type</code>
210    * is set to <code>'both'</code> and pattern is either null or not set. The
211    *  message format string for this message may optionally include
212    * <code>{0}</code>, <code>{1}</code>, <code>{4}</code>
213    * placeholdes, which will be replaced  by input value, component label
214    * and a date-time example, based on the <code>dateStyle</code> and
215    * <code>timeStyle</code> set in the converter.</p>
216    */
217   public static final String CONVERT_BOTH_MESSAGE_ID =
218       "org.apache.myfaces.trinidad.convert.DateTimeConverter.CONVERT_BOTH";
219 
220   /**
221    * Creates a DateTimeConverter
222    */
223   public DateTimeConverter()
224   {
225   }
226 
227   /**
228    * Creates a DateTimeConverter with the specified SimpleDateFormat format
229    * pattern
230    * @param pattern a primary pattern;  this will be used to format
231    *        and parser strings.
232    */
233   public DateTimeConverter(String pattern)
234   {
235     this();
236     setPattern(pattern);
237   }
238 
239   /**
240    * Creates a DateTimeConverter with the specified SimpleDateFormat format
241    * pattern and a secondary pattern.
242    * @param pattern a primary pattern;  this will be used to format
243    *        and parser strings.
244    * @param secondaryPattern a second pattern, which will be used
245    *        as a second attempt to parse a string if the primary pattern or
246    *        styles fail, but is never used for formatting strings.
247    */
248   public DateTimeConverter(String pattern, String secondaryPattern)
249   {
250      this(pattern);
251      setSecondaryPattern(secondaryPattern);
252 
253   }
254 
255   /**
256    * <p>Convert the specified string value, which is associated with
257    * the specified {@link UIComponent}, into a java.util.Date object
258    * based on the values set.</p>
259    *
260    * @param context {@link FacesContext} for the request being processed
261    * @param component {@link UIComponent} with which this model object
262    *  value is associated
263    * @param value String value to be converted (may be <code>null</code>)
264    *
265    * @return <code>null</code> if the value to convert is <code>null</code>,
266    *  otherwise return a java.util.Date object.
267    *
268    * @exception ConverterException if conversion cannot be successfully
269    *  performed
270    * @exception NullPointerException if <code>context</code> or
271    *  <code>component</code> is <code>null</code>
272    *
273    * @exception IllegalArgumentException if the <code>value</code> is of
274    * type other than {@link java.util.Date}, {@link java.lang.String}. The
275    * <code>value</code> can be null.
276    */
277   @Override
278   public Object getAsObject(
279     FacesContext context,
280     UIComponent component,
281     String value)
282   {
283     Date date = _getParsedDate(context, component, value);
284     if (date != null)
285     {
286       _fillTimePortion(context, component, date);
287     }
288 
289     return date;
290   }
291 
292   /**
293    * <p>Convert the model Date object value, into a String based on the pattern
294    *  or styles.</p>
295    * @param context {@link FacesContext} for the request being processed
296    * @param component {@link UIComponent} with which this model object
297    *  value is associated
298    * @param value Model object value to be converted
299    *  (may be <code>null</code>)
300    *
301    * @return a zero-length String if value is <code>null</code>,
302    *  otherwise the result of the conversion
303    *
304    * @exception ConverterException if conversion cannot be successfully
305    *  performed
306    * @exception NullPointerException if <code>context</code> or
307    *  <code>component</code> is <code>null</code>
308    */
309   @Override
310   public String getAsString(
311     FacesContext context,
312     UIComponent component,
313     Object value
314     )
315   {
316     if (context == null || component == null)
317       throw new NullPointerException(_LOG.getMessage(
318         "NULL_FACESCONTEXT_OR_UICOMPONENT"));
319 
320     if (null == value)
321       return "";
322 
323     if (value instanceof String)
324       return (String)value;
325 
326     if (!(value instanceof Date))
327       throw new ClassCastException(_LOG.getMessage(
328         "VALUE_IS_NOT_DATE_TYPE_IT_IS", new Object[]{value,value.getClass()}));
329 
330     DateFormat format = _getDateFormat(context, getPattern(), false);
331     return format.format(value);
332   }
333 
334  /**
335   * <p>Custom error message to be used, for creating detail part of the {@link FacesMessage},
336   * for values that cannot be converted to {@link java.util.Date} when the
337   * <code>pattern / secondary pattern</code> is set or when <code>type</code>
338   * is set to <code>'date'</code>.</p>
339   * Overrides detail message identified by message id {@link #CONVERT_DATE_MESSAGE_ID}
340   * @param convertDateMessageDetail custom error message.
341   *
342   */
343   public void setMessageDetailConvertDate(String convertDateMessageDetail)
344   {
345     _facesBean.setProperty(_CONVERT_DATE_MESSAGE_DETAIL_KEY, convertDateMessageDetail);
346   }
347 
348   /**
349    * <p>Return custom detail error message that was set for creating {@link FacesMessage},
350    * for values that cannot be converted to {@link java.util.Date} when
351    * <code>pattern / secondary pattern</code> is set or
352    * when <code>type</code> is set to <code>'date'</code>.</p>
353    * @return custom error message that was set.
354    * @see #setMessageDetailConvertDate(String)
355    */
356   public String getMessageDetailConvertDate()
357   {
358     Object msg = _facesBean.getProperty(_CONVERT_DATE_MESSAGE_DETAIL_KEY);
359     return ComponentUtils.resolveString(msg);
360   }
361 
362   /**
363    * <p>Custom error message to be used, for creating detail part of the {@link FacesMessage},
364    * for time based value that cannot be converted to date
365    * when <code>type</code> is set to <code>'time'</code>.</p>
366    * Overrides detail message identified by message id {@link #CONVERT_TIME_MESSAGE_ID}
367    * @param convertTimeMessageDetail custom error message.
368    */
369   public void setMessageDetailConvertTime(String convertTimeMessageDetail)
370   {
371     _facesBean.setProperty(_CONVERT_TIME_MESSAGE_DETAIL_KEY, convertTimeMessageDetail);
372   }
373 
374   /**
375    * <p>Return custom detail error message that was set for creating {@link FacesMessage},
376    * for values that cannot be converted to {@link java.util.Date}
377    * when <code>type</code> is set to <code>'time'</code>.
378    * @return custom error message that was set.</p>
379    * @see #setMessageDetailConvertTime(java.lang.String)
380    */
381   public String getMessageDetailConvertTime()
382   {
383     Object msg =_facesBean.getProperty(_CONVERT_TIME_MESSAGE_DETAIL_KEY);
384     return ComponentUtils.resolveString(msg);
385   }
386 
387   /**
388    * <p>Custom error message to be used, for creating detail part of the {@link FacesMessage},
389    * for date-time based value that cannot be converted to {@link java.util.Date}
390    * when <code>type</code> is set to <code>'both'</code>.</p>
391    * Overrides detail message identified by message id {@link #CONVERT_BOTH_MESSAGE_ID}
392    * @param convertBothMessageDetail custom error message.
393    * @see #CONVERT_BOTH_MESSAGE_ID
394    */
395   public void setMessageDetailConvertBoth(String convertBothMessageDetail)
396   {
397     _facesBean.setProperty(_CONVERT_BOTH_MESSAGE_DETAIL_KEY, convertBothMessageDetail);
398   }
399 
400   /**
401    * Return custom detail error message that was set for creating {@link FacesMessage},
402    * for values that cannot be converted to {@link java.util.Date}
403    * when <code>type</code> is set to <code>'both'</code>.
404    * @return custom error message that was set.
405    * @see #setMessageDetailConvertBoth(java.lang.String)
406    */
407   public String getMessageDetailConvertBoth()
408   {
409      Object msg = _facesBean.getProperty(_CONVERT_BOTH_MESSAGE_DETAIL_KEY);
410      return ComponentUtils.resolveString(msg);
411   }
412 
413   /**
414    * <p>Custom hintDate message.</p>
415    * Overrides default hint message
416    * @param hintDate Custom hint message.
417    */
418   public void setHintDate(String hintDate)
419   {
420     _facesBean.setProperty(_HINT_DATE_KEY, hintDate);
421   }
422 
423   /**
424    * <p>Return custom hintDate message.</p>
425    * @return Custom hint message.
426    * @see  #setHintDate(String)
427    */
428   public String getHintDate()
429   {
430     Object obj = _facesBean.getProperty(_HINT_DATE_KEY);
431     return ComponentUtils.resolveString(obj);
432   }
433 
434   /**
435    * <p>Custom hintTime message.</p>
436    * Overrides default hint message
437    * @param hintTime Custom hint message.
438    */
439   public void setHintTime(String hintTime)
440   {
441     _facesBean.setProperty(_HINT_TIME_KEY, hintTime);
442   }
443 
444   /**
445    * <p>Return custom hintTime message.</p>
446    * @return Custom hint message.
447    * @see  #setHintTime(String)
448    */
449   public String getHintTime()
450   {
451     Object obj = _facesBean.getProperty(_HINT_TIME_KEY);
452     return ComponentUtils.resolveString(obj);
453   }
454 
455   /**
456    * <p>Custom hintBoth message.</p>
457    * Overrides default hint message
458    * @param hintBoth Custom hint message.
459    */
460   public void setHintBoth(String hintBoth)
461   {
462     _facesBean.setProperty(_HINT_BOTH_KEY, hintBoth);
463   }
464 
465   /**
466    * <p>Return custom hintBoth message.</p>
467    * @return Custom hint message.
468    * @see  #setHintBoth(String)
469    */
470   public String getHintBoth()
471   {
472     Object obj = _facesBean.getProperty(_HINT_BOTH_KEY);
473     return ComponentUtils.resolveString(obj);
474   }
475 
476   /**
477    * Gets the existing date from the component.
478    * This date will be used to fill in missing portions of the new date.
479    * For example, if the new date is missing the time, the time portion
480    * from the existing date will be used.
481    * <P>
482    * This implementation checks to see if the component is a ValueHolder, and
483    * calls getValue() and returns the result if it is a Date instance.
484    * @param component The component to get the existing date from.
485    * @return null if there is no existing date.
486    */
487   protected Date getDate(FacesContext context, UIComponent component)
488   {
489     if (component instanceof ValueHolder)
490     {
491       Object value = ((ValueHolder)component).getValue();
492       if(value instanceof Date)
493       {
494         return (Date)value;
495       }
496     }
497     return null;
498   }
499 
500   private Date _getParsedDate(FacesContext context,
501                               UIComponent component,
502                               String value)
503   {
504     if (context == null || component == null)
505       throw new NullPointerException(_LOG.getMessage(
506         "NULL_FACESCONTEXT_OR_UICOMPONENT"));
507 
508     if (null == value)
509       return null;
510 
511     value = value.trim();
512 
513     if ( 1 > value.length() )
514       return null;
515 
516     try
517     {
518       String pattern = getPattern();
519       if (pattern == null)
520       {
521         // get the pattern based on the style and type that has been set.
522         DateFormat format = getDateFormat(context, null, true);
523         if (format instanceof SimpleDateFormat)
524         {
525           pattern = ((SimpleDateFormat)format).toPattern();
526         }
527       }
528 
529       if (pattern != null)
530       {
531         return _doLenientParse(context, component, value, pattern);
532       }
533       else
534       {
535         // more unlikely that we will get null pattern here but just to be safe
536         return _parse(context, component, value, null);
537       }
538     }
539     catch (ConverterException ce)
540     {
541       try
542       {
543         // If the parsing fails with primary pattern or with the styles
544         // then we try with the secondary pattern.
545         String secPattern = getSecondaryPattern();
546         if ( secPattern != null)
547         {
548           return _doLenientParse(context, component, value, secPattern);
549         }
550       }
551       catch(ConverterException secondaryCe)
552       {
553         // either way we throw the first exception.
554         ;
555       }
556       throw ce;
557     }
558   }
559 
560   // for fixing bug 4469819.
561   /**
562    * Fill in the time portion of the new date with the time from the previous
563    * date value if the converter displays only date. For now, we are not
564    * bothered about filling all the missing parts of the pattern. But in
565    * future we would consider that.
566    */
567   private void _fillTimePortion(
568     FacesContext context,
569     UIComponent component,
570     Date newDate)
571   {
572     // get the previous date value
573     Date prevDate = getDate(context, component);
574 
575     // if the component doesn't have any date value before, return
576     if (prevDate == null)
577     {
578       return;
579     }
580 
581     // if the converter uses timePortion, then we need not do anything
582     String pattern = getPattern();
583     if (pattern == null && !"date".equals(getType()))
584     {
585       return;
586     }
587 
588     // find out the missing time components from the pattern
589     boolean fillMilliSeconds = true;
590     boolean fillSeconds = true;
591     boolean fillMinutes = true;
592     boolean fillHour = true;
593 
594     if (pattern != null)
595     {
596       int patternLen = pattern.length();
597       for (int currCharIndex = 0; currCharIndex < patternLen; currCharIndex++)
598       {
599         switch (pattern.charAt(currCharIndex))
600         {
601           case 'S':
602             fillMilliSeconds = false;
603             break;
604           case 's':
605             fillSeconds = false;
606             break;
607           case 'm':
608             fillMinutes = false;
609             break;
610           case 'h':
611           case 'H':
612           case 'k':
613           case 'K':
614             fillHour = false;
615             break;
616         }
617       }
618     }
619 
620     // fill only if any of the time components are missing
621     if ( fillMilliSeconds || fillSeconds || fillMinutes || fillHour )
622     {
623       TimeZone timeZone = _getTimeZone();
624 
625       // convert the previous date value wrt client's timeZone
626       Calendar prevCal = Calendar.getInstance(timeZone);
627       prevCal.setTime(prevDate);
628       // convert the new date value wrt client's timeZone
629       Calendar newCal = Calendar.getInstance(timeZone);
630       newCal.setTime(newDate);
631 
632       // extract all the missing time portions from the previous date value
633       // and set it to the new date value.
634       if (fillMilliSeconds)
635       {
636         newCal.set(Calendar.MILLISECOND, prevCal.get(Calendar.MILLISECOND));
637       }
638 
639       if (fillSeconds)
640       {
641         newCal.set(Calendar.SECOND, prevCal.get(Calendar.SECOND));
642       }
643 
644       if(fillMinutes)
645       {
646         newCal.set(Calendar.MINUTE, prevCal.get(Calendar.MINUTE));
647       }
648 
649       if(fillHour)
650       {
651         newCal.set(Calendar.HOUR_OF_DAY, prevCal.get(Calendar.HOUR_OF_DAY));
652       }
653 
654       // modify the new date value.
655       newDate.setTime(newCal.getTimeInMillis());
656     }
657   }
658 
659   private Date _parse(
660     FacesContext context,
661     UIComponent component,
662     String value,
663     String pattern
664     )
665   {
666     DateFormat fmt = getDateFormat(context, pattern, true);
667     try
668     {
669       return fmt.parse(value);
670 
671     } catch (ConverterException ce)
672     {
673       throw ce;
674     }
675     catch (ParseException pe)
676     {
677       Object[] params = _getPlaceHolderParameters(context, component, value);
678       throw new ConverterException(getParseErrorMessage(context, component,
679                                                         pattern, params),
680            pe);
681     }
682   }
683 
684   private void _addConveniencePattern(Set<String> patterns)
685   {
686     //see TRINIDAD-859
687     patterns.add("MMMM dd, yy");
688     patterns.add("dd-MMMM-yy");
689     patterns.add("MMMM/dd/yy");
690   }
691   
692   private Date _doLenientParse(
693     FacesContext context,
694     UIComponent component,
695     String value,
696     String pattern
697     )
698   {
699     // do lenient parsing for the pattern supplied.
700     // accept derived patterns during
701     // parsing, allowing:
702     // 01/13/99  --> 13-Jan-99
703     // 03/Oct/99 --> 03-Oct-99
704     // 03.Oct.99 --> 03-Oct-99
705 
706     ConverterException ce;
707     try
708     {
709       return _parse(context, component, value, pattern);
710     }
711     catch (ConverterException convException)
712     {
713       // Let us save this exception to throw, if in case we have exhausted
714       // all possible patterns
715       ce = convException;
716       
717       Set<String> patterns = new HashSet<String>();
718       Set<String> lenientPatterns = new HashSet<String>();
719       patterns.add(pattern);
720       
721       // we apply some patterns for convenience reasons 
722       // (see TRINIDAD-859)
723       _addConveniencePattern(patterns);
724       
725       for (String tmpPattern : patterns)
726       {
727         lenientPatterns.addAll(_getLenientPatterns(tmpPattern));
728       }
729 
730       for (String lenientPattern : lenientPatterns)
731       {
732         try
733         {
734           return _parse(context, component, value, lenientPattern);
735         }
736         catch (ConverterException e)
737         {
738           // Just do nothing with the excpetion - we still need to evaluate
739           // for other possible patterns, and we will throw the initially caught
740           // exception, which will convey to the user the appropriate message.
741           continue;
742         }
743       }
744       throw ce;
745     }
746   }
747 
748 
749 
750   /**
751    * <p>Set the <code>Locale</code> to be used when parsing or formatting
752    * dates and times.  If set to <code>null</code>, the <code>Locale</code>
753    * stored in the {@link javax.faces.component.UIViewRoot} for the current
754    * request will be utilized.</p>
755    *
756    * @param locale The new <code>Locale</code> (or <code>null</code>)
757    */
758   @Override
759   public void setLocale(Locale locale)
760   {
761     _facesBean.setProperty(_LOCALE_KEY, locale);
762   }
763 
764  /**
765   * <p>Return the <code>Locale</code> that was set.
766   * If not explicitly set, the <code>Locale</code> stored
767   * in the {@link javax.faces.component.UIViewRoot} for the current
768   * request is used during call to <code>getAsObject</code> and
769   * <code>getAsString</code>.</p>
770   */
771   @Override
772   public Locale getLocale()
773   {
774     Object locale = _facesBean.getProperty(_LOCALE_KEY);
775     return ComponentUtils.resolveLocale(locale);
776   }
777 
778   /**
779    * <p>Set the format pattern to be used when formatting and parsing
780    * dates and times.  Valid values are those supported by
781    * <code>java.text.SimpleDateFormat</code>.
782    * An invalid value will cause a {@link ConverterException} when
783    * <code>getAsObject()</code> or <code>getAsString()</code> is called.</p>
784    *
785    * @param pattern The new format pattern
786    */
787   @Override
788   public void setPattern(String pattern)
789   {
790     _facesBean.setProperty(_PATTERN_KEY, pattern);
791   }
792 
793   /**
794    * <p>Return the format pattern to be used when formatting and
795    * parsing dates and times.</p>
796    */
797   @Override
798   public String getPattern()
799   {
800     Object pattern = _facesBean.getProperty(_PATTERN_KEY);
801     return ComponentUtils.resolveString(pattern);
802   }
803 
804   /**
805    * <p>Set the <code>TimeZone</code> used to interpret a time value.</p>
806    *
807    * @param timeZone The new time zone
808    */
809   @Override
810   public void setTimeZone(TimeZone timeZone)
811   {
812     _facesBean.setProperty(_TIME_ZONE_KEY, timeZone);
813   }
814 
815  /**
816   * <p>Return the <code>TimeZone</code> that is used to interpret a time value.
817   * If not explicitly set or if a null value is set, then during call to
818   * <code>getAsObject</code> and <code>getAsString</code>, the time zone set
819   * for the web-app is used. If time zone is not set for the web-app then
820   * the default time zone of <code>GMT</code> is used.</p>
821   */
822   @Override
823   public TimeZone getTimeZone()
824   {
825     Object timeZone = _facesBean.getProperty(_TIME_ZONE_KEY);
826     return ComponentUtils.resolveTimeZone(timeZone);
827   }
828 
829   /**
830   * <p>Set the type of value to be formatted or parsed.
831   * Valid values are <code>both</code>, <code>date</code>, or
832   * <code>time</code>.
833   * An invalid value will cause a {@link IllegalStateException} when
834   * <code>getAsObject()</code> or <code>getAsString()</code> is called.</p>
835   *
836   * @param type The new date style
837   */
838   @Override
839   public void setType(String type)
840   {
841     _facesBean.setProperty(_TYPE_KEY, type);
842   }
843 
844   /**
845    * <p>Return the type of value to be formatted or parsed.
846    * If not explicitly set, the default type, <code>date</code>
847    * is returned.</p>
848    */
849   @Override
850   public String getType()
851   {
852     Object type = _facesBean.getProperty(_TYPE_KEY);
853     return ComponentUtils.resolveString(type, "date");
854   }
855 
856   /**
857    * <p>Set the style to be used to format or parse dates.  Valid values
858    * are <code>default</code>, <code>shortish</code>
859    * <code>short</code>, <code>medium</code>,
860    * <code>long</code>, and <code>full</code>.
861    * An invalid value will cause a {@link IllegalStateException} when
862    * <code>getAsObject()</code> or <code>getAsString()</code> is called.</p>
863    *
864    * @param dateStyle The new style code
865    */
866   @Override
867   public void setDateStyle(String dateStyle)
868   {
869     _facesBean.setProperty(_DATE_STYLE_KEY, dateStyle);
870   }
871 
872   /**
873    * <p>Return the style to be used to format or parse dates.  If not set,
874    * the default value, <code>shortish</code>, is returned.</p>
875    * @see #setDateStyle(java.lang.String)
876    * @return date style
877    */
878   @Override
879   public String getDateStyle()
880   {
881     Object dateStyle = _facesBean.getProperty(_DATE_STYLE_KEY);
882     return ComponentUtils.resolveString(dateStyle, "shortish");
883   }
884 
885   /**
886    * <p>Set the style to be used to format or parse times.  Valid values
887    * are <code>default</code>, <code>short</code>,
888    * <code>medium</code>, <code>long</code>, and <code>full</code>.
889    * An invalid value will cause a {@link IllegalStateException} when
890    * <code>getAsObject()</code> or <code>getAsString()</code> is called.</p>
891    *
892    * @param timeStyle The new style code
893    */
894   @Override
895   public void setTimeStyle(String timeStyle)
896   {
897     _facesBean.setProperty(_TIME_STYLE_KEY, timeStyle);
898   }
899 
900   /**
901    * <p>Return the style to be used to format or parse times.  If not set,
902    * the default value, <code>short</code>, is returned.</p>
903    */
904   @Override
905   public String getTimeStyle()
906   {
907     Object timeStyle = _facesBean.getProperty(_TIME_STYLE_KEY);
908     return ComponentUtils.resolveString(timeStyle, "short");
909   }
910 
911   /**
912    * <p>Second pattern which will be used to parse string in
913    * <code>getAsObject</code> if pattern or styles fail. But is never
914    * used for formatting to string in <code>getAsString()</code>.</p>
915    * @param secondaryPattern a second pattern which will be used
916    *        as a second attempt to parse a string if the primary pattern or
917    *        styles fail, but is never used for formatting strings.
918    */
919   public void setSecondaryPattern(String secondaryPattern)
920   {
921     _facesBean.setProperty(_SECONDARY_PATTERN_KEY, secondaryPattern);
922   }
923 
924   /**
925    * <p>Return the secondary pattern used to parse string when parsing by
926    * pattern or style fails.</p>
927    */
928   public String getSecondaryPattern()
929   {
930     Object secPattern = _facesBean.getProperty(_SECONDARY_PATTERN_KEY);
931     return ComponentUtils.resolveString(secPattern);
932   }
933 
934   @Override
935   public boolean isTransient()
936   {
937     return _isTransient;
938   }
939 
940   @Override
941   public void setTransient(boolean isTransient)
942   {
943     _isTransient = isTransient;
944   }
945 
946   @Override
947   public Object saveState(FacesContext context)
948   {
949     return _facesBean.saveState(context);
950   }
951 
952   @Override
953   public void restoreState(FacesContext context, Object state)
954   {
955     _facesBean.restoreState(context, state);
956   }
957 
958   /**
959    * <p>Set the {@link ValueBinding} used to calculate the value for the
960    * specified attribute if any.</p>
961    *
962    * @param name Name of the attribute for which to set a {@link ValueBinding}
963    * @param binding The {@link ValueBinding} to set, or <code>null</code>
964    *  to remove any currently set {@link ValueBinding}
965    *
966    * @exception NullPointerException if <code>name</code>
967    *  is <code>null</code>
968    * @exception IllegalArgumentException if <code>name</code> is not a valid
969    *            attribute of this converter
970    */
971   public void setValueBinding(String name, ValueBinding binding)
972   {
973     ConverterUtils.setValueBinding(_facesBean, name, binding) ;
974   }
975 
976 
977   /**
978    * <p>Return the {@link ValueBinding} used to calculate the value for the
979    * specified attribute name, if any.</p>
980    *
981    * @param name Name of the attribute or property for which to retrieve a
982    *  {@link ValueBinding}
983    *
984    * @exception NullPointerException if <code>name</code>
985    *  is <code>null</code>
986    * @exception IllegalArgumentException if <code>name</code> is not a valid
987    * attribute of this converter
988    */
989   public ValueBinding getValueBinding(String name)
990   {
991     return ConverterUtils.getValueBinding(_facesBean, name);
992   }
993 
994   /**
995    * <p>Compares this DateTimeConverter with the specified Object for
996    * equality.</p>
997    * @param object  Object to which this DateTimeConverter is to be compared.
998    * @return true if and only if the specified Object is a DateTimeConverter
999    * and if all parameters are equal.
1000    */
1001   @Override
1002   public boolean equals(Object object)
1003   {
1004     if (this == object)
1005       return true;
1006 
1007     if(object instanceof DateTimeConverter)
1008     {
1009       DateTimeConverter other = (DateTimeConverter)object;
1010 
1011       if ( (isTransient() == other.isTransient())
1012            && ConverterUtils.equals(getDateStyle(), other.getDateStyle())
1013            && ConverterUtils.equals(getLocale(), other.getLocale())
1014            && ConverterUtils.equals(getPattern(), other.getPattern())
1015            && ConverterUtils.equals(getTimeStyle(), other.getTimeStyle())
1016            && ConverterUtils.equals(getTimeZone(), other.getTimeZone())
1017            && ConverterUtils.equals(getType(), other.getType())
1018            && ConverterUtils.equals(getSecondaryPattern(), other.getSecondaryPattern())
1019            && ConverterUtils.equals(getMessageDetailConvertDate(),
1020                                     other.getMessageDetailConvertDate())
1021            && ConverterUtils.equals(getMessageDetailConvertTime(),
1022                                     other.getMessageDetailConvertTime())
1023            && ConverterUtils.equals(getMessageDetailConvertBoth(),
1024                                     other.getMessageDetailConvertBoth())
1025          )
1026       {
1027         return true;
1028       }
1029     }
1030     return false;
1031   }
1032 
1033   /**
1034    * <p>Returns the hash code for this Converter.</p>
1035    * @return a hash code value for this object.
1036    */
1037   @Override
1038   public int hashCode()
1039   {
1040     int result = 17;
1041     result = result * 37 + (isTransient()? 1 : 0);
1042     result = result * 37 + _getHashValue(getDateStyle());
1043     result = result * 37 + _getHashValue(getLocale());
1044     result = result * 37 + _getHashValue(getPattern());
1045     result = result * 37 + _getHashValue(getTimeStyle());
1046     result = result * 37 + _getHashValue(getTimeZone());
1047     result = result * 37 + _getHashValue(getType());
1048     result = result * 37 + _getHashValue(getSecondaryPattern());
1049     result = result * 37 + _getHashValue(getMessageDetailConvertDate());
1050     result = result * 37 + _getHashValue(getMessageDetailConvertTime());
1051     result = result * 37 + _getHashValue(getMessageDetailConvertBoth());
1052     return result;
1053   }
1054 
1055   protected final DateFormat getDateFormat(
1056     FacesContext context,
1057     String pattern,
1058     boolean forParsing
1059     ) throws ConverterException
1060   {
1061     ConverterException exception = null;
1062     try
1063     {
1064       DateFormat format = _getDateFormat(context, pattern, forParsing);
1065       return format;
1066     }
1067     catch (ConverterException ce)
1068     {
1069       exception = ce;
1070     }
1071     catch (Exception e)
1072     {
1073       exception = new ConverterException(e);
1074     }
1075     throw exception;
1076   }
1077 
1078   /**
1079    * Returns the TimeZone that will be set on DateFormat for formatting
1080    * and parsing the dates. By default, this just returns the specified
1081    * time zone, the one that is set on the DateTimeConverter or in the
1082    * Adf-Faces config.
1083    */
1084   protected TimeZone getFormattingTimeZone(TimeZone tZone)
1085   {
1086     return tZone;
1087   }
1088 
1089   // This is used while displaying error message at the client side.
1090   // Identifies the pattern expected to be matched.
1091   private String[] _getExpectedPatterns(FacesContext context)
1092   {
1093     String pattern = getPattern();
1094 
1095     if ( pattern != null )
1096     {
1097       return _getAllowedPatterns(context, pattern, getSecondaryPattern());
1098     }
1099     else
1100     {
1101       String datePattern = null;
1102 
1103       try
1104       {
1105         DateFormat format  = getDateFormat(context, null,false);
1106         if ((format != null) && (format instanceof SimpleDateFormat))
1107         {
1108           datePattern = ((SimpleDateFormat)format).toPattern();
1109         }
1110       }
1111       catch (ConverterException ce)
1112       {
1113         // Do nothing here. Check to see if secondary pattern is available.
1114         ;
1115       }
1116       return _getAllowedPatterns(context, datePattern, getSecondaryPattern());
1117     }
1118   }
1119 
1120   protected final  FacesMessage getParseErrorMessage(
1121     FacesContext context,
1122     UIComponent component,
1123     String pattern,
1124     Object[] params
1125     )
1126   {
1127     // if pattern is set then - conversion would have been carried out using
1128     // the pattern or secondary pattern.
1129     String key = getViolationMessageKey(pattern);
1130     return _getConvertErrorFacesMessage(context, key, params, component);
1131 
1132   }
1133 
1134   protected final String getExample(FacesContext context)
1135   {
1136     String[] expectedPatterns = _getExpectedPatterns(context);
1137 
1138      assert((expectedPatterns != null) && (expectedPatterns.length >= 1));
1139      String example = expectedPatterns[0];
1140      return example;
1141   }
1142 
1143   private String[] _getAllowedPatterns(
1144     FacesContext context,
1145     String mainPattern,
1146     String secondaryPattern
1147     )
1148   {
1149     String[] patterns;
1150 
1151     if (mainPattern != null)
1152     {
1153       if (secondaryPattern != null)
1154       {
1155         patterns = new String[]{mainPattern, secondaryPattern};
1156       }
1157       else
1158       {
1159         patterns = new String[]{mainPattern};
1160       }
1161     }
1162     else
1163     {
1164       patterns = new String[]{secondaryPattern};
1165     }
1166 
1167     // Convert each pattern into an example
1168     for (int i = 0; i < patterns.length; i++)
1169     {
1170       patterns[i] = _getExample(context, patterns[i]);
1171     }
1172 
1173     return patterns;
1174   }
1175 
1176   /**
1177    * <p>Return the style constant for the specified style name.</p>
1178    * If invalid throw IllegalStateException.
1179    *
1180    * @param dateStyle Name of the date style for which to return a constant
1181    *
1182    */
1183   private static final int _getDateStyle(String dateStyle)
1184   {
1185     if (dateStyle.equals("shortish"))
1186     {
1187       return _SHORTISH;
1188     }
1189     else if (dateStyle.equals("default"))
1190     {
1191       return (DateFormat.DEFAULT);
1192     }
1193     else if (dateStyle.equals("short"))
1194     {
1195       return (DateFormat.SHORT);
1196     }
1197     else if (dateStyle.equals("medium"))
1198     {
1199       return (DateFormat.MEDIUM);
1200     }
1201     else if (dateStyle.equals("long"))
1202     {
1203       return (DateFormat.LONG);
1204     }
1205     else if (dateStyle.equals("full"))
1206     {
1207       return (DateFormat.FULL);
1208     }
1209     else
1210       throw new IllegalStateException(_LOG.getMessage(
1211         "INVALID_DATE_STYLE", dateStyle));
1212   }
1213 
1214   private static final int _getTimeStyle(String timeStyle)
1215   {
1216     if ("default".equals(timeStyle))
1217     {
1218       return (DateFormat.DEFAULT);
1219     }
1220     else if ("short".equals(timeStyle))
1221     {
1222       return (DateFormat.SHORT);
1223     }
1224     else if ("medium".equals(timeStyle))
1225     {
1226       return (DateFormat.MEDIUM);
1227     }
1228     else if ("long".equals(timeStyle))
1229     {
1230       return (DateFormat.LONG);
1231     }
1232     else if ("full".equals(timeStyle))
1233     {
1234       return (DateFormat.FULL);
1235     }
1236     else
1237       throw new IllegalStateException(_LOG.getMessage(
1238         "INVALID_TIME_STYLE", timeStyle));
1239   }
1240 
1241   /**
1242    * <p>The valid values for type are date,time and both. Any value other than this
1243    * would result in a ConverterException.</p>
1244    * @return type
1245    */
1246   private static int _getType(String type)
1247   {
1248     if ("date".equals(type))
1249       return _TYPE_DATE;
1250     else if ("time".equals(type))
1251       return _TYPE_TIME;
1252     else if ("both".equals(type))
1253       return _TYPE_BOTH;
1254     else
1255       throw new IllegalStateException(_LOG.getMessage(
1256         "INVALID_TYPE", type));
1257   }
1258 
1259   // Don't use this for Array Object and other objects which don't implement
1260   // their hashCode()
1261   private static int _getHashValue(Object obj)
1262   {
1263     return obj == null? 0 : obj.hashCode();
1264   }
1265 
1266   private static Set<String> _getLenientPatterns(String pattern)
1267   {
1268     //Create patterns so as to be lenient.
1269     // allow for
1270     // 01/13/99  --> 13-Jan-99 [MMM -> MM], [MMM -> M]
1271     // Apply the below conversion to the above obtained patterns and the actual
1272     // patern
1273     // 03/Oct/99 --> 03-Oct-99
1274     // 03.Oct.99 --> 03-Oct-99
1275 
1276     Set<String> patterns = new HashSet<String>();
1277 
1278     String[] leniencyApplicablePatterns = new String[1];
1279     leniencyApplicablePatterns[0] = pattern;
1280 
1281     if (pattern.indexOf("MMM") != -1)
1282     {
1283       leniencyApplicablePatterns = new String[3];
1284       leniencyApplicablePatterns[0] = pattern;
1285 
1286       String str1 = pattern.replaceAll("MMM", "MM");
1287       patterns.add(str1);
1288       leniencyApplicablePatterns[1] = str1;
1289 
1290       String str2 = pattern.replaceAll("MMM", "M");
1291       leniencyApplicablePatterns[2] = str2;
1292       patterns.add(str2);
1293     }
1294 
1295     // Apply the leninecy to the above obtained patterns which was obtained
1296     // after replacing MMM -> MM and MMM -> M and the actual pattern
1297     for (int i = 0; i < leniencyApplicablePatterns.length ; i++)
1298     {
1299       if (leniencyApplicablePatterns[i].indexOf('/') != -1)
1300       {
1301         patterns.add(leniencyApplicablePatterns[i].replaceAll("/", "-"));
1302         patterns.add(leniencyApplicablePatterns[i].replaceAll("/", "."));
1303       }
1304       if (leniencyApplicablePatterns[i].indexOf('-') != -1)
1305       {
1306         patterns.add(leniencyApplicablePatterns[i].replaceAll("-","/"));
1307         patterns.add(leniencyApplicablePatterns[i].replaceAll("-","."));
1308       }
1309       if (leniencyApplicablePatterns[i].indexOf('.') != -1)
1310       {
1311         patterns.add(leniencyApplicablePatterns[i].replaceAll("\\.","/"));
1312         patterns.add(leniencyApplicablePatterns[i].replaceAll("\\.", "-"));
1313       }
1314     }
1315     return patterns;
1316   }
1317 
1318   private Object[] _getPlaceHolderParameters(
1319     FacesContext context,
1320     UIComponent component,
1321     String value)
1322   {
1323      Object label = ConverterUtils.getComponentLabel(component);
1324      String example = getExample(context);
1325      Object[] params = {label, value, example};
1326      return params;
1327   }
1328 
1329   private Object _getRawConvertBothMessageDetail()
1330   {
1331     return _facesBean.getRawProperty(_CONVERT_BOTH_MESSAGE_DETAIL_KEY);
1332   }
1333 
1334   private Object _getRawConvertDateMessageDetail()
1335   {
1336     return _facesBean.getRawProperty(_CONVERT_DATE_MESSAGE_DETAIL_KEY);
1337   }
1338 
1339   private Object _getRawConvertTimeMessageDetail()
1340   {
1341     return _facesBean.getRawProperty(_CONVERT_TIME_MESSAGE_DETAIL_KEY);
1342   }
1343 
1344   private FacesMessage _getConvertErrorFacesMessage(
1345     FacesContext context,
1346     String key,
1347     Object[] params,
1348     UIComponent component
1349     )
1350   {
1351     Object msgPattern = getMessagePattern(context, key, params, component);
1352     return MessageFactory.getMessage(context, key, msgPattern,
1353                                      params, component);
1354   }
1355   
1356   private String _getExample(FacesContext context, String pattern)
1357   {
1358     DateFormat format = _getDateFormat(context, pattern, false);
1359     return format.format(_EXAMPLE_DATE);
1360   }
1361 
1362 
1363   protected Object getMessagePattern(
1364       FacesContext context,
1365       String key,
1366       Object[] params,
1367       UIComponent component
1368       )
1369   {
1370     Object msgPattern;
1371     if (CONVERT_DATE_MESSAGE_ID.equals(key))
1372     {
1373       msgPattern = _getRawConvertDateMessageDetail();
1374     }
1375     else if (CONVERT_TIME_MESSAGE_ID.equals(key))
1376     {
1377       msgPattern = _getRawConvertTimeMessageDetail();
1378     }
1379     else if (CONVERT_BOTH_MESSAGE_ID.equals(key))
1380     {
1381       msgPattern = _getRawConvertBothMessageDetail();
1382     }
1383     else
1384     {
1385       // THIS CAN NEVER HAPPEN!
1386       throw new IllegalArgum