View Javadoc

1   /*
2    * Copyright 2006-2016 The JGUIraffe Team.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.jguiraffe.transform;
17  
18  import java.sql.Time;
19  import java.sql.Timestamp;
20  import java.text.DateFormat;
21  import java.text.ParseException;
22  import java.text.ParsePosition;
23  import java.util.Calendar;
24  import java.util.Date;
25  import java.util.Locale;
26  
27  import org.apache.commons.configuration.Configuration;
28  import org.apache.commons.configuration.MapConfiguration;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  
32  /**
33   * <p>
34   * An abstract base class for date transformer objects.
35   * </p>
36   * <p>
37   * Date transformers know how to handle certain kinds of date formats. They can
38   * <ul>
39   * <li>validate a string entered by a user to verify that it contains a valid
40   * date according to the supported format</li>
41   * <li>perform certain semantic checks, e.g. whether the date is in the past or
42   * future</li>
43   * <li>transform a valid string into a <code>java.util.Date</code> object</li>
44   * <li>transform a <code>java.util.Date</code> object into a string
45   * representation.</li>
46   * </ul>
47   * </p>
48   * <p>
49   * This base class already implements the major part of the required
50   * functionality. Concrete sub classes are responsible of creating an
51   * appropriate <code>DateFormat</code> object that is able to parse the
52   * specific date format.
53   * </p>
54   * <p>
55   * There are some properties for customizing the parsing of date strings. These
56   * properties can be set either through the set methods provided by this class
57   * or using a <code>&lt;properties&gt;</code> section in the builder script
58   * that declares the transformer. The following properties are supported: <table
59   * border="1">
60   * <tr>
61   * <th>Property</th>
62   * <th>Description</th>
63   * <th>Default</th>
64   * </tr>
65   * <tr>
66   * <td valign="top">style</td>
67   * <td>Defines the style of the date. This can be one of the style constants
68   * declared by the <code>java.text.DateFormat</code> class like
69   * <code>SHORT</code> or <code>FULL</code>.</td>
70   * <td valign="top">SHORT</td>
71   * </tr>
72   * <tr>
73   * <td valign="top">lenient</td>
74   * <td>Specifies the lenient mode for parsing dates. The lenient flag has the
75   * same meaning as described in the documentation of the
76   * <code>java.text.DateFormat</code> class and controls how strict the parsing
77   * process is. Note that lenient mode is turned off per default.</td>
78   * <td valign="top">false</td>
79   * </tr>
80   * <tr>
81   * <td valign="top">referenceDate</td>
82   * <td>With this property a reference date can be specified that is used for
83   * testing semantic correctness. For instance, if one of the <code>after</code>
84   * or <code>before</code> flags described below are set, it can be tested
85   * whether the entered date is after or before this reference date. The property
86   * must be a string conforming to one of the formats supported by
87   * <code>java.sql.Timestamp</code>, <code>java.sql.Date</code>, or
88   * <code>java.sql.Time</code>.</td>
89   * <td valign="top">current date</td>
90   * </tr>
91   * <tr>
92   * <td valign="top">after</td>
93   * <td>If this boolean flag is set, the entered date must be after the
94   * reference date.</td>
95   * <td valign="top">false</td>
96   * </tr>
97   * <tr>
98   * <td valign="top">before</td>
99   * <td>If this boolean flag is set, the entered date must be before the
100  * reference date. Note that the properties <code>before</code> and
101  * <code>after</code> are mutual exclusive.</td>
102  * <td valign="top">false</td>
103  * </tr>
104  * <tr>
105  * <td valign="top">equal</td>
106  * <td>This flag is evaluated only if <code>after</code> or
107  * <code>before</code> is <b>true</b>. In this case, it controls whether the
108  * reference date is included in the comparison. So a comparison can be
109  * specified whether the entered date is before or equal a reference date.</td>
110  * <td valign="top">false</td>
111  * </tr>
112  * </table>
113  * </p>
114  * <p>
115  * Depending on the performed validations this validator implementation can
116  * create a bunch of error messages. The following table lists all supported
117  * error messages: <table border="1">
118  * <tr>
119  * <th>Message key</th>
120  * <th>Description</th>
121  * <th>Parameters</th>
122  * </tr>
123  * <tr>
124  * <td valign="top"><code>{@value ValidationMessageConstants#ERR_INVALID_DATE}</code></td>
125  * <td valign="top">The passed in string cannot be parsed to a date object.</td>
126  * <td valign="top">{0} = the date string</td>
127  * </tr>
128  * <tr>
129  * <td valign="top"><code>{@value ValidationMessageConstants#ERR_DATE_AFTER}</code></td>
130  * <td valign="top">The entered date must be after the reference date.</td>
131  * <td valign="top">{0} = the (formatted) reference date</td>
132  * </tr>
133  * <tr>
134  * <td valign="top"><code>{@value ValidationMessageConstants#ERR_DATE_AFTER_EQUAL}</code></td>
135  * <td valign="top">The entered date must be after or equal the reference date.</td>
136  * <td valign="top">{0} = the (formatted) reference date</td>
137  * </tr>
138  * <tr>
139  * <td valign="top"><code>{@value ValidationMessageConstants#ERR_DATE_BEFORE}</code></td>
140  * <td valign="top">The entered date must be before the reference date.</td>
141  * <td valign="top">{0} = the (formatted) reference date</td>
142  * </tr>
143  * <tr>
144  * <td valign="top"><code>{@value ValidationMessageConstants#ERR_DATE_BEFORE_EQUAL}</code></td>
145  * <td valign="top">The entered date must be before or equal the reference
146  * date.</td>
147  * <td valign="top">{0} = the (formatted) reference date</td>
148  * </tr>
149  * </table>
150  * </p>
151  * <p>
152  * This class implements both the <code>{@link Transformer}</code> and the
153  * <code>{@link Validator}</code> interfaces. The <code>Transformer</code>
154  * implementation can work in both directions: if a <code>Date</code> object
155  * is passed in, it will format the date to a string using the specified format.
156  * Otherwise the passed in object is tried to be converted to a date.
157  * </p>
158  * <p>
159  * Instances can be shared between multiple input components. It is especially
160  * possible to use an instance as both (read and write) transformer and
161  * validator for an input component at the same time (provided that the same
162  * properties are used). However the class is not thread-safe.
163  * </p>
164  *
165  * @author Oliver Heger
166  * @version $Id: DateTransformerBase.java 205 2012-01-29 18:29:57Z oheger $
167  * @see ValidationMessageConstants
168  */
169 public abstract class DateTransformerBase implements Transformer, Validator
170 {
171     /** Constant for the style property. */
172     protected static final String PROP_STYLE = "style";
173 
174     /** Constant for the lenient property. */
175     protected static final String PROP_LENIENT = "lenient";
176 
177     /** Constant for the referenceDate property. */
178     protected static final String PROP_REFERENCE_DATE = "referenceDate";
179 
180     /** Constant for the before property. */
181     protected static final String PROP_BEFORE = "before";
182 
183     /** Constant for the after property. */
184     protected static final String PROP_AFTER = "after";
185 
186     /** Constant for the equal property. */
187     protected static final String PROP_EQUAL = "equal";
188 
189     /** An array with the calendar fields used for a date component. */
190     private static final int[] CALENDAR_DATE_FIELDS = {
191             Calendar.YEAR, Calendar.MONTH, Calendar.DATE
192     };
193 
194     /** An array with the calendar fields used for a time component. */
195     private static final int[] CALENDAR_TIME_FIELDS = {
196             Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND,
197             Calendar.MILLISECOND
198     };
199 
200     /** The logger. */
201     private final Log log = LogFactory.getLog(getClass());
202 
203     /**
204      * Stores the internally used reference date that has been converted into a
205      * date object.
206      */
207     private Date internalReferenceDate;
208 
209     /** Stores the reference date for comparisons. */
210     private String referenceDate;
211 
212     /** Stores the style of the date. */
213     private int dateStyle;
214 
215     /** Stores the lenient flag. */
216     private boolean lenient;
217 
218     /** Stores the before flag. */
219     private boolean before;
220 
221     /** Stores the after flag. */
222     private boolean after;
223 
224     /** Stores the equal flag. */
225     private boolean equal;
226 
227     /**
228      * Creates a new instance of <code>DateTransformerBase</code>.
229      */
230     protected DateTransformerBase()
231     {
232         setStyle(DateFormat.SHORT);
233     }
234 
235     /**
236      * Returns the style for the date to be parsed.
237      *
238      * @return the date style
239      */
240     public int getStyle()
241     {
242         return dateStyle;
243     }
244 
245     /**
246      * Sets the style for the date to be parsed. This is one of the style
247      * constants defined by the <code>DateFormat</code> class, e.g.
248      * <code>DateFormat.SHORT</code> or <code>DateFormat.MEDIUM</code>.
249      *
250      * @param dateStyle the style for the date
251      */
252     public void setStyle(int dateStyle)
253     {
254         this.dateStyle = dateStyle;
255     }
256 
257     /**
258      * Returns the lenient flag.
259      *
260      * @return the lenient flag
261      */
262     public boolean isLenient()
263     {
264         return lenient;
265     }
266 
267     /**
268      * Sets the lenient flag.
269      *
270      * @param lenient the lenient flag
271      */
272     public void setLenient(boolean lenient)
273     {
274         this.lenient = lenient;
275     }
276 
277     /**
278      * Returns the reference date.
279      *
280      * @return the reference date
281      */
282     public String getReferenceDate()
283     {
284         return referenceDate;
285     }
286 
287     /**
288      * Sets the reference date. This date will be used for before or after
289      * comparisons. The date is set as a string. This string must conform to the
290      * format supported by the date classes in the <code>java.sql</code>
291      * package.
292      *
293      * @param referenceDate the reference date
294      * @throws IllegalArgumentException if the date has not the expected format
295      */
296     public void setReferenceDate(String referenceDate)
297     {
298         this.referenceDate = referenceDate;
299         internalReferenceDate = (referenceDate == null) ? null
300                 : transformSqlDate(referenceDate);
301     }
302 
303     /**
304      * Returns the before flag.
305      *
306      * @return the before flag
307      */
308     public boolean isBefore()
309     {
310         return before;
311     }
312 
313     /**
314      * Sets the before flag. If set, the validate method checks whether the
315      * passed in date is before the reference date.
316      *
317      * @param before the before flag
318      */
319     public void setBefore(boolean before)
320     {
321         this.before = before;
322     }
323 
324     /**
325      * Returns the after flag.
326      *
327      * @return the after flag
328      */
329     public boolean isAfter()
330     {
331         return after;
332     }
333 
334     /**
335      * Sets the after flag. If set, the validate method checks whether the
336      * passed in date is after the reference date.
337      *
338      * @param after the after flag
339      */
340     public void setAfter(boolean after)
341     {
342         this.after = after;
343     }
344 
345     /**
346      * Returns the equal flag.
347      *
348      * @return the equal flag
349      */
350     public boolean isEqual()
351     {
352         return equal;
353     }
354 
355     /**
356      * Sets the equal flag. This flag is evaluated if one of the
357      * <code>before</code> or <code>after</code> flags is set. In this case
358      * the reference date is included into the comparison.
359      *
360      * @param equal the value of the equal flag
361      */
362     public void setEqual(boolean equal)
363     {
364         this.equal = equal;
365     }
366 
367     /**
368      * Transforms the specified object. This implementation is able to transform
369      * a date in string form to a <code>java.util.Date</code> object. If the
370      * date is invalid, an exception is thrown. The method does not perform any
371      * additional validity checks. This means that any valid date will be
372      * returned, even if it conflicts with a reference date.
373      *
374      * @param o the object to be transformed
375      * @param ctx the transformer context
376      * @return the transformed object
377      * @throws Exception if an error occurs
378      */
379     public Object transform(Object o, TransformerContext ctx) throws Exception
380     {
381         if (o instanceof Date)
382         {
383             return transformToString((Date) o, ctx);
384         }
385         else
386         {
387             return transformToDate(o, ctx);
388         }
389     }
390 
391     /**
392      * Validates the passed in object. This implementation transforms the object
393      * into a string and checks whether it represents a valid date. If the
394      * <code>before</code> or <code>after</code> flags have been set, the
395      * date will also be compared to a reference date. A <b>null</b> input will
396      * be considered valid.
397      *
398      * @param o the object to be validated
399      * @param ctx the transformer context
400      * @return an object with the results of the validation
401      */
402     public ValidationResult isValid(Object o, TransformerContext ctx)
403     {
404         String strDate = checkDefinedDate(o);
405         if (strDate == null)
406         {
407             return DefaultValidationResult.VALID;
408         }
409 
410         Configuration config = new MapConfiguration(ctx.properties());
411         DateFormat fmt = initializeFormat(ctx.getLocale(), config);
412         try
413         {
414             Date dt = transformDate(strDate, fmt);
415             return isDateValid(dt, fmt, ctx, config);
416         }
417         catch (ParseException pex)
418         {
419             return errorResult(ValidationMessageConstants.ERR_INVALID_DATE,
420                     ctx, strDate);
421         }
422     }
423 
424     /**
425      * Writes the given date part into the specified date object. This method
426      * will write the date component into a combined date/time object leaving
427      * the time component untouched. This is useful for instance if a GUI has
428      * different input fields for the date and the time, but in the data model
429      * only a single <code>Date</code> object is used. For example, if the
430      * <code>dateTime</code> parameter has the value
431      * <code>2008-01-29 22:17:59</code> and <code>datePart</code> is
432      * <code>2008-02-05</code>, the result will be
433      * <code>2008-02-05 22:17:59</code>.
434      *
435      * @param dateTime the combined date/time object
436      * @param datePart the date part
437      * @return the changed date/time object
438      * @throws IllegalArgumentException if one of the date parameters is <b>null</b>
439      */
440     public static Date updateDatePart(Date dateTime, Date datePart)
441     {
442         return updateComponent(dateTime, datePart, CALENDAR_DATE_FIELDS);
443     }
444 
445     /**
446      * Writes the given time part into the specified date object. This method
447      * will write the time component into a combined date/time object leaving
448      * the date component untouched. This is useful for instance if a GUI has
449      * different input fields for the date and the time, but in the data model
450      * only a single <code>Date</code> object is used. For example, if the
451      * <code>dateTime</code> parameter has the value
452      * <code>2008-01-29 22:17:59</code> and <code>timePart</code> is
453      * <code>10:22:05</code>, the result will be
454      * <code>2008-02-05 10:22:05</code>.
455      *
456      * @param dateTime the combined date/time object
457      * @param timePart the time part
458      * @return the changed date/time object
459      * @throws IllegalArgumentException if one of the date parameters is <b>null</b>
460      */
461     public static Date updateTimePart(Date dateTime, Date timePart)
462     {
463         return updateComponent(dateTime, timePart, CALENDAR_TIME_FIELDS);
464     }
465 
466     /**
467      * Performs a transformation to a date object. Tries to parse the string
468      * representation of the parsed in object.
469      *
470      * @param o the object to be transformed
471      * @param ctx the transformer context
472      * @return the transformed object
473      * @throws Exception if an error occurs
474      */
475     protected Object transformToDate(Object o, TransformerContext ctx)
476             throws Exception
477     {
478         String strDate = checkDefinedDate(o);
479         if (strDate == null)
480         {
481             return null;
482         }
483 
484         Configuration config = new MapConfiguration(ctx.properties());
485         return transformDate(strDate, initializeFormat(ctx.getLocale(), config));
486     }
487 
488     /**
489      * Performs a transformation from a date to string. This method is called if
490      * the object to be transformed is already a date. In this case this
491      * transformer class works in the opposite direction.
492      *
493      * @param dt the date to be transformed
494      * @param ctx the transformer context
495      * @return the transformed object
496      * @throws Exception if an error occurs
497      */
498     protected Object transformToString(Date dt, TransformerContext ctx)
499             throws Exception
500     {
501         Configuration config = new MapConfiguration(ctx.properties());
502         return initializeFormat(ctx.getLocale(), config).format(dt);
503     }
504 
505     /**
506      * Returns the reference date to be used. If a reference date is defined in
507      * the configuration, it is used. Otherwise the internally set reference
508      * date will be returned. If no reference date has been set, the
509      * <code>getDefaultReferenceDate()</code> method is called.
510      *
511      * @param config the configuration with the current properties
512      * @return the reference date
513      * @throws IllegalArgumentException if the reference date is in an incorrect
514      *         format
515      */
516     protected Date getReferenceDateProperty(Configuration config)
517     {
518         String strDate = config.getString(PROP_REFERENCE_DATE);
519         if (strDate != null)
520         {
521             return transformSqlDate(strDate);
522         }
523         else
524         {
525             return (internalReferenceDate != null) ? internalReferenceDate
526                     : getDefaultReferenceDate();
527         }
528     }
529 
530     /**
531      * Creates a new default reference date. This method is invoked when before
532      * or after comparisons have to be performed, but no reference date has been
533      * set. This implementation returns a <code>Date</code> object for the
534      * current date (only date, no time portion).
535      *
536      * @return the default reference date
537      */
538     protected Date getDefaultReferenceDate()
539     {
540         Calendar cal = Calendar.getInstance();
541         cal.set(Calendar.HOUR_OF_DAY, 0);
542         cal.clear(Calendar.MINUTE);
543         cal.clear(Calendar.SECOND);
544         cal.clear(Calendar.MILLISECOND);
545         return cal.getTime();
546     }
547 
548     /**
549      * Transforms a date in string form to a date object. This method expects
550      * that the date is in a <code>java.sql</code> compatible format.
551      *
552      * @param strDate the date as a string
553      * @return the converted date object
554      * @throws IllegalArgumentException if the date cannot be converted
555      */
556     protected Date transformSqlDate(String strDate)
557     {
558         if (log.isDebugEnabled())
559         {
560             log.debug("Trying to transform date string " + strDate);
561         }
562         try
563         {
564             return Timestamp.valueOf(strDate);
565         }
566         catch (IllegalArgumentException iex)
567         {
568             // no timestamp
569             log.debug("Not a time stamp.");
570         }
571 
572         try
573         {
574             return java.sql.Date.valueOf(strDate);
575         }
576         catch (IllegalArgumentException iex)
577         {
578             // no date
579             log.debug("Not a date.");
580         }
581 
582         return Time.valueOf(strDate);
583     }
584 
585     /**
586      * Parses the specified date string. This implementation uses the passed in
587      * <code>DateFormat</code> object for this purpose.
588      *
589      * @param date the date to be parsed
590      * @param fmt the <code>DateFormat</code> to be used
591      * @return the parsed date
592      * @throws ParseException if the date cannot be parsed
593      */
594     protected Date transformDate(String date, DateFormat fmt)
595             throws ParseException
596     {
597         ParsePosition ppos = new ParsePosition(0);
598         Date result = fmt.parse(date, ppos);
599         if (ppos.getErrorIndex() >= 0 || ppos.getIndex() < date.length())
600         {
601             throw new ParseException("Invalid date: " + date, ppos.getIndex());
602         }
603         return result;
604     }
605 
606     /**
607      * Returns an initialized format object. This implementation calls
608      * <code>createFormat()</code> for obtaining a new format object. Then the
609      * object is initialized based on the currently set properties.
610      *
611      * @param locale the locale
612      * @param config the properties associated with the current context
613      * @return the initialized format object
614      */
615     protected DateFormat initializeFormat(Locale locale, Configuration config)
616     {
617         DateFormat fmt = createFormat(locale, config.getInt(PROP_STYLE,
618                 getStyle()), config);
619         fmt.setLenient(config.getBoolean(PROP_LENIENT, isLenient()));
620         return fmt;
621     }
622 
623     /**
624      * Checks the specified date. This method is called by
625      * <code>isValid()</code> if the entered date is syntactically correct. It
626      * checks for semantic correctness, e.g. whether the date is in correct
627      * relation to the reference date.
628      *
629      * @param date the date to check
630      * @param fmt the date format object to be used
631      * @param ctx the transformer context
632      * @param config the configuration with the properties
633      * @return a <code>ValidationResult</code> object with the result of the
634      *         validation
635      */
636     protected ValidationResult isDateValid(Date date, DateFormat fmt,
637             TransformerContext ctx, Configuration config)
638     {
639         String errorKey = null;
640 
641         if (config.getBoolean(PROP_AFTER, isAfter())
642                 || config.getBoolean(PROP_BEFORE, isBefore()))
643         {
644             int comp = date.compareTo(getReferenceDateProperty(config));
645             boolean eqProp = config.getBoolean(PROP_EQUAL, isEqual());
646             boolean eq = eqProp && comp == 0;
647 
648             if (config.getBoolean(PROP_AFTER, isAfter()))
649             {
650                 if (comp <= 0 && !eq)
651                 {
652                     errorKey = eqProp ? ValidationMessageConstants.ERR_DATE_AFTER_EQUAL
653                             : ValidationMessageConstants.ERR_DATE_AFTER;
654                 }
655             }
656 
657             if (config.getBoolean(PROP_BEFORE, isBefore()))
658             {
659                 if (comp >= 0 && !eq)
660                 {
661                     errorKey = eqProp ? ValidationMessageConstants.ERR_DATE_BEFORE_EQUAL
662                             : ValidationMessageConstants.ERR_DATE_BEFORE;
663                 }
664             }
665         }
666 
667         if (errorKey != null)
668         {
669             // an error has occurred => create an error message
670             // with the properly formatted reference date as parameter
671             String refDate = fmt.format(getReferenceDateProperty(config));
672             return errorResult(errorKey, ctx, refDate);
673         }
674         else
675         {
676             return DefaultValidationResult.VALID;
677         }
678     }
679 
680     /**
681      * Creates a validation result if an error occurred.
682      *
683      * @param errorKey the key of the error message
684      * @param ctx the transformer context
685      * @param params optional parameters for the error message
686      * @return the validation result with this error
687      */
688     protected ValidationResult errorResult(String errorKey,
689             TransformerContext ctx, Object... params)
690     {
691         DefaultValidationResult vr = new DefaultValidationResult.Builder()
692                 .addValidationMessage(
693                         ctx.getValidationMessageHandler().getValidationMessage(
694                                 ctx, errorKey, params)).build();
695         return vr;
696     }
697 
698     /**
699      * Creates a <code>DateFormat</code> object for parsing dates of the
700      * supported format. Concrete sub classes have to return an appropriate
701      * instance of <code>DateFormat</code> (an implementation will probably
702      * call the correct <code>getXXXInstance()</code> factory method of
703      * <code>DateFormat</code>).
704      *
705      * @param locale the locale
706      * @param style the style to be used
707      * @param config a configuration object for accessing the current properties
708      * @return the <code>DateFormat</code> object to be used for parsing
709      */
710     protected abstract DateFormat createFormat(Locale locale, int style,
711             Configuration config);
712 
713     /**
714      * Checks whether the date is defined. It is defined if it is not <b>null</b>
715      * and no empty string.
716      *
717      * @param o the object to be checked
718      * @return the object transformed to a string (<b>null</b> if the
719      *         parameter is undefined)
720      */
721     private String checkDefinedDate(Object o)
722     {
723         if (o == null)
724         {
725             return null;
726         }
727 
728         String strDate = String.valueOf(o);
729         return (strDate.length() < 1) ? null : strDate;
730     }
731 
732     /**
733      * Converts a date object to a calendar. If the date is undefined, an
734      * exception will be thrown.
735      *
736      * @param dt the date
737      * @return the calendar
738      * @throws IllegalArgumentException if the date parameter is undefined
739      */
740     private static Calendar dateToCalendar(Date dt)
741     {
742         if (dt == null)
743         {
744             throw new IllegalArgumentException(
745                     "Date parameter must not be null!");
746         }
747 
748         Calendar cal = Calendar.getInstance();
749         cal.setTime(dt);
750         return cal;
751     }
752 
753     /**
754      * Copies a set of fields from one calendar to another one.
755      *
756      * @param cal1 the target calendar
757      * @param cal2 the source calendar
758      * @param fields the fields to copy
759      */
760     private static void copyCalendarFields(Calendar cal1, Calendar cal2,
761             int[] fields)
762     {
763         for (int field : fields)
764         {
765             cal1.set(field, cal2.get(field));
766         }
767     }
768 
769     /**
770      * Updates a component of a date/time object.
771      *
772      * @param dateTime the date/time object
773      * @param component the component
774      * @param fields the fields to be copied for the component
775      * @return the resulting date/time object
776      * @throws IllegalArgumentException if one of the dates is undefined
777      */
778     private static Date updateComponent(Date dateTime, Date component,
779             int[] fields)
780     {
781         Calendar cal1 = dateToCalendar(dateTime);
782         Calendar cal2 = dateToCalendar(component);
783         copyCalendarFields(cal1, cal2, fields);
784         return cal1.getTime();
785     }
786 }