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