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.ArrayList;
27  import java.util.Arrays;
28  import java.util.Calendar;
29  import java.util.Date;
30  import java.util.GregorianCalendar;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Locale;
34  import java.util.Map;
35  import java.util.MissingResourceException;
36  import java.util.ResourceBundle;
37  import java.util.TimeZone;
38  
39  import javax.el.ValueExpression;
40  
41  import javax.faces.application.FacesMessage;
42  import javax.faces.component.StateHolder;
43  import javax.faces.component.UIComponent;
44  import javax.faces.component.ValueHolder;
45  import javax.faces.context.FacesContext;
46  import javax.faces.convert.Converter;
47  import javax.faces.convert.ConverterException;
48  import javax.faces.el.ValueBinding;
49  
50  import org.apache.myfaces.trinidad.bean.FacesBean;
51  import org.apache.myfaces.trinidad.bean.PropertyKey;
52  import org.apache.myfaces.trinidad.context.RequestContext;
53  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
54  import org.apache.myfaces.trinidad.util.ComponentUtils;
55  import org.apache.myfaces.trinidad.util.MessageFactory;
56  
57  
58  /**
59   * <p>{@link Converter} implementation for <code>java.util.Date</code>
60   * values. Converts an strings to and from java.util.Date objects.</p>
61   *
62   * The converter has additonal features than standard JSF
63   * {@link javax.faces.convert.DateTimeConverter}.
64   *
65   * New dateStyle <code>shortish</code> has been introduced. Shortish is identical
66   * to <code>short</code> but forces the year to be a full four digits.
67   * If dateStyle is not set, then <code>dateStyle</code> defaults to
68   * <code>shortish</code>.
69   *
70   *  <p>Timezone can be set per web-app in trinidad-config.xml configuration file.
71   * If <code>timeZone</code> is not set on the converter, then timezone will be defaulted to the
72   * value set in trinidad-config.xml configuration file. If it is not set in the
73   * configuration file, then it will be defaulted to GMT.</p>
74   *
75   * <p>The converter always allows a level of <code>leniency</code> while converting
76   * user input value to date to the following extent.
77   * <ul>
78   * <li>A converter with associated pattern 'MMM' for month, when attached to any
79   * value holder, will accept values with month specified in the form 'MM' or 'M'
80   * as valid.</li>
81   * <li>Allows use of separators '-' or '.' or '/' irrespective of the separator
82   * specified in the associated pattern.</li>
83   * <li>The leniency is applicable to both 'pattern' and 'secondaryPattern'.</li>
84   * </ul></p>
85   * <p>
86   * For example:</br>
87   * When pattern on the converter is set to "MMM/d/yyyy" the following inputs
88   * are tolerated as valid by the converter.</br>
89   * <dl>
90   * <dt>Jan/4/2004</dt>
91   * <dt>Jan-4-2004</dt>
92   * <dt>Jan.4.2004</dt>
93   * <dt>01/4/2004</dt>
94   * <dt>01-4-2004</dt>
95   * <dt>01.4.2004</dt>
96   * <dt>1/4/2004</dt>
97   * <dt>1-4-2004</dt>
98   * <dt>1.4.2004</dt>
99   * </dl>
100  * </p>
101  *
102  * The detail part of faces message,for conversion errors can be customized
103  * by overriding the message associated with each CONVERT_[TYPE]_MESSAGE_ID.
104  *
105  * <p>The methods used for customizing the detail message associated with each id
106  * is given below:</p>
107  * <ol>
108  * <li>{@link #CONVERT_DATE_MESSAGE_ID} - {@link #setMessageDetailConvertDate(String)}</li>
109  * <li>{@link #CONVERT_TIME_MESSAGE_ID} - {@link #setMessageDetailConvertTime(String) }</li>
110  * <li>{@link #CONVERT_BOTH_MESSAGE_ID} - {@link #setMessageDetailConvertBoth(String) }</li>
111  * </ol> The custom messages can contain placeholders, which will be replaced with
112  * values as specified in its corresponding message id.
113  *
114  * <p>The <code>getAsObject()</code> method parses a String into a
115  * <code>java.util.Date</code>, according to the following algorithm:</p>
116  * <ul>
117  * <li>If the specified String is null, return
118  *     a <code>null</code>.  Otherwise, trim leading and trailing
119  *     whitespace before proceeding.</li>
120  * <li>If the specified String - after trimming - has a zero length,
121  *     return <code>null</code>.</li>
122  * <li>If the <code>locale</code> property is not null,
123  *     use that <code>Locale</code> for managing parsing.  Otherwise, use the
124  *     <code>Locale</code> from the <code>UIViewRoot</code>.</li>
125  * <li>If a <code>pattern</code> has been specified, its syntax must conform
126  *     the rules specified by <code>java.text.SimpleDateFormat</code>.  Such
127  *     a pattern will be used to parse, and the <code>type</code>,
128  *     <code>dateStyle</code>, and <code>timeStyle</code> properties
129  *     will be ignored.</li>
130  * <li>If a <code>pattern</code> has not been specified, parsing will be based
131  *     on the <code>type</code> property, which expects a date value, a time
132  *     value, or both.  Any date and time values included will be parsed in
133  *     accordance to the styles specified by <code>dateStyle</code> and
134  *     <code>timeStyle</code>, respectively.</li>
135  * <li>If conversion fails with <code>pattern</code> or <code>style</code>
136  *     and if <code> secondaryPattern</code> is set, re parsers based on the
137  *     <code>secondaryPattern</code>. Syntax for <code>secondaryPattern</code>
138  *     must conform to the rules specified by
139  *     <code>java.text.SimpleDateFormat</code>.</li>
140  * <li>Parsing is lenient as outlined earlier and is not the same as setting
141  *     leniency on <code>java.text.DateFormat</code>
142  * </ul>
143  *
144  * <p>The <code>getAsString()</code> method expects a value of type
145  * <code>java.util.Date</code> (or a subclass), and creates a formatted
146  * String according to the following algorithm:</p>
147  * <ul>
148  * <li>If the specified value is null, return a zero-length String.</li>
149  * <li>If the specified value is a String, return it unmodified.</li>
150  * <li>If the <code>locale</code> property is not null,
151  *     use that <code>Locale</code> for managing formatting.  Otherwise, use the
152  *     <code>Locale</code> from the <code>UIViewRoot</code>.</li>
153  * <li>If a <code>pattern</code> has been specified, its syntax must conform
154  *     the rules specified by <code>java.text.SimpleDateFormat</code>. Such
155  *     a pattern will be used to format, and the <code>type</code>,
156  *     <code>dateStyle</code>, and <code>timeStyle</code> properties
157  *     will be ignored.</li>
158  * <li>If a <code>pattern</code> has not been specified, formatting will be
159  *     based on the <code>type</code> property, which includes a date value,
160  *     a time value, or both into the formatted String.  Any date and time
161  *     values included will be formatted in accordance to the styles specified
162  *     by <code>dateStyle</code> and <code>timeStyle</code>, respectively.</li>
163  * <li><code>secondaryPattern</code> even if set is never used for formatting
164  *     to a String</li>
165  * </ul>
166  *
167  * @see #CONVERT_DATE_MESSAGE_ID
168  * @see #CONVERT_TIME_MESSAGE_ID
169  * @see #CONVERT_BOTH_MESSAGE_ID
170  * @see java.text.DateFormat
171  * @see java.text.SimpleDateFormat
172  *
173  * @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 $
174  */
175 public class DateTimeConverter extends javax.faces.convert.DateTimeConverter
176                                implements Converter, StateHolder
177 
178 {
179 
180   /**
181    * <p>Standard converter id for this converter.</p>
182    */
183   public static final String CONVERTER_ID = "org.apache.myfaces.trinidad.DateTime";
184 
185   /**
186    * <p>The message identifier of the FacesMessage to be created if
187    * the value cannot be converted to a date, when <code>pattern</code>
188    * is null or not set and <code>type</code> is set to <code>'date'</code>.
189    * Or when failures occurs when value cannot be converted to a date
190    * based on the pattern set. The message format string for this message
191    * may optionally include <code>{0}</code>, <code>{1}</code>, <code>{4}</code>
192    * placeholdes, which will be replaced  by input value, component label
193    * and example date based on the <code>pattern</code> or based on the
194    * <code>style</code> when <code>type</code> is set to <code>'date'</code>.</p>
195    */
196   public static final String CONVERT_DATE_MESSAGE_ID =
197       "org.apache.myfaces.trinidad.convert.DateTimeConverter.CONVERT_DATE";
198 
199   /**
200    * <p>The message identifier of the FacesMessage to be created if
201    * the value cannot be converted date time object, when <code>type</code>
202    * is set to <code>'time'</code> and pattern is null or not set.
203    * The message format string for this message may optionally include
204    * <code>{0}</code>, <code>{1}</code>, <code>{4}</code>
205    * placeholdes, which will be replaced  by input value, component label
206    * and a time example, based on the <code>timeStyle</code>
207    * set in the converter.</p>
208    */
209   public static final String CONVERT_TIME_MESSAGE_ID =
210       "org.apache.myfaces.trinidad.convert.DateTimeConverter.CONVERT_TIME";
211 
212   /**
213    * <p>The message identifier of the FacesMessage to be created if
214    * the value cannot be converted to a date when <code>type</code>
215    * is set to <code>'both'</code> and pattern is either null or not set. The
216    *  message format string for this message may optionally include
217    * <code>{0}</code>, <code>{1}</code>, <code>{4}</code>
218    * placeholdes, which will be replaced  by input value, component label
219    * and a date-time example, based on the <code>dateStyle</code> and
220    * <code>timeStyle</code> set in the converter.</p>
221    */
222   public static final String CONVERT_BOTH_MESSAGE_ID =
223       "org.apache.myfaces.trinidad.convert.DateTimeConverter.CONVERT_BOTH";
224 
225   /**
226    * Creates a DateTimeConverter
227    */
228   public DateTimeConverter()
229   {
230   }
231 
232   /**
233    * Creates a DateTimeConverter with the specified SimpleDateFormat format
234    * pattern
235    * @param pattern a primary pattern;  this will be used to format
236    *        and parser strings.
237    */
238   public DateTimeConverter(String pattern)
239   {
240     this();
241     setPattern(pattern);
242   }
243 
244   /**
245    * Creates a DateTimeConverter with the specified SimpleDateFormat format
246    * pattern and a secondary pattern.
247    * @param pattern a primary pattern;  this will be used to format
248    *        and parser strings.
249    * @param secondaryPattern a second pattern, which will be used
250    *        as a second attempt to parse a string if the primary pattern or
251    *        styles fail, but is never used for formatting strings.
252    */
253   public DateTimeConverter(String pattern, String secondaryPattern)
254   {
255      this(pattern);
256      setSecondaryPattern(secondaryPattern);
257 
258   }
259 
260   /**
261    * <p>Convert the specified string value, which is associated with
262    * the specified {@link UIComponent}, into a java.util.Date object
263    * based on the values set.</p>
264    *
265    * @param context {@link FacesContext} for the request being processed
266    * @param component {@link UIComponent} with which this model object
267    *  value is associated
268    * @param value String value to be converted (may be <code>null</code>)
269    *
270    * @return <code>null</code> if the value to convert is <code>null</code>,
271    *  otherwise return a java.util.Date object.
272    *
273    * @exception ConverterException if conversion cannot be successfully
274    *  performed
275    * @exception NullPointerException if <code>context</code> or
276    *  <code>component</code> is <code>null</code>
277    *
278    * @exception IllegalArgumentException if the <code>value</code> is of
279    * type other than {@link java.util.Date}, {@link java.lang.String}. The
280    * <code>value</code> can be null.
281    */
282   @Override
283   public Object getAsObject(
284     FacesContext context,
285     UIComponent component,
286     String value)
287   {
288     Date date = _getParsedDate(context, component, value);
289     if (date != null)
290     {
291       _fillTimePortion(context, component, date);
292     }
293 
294     return date;
295   }
296 
297   /**
298    * <p>Convert the model Date object value, into a String based on the pattern
299    *  or styles.</p>
300    * @param context {@link FacesContext} for the request being processed
301    * @param component {@link UIComponent} with which this model object
302    *  value is associated
303    * @param value Model object value to be converted
304    *  (may be <code>null</code>)
305    *
306    * @return a zero-length String if value is <code>null</code>,
307    *  otherwise the result of the conversion
308    *
309    * @exception ConverterException if conversion cannot be successfully
310    *  performed
311    * @exception NullPointerException if <code>context</code> or
312    *  <code>component</code> is <code>null</code>
313    */
314   @Override
315   public String getAsString(
316     FacesContext context,
317     UIComponent component,
318     Object value
319     )
320   {
321     if (context == null || component == null)
322       throw new NullPointerException(_LOG.getMessage(
323         "NULL_FACESCONTEXT_OR_UICOMPONENT"));
324 
325     if (null == value)
326       return "";
327 
328     if (value instanceof String)
329       return (String)value;
330 
331     if (!(value instanceof Date))
332       throw new ClassCastException(_LOG.getMessage(
333         "VALUE_IS_NOT_DATE_TYPE_IT_IS", new Object[]{value,value.getClass()}));
334 
335     DateFormat format = _getDateFormat(context, getPattern(), false, (Date)value);
336     return format.format(value);
337   }
338 
339  /**
340   * <p>Custom error message to be used, for creating detail part of the {@link FacesMessage},
341   * for values that cannot be converted to {@link java.util.Date} when the
342   * <code>pattern / secondary pattern</code> is set or when <code>type</code>
343   * is set to <code>'date'</code>.</p>
344   * Overrides detail message identified by message id {@link #CONVERT_DATE_MESSAGE_ID}
345   * @param convertDateMessageDetail custom error message.
346   *
347   */
348   public void setMessageDetailConvertDate(String convertDateMessageDetail)
349   {
350     _facesBean.setProperty(_CONVERT_DATE_MESSAGE_DETAIL_KEY, convertDateMessageDetail);
351   }
352 
353   /**
354    * <p>Return custom detail error message that was set for creating {@link FacesMessage},
355    * for values that cannot be converted to {@link java.util.Date} when
356    * <code>pattern / secondary pattern</code> is set or
357    * when <code>type</code> is set to <code>'date'</code>.</p>
358    * @return custom error message that was set.
359    * @see #setMessageDetailConvertDate(String)
360    */
361   public String getMessageDetailConvertDate()
362   {
363     Object msg = _facesBean.getProperty(_CONVERT_DATE_MESSAGE_DETAIL_KEY);
364     return ComponentUtils.resolveString(msg);
365   }
366 
367   /**
368    * <p>Custom error message to be used, for creating detail part of the {@link FacesMessage},
369    * for time based value that cannot be converted to date
370    * when <code>type</code> is set to <code>'time'</code>.</p>
371    * Overrides detail message identified by message id {@link #CONVERT_TIME_MESSAGE_ID}
372    * @param convertTimeMessageDetail custom error message.
373    */
374   public void setMessageDetailConvertTime(String convertTimeMessageDetail)
375   {
376     _facesBean.setProperty(_CONVERT_TIME_MESSAGE_DETAIL_KEY, convertTimeMessageDetail);
377   }
378 
379   /**
380    * <p>Return custom detail error message that was set for creating {@link FacesMessage},
381    * for values that cannot be converted to {@link java.util.Date}
382    * when <code>type</code> is set to <code>'time'</code>.
383    * @return custom error message that was set.</p>
384    * @see #setMessageDetailConvertTime(java.lang.String)
385    */
386   public String getMessageDetailConvertTime()
387   {
388     Object msg =_facesBean.getProperty(_CONVERT_TIME_MESSAGE_DETAIL_KEY);
389     return ComponentUtils.resolveString(msg);
390   }
391 
392   /**
393    * <p>Custom error message to be used, for creating detail part of the {@link FacesMessage},
394    * for date-time based value that cannot be converted to {@link java.util.Date}
395    * when <code>type</code> is set to <code>'both'</code>.</p>
396    * Overrides detail message identified by message id {@link #CONVERT_BOTH_MESSAGE_ID}
397    * @param convertBothMessageDetail custom error message.
398    * @see #CONVERT_BOTH_MESSAGE_ID
399    */
400   public void setMessageDetailConvertBoth(String convertBothMessageDetail)
401   {
402     _facesBean.setProperty(_CONVERT_BOTH_MESSAGE_DETAIL_KEY, convertBothMessageDetail);
403   }
404 
405   /**
406    * Return custom detail error message that was set for creating {@link FacesMessage},
407    * for values that cannot be converted to {@link java.util.Date}
408    * when <code>type</code> is set to <code>'both'</code>.
409    * @return custom error message that was set.
410    * @see #setMessageDetailConvertBoth(java.lang.String)
411    */
412   public String getMessageDetailConvertBoth()
413   {
414      Object msg = _facesBean.getProperty(_CONVERT_BOTH_MESSAGE_DETAIL_KEY);
415      return ComponentUtils.resolveString(msg);
416   }
417 
418   /**
419    * <p>Custom hintDate message.</p>
420    * Overrides default hint message
421    * @param hintDate Custom hint message.
422    */
423   public void setHintDate(String hintDate)
424   {
425     _facesBean.setProperty(_HINT_DATE_KEY, hintDate);
426   }
427 
428   /**
429    * <p>Return custom hintDate message.</p>
430    * @return Custom hint message.
431    * @see  #setHintDate(String)
432    */
433   public String getHintDate()
434   {
435     Object obj = _facesBean.getProperty(_HINT_DATE_KEY);
436     return ComponentUtils.resolveString(obj);
437   }
438 
439   /**
440    * <p>Custom hintTime message.</p>
441    * Overrides default hint message
442    * @param hintTime Custom hint message.
443    */
444   public void setHintTime(String hintTime)
445   {
446     _facesBean.setProperty(_HINT_TIME_KEY, hintTime);
447   }
448 
449   /**
450    * <p>Return custom hintTime message.</p>
451    * @return Custom hint message.
452    * @see  #setHintTime(String)
453    */
454   public String getHintTime()
455   {
456     Object obj = _facesBean.getProperty(_HINT_TIME_KEY);
457     return ComponentUtils.resolveString(obj);
458   }
459 
460   /**
461    * <p>Custom hintBoth message.</p>
462    * Overrides default hint message
463    * @param hintBoth Custom hint message.
464    */
465   public void setHintBoth(String hintBoth)
466   {
467     _facesBean.setProperty(_HINT_BOTH_KEY, hintBoth);
468   }
469 
470   /**
471    * <p>Return custom hintBoth message.</p>
472    * @return Custom hint message.
473    * @see  #setHintBoth(String)
474    */
475   public String getHintBoth()
476   {
477     Object obj = _facesBean.getProperty(_HINT_BOTH_KEY);
478     return ComponentUtils.resolveString(obj);
479   }
480 
481   /**
482    * Gets the existing date from the component.
483    * This date will be used to fill in missing portions of the new date.
484    * For example, if the new date is missing the time, the time portion
485    * from the existing date will be used.
486    * <P>
487    * This implementation checks to see if the component is a ValueHolder, and
488    * calls getValue() and returns the result if it is a Date instance.
489    * @param component The component to get the existing date from.
490    * @return null if there is no existing date.
491    */
492   protected Date getDate(FacesContext context, UIComponent component)
493   {
494     if (component instanceof ValueHolder)
495     {
496       Object value = ((ValueHolder)component).getValue();
497       if(value instanceof Date)
498       {
499         return (Date)value;
500       }
501     }
502     return null;
503   }
504   
505   private Locale _extractConverterLocale(
506     FacesContext context)
507   {
508     Locale locale = getLocale();
509     if (null == locale)
510     {
511       RequestContext reqContext = RequestContext.getCurrentInstance();  
512       if (reqContext != null)
513       {
514         locale = reqContext.getFormattingLocale();
515       }
516       if (locale == null)
517       {
518         locale = context.getViewRoot().getLocale();
519       }
520     }
521     return locale;
522   }
523 
524   private Date _getParsedDate(FacesContext context,
525                               UIComponent component,
526                               String value)
527   {
528     if (context == null || component == null)
529       throw new NullPointerException(_LOG.getMessage(
530         "NULL_FACESCONTEXT_OR_UICOMPONENT"));
531 
532     if (null == value)
533       return null;
534 
535     value = value.trim();
536 
537     if ( 1 > value.length() )
538       return null;
539 
540     try
541     {
542       String pattern = getPattern();
543       if (pattern == null)
544       {
545         // get the pattern based on the style and type that has been set.
546         DateFormat format = getDateFormat(context, null, true, null);
547         if (format instanceof SimpleDateFormat)
548         {
549           pattern = ((SimpleDateFormat)format).toPattern();
550         }
551       }
552 
553       if (pattern != null)
554       {
555         return _doLenientParse(context, component, value, pattern);
556       }
557       else
558       {
559         // more unlikely that we will get null pattern here but just to be safe
560         return _parse(context, component, value, null);
561       }
562     }
563     catch (ConverterException ce)
564     {
565       try
566       {
567         // If the parsing fails with primary pattern or with the styles
568         // then we try with the secondary pattern.
569         String secPattern = getSecondaryPattern();
570         if ( secPattern != null)
571         {
572           return _doLenientParse(context, component, value, secPattern);
573         }
574       }
575       catch(ConverterException secondaryCe)
576       {
577         // either way we throw the first exception.
578         ;
579       }
580       throw ce;
581     }
582   }
583 
584   // for fixing bug 4469819.
585   /**
586    * Fill in the time portion of the new date with the time from the previous
587    * date value if the converter displays only date. For now, we are not
588    * bothered about filling all the missing parts of the pattern. But in
589    * future we would consider that.
590    */
591   private void _fillTimePortion(
592     FacesContext context,
593     UIComponent component,
594     Date newDate)
595   {
596     // get the previous date value
597     Date prevDate = getDate(context, component);
598 
599     // if the component doesn't have any date value before, return
600     if (prevDate == null)
601     {
602       return;
603     }
604 
605     // if the converter uses timePortion, then we need not do anything
606     String pattern = getPattern();
607     if (pattern == null && !"date".equals(getType()))
608     {
609       return;
610     }
611 
612     // find out the missing time components from the pattern
613     boolean fillMilliSeconds = true;
614     boolean fillSeconds = true;
615     boolean fillMinutes = true;
616     boolean fillHour = true;
617 
618     if (pattern != null)
619     {
620       int patternLen = pattern.length();
621       for (int currCharIndex = 0; currCharIndex < patternLen; currCharIndex++)
622       {
623         switch (pattern.charAt(currCharIndex))
624         {
625           case 'S':
626             fillMilliSeconds = false;
627             break;
628           case 's':
629             fillSeconds = false;
630             break;
631           case 'm':
632             fillMinutes = false;
633             break;
634           case 'h':
635           case 'H':
636           case 'k':
637           case 'K':
638             fillHour = false;
639             break;
640         }
641       }
642     }
643 
644     // fill only if any of the time components are missing
645     if ( fillMilliSeconds || fillSeconds || fillMinutes || fillHour )
646     {
647       TimeZone timeZone = _getTimeZone();
648 
649       // convert the previous date value wrt client's timeZone
650       Calendar prevCal = Calendar.getInstance(timeZone);
651       prevCal.setTime(prevDate);
652       // convert the new date value wrt client's timeZone
653       Calendar newCal = Calendar.getInstance(timeZone);
654       newCal.setTime(newDate);
655 
656       // extract all the missing time portions from the previous date value
657       // and set it to the new date value.
658       if (fillMilliSeconds)
659       {
660         newCal.set(Calendar.MILLISECOND, prevCal.get(Calendar.MILLISECOND));
661       }
662 
663       if (fillSeconds)
664       {
665         newCal.set(Calendar.SECOND, prevCal.get(Calendar.SECOND));
666       }
667 
668       if(fillMinutes)
669       {
670         newCal.set(Calendar.MINUTE, prevCal.get(Calendar.MINUTE));
671       }
672 
673       if(fillHour)
674       {
675         newCal.set(Calendar.HOUR_OF_DAY, prevCal.get(Calendar.HOUR_OF_DAY));
676       }
677 
678       // modify the new date value.
679       newDate.setTime(newCal.getTimeInMillis());
680     }
681   }
682 
683   private Date _parse(
684     FacesContext context,
685     UIComponent component,
686     String value,
687     String pattern
688     )
689   {
690     DateFormat fmt = getDateFormat(context, pattern, true, null);
691     try
692     {
693       return fmt.parse(value);
694 
695     } catch (ConverterException ce)
696     {
697       throw ce;
698     }
699     catch (ParseException pe)
700     {
701       Object[] params = _getPlaceHolderParameters(context, component, value);
702       throw new ConverterException(getParseErrorMessage(context, component,
703                                                         pattern, params),
704            pe);
705     }
706   }
707 
708   /**
709    * Does some more lenient parsing than the stric JSF standard wants.
710    */
711   private Date _doLenientParse(
712     FacesContext context,
713     UIComponent component,
714     String value,
715     String pattern
716     )
717   {
718     // When a pattern (e.g. dd.MM.yyyy HH:mm' Uhr ') requires a whitespace
719     // at the end, we should honor that. As the JSF spec (see http://bit.ly/kTelf)
720     // wants the converter to trim leading/trailing whitespace, we have to append
721     // one, if the pattern requires it at the end...
722     // TODO at the beginning as well ?
723     if(pattern.endsWith(" '"))
724     {
725       value += " ";
726     }
727 
728     // do lenient parsing for the pattern supplied.
729     // accept derived patterns during
730     // parsing, allowing:
731     // 01/13/99  --> 13-Jan-99
732     // 03/Oct/99 --> 03-Oct-99
733     // 03.Oct.99 --> 03-Oct-99
734 
735     ConverterException ce;
736     try
737     {
738       return _parse(context, component, value, pattern);
739     }
740     catch (ConverterException convException)
741     {
742       // Let us save this exception to throw, if in case we have exhausted
743       // all possible patterns
744       ce = convException;
745 
746       List<String> patterns = new ArrayList<String>();
747       patterns.add(pattern);
748 
749       Locale locale = _extractConverterLocale(context);
750       // we apply some patterns for convenience reasons (see TRINIDAD-859, 1262)
751       if (_CONVENIENCE_PATTERNS.containsKey(locale)) 
752       {
753         patterns.addAll(_CONVENIENCE_PATTERNS.get(locale));          
754       }        
755       List<String> lenientPatterns = new ArrayList<String>();
756       for (String tmpPattern : patterns)
757       {
758         lenientPatterns.addAll(_getLenientPatterns(tmpPattern));
759       }
760 
761       for (String lenientPattern : lenientPatterns)
762       {
763         try
764         {
765           return _parse(context, component, value, lenientPattern);
766         }
767         catch (ConverterException e)
768         {
769           // Just do nothing with the excpetion - we still need to evaluate
770           // for other possible patterns, and we will throw the initially caught
771           // exception, which will convey to the user the appropriate message.
772           continue;
773         }
774       }
775       throw ce;
776     }
777   }
778 
779 
780 
781   /**
782    * <p>Set the <code>Locale</code> to be used when parsing or formatting
783    * dates and times.  If set to <code>null</code>, the <code>Locale</code>
784    * stored in the {@link javax.faces.component.UIViewRoot} for the current
785    * request will be utilized.</p>
786    *
787    * @param locale The new <code>Locale</code> (or <code>null</code>)
788    */
789   @Override
790   public void setLocale(Locale locale)
791   {
792     _facesBean.setProperty(_LOCALE_KEY, locale);
793   }
794 
795  /**
796   * <p>Return the <code>Locale</code> that was set.
797   * If not explicitly set, the <code>Locale</code> stored
798   * in the {@link javax.faces.component.UIViewRoot} for the current
799   * request is used during call to <code>getAsObject</code> and
800   * <code>getAsString</code>.</p>
801   */
802   @Override
803   public Locale getLocale()
804   {
805     Object locale = _facesBean.getProperty(_LOCALE_KEY);
806     return ComponentUtils.resolveLocale(locale);
807   }
808 
809   /**
810    * <p>Set the format pattern to be used when formatting and parsing
811    * dates and times.  Valid values are those supported by
812    * <code>java.text.SimpleDateFormat</code>.
813    * An invalid value will cause a {@link ConverterException} when
814    * <code>getAsObject()</code> or <code>getAsString()</code> is called.</p>
815    *
816    * @param pattern The new format pattern
817    */
818   @Override
819   public void setPattern(String pattern)
820   {
821     _facesBean.setProperty(_PATTERN_KEY, pattern);
822   }
823 
824   /**
825    * <p>Return the format pattern to be used when formatting and
826    * parsing dates and times.</p>
827    */
828   @Override
829   public String getPattern()
830   {
831     Object pattern = _facesBean.getProperty(_PATTERN_KEY);
832     return ComponentUtils.resolveString(pattern);
833   }
834 
835   /**
836    * <p>Set the <code>TimeZone</code> used to interpret a time value.</p>
837    *
838    * @param timeZone The new time zone
839    */
840   @Override
841   public void setTimeZone(TimeZone timeZone)
842   {
843     _facesBean.setProperty(_TIME_ZONE_KEY, timeZone);
844   }
845 
846  /**
847   * <p>Return the <code>TimeZone</code> that is used to interpret a time value.
848   * If not explicitly set or if a null value is set, then during call to
849   * <code>getAsObject</code> and <code>getAsString</code>, the time zone set
850   * for the web-app is used. If time zone is not set for the web-app then
851   * the default time zone of <code>GMT</code> is used.</p>
852   */
853   @Override
854   public TimeZone getTimeZone()
855   {
856     Object timeZone = _facesBean.getProperty(_TIME_ZONE_KEY);
857     return ComponentUtils.resolveTimeZone(timeZone);
858   }
859 
860   /**
861   * <p>Set the type of value to be formatted or parsed.
862   * Valid values are <code>both</code>, <code>date</code>, or
863   * <code>time</code>.
864   * An invalid value will cause a {@link IllegalStateException} when
865   * <code>getAsObject()</code> or <code>getAsString()</code> is called.</p>
866   *
867   * @param type The new date style
868   */
869   @Override
870   public void setType(String type)
871   {
872     _facesBean.setProperty(_TYPE_KEY, type);
873   }
874 
875   /**
876    * <p>Return the type of value to be formatted or parsed.
877    * If not explicitly set, the default type, <code>date</code>
878    * is returned.</p>
879    */
880   @Override
881   public String getType()
882   {
883     Object type = _facesBean.getProperty(_TYPE_KEY);
884     return ComponentUtils.resolveString(type, "date");
885   }
886 
887   /**
888    * <p>Set the style to be used to format or parse dates.  Valid values
889    * are <code>default</code>, <code>shortish</code>
890    * <code>short</code>, <code>medium</code>,
891    * <code>long</code>, and <code>full</code>.
892    * An invalid value will cause a {@link IllegalStateException} when
893    * <code>getAsObject()</code> or <code>getAsString()</code> is called.</p>
894    *
895    * @param dateStyle The new style code
896    */
897   @Override
898   public void setDateStyle(String dateStyle)
899   {
900     _facesBean.setProperty(_DATE_STYLE_KEY, dateStyle);
901   }
902 
903   /**
904    * <p>Return the style to be used to format or parse dates.  If not set,
905    * the default value, <code>shortish</code>, is returned.</p>
906    * @see #setDateStyle(java.lang.String)
907    * @return date style
908    */
909   @Override
910   public String getDateStyle()
911   {
912     Object dateStyle = _facesBean.getProperty(_DATE_STYLE_KEY);
913     return ComponentUtils.resolveString(dateStyle, "shortish");
914   }
915 
916   /**
917    * <p>Set the style to be used to format or parse times.  Valid values
918    * are <code>default</code>, <code>short</code>,
919    * <code>medium</code>, <code>long</code>, and <code>full</code>.
920    * An invalid value will cause a {@link IllegalStateException} when
921    * <code>getAsObject()</code> or <code>getAsString()</code> is called.</p>
922    *
923    * @param timeStyle The new style code
924    */
925   @Override
926   public void setTimeStyle(String timeStyle)
927   {
928     _facesBean.setProperty(_TIME_STYLE_KEY, timeStyle);
929   }
930 
931   /**
932    * <p>Return the style to be used to format or parse times.  If not set,
933    * the default value, <code>short</code>, is returned.</p>
934    */
935   @Override
936   public String getTimeStyle()
937   {
938     Object timeStyle = _facesBean.getProperty(_TIME_STYLE_KEY);
939     return ComponentUtils.resolveString(timeStyle, "short");
940   }
941 
942   /**
943    * <p>Second pattern which will be used to parse string in
944    * <code>getAsObject</code> if pattern or styles fail. But is never
945    * used for formatting to string in <code>getAsString()</code>.</p>
946    * @param secondaryPattern a second pattern which will be used
947    *        as a second attempt to parse a string if the primary pattern or
948    *        styles fail, but is never used for formatting strings.
949    */
950   public void setSecondaryPattern(String secondaryPattern)
951   {
952     _facesBean.setProperty(_SECONDARY_PATTERN_KEY, secondaryPattern);
953   }
954 
955   /**
956    * <p>Return the secondary pattern used to parse string when parsing by
957    * pattern or style fails.</p>
958    */
959   public String getSecondaryPattern()
960   {
961     Object secPattern = _facesBean.getProperty(_SECONDARY_PATTERN_KEY);
962     return ComponentUtils.resolveString(secPattern);
963   }
964 
965   @Override
966   public boolean isTransient()
967   {
968     return _isTransient;
969   }
970 
971   @Override
972   public void setTransient(boolean isTransient)
973   {
974     _isTransient = isTransient;
975   }
976 
977   @Override
978   public Object saveState(FacesContext context)
979   {
980     return _facesBean.saveState(context);
981   }
982 
983   @Override
984   public void restoreState(FacesContext context, Object state)
985   {
986     _facesBean.restoreState(context, state);
987   }
988 
989   /**
990    * <p>Set the {@link ValueExpression} used to calculate the value for the
991    * specified attribute if any.</p>
992    *
993    * @param name Name of the attribute for which to set a {@link ValueExpression}
994    * @param expression The {@link ValueExpression} to set, or <code>null</code>
995    *  to remove any currently set {@link ValueExpression}
996    *
997    * @exception NullPointerException if <code>name</code>
998    *  is <code>null</code>
999    * @exception IllegalArgumentException if <code>name</code> is not a valid
1000    *            attribute of this converter
1001    */
1002   public void setValueExpression(String name, ValueExpression expression)
1003   {
1004     ConverterUtils.setValueExpression(_facesBean, name, expression) ;
1005   }
1006 
1007 
1008   /**
1009    * <p>Return the {@link ValueExpression} used to calculate the value for the
1010    * specified attribute name, if any.</p>
1011    *
1012    * @param name Name of the attribute or property for which to retrieve a
1013    *  {@link ValueExpression}
1014    *
1015    * @exception NullPointerException if <code>name</code>
1016    *  is <code>null</code>
1017    * @exception IllegalArgumentException if <code>name</code> is not a valid
1018    * attribute of this converter
1019    */
1020   public ValueExpression getValueExpression(String name)
1021   {
1022     return ConverterUtils.getValueExpression(_facesBean, name);
1023   }
1024 
1025   /**
1026    * <p>Set the {@link ValueBinding} used to calculate the value for the
1027    * specified attribute if any.</p>
1028    *
1029    * @param name Name of the attribute for which to set a {@link ValueBinding}
1030    * @param binding The {@link ValueBinding} to set, or <code>null</code>
1031    *  to remove any currently set {@link ValueBinding}
1032    *
1033    * @exception NullPointerException if <code>name</code>
1034    *  is <code>null</code>
1035    * @exception IllegalArgumentException if <code>name</code> is not a valid
1036    *            attribute of this converter
1037    * @deprecated
1038    */
1039   public void setValueBinding(String name, ValueBinding binding)
1040   {
1041     ConverterUtils.setValueBinding(_facesBean, name, binding) ;
1042   }
1043 
1044 
1045   /**
1046    * <p>Return the {@link ValueBinding} used to calculate the value for the
1047    * specified attribute name, if any.</p>
1048    *
1049    * @param name Name of the attribute or property for which to retrieve a
1050    *  {@link ValueBinding}
1051    *
1052    * @exception NullPointerException if <code>name</code>
1053    *  is <code>null</code>
1054    * @exception IllegalArgumentException if <code>name</code> is not a valid
1055    * attribute of this converter
1056    * @deprecated
1057    */
1058   public ValueBinding getValueBinding(String name)
1059   {
1060     return ConverterUtils.getValueBinding(_facesBean, name);
1061   }
1062 
1063   /**
1064    * <p>Compares this DateTimeConverter with the specified Object for
1065    * equality.</p>
1066    * @param object  Object to which this DateTimeConverter is to be compared.
1067    * @return true if and only if the specified Object is a DateTimeConverter
1068    * and if all parameters are equal.
1069    */
1070   @Override
1071   public boolean equals(Object object)
1072   {
1073     if (this == object)
1074       return true;
1075 
1076     if(object instanceof DateTimeConverter)
1077     {
1078       DateTimeConverter other = (DateTimeConverter)object;
1079 
1080       if ( (isTransient() == other.isTransient())
1081            && ConverterUtils.equals(getDateStyle(), other.getDateStyle())
1082            && ConverterUtils.equals(getLocale(), other.getLocale())
1083            && ConverterUtils.equals(getPattern(), other.getPattern())
1084            && ConverterUtils.equals(getTimeStyle(), other.getTimeStyle())
1085            && ConverterUtils.equals(getTimeZone(), other.getTimeZone())
1086            && ConverterUtils.equals(getType(), other.getType())
1087            && ConverterUtils.equals(getSecondaryPattern(), other.getSecondaryPattern())
1088            && ConverterUtils.equals(getMessageDetailConvertDate(),
1089                                     other.getMessageDetailConvertDate())
1090            && ConverterUtils.equals(getMessageDetailConvertTime(),
1091                                     other.getMessageDetailConvertTime())
1092            && ConverterUtils.equals(getMessageDetailConvertBoth(),
1093                                     other.getMessageDetailConvertBoth())
1094          )
1095       {
1096         return true;
1097       }
1098     }
1099     return false;
1100   }
1101 
1102   /**
1103    * <p>Returns the hash code for this Converter.</p>
1104    * @return a hash code value for this object.
1105    */
1106   @Override
1107   public int hashCode()
1108   {
1109     int result = 17;
1110     result = result * 37 + (isTransient()? 1 : 0);
1111     result = result * 37 + _getHashValue(getDateStyle());
1112     result = result * 37 + _getHashValue(getLocale());
1113     result = result * 37 + _getHashValue(getPattern());
1114     result = result * 37 + _getHashValue(getTimeStyle());
1115     result = result * 37 + _getHashValue(getTimeZone());
1116     result = result * 37 + _getHashValue(getType());
1117     result = result * 37 + _getHashValue(getSecondaryPattern());
1118     result = result * 37 + _getHashValue(getMessageDetailConvertDate());
1119     result = result * 37 + _getHashValue(getMessageDetailConvertTime());
1120     result = result * 37 + _getHashValue(getMessageDetailConvertBoth());
1121     return result;
1122   }
1123 
1124   protected final DateFormat getDateFormat(
1125     FacesContext context,
1126     String pattern,
1127     boolean forParsing,
1128     Date    targetDate
1129     ) throws ConverterException
1130   {
1131     ConverterException exception = null;
1132     try
1133     {
1134       DateFormat format = _getDateFormat(context, pattern, forParsing, targetDate);
1135       return format;
1136     }
1137     catch (ConverterException ce)
1138     {
1139       exception = ce;
1140     }
1141     catch (Exception e)
1142     {
1143       exception = new ConverterException(e);
1144     }
1145     throw exception;
1146   }
1147 
1148   /**
1149    * Returns the TimeZone that will be set on DateFormat for formatting
1150    * and parsing the dates. By default, this just returns the specified
1151    * time zone, the one that is set on the DateTimeConverter or in the
1152    * Adf-Faces config.
1153    */
1154   protected TimeZone getFormattingTimeZone(TimeZone tZone)
1155   {
1156     return getFormattingTimeZone (tZone, null);
1157   }
1158 
1159   /**
1160    * Returns the timeZone for formatting and parsing the date. 
1161    * TRINIDAD-1512: In some cases,timezone varies depending on the targetDate, 
1162    * e.g. daylight savings.
1163    */
1164   protected TimeZone getFormattingTimeZone(TimeZone tZone, Date targetDate)
1165   {
1166     return tZone;
1167   }
1168 
1169   // This is used while displaying error message at the client side.
1170   // Identifies the pattern expected to be matched.
1171   private String[] _getExpectedPatterns(FacesContext context)
1172   {
1173     String pattern = getPattern();
1174 
1175     if ( pattern != null )
1176     {
1177       return _getAllowedPatterns(context, pattern, getSecondaryPattern());
1178     }
1179     else
1180     {
1181       String datePattern = null;
1182 
1183       try
1184       {
1185         DateFormat format  = getDateFormat(context, null,false, null);
1186         if ((format != null) && (format instanceof SimpleDateFormat))
1187         {
1188           datePattern = ((SimpleDateFormat)format).toPattern();
1189         }
1190       }
1191       catch (ConverterException ce)
1192       {
1193         // Do nothing here. Check to see if secondary pattern is available.
1194         ;
1195       }
1196       return _getAllowedPatterns(context, datePattern, getSecondaryPattern());
1197     }
1198   }
1199 
1200   protected final  FacesMessage getParseErrorMessage(
1201     FacesContext context,
1202     UIComponent component,
1203     String pattern,
1204     Object[] params
1205     )
1206   {
1207     // if pattern is set then - conversion would have been carried out using
1208     // the pattern or secondary pattern.
1209     String key = getViolationMessageKey(pattern);
1210     return _getConvertErrorFacesMessage(context, key, params, component);
1211 
1212   }
1213 
1214   protected final String getExample(FacesContext context)
1215   {
1216     String[] expectedPatterns = _getExpectedPatterns(context);
1217 
1218      assert((expectedPatterns != null) && (expectedPatterns.length >= 1));
1219      String example = expectedPatterns[0];
1220      return example;
1221   }
1222 
1223   private String[] _getAllowedPatterns(
1224     FacesContext context,
1225     String mainPattern,
1226     String secondaryPattern
1227     )
1228   {
1229     String[] patterns;
1230 
1231     if (mainPattern != null)
1232     {
1233       if (secondaryPattern != null)
1234       {
1235         patterns = new String[]{mainPattern, secondaryPattern};
1236       }
1237       else
1238       {
1239         patterns = new String[]{mainPattern};
1240       }
1241     }
1242     else
1243     {
1244       patterns = new String[]{secondaryPattern};
1245     }
1246 
1247     // Convert each pattern into an example
1248     for (int i = 0; i < patterns.length; i++)
1249     {
1250       patterns[i] = _getExample(context, patterns[i]);
1251     }
1252 
1253     return patterns;
1254   }
1255 
1256   /**
1257    * <p>Return the style constant for the specified style name.</p>
1258    * If invalid throw IllegalStateException.
1259    *
1260    * @param dateStyle Name of the date style for which to return a constant
1261    *
1262    */
1263   private static final int _getDateStyle(String dateStyle)
1264   {
1265     if (dateStyle.equals("shortish"))
1266     {
1267       return _SHORTISH;
1268     }
1269     else if (dateStyle.equals("default"))
1270     {
1271       return (DateFormat.DEFAULT);
1272     }
1273     else if (dateStyle.equals("short"))
1274     {
1275       return (DateFormat.SHORT);
1276     }
1277     else if (dateStyle.equals("medium"))
1278     {
1279       return (DateFormat.MEDIUM);
1280     }
1281     else if (dateStyle.equals("long"))
1282     {
1283       return (DateFormat.LONG);
1284     }
1285     else if (dateStyle.equals("full"))
1286     {
1287       return (DateFormat.FULL);
1288     }
1289     else
1290       throw new IllegalStateException(_LOG.getMessage(
1291         "INVALID_DATE_STYLE", dateStyle));
1292   }
1293 
1294   private static final int _getTimeStyle(String timeStyle)
1295   {
1296     if ("default".equals(timeStyle))
1297     {
1298       return (DateFormat.DEFAULT);
1299     }
1300     else if ("short".equals(timeStyle))
1301     {
1302       return (DateFormat.SHORT);
1303     }
1304     else if ("medium".equals(timeStyle))
1305     {
1306       return (DateFormat.MEDIUM);
1307     }
1308     else if ("long".equals(timeStyle))
1309     {
1310       return (DateFormat.LONG);
1311     }
1312     else if ("full".equals(timeStyle))
1313     {
1314       return (DateFormat.FULL);
1315     }
1316     else
1317       throw new IllegalStateException(_LOG.getMessage(
1318         "INVALID_TIME_STYLE", timeStyle));
1319   }
1320 
1321   /**
1322    * <p>The valid values for type are date,time and both. Any value other than this
1323    * would result in a ConverterException.</p>
1324    * @return type
1325    */
1326   private static int _getType(String type)
1327   {
1328     if ("date".equals(type))
1329       return _TYPE_DATE;
1330     else if ("time".equals(type))
1331       return _TYPE_TIME;
1332     else if ("both".equals(type))
1333       return _TYPE_BOTH;
1334     else
1335       throw new IllegalStateException(_LOG.getMessage(
1336         "INVALID_TYPE", type));
1337   }
1338 
1339   // Don't use this for Array Object and other objects which don't implement
1340   // their hashCode()
1341   private static int _getHashValue(Object obj)
1342   {
1343     return obj == null? 0 : obj.hashCode();
1344   }
1345 
1346   private static List<String> _getLenientPatterns(String pattern)
1347   {
1348     //Create patterns so as to be lenient.
1349     // allow for
1350     // 01/13/99  --> 13-Jan-99 [MMM -> MM], [MMM -> M]
1351     // Apply the below conversion to the above obtained patterns and the actual
1352     // patern
1353     // 03/Oct/99 --> 03-Oct-99
1354     // 03.Oct.99 --> 03-Oct-99
1355 
1356     List<String> patterns = new ArrayList<String>();
1357     // Don't forget to add the actual pattern.
1358     patterns.add(pattern);
1359 
1360     String[] leniencyApplicablePatterns = new String[1];
1361     leniencyApplicablePatterns[0] = pattern;
1362 
1363     if (pattern.indexOf("MMM") != -1)
1364     {
1365       leniencyApplicablePatterns = new String[3];
1366       leniencyApplicablePatterns[0] = pattern;
1367 
1368       String str1 = pattern.replaceAll("MMM", "MM");
1369       patterns.add(str1);
1370       leniencyApplicablePatterns[1] = str1;
1371 
1372       String str2 = pattern.replaceAll("MMM", "M");
1373       leniencyApplicablePatterns[2] = str2;
1374       patterns.add(str2);
1375     }
1376 
1377     // Apply the leninecy (German for leniency?) to the above obtained patterns which was obtained
1378     // after replacing MMM -> MM and MMM -> M and the actual pattern
1379     int len = leniencyApplicablePatterns.length;
1380     if (pattern.indexOf('/') != -1)
1381     {
1382       for (int i = 0; i < len; i++)
1383         patterns.add(leniencyApplicablePatterns[i].replaceAll("/", "-"));
1384       
1385       for (int i = 0; i < len; i++)
1386         patterns.add(leniencyApplicablePatterns[i].replaceAll("/", "."));
1387     }
1388     else if (pattern.indexOf('-') != -1)
1389     {
1390       for (int i = 0; i < len; i++)
1391         patterns.add(leniencyApplicablePatterns[i].replaceAll("-", "/"));
1392       
1393       for (int i = 0; i < len; i++)
1394         patterns.add(leniencyApplicablePatterns[i].replaceAll("-", "."));
1395     }
1396     else if (pattern.indexOf('.') != -1)
1397     {
1398       for (int i = 0; i < len; i++)
1399         patterns.add(leniencyApplicablePatterns[i].replaceAll("\\.", "/"));
1400       
1401       for (int i = 0; i < len; i++)
1402         patterns.add(leniencyApplicablePatterns[i].replaceAll("\\.", "-"));
1403     }
1404       
1405     return patterns;
1406   }
1407 
1408   private Object[] _getPlaceHolderParameters(
1409     FacesContext context,
1410     UIComponent component,
1411     String value)
1412   {
1413      Object label = ConverterUtils.getComponentLabel(component);
1414      String example = getExample(context);
1415      Object[] params = {label, value, example};
1416      return params;
1417   }
1418 
1419   private Object _getRawConvertBothMessageDetail()
1420   {
1421     return _facesBean.getRawProperty(_CONVERT_BOTH_MESSAGE_DETAIL_KEY);
1422   }
1423 
1424   private Object _getRawConvertDateMessageDetail()
1425   {
1426     return _facesBean.getRawProperty(_CONVERT_DATE_MESSAGE_DETAIL_KEY);
1427   }
1428 
1429   private Object _getRawConvertTimeMessageDetail()
1430   {
1431     return _facesBean.getRawProperty(_CONVERT_TIME_MESSAGE_DETAIL_KEY);
1432   }
1433 
1434   private FacesMessage _getConvertErrorFacesMessage(
1435     FacesContext context,
1436     String key,
1437     Object[] params,
1438     UIComponent component
1439     )
1440   {
1441     Object msgPattern = getMessagePattern(context, key, params, component);
1442     return MessageFactory.getMessage(context, key, msgPattern,
1443                                      params, component);
1444   }
1445   
1446   private String _getExample(FacesContext context, String pattern)
1447   {
1448     DateFormat format = _getDateFormat(context, pattern, false, _EXAMPLE_DATE);
1449     return format.format(_EXAMPLE_DATE);
1450   }
1451 
1452 
1453   protected Object getMessagePattern(
1454       FacesContext context,
1455       String key,
1456       Object[] params,
1457       UIComponent component
1458       )
1459   {
1460     Object msgPattern;
1461     if (CONVERT_DATE_MESSAGE_ID.equals(key))
1462     {
1463       msgPattern = _getRawConvertDateMessageDetail();
1464     }
1465     else if (CONVERT_TIME_MESSAGE_ID.equals(key))
1466     {
1467       msgPattern = _getRawConvertTimeMessageDetail();
1468     }
1469     else if (CONVERT_BOTH_MESSAGE_ID.equals(key))
1470     {
1471       msgPattern = _getRawConvertBothMessageDetail();
1472     }
1473     else
1474     {
1475       // THIS CAN NEVER HAPPEN!
1476       throw new IllegalArgumentException(_LOG.getMessage(
1477         "ILLEGAL_MESSAGE_ID", key));
1478     }
1479       
1480     return msgPattern;
1481   }
1482 
1483   protected String getViolationMessageKey(String pattern)
1484   {
1485     String key = null;
1486     // if pattern is null, then use ViolationMessage based on type specified
1487     if (getPattern() == null || pattern == null)
1488     {
1489       String type = getType();
1490       if("date".equals(type))
1491       {
1492         key = CONVERT_DATE_MESSAGE_ID;
1493       }
1494       else if ("time".equals(type))
1495       {
1496         key = CONVERT_TIME_MESSAGE_ID;
1497       }
1498       else if ("both".equals(type))
1499       {
1500         key = CONVERT_BOTH_MESSAGE_ID;
1501       }
1502       else if (pattern == null)
1503       {
1504         // The chances of this happening is remote. If this was the case..
1505         // it should have happend during early stages of processing.
1506         throw new IllegalArgumentException(_LOG.getMessage(
1507           "ILLEGAL_ATTRIBUTE_TYPE_VALUE", type));
1508       }
1509     }
1510 
1511     if (key == null)
1512     {
1513       // if pattern is specified explicitly, then use this key
1514       key = CONVERT_DATE_MESSAGE_ID;
1515     }
1516 
1517     return key;
1518   }
1519 
1520   private SimpleDateFormat _getSimpleDateFormat(String pattern, Locale locale)
1521   {
1522     String variant = locale.getVariant();
1523     SimpleDateFormat sdf = null;
1524 
1525     if ((variant != null) && (variant.toUpperCase().startsWith("ORACLE")))
1526     {
1527       // This is a special Oracle format, we have to build a special formatter
1528       // using Oracle style resources.
1529       try
1530       {
1531         ResourceBundle oraElementsData =
1532           ResourceBundle.getBundle(_ORA_LOCALE_ELEMENTS_BASE, locale);
1533 
1534         DateFormatSymbols syms = new DateFormatSymbols(locale);
1535 
1536         String[] res = oraElementsData.getStringArray("AmPmMarkers");
1537         if (res != null)
1538           syms.setAmPmStrings(res);
1539         res = oraElementsData.getStringArray("Eras");
1540         if (res != null)
1541           syms.setEras(res);
1542         res = oraElementsData.getStringArray("MonthNames");
1543         if (res != null)
1544           syms.setMonths(res);
1545         res = oraElementsData.getStringArray("MonthAbbreviations");
1546         if (res != null)
1547           syms.setShortMonths(res);
1548         res = oraElementsData.getStringArray("DayAbbreviations");
1549         if (res != null)
1550           syms.setShortWeekdays(res);
1551         res = oraElementsData.getStringArray("DayNames");
1552         if (res != null)
1553           syms.setWeekdays(res);
1554         sdf = new SimpleDateFormat(pattern, syms);
1555       }
1556       catch (MissingResourceException e)
1557       {
1558         // the Oracle resource bundle must be screwed up, just use the default
1559         sdf = new SimpleDateFormat(pattern, locale);
1560       }
1561     }
1562     else
1563       sdf = new SimpleDateFormat(pattern, locale);
1564 
1565     return sdf;
1566   }
1567 
1568   /**
1569    * Returns a SimpleDateFormat based on the passed in SimpleDateFormat that
1570    * uses at least 4 digit years if it uses years at all.
1571    * <p>
1572    * If <code>format</code> already uses at least 4 digit years, then
1573    * the <code>format</code> instance will be returned from this function
1574    * unchanged.
1575    */
1576   private SimpleDateFormat _get4YearFormat(
1577     SimpleDateFormat format,
1578     Locale           loc
1579     )
1580   {
1581     String formatPattern = format.toPattern();
1582 
1583     //
1584     // search through the formatPattern for a less than 4 year pattern
1585     //
1586     int patternLen = formatPattern.length();
1587 
1588     int firstYIndex = -1;
1589     int yCount = 0;
1590     boolean inQuotes = false;
1591 
1592     int currCharIndex = 0;
1593 
1594     for (; currCharIndex < patternLen; currCharIndex++)
1595     {
1596       char currChar = formatPattern.charAt(currCharIndex);
1597 
1598       switch (currChar)
1599       {
1600         case 'y':
1601         if (!inQuotes)
1602         {
1603           // save the location of the first y
1604           if (firstYIndex < 0)
1605             firstYIndex = currCharIndex;
1606 
1607           yCount++;
1608         }
1609         break;
1610 
1611         case '\'':
1612         if (inQuotes)
1613         {
1614           int nextCharIndex = currCharIndex + 1;
1615 
1616           if ((nextCharIndex < patternLen) &&
1617               ('\'' == formatPattern.charAt(nextCharIndex)))
1618           {
1619             // this is just an escaped quote, so ignore
1620             currCharIndex++;
1621           }
1622           else
1623           {
1624             // no longer quoted
1625             inQuotes = false;
1626           }
1627         }
1628         else
1629         {
1630           // we're now in quotes
1631           inQuotes = true;
1632         }
1633         // fall through
1634 
1635         default:
1636         {
1637           // we only replace the first set of years
1638           // FIXME: this break does nothing, so commenting out;  is
1639           // it really logic that needs to be fixed?
1640           /*
1641           if ((yCount > 0) && (yCount < 4))
1642           {
1643             break;
1644           }
1645           */
1646         }
1647       }
1648     }
1649 
1650     // we have some years, but not enough, so add enough y's to the current
1651     // y's to get us to 4
1652     // =-= bts should we actually be guaranteeing 4 digit years?  Maybe we
1653     //         should guarantee that we use the same year format as the
1654     //         MEDIUM format.
1655     if ((yCount > 0) && (yCount < 4))
1656     {
1657       StringBuffer newFormatPattern = new StringBuffer(patternLen + 4 - yCount);
1658 
1659       // append everything from the orginal string before the first y
1660       if (firstYIndex > 0)
1661       {
1662         newFormatPattern.append(formatPattern.substring(0, firstYIndex));
1663       }
1664 
1665       // add in the correct number of y's
1666       for (; yCount < 4; yCount++)
1667       {
1668         newFormatPattern.append('y');
1669       }
1670 
1671       // append everything from the orginal string after the last y
1672       if (firstYIndex < patternLen)
1673       {
1674         newFormatPattern.append(formatPattern.substring(firstYIndex,
1675                                                         patternLen));
1676       }
1677 
1678       // create the new format
1679       //format = new SimpleDateFormat(newFormatPattern.toString(), loc);
1680       format = _getSimpleDateFormat(newFormatPattern.toString(), loc);
1681     }
1682 
1683     return format;
1684   }
1685 
1686   private DateFormat _getDateFormat(
1687     FacesContext context,
1688     String pattern,
1689     boolean forParsing,
1690     Date    targetDate
1691     )
1692   {
1693     Locale locale = _extractConverterLocale(context);
1694     TimeZone tZone = _getTimeZone();
1695 
1696     DateFormat format = _getCachedFormat(locale, tZone, pattern);
1697 
1698     if (format != null)
1699     {
1700       format.setTimeZone(tZone);
1701     }
1702     else
1703     {
1704       // create a format off of the styles.
1705       // We will change shortish to short only at place where it is required.
1706       // Otherwise we may end up throwing convert exception for case where
1707       // dateStyle is invalid. So evaluating only at the required place.
1708       if ( null == pattern || "".equals(pattern))
1709       {
1710         int type = _getType(getType());
1711 
1712         if (type == _TYPE_DATE || type == _TYPE_BOTH)
1713         {
1714           int actualDateStyle = _getDateStyle(getDateStyle());
1715           int dateStyle = _changeShortishAsShortIfNeeded(actualDateStyle);
1716 
1717           if (type == _TYPE_DATE)
1718           {
1719             format = DateFormat.getDateInstance(dateStyle, locale);
1720           }
1721           else
1722           {
1723             int timeStyle = _getTimeStyle(getTimeStyle());
1724             format = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
1725           }
1726         }
1727         if (type == _TYPE_TIME)
1728         {
1729           int timeStyle = _getTimeStyle(getTimeStyle());
1730           format = DateFormat.getTimeInstance(timeStyle, locale);
1731         }
1732       }
1733       else
1734       {
1735         // create a format off of the pattern
1736         format = _getSimpleDateFormat(pattern, locale);
1737       }
1738 
1739       if (format instanceof SimpleDateFormat && !forParsing)
1740       {
1741         SimpleDateFormat simpleFormat = (SimpleDateFormat)format;
1742 
1743         // make sure that we have a 4 digit year for "shortish"
1744         // dates
1745         // DO NOT CHANGE THE FOLLOWING LINE to "dateStyle";  this
1746         // must be retrieved from the instance variable!  (See above)
1747         // and we need to apply shortish only if it is of date type or
1748         // type is date and time.
1749         if (null == pattern && "shortish".equals(getDateStyle()) )
1750         {
1751           int type = _getType(getType());
1752           if (type == _TYPE_DATE || type == _TYPE_BOTH )
1753           {
1754             simpleFormat = _get4YearFormat(simpleFormat, locale);
1755             format = simpleFormat;
1756           }
1757         }
1758 
1759         Calendar cal;
1760         RequestContext reqContext = RequestContext.getCurrentInstance();
1761         if (reqContext == null)
1762         {
1763           cal = null;
1764           if(_LOG.isWarning())
1765           {
1766             _LOG.warning("NO_REQUESTCONTEXT_TWO_DIGIT_YEAR_START_DEFAULT");
1767           }
1768         }
1769         else
1770         {
1771           cal = new GregorianCalendar(reqContext.getTwoDigitYearStart(), 0, 0);
1772         }
1773         if (cal != null)
1774           simpleFormat.set2DigitYearStart(cal.getTime());
1775       }
1776 
1777       // Bug 2002065
1778       format.setLenient(false);
1779 
1780       // Bug 2317641
1781       // include timezone in date format
1782       if (tZone != null)
1783       {
1784         TimeZone formatTZone = getFormattingTimeZone(tZone, targetDate);
1785         format.setTimeZone(formatTZone);
1786       }
1787 
1788       // cache the format
1789       _cacheFormat(format, locale, tZone, pattern);
1790     }
1791 
1792     return format;
1793   }
1794 
1795   private int _changeShortishAsShortIfNeeded(int dateStyle)
1796   {
1797     if (dateStyle == _SHORTISH)
1798       dateStyle = DateFormat.SHORT;
1799     return dateStyle;
1800   }
1801 
1802   // 1. For the given locale get the timeZone map
1803   // 2. For the given timeZone get the pattern,type map
1804   // 3. For the given pattern and type, dateStyle, timeStyle from the
1805   // InfoHolderMap- get the date format.
1806   private DateFormat _getCachedFormat(
1807     Locale locale,
1808     TimeZone tZone,
1809     String pattern
1810     )
1811   {
1812     // See _cacheFormat()
1813     return null;
1814   }
1815 
1816   // caching is based on pattern, type, dateStyle and timeStyle
1817   private void _cacheFormat(
1818     DateFormat format,
1819     Locale locale,
1820     TimeZone tZone,
1821     String pattern
1822     )
1823   {
1824     // We formerly attempted to cache the SimpleDateFormat in
1825     // a global, static HashMap.  However, this is invalid:  SimpleDateFormats
1826     // are not threadsafe, and can only be cached within a thread.  This
1827     // architecture must be revisited and implemented in a thread-safe fashion.
1828   }
1829 
1830   private TimeZone _getTimeZone()
1831   {
1832     TimeZone tZone = getTimeZone();
1833 
1834     if (tZone == null)
1835     {
1836       RequestContext context = RequestContext.getCurrentInstance();
1837       if (context == null)
1838       {
1839         _LOG.warning("NO_REQUESTCONTEXT_TIMEZONE_DEFAULT");
1840       }
1841       else
1842       {
1843         tZone = context.getTimeZone();
1844       }
1845 
1846       // If RequestContext is null or if it returns a null,
1847       // then set it to the default time zone which is GMT time zone
1848       if (tZone == null)
1849       {
1850         tZone = _DEFAULT_TIME_ZONE;
1851       }
1852     }
1853 
1854     return tZone;
1855   }
1856 
1857   private static final FacesBean.Type _TYPE = new FacesBean.Type();
1858 
1859   private static final PropertyKey _DATE_STYLE_KEY
1860     = _TYPE.registerKey("dateStyle", String.class, "shortish");
1861 
1862   private static final PropertyKey _LOCALE_KEY
1863     = _TYPE.registerKey("locale", Locale.class);
1864 
1865   private static final PropertyKey _PATTERN_KEY
1866     = _TYPE.registerKey("pattern", String.class);
1867 
1868   private static final PropertyKey _SECONDARY_PATTERN_KEY
1869     = _TYPE.registerKey("secondaryPattern", String.class);
1870 
1871   private static final PropertyKey _TIME_STYLE_KEY
1872     = _TYPE.registerKey("timeStyle", String.class, "short");
1873 
1874   private static final PropertyKey _TIME_ZONE_KEY
1875     = _TYPE.registerKey("timeZone", TimeZone.class);
1876 
1877   private static final PropertyKey  _TYPE_KEY
1878     = _TYPE.registerKey("type", String.class, "date");
1879 
1880   private static final PropertyKey _CONVERT_DATE_MESSAGE_DETAIL_KEY
1881     = _TYPE.registerKey("messageDetailConvertDate", String.class);
1882 
1883   private static final PropertyKey _CONVERT_TIME_MESSAGE_DETAIL_KEY
1884     = _TYPE.registerKey("messageDetailConvertTime", String.class);
1885 
1886   private static final PropertyKey _CONVERT_BOTH_MESSAGE_DETAIL_KEY
1887     = _TYPE.registerKey("messageDetailConvertBoth", String.class);
1888   
1889   private static final PropertyKey  _HINT_DATE_KEY =
1890     _TYPE.registerKey("hintDate", String.class);
1891 
1892   private static final PropertyKey  _HINT_TIME_KEY =
1893     _TYPE.registerKey("hintTime", String.class);
1894 
1895   private static final PropertyKey  _HINT_BOTH_KEY =
1896     _TYPE.registerKey("hintBoth", String.class);
1897 
1898   private FacesBean _facesBean = ConverterUtils.getFacesBean(_TYPE);
1899 
1900   private boolean _isTransient;
1901 
1902   private static final TimeZone _DEFAULT_TIME_ZONE = TimeZone.getTimeZone("GMT");
1903 
1904   private static final int _SHORTISH       = -2;
1905 
1906   private static final int _TYPE_DATE      = 0;
1907 
1908   private static final int _TYPE_TIME      = 2;
1909 
1910   private static final int _TYPE_BOTH      = 4;
1911 
1912   private static final String _ORA_LOCALE_ELEMENTS_BASE =
1913     "org.apache.myfaces.trinidad.resource.LocaleElements";
1914 
1915   private static final TrinidadLogger _LOG  = TrinidadLogger.createTrinidadLogger(DateTimeConverter.class);
1916   private static final Date _EXAMPLE_DATE;
1917   /**
1918    * All entries added to this map MUST also be added to the client map:
1919    * trinidad-impl\src\main\javascript\META-INF\adf\jsLibs\DateFormat.js->_CONVENIENCE_PATTERNS
1920    * (in TrDateTimeConverter.prototype._initConveniencePatterns)
1921    */
1922   private static final Map<Locale, List<String>> _CONVENIENCE_PATTERNS = 
1923     new HashMap<Locale, List<String>>();
1924   private static final List<String> _US_CONVENIENCE_PATTERNS =
1925     Arrays.asList("MMMM dd, yy", "MMMM/dd/yy", "dd-MMMM-yy");
1926 
1927   static
1928   {
1929     Calendar dateFactory = Calendar.getInstance();
1930     dateFactory.set(1998, 10, 29, 15, 45);
1931     _EXAMPLE_DATE = dateFactory.getTime();
1932     
1933     // All entries added to this map MUST also be added to the client map:
1934     // trinidad-impl\src\main\javascript\META-INF\adf\jsLibs\DateFormat.js->_CONVENIENCE_PATTERNS
1935     // (in TrDateTimeConverter.prototype._initConveniencePatterns)
1936     _CONVENIENCE_PATTERNS.put(Locale.US, _US_CONVENIENCE_PATTERNS);  
1937   }
1938 }