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