001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.lang3.time;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.io.Serializable;
022import java.text.DateFormat;
023import java.text.DateFormatSymbols;
024import java.text.FieldPosition;
025import java.util.ArrayList;
026import java.util.Calendar;
027import java.util.Date;
028import java.util.List;
029import java.util.Locale;
030import java.util.TimeZone;
031import java.util.concurrent.ConcurrentHashMap;
032import java.util.concurrent.ConcurrentMap;
033
034import org.apache.commons.lang3.exception.ExceptionUtils;
035
036/**
037 * <p>FastDatePrinter is a fast and thread-safe version of
038 * {@link java.text.SimpleDateFormat}.</p>
039 *
040 * <p>To obtain a FastDatePrinter, use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} 
041 * or another variation of the factory methods of {@link FastDateFormat}.</p>
042 * 
043 * <p>Since FastDatePrinter is thread safe, you can use a static member instance:</p>
044 * <code>
045 *     private static final DatePrinter DATE_PRINTER = FastDateFormat.getInstance("yyyy-MM-dd");
046 * </code>
047 * 
048 * <p>This class can be used as a direct replacement to
049 * {@code SimpleDateFormat} in most formatting situations.
050 * This class is especially useful in multi-threaded server environments.
051 * {@code SimpleDateFormat} is not thread-safe in any JDK version,
052 * nor will it be as Sun have closed the bug/RFE.
053 * </p>
054 *
055 * <p>Only formatting is supported by this class, but all patterns are compatible with
056 * SimpleDateFormat (except time zones and some year patterns - see below).</p>
057 *
058 * <p>Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent
059 * time zones in RFC822 format (eg. {@code +0800} or {@code -1100}).
060 * This pattern letter can be used here (on all JDK versions).</p>
061 *
062 * <p>In addition, the pattern {@code 'ZZ'} has been made to represent
063 * ISO 8601 extended format time zones (eg. {@code +08:00} or {@code -11:00}).
064 * This introduces a minor incompatibility with Java 1.4, but at a gain of
065 * useful functionality.</p>
066 * 
067 * <p>Starting with JDK7, ISO 8601 support was added using the pattern {@code 'X'}.
068 * To maintain compatibility, {@code 'ZZ'} will continue to be supported, but using
069 * one of the {@code 'X'} formats is recommended.
070 *
071 * <p>Javadoc cites for the year pattern: <i>For formatting, if the number of
072 * pattern letters is 2, the year is truncated to 2 digits; otherwise it is
073 * interpreted as a number.</i> Starting with Java 1.7 a pattern of 'Y' or
074 * 'YYY' will be formatted as '2003', while it was '03' in former Java
075 * versions. FastDatePrinter implements the behavior of Java 7.</p>
076 *
077 * @since 3.2
078 * @see FastDateParser
079 */
080public class FastDatePrinter implements DatePrinter, Serializable {
081    // A lot of the speed in this class comes from caching, but some comes
082    // from the special int to StringBuffer conversion.
083    //
084    // The following produces a padded 2 digit number:
085    //   buffer.append((char)(value / 10 + '0'));
086    //   buffer.append((char)(value % 10 + '0'));
087    //
088    // Note that the fastest append to StringBuffer is a single char (used here).
089    // Note that Integer.toString() is not called, the conversion is simply
090    // taking the value and adding (mathematically) the ASCII value for '0'.
091    // So, don't change this code! It works and is very fast.
092
093    /**
094     * Required for serialization support.
095     *
096     * @see java.io.Serializable
097     */
098    private static final long serialVersionUID = 1L;
099
100    /**
101     * FULL locale dependent date or time style.
102     */
103    public static final int FULL = DateFormat.FULL;
104    /**
105     * LONG locale dependent date or time style.
106     */
107    public static final int LONG = DateFormat.LONG;
108    /**
109     * MEDIUM locale dependent date or time style.
110     */
111    public static final int MEDIUM = DateFormat.MEDIUM;
112    /**
113     * SHORT locale dependent date or time style.
114     */
115    public static final int SHORT = DateFormat.SHORT;
116
117    /**
118     * The pattern.
119     */
120    private final String mPattern;
121    /**
122     * The time zone.
123     */
124    private final TimeZone mTimeZone;
125    /**
126     * The locale.
127     */
128    private final Locale mLocale;
129    /**
130     * The parsed rules.
131     */
132    private transient Rule[] mRules;
133    /**
134     * The estimated maximum length.
135     */
136    private transient int mMaxLengthEstimate;
137
138    // Constructor
139    //-----------------------------------------------------------------------
140    /**
141     * <p>Constructs a new FastDatePrinter.</p>
142     * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)}  or another variation of the 
143     * factory methods of {@link FastDateFormat} to get a cached FastDatePrinter instance.
144     *
145     * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern
146     * @param timeZone  non-null time zone to use
147     * @param locale  non-null locale to use
148     * @throws NullPointerException if pattern, timeZone, or locale is null.
149     */
150    protected FastDatePrinter(final String pattern, final TimeZone timeZone, final Locale locale) {
151        mPattern = pattern;
152        mTimeZone = timeZone;
153        mLocale = locale;
154
155        init();
156    }
157
158    /**
159     * <p>Initializes the instance for first use.</p>
160     */
161    private void init() {
162        final List<Rule> rulesList = parsePattern();
163        mRules = rulesList.toArray(new Rule[rulesList.size()]);
164
165        int len = 0;
166        for (int i=mRules.length; --i >= 0; ) {
167            len += mRules[i].estimateLength();
168        }
169
170        mMaxLengthEstimate = len;
171    }
172
173    // Parse the pattern
174    //-----------------------------------------------------------------------
175    /**
176     * <p>Returns a list of Rules given a pattern.</p>
177     *
178     * @return a {@code List} of Rule objects
179     * @throws IllegalArgumentException if pattern is invalid
180     */
181    protected List<Rule> parsePattern() {
182        final DateFormatSymbols symbols = new DateFormatSymbols(mLocale);
183        final List<Rule> rules = new ArrayList<Rule>();
184
185        final String[] ERAs = symbols.getEras();
186        final String[] months = symbols.getMonths();
187        final String[] shortMonths = symbols.getShortMonths();
188        final String[] weekdays = symbols.getWeekdays();
189        final String[] shortWeekdays = symbols.getShortWeekdays();
190        final String[] AmPmStrings = symbols.getAmPmStrings();
191
192        final int length = mPattern.length();
193        final int[] indexRef = new int[1];
194
195        for (int i = 0; i < length; i++) {
196            indexRef[0] = i;
197            final String token = parseToken(mPattern, indexRef);
198            i = indexRef[0];
199
200            final int tokenLen = token.length();
201            if (tokenLen == 0) {
202                break;
203            }
204
205            Rule rule;
206            final char c = token.charAt(0);
207
208            switch (c) {
209            case 'G': // era designator (text)
210                rule = new TextField(Calendar.ERA, ERAs);
211                break;
212            case 'y': // year (number)
213            case 'Y': // week year
214                if (tokenLen == 2) {
215                    rule = TwoDigitYearField.INSTANCE;
216                } else {
217                    rule = selectNumberRule(Calendar.YEAR, tokenLen < 4 ? 4 : tokenLen);
218                }
219                if (c == 'Y') {
220                    rule = new WeekYear((NumberRule) rule);
221                }
222                break;
223            case 'M': // month in year (text and number)
224                if (tokenLen >= 4) {
225                    rule = new TextField(Calendar.MONTH, months);
226                } else if (tokenLen == 3) {
227                    rule = new TextField(Calendar.MONTH, shortMonths);
228                } else if (tokenLen == 2) {
229                    rule = TwoDigitMonthField.INSTANCE;
230                } else {
231                    rule = UnpaddedMonthField.INSTANCE;
232                }
233                break;
234            case 'd': // day in month (number)
235                rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
236                break;
237            case 'h': // hour in am/pm (number, 1..12)
238                rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen));
239                break;
240            case 'H': // hour in day (number, 0..23)
241                rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
242                break;
243            case 'm': // minute in hour (number)
244                rule = selectNumberRule(Calendar.MINUTE, tokenLen);
245                break;
246            case 's': // second in minute (number)
247                rule = selectNumberRule(Calendar.SECOND, tokenLen);
248                break;
249            case 'S': // millisecond (number)
250                rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
251                break;
252            case 'E': // day in week (text)
253                rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays);
254                break;
255            case 'u': // day in week (number)
256                rule = new DayInWeekField(selectNumberRule(Calendar.DAY_OF_WEEK, tokenLen));
257                break;
258            case 'D': // day in year (number)
259                rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
260                break;
261            case 'F': // day of week in month (number)
262                rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
263                break;
264            case 'w': // week in year (number)
265                rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
266                break;
267            case 'W': // week in month (number)
268                rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
269                break;
270            case 'a': // am/pm marker (text)
271                rule = new TextField(Calendar.AM_PM, AmPmStrings);
272                break;
273            case 'k': // hour in day (1..24)
274                rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
275                break;
276            case 'K': // hour in am/pm (0..11)
277                rule = selectNumberRule(Calendar.HOUR, tokenLen);
278                break;
279            case 'X': // ISO 8601 
280                rule = Iso8601_Rule.getRule(tokenLen);
281                break;    
282            case 'z': // time zone (text)
283                if (tokenLen >= 4) {
284                    rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.LONG);
285                } else {
286                    rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.SHORT);
287                }
288                break;
289            case 'Z': // time zone (value)
290                if (tokenLen == 1) {
291                    rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
292                } else if (tokenLen == 2) {
293                    rule = Iso8601_Rule.ISO8601_HOURS_COLON_MINUTES;
294                } else {
295                    rule = TimeZoneNumberRule.INSTANCE_COLON;
296                }
297                break;
298            case '\'': // literal text
299                final String sub = token.substring(1);
300                if (sub.length() == 1) {
301                    rule = new CharacterLiteral(sub.charAt(0));
302                } else {
303                    rule = new StringLiteral(sub);
304                }
305                break;
306            default:
307                throw new IllegalArgumentException("Illegal pattern component: " + token);
308            }
309
310            rules.add(rule);
311        }
312
313        return rules;
314    }
315
316    /**
317     * <p>Performs the parsing of tokens.</p>
318     *
319     * @param pattern  the pattern
320     * @param indexRef  index references
321     * @return parsed token
322     */
323    protected String parseToken(final String pattern, final int[] indexRef) {
324        final StringBuilder buf = new StringBuilder();
325
326        int i = indexRef[0];
327        final int length = pattern.length();
328
329        char c = pattern.charAt(i);
330        if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
331            // Scan a run of the same character, which indicates a time
332            // pattern.
333            buf.append(c);
334
335            while (i + 1 < length) {
336                final char peek = pattern.charAt(i + 1);
337                if (peek == c) {
338                    buf.append(c);
339                    i++;
340                } else {
341                    break;
342                }
343            }
344        } else {
345            // This will identify token as text.
346            buf.append('\'');
347
348            boolean inLiteral = false;
349
350            for (; i < length; i++) {
351                c = pattern.charAt(i);
352
353                if (c == '\'') {
354                    if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
355                        // '' is treated as escaped '
356                        i++;
357                        buf.append(c);
358                    } else {
359                        inLiteral = !inLiteral;
360                    }
361                } else if (!inLiteral &&
362                         (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
363                    i--;
364                    break;
365                } else {
366                    buf.append(c);
367                }
368            }
369        }
370
371        indexRef[0] = i;
372        return buf.toString();
373    }
374
375    /**
376     * <p>Gets an appropriate rule for the padding required.</p>
377     *
378     * @param field  the field to get a rule for
379     * @param padding  the padding required
380     * @return a new rule with the correct padding
381     */
382    protected NumberRule selectNumberRule(final int field, final int padding) {
383        switch (padding) {
384        case 1:
385            return new UnpaddedNumberField(field);
386        case 2:
387            return new TwoDigitNumberField(field);
388        default:
389            return new PaddedNumberField(field, padding);
390        }
391    }
392
393    // Format methods
394    //-----------------------------------------------------------------------
395    /**
396     * <p>Formats a {@code Date}, {@code Calendar} or
397     * {@code Long} (milliseconds) object.</p>
398     * @deprecated Use {{@link #format(Date)}, {{@link #format(Calendar)}, {{@link #format(long)}, or {{@link #format(Object)}
399     * @param obj  the object to format
400     * @param toAppendTo  the buffer to append to
401     * @param pos  the position - ignored
402     * @return the buffer passed in
403     */
404    @Deprecated
405    @Override
406    public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
407        if (obj instanceof Date) {
408            return format((Date) obj, toAppendTo);
409        } else if (obj instanceof Calendar) {
410            return format((Calendar) obj, toAppendTo);
411        } else if (obj instanceof Long) {
412            return format(((Long) obj).longValue(), toAppendTo);
413        } else {
414            throw new IllegalArgumentException("Unknown class: " +
415                (obj == null ? "<null>" : obj.getClass().getName()));
416        }
417    }
418
419    /**
420     * <p>Formats a {@code Date}, {@code Calendar} or
421     * {@code Long} (milliseconds) object.</p>
422     * @since 3.5
423     * @param obj  the object to format
424     * @return The formatted value.
425     */
426    String format(Object obj) {
427        if (obj instanceof Date) {
428            return format((Date) obj);
429        } else if (obj instanceof Calendar) {
430            return format((Calendar) obj);
431        } else if (obj instanceof Long) {
432            return format(((Long) obj).longValue());
433        } else {
434            throw new IllegalArgumentException("Unknown class: " +
435                (obj == null ? "<null>" : obj.getClass().getName()));
436        }
437    }
438
439    /* (non-Javadoc)
440     * @see org.apache.commons.lang3.time.DatePrinter#format(long)
441     */
442    @Override
443    public String format(final long millis) {
444        final Calendar c = newCalendar();
445        c.setTimeInMillis(millis);
446        return applyRulesToString(c);
447    }
448
449    /**
450     * Creates a String representation of the given Calendar by applying the rules of this printer to it.
451     * @param c the Calender to apply the rules to.
452     * @return a String representation of the given Calendar.
453     */
454    private String applyRulesToString(final Calendar c) {
455        return applyRules(c, new StringBuilder(mMaxLengthEstimate)).toString();
456    }
457
458    /**
459     * Creation method for new calender instances.
460     * @return a new Calendar instance.
461     */
462    private Calendar newCalendar() {
463        return Calendar.getInstance(mTimeZone, mLocale);
464    }
465
466    /* (non-Javadoc)
467     * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date)
468     */
469    @Override
470    public String format(final Date date) {
471        final Calendar c = newCalendar();
472        c.setTime(date);
473        return applyRulesToString(c);
474    }
475
476    /* (non-Javadoc)
477     * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar)
478     */
479    @Override
480    public String format(final Calendar calendar) {
481        return format(calendar, new StringBuilder(mMaxLengthEstimate)).toString();
482    }
483
484    /* (non-Javadoc)
485     * @see org.apache.commons.lang3.time.DatePrinter#format(long, java.lang.StringBuffer)
486     */
487    @Override
488    public StringBuffer format(final long millis, final StringBuffer buf) {
489        return format(new Date(millis), buf);
490    }
491
492    /* (non-Javadoc)
493     * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, java.lang.StringBuffer)
494     */
495    @Override
496    public StringBuffer format(final Date date, final StringBuffer buf) {
497        final Calendar c = newCalendar();
498        c.setTime(date);
499        return applyRules(c, buf);
500    }
501
502    /* (non-Javadoc)
503     * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar, java.lang.StringBuffer)
504     */
505    @Override
506    public StringBuffer format(final Calendar calendar, final StringBuffer buf) {
507        // do not pass in calendar directly, this will cause TimeZone of FastDatePrinter to be ignored
508        return format(calendar.getTime(), buf);
509    }
510
511    /* (non-Javadoc)
512     * @see org.apache.commons.lang3.time.DatePrinter#format(long, java.lang.Appendable)
513     */
514    @Override
515    public <B extends Appendable> B format(final long millis, final B buf) {
516        return format(new Date(millis), buf);
517    }
518
519    /* (non-Javadoc)
520     * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, java.lang.Appendable)
521     */
522    @Override
523    public <B extends Appendable> B format(final Date date, final B buf) {
524        final Calendar c = newCalendar();
525        c.setTime(date);
526        return applyRules(c, buf);
527    }
528
529    /* (non-Javadoc)
530     * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar, java.lang.Appendable)
531     */
532    @Override
533    public <B extends Appendable> B format(Calendar calendar, final B buf) {
534        // do not pass in calendar directly, this will cause TimeZone of FastDatePrinter to be ignored
535        if(!calendar.getTimeZone().equals(mTimeZone)) {
536            calendar = (Calendar)calendar.clone();
537            calendar.setTimeZone(mTimeZone);
538        }
539        return applyRules(calendar, buf);
540    }
541
542    /**
543     * <p>Performs the formatting by applying the rules to the
544     * specified calendar.</p>
545     *
546     * @param calendar  the calendar to format
547     * @param buf  the buffer to format into
548     * @param <B> the Appendable class type, usually StringBuilder or StringBuffer.
549     * @return the specified string buffer
550     */
551    protected <B extends Appendable> B applyRules(final Calendar calendar, final B buf) {
552        try {
553            for (final Rule rule : mRules) {
554                rule.appendTo(buf, calendar);
555            }
556        } catch (IOException ioe) {
557            ExceptionUtils.rethrow(ioe);
558        }
559        return buf;
560    }
561
562    // Accessors
563    //-----------------------------------------------------------------------
564    /* (non-Javadoc)
565     * @see org.apache.commons.lang3.time.DatePrinter#getPattern()
566     */
567    @Override
568    public String getPattern() {
569        return mPattern;
570    }
571
572    /* (non-Javadoc)
573     * @see org.apache.commons.lang3.time.DatePrinter#getTimeZone()
574     */
575    @Override
576    public TimeZone getTimeZone() {
577        return mTimeZone;
578    }
579
580    /* (non-Javadoc)
581     * @see org.apache.commons.lang3.time.DatePrinter#getLocale()
582     */
583    @Override
584    public Locale getLocale() {
585        return mLocale;
586    }
587
588    /**
589     * <p>Gets an estimate for the maximum string length that the
590     * formatter will produce.</p>
591     *
592     * <p>The actual formatted length will almost always be less than or
593     * equal to this amount.</p>
594     *
595     * @return the maximum formatted length
596     */
597    public int getMaxLengthEstimate() {
598        return mMaxLengthEstimate;
599    }
600
601    // Basics
602    //-----------------------------------------------------------------------
603    /**
604     * <p>Compares two objects for equality.</p>
605     *
606     * @param obj  the object to compare to
607     * @return {@code true} if equal
608     */
609    @Override
610    public boolean equals(final Object obj) {
611        if (obj instanceof FastDatePrinter == false) {
612            return false;
613        }
614        final FastDatePrinter other = (FastDatePrinter) obj;
615        return mPattern.equals(other.mPattern)
616            && mTimeZone.equals(other.mTimeZone) 
617            && mLocale.equals(other.mLocale);
618    }
619
620    /**
621     * <p>Returns a hashcode compatible with equals.</p>
622     *
623     * @return a hashcode compatible with equals
624     */
625    @Override
626    public int hashCode() {
627        return mPattern.hashCode() + 13 * (mTimeZone.hashCode() + 13 * mLocale.hashCode());
628    }
629
630    /**
631     * <p>Gets a debugging string version of this formatter.</p>
632     *
633     * @return a debugging string
634     */
635    @Override
636    public String toString() {
637        return "FastDatePrinter[" + mPattern + "," + mLocale + "," + mTimeZone.getID() + "]";
638    }
639
640    // Serializing
641    //-----------------------------------------------------------------------
642    /**
643     * Create the object after serialization. This implementation reinitializes the
644     * transient properties.
645     *
646     * @param in ObjectInputStream from which the object is being deserialized.
647     * @throws IOException if there is an IO issue.
648     * @throws ClassNotFoundException if a class cannot be found.
649     */
650    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
651        in.defaultReadObject();
652        init();
653    }
654
655    /**
656     * Appends two digits to the given buffer.
657     *
658     * @param buffer the buffer to append to.
659     * @param value the value to append digits from.
660     */
661    private static void appendDigits(final Appendable buffer, final int value) throws IOException {
662        buffer.append((char)(value / 10 + '0'));
663        buffer.append((char)(value % 10 + '0'));
664    }
665
666    private static final int MAX_DIGITS = 10; // log10(Integer.MAX_VALUE) ~= 9.3
667
668    /**
669     * Appends all digits to the given buffer.
670     *
671     * @param buffer the buffer to append to.
672     * @param value the value to append digits from.
673     */
674    private static void appendFullDigits(final Appendable buffer, int value, int minFieldWidth) throws IOException {
675        // build up decimal representation in reverse
676        char[] work = new char[MAX_DIGITS];
677        int digit = 0;
678        while(value!=0) {
679            work[digit++] = (char)(value % 10 + '0');
680            value = value / 10;
681        }
682
683        // pad with zeros
684        while(digit<minFieldWidth) {
685            buffer.append('0');
686            --minFieldWidth;
687        }
688
689        // reverse
690        while(--digit>=0) {
691            buffer.append(work[digit]);
692        }
693    }
694
695    // Rules
696    //-----------------------------------------------------------------------
697    /**
698     * <p>Inner class defining a rule.</p>
699     */
700    private interface Rule {
701        /**
702         * Returns the estimated length of the result.
703         *
704         * @return the estimated length
705         */
706        int estimateLength();
707
708        /**
709         * Appends the value of the specified calendar to the output buffer based on the rule implementation.
710         *
711         * @param buf the output buffer
712         * @param calendar calendar to be appended
713         */
714        void appendTo(Appendable buf, Calendar calendar) throws IOException;
715    }
716
717    /**
718     * <p>Inner class defining a numeric rule.</p>
719     */
720    private interface NumberRule extends Rule {
721        /**
722         * Appends the specified value to the output buffer based on the rule implementation.
723         *
724         * @param buffer the output buffer
725         * @param value the value to be appended
726         */
727        void appendTo(Appendable buffer, int value) throws IOException;
728    }
729
730    /**
731     * <p>Inner class to output a constant single character.</p>
732     */
733    private static class CharacterLiteral implements Rule {
734        private final char mValue;
735
736        /**
737         * Constructs a new instance of {@code CharacterLiteral}
738         * to hold the specified value.
739         *
740         * @param value the character literal
741         */
742        CharacterLiteral(final char value) {
743            mValue = value;
744        }
745
746        /**
747         * {@inheritDoc}
748         */
749        @Override
750        public int estimateLength() {
751            return 1;
752        }
753
754        /**
755         * {@inheritDoc}
756         */
757        @Override
758        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
759            buffer.append(mValue);
760        }
761    }
762
763    /**
764     * <p>Inner class to output a constant string.</p>
765     */
766    private static class StringLiteral implements Rule {
767        private final String mValue;
768
769        /**
770         * Constructs a new instance of {@code StringLiteral}
771         * to hold the specified value.
772         *
773         * @param value the string literal
774         */
775        StringLiteral(final String value) {
776            mValue = value;
777        }
778
779        /**
780         * {@inheritDoc}
781         */
782        @Override
783        public int estimateLength() {
784            return mValue.length();
785        }
786
787        /**
788         * {@inheritDoc}
789         */
790        @Override
791        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
792            buffer.append(mValue);
793        }
794    }
795
796    /**
797     * <p>Inner class to output one of a set of values.</p>
798     */
799    private static class TextField implements Rule {
800        private final int mField;
801        private final String[] mValues;
802
803        /**
804         * Constructs an instance of {@code TextField}
805         * with the specified field and values.
806         *
807         * @param field the field
808         * @param values the field values
809         */
810        TextField(final int field, final String[] values) {
811            mField = field;
812            mValues = values;
813        }
814
815        /**
816         * {@inheritDoc}
817         */
818        @Override
819        public int estimateLength() {
820            int max = 0;
821            for (int i=mValues.length; --i >= 0; ) {
822                final int len = mValues[i].length();
823                if (len > max) {
824                    max = len;
825                }
826            }
827            return max;
828        }
829
830        /**
831         * {@inheritDoc}
832         */
833        @Override
834        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
835            buffer.append(mValues[calendar.get(mField)]);
836        }
837    }
838
839    /**
840     * <p>Inner class to output an unpadded number.</p>
841     */
842    private static class UnpaddedNumberField implements NumberRule {
843        private final int mField;
844
845        /**
846         * Constructs an instance of {@code UnpadedNumberField} with the specified field.
847         *
848         * @param field the field
849         */
850        UnpaddedNumberField(final int field) {
851            mField = field;
852        }
853
854        /**
855         * {@inheritDoc}
856         */
857        @Override
858        public int estimateLength() {
859            return 4;
860        }
861
862        /**
863         * {@inheritDoc}
864         */
865        @Override
866        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
867            appendTo(buffer, calendar.get(mField));
868        }
869
870        /**
871         * {@inheritDoc}
872         */
873        @Override
874        public final void appendTo(final Appendable buffer, final int value) throws IOException {
875            if (value < 10) {
876                buffer.append((char)(value + '0'));
877            } else if (value < 100) {
878                appendDigits(buffer, value);
879            } else {
880               appendFullDigits(buffer, value, 1);
881            }
882        }
883    }
884
885    /**
886     * <p>Inner class to output an unpadded month.</p>
887     */
888    private static class UnpaddedMonthField implements NumberRule {
889        static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();
890
891        /**
892         * Constructs an instance of {@code UnpaddedMonthField}.
893         *
894         */
895        UnpaddedMonthField() {
896            super();
897        }
898
899        /**
900         * {@inheritDoc}
901         */
902        @Override
903        public int estimateLength() {
904            return 2;
905        }
906
907        /**
908         * {@inheritDoc}
909         */
910        @Override
911        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
912            appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
913        }
914
915        /**
916         * {@inheritDoc}
917         */
918        @Override
919        public final void appendTo(final Appendable buffer, final int value) throws IOException {
920            if (value < 10) {
921                buffer.append((char)(value + '0'));
922            } else {
923                appendDigits(buffer, value);
924            }
925        }
926    }
927
928    /**
929     * <p>Inner class to output a padded number.</p>
930     */
931    private static class PaddedNumberField implements NumberRule {
932        private final int mField;
933        private final int mSize;
934
935        /**
936         * Constructs an instance of {@code PaddedNumberField}.
937         *
938         * @param field the field
939         * @param size size of the output field
940         */
941        PaddedNumberField(final int field, final int size) {
942            if (size < 3) {
943                // Should use UnpaddedNumberField or TwoDigitNumberField.
944                throw new IllegalArgumentException();
945            }
946            mField = field;
947            mSize = size;
948        }
949
950        /**
951         * {@inheritDoc}
952         */
953        @Override
954        public int estimateLength() {
955            return mSize;
956        }
957
958        /**
959         * {@inheritDoc}
960         */
961        @Override
962        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
963            appendTo(buffer, calendar.get(mField));
964        }
965
966        /**
967         * {@inheritDoc}
968         */
969        @Override
970        public final void appendTo(final Appendable buffer, int value) throws IOException {
971            appendFullDigits(buffer, value, mSize);
972        }
973    }
974
975    /**
976     * <p>Inner class to output a two digit number.</p>
977     */
978    private static class TwoDigitNumberField implements NumberRule {
979        private final int mField;
980
981        /**
982         * Constructs an instance of {@code TwoDigitNumberField} with the specified field.
983         *
984         * @param field the field
985         */
986        TwoDigitNumberField(final int field) {
987            mField = field;
988        }
989
990        /**
991         * {@inheritDoc}
992         */
993        @Override
994        public int estimateLength() {
995            return 2;
996        }
997
998        /**
999         * {@inheritDoc}
1000         */
1001        @Override
1002        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
1003            appendTo(buffer, calendar.get(mField));
1004        }
1005
1006        /**
1007         * {@inheritDoc}
1008         */
1009        @Override
1010        public final void appendTo(final Appendable buffer, final int value) throws IOException {
1011            if (value < 100) {
1012                appendDigits(buffer, value);
1013            } else {
1014                appendFullDigits(buffer, value, 2);
1015            }
1016        }
1017    }
1018
1019    /**
1020     * <p>Inner class to output a two digit year.</p>
1021     */
1022    private static class TwoDigitYearField implements NumberRule {
1023        static final TwoDigitYearField INSTANCE = new TwoDigitYearField();
1024
1025        /**
1026         * Constructs an instance of {@code TwoDigitYearField}.
1027         */
1028        TwoDigitYearField() {
1029            super();
1030        }
1031
1032        /**
1033         * {@inheritDoc}
1034         */
1035        @Override
1036        public int estimateLength() {
1037            return 2;
1038        }
1039
1040        /**
1041         * {@inheritDoc}
1042         */
1043        @Override
1044        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
1045            appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
1046        }
1047
1048        /**
1049         * {@inheritDoc}
1050         */
1051        @Override
1052        public final void appendTo(final Appendable buffer, final int value) throws IOException {
1053            appendDigits(buffer, value);
1054        }
1055    }
1056
1057    /**
1058     * <p>Inner class to output a two digit month.</p>
1059     */
1060    private static class TwoDigitMonthField implements NumberRule {
1061        static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();
1062
1063        /**
1064         * Constructs an instance of {@code TwoDigitMonthField}.
1065         */
1066        TwoDigitMonthField() {
1067            super();
1068        }
1069
1070        /**
1071         * {@inheritDoc}
1072         */
1073        @Override
1074        public int estimateLength() {
1075            return 2;
1076        }
1077
1078        /**
1079         * {@inheritDoc}
1080         */
1081        @Override
1082        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
1083            appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
1084        }
1085
1086        /**
1087         * {@inheritDoc}
1088         */
1089        @Override
1090        public final void appendTo(final Appendable buffer, final int value) throws IOException {
1091            appendDigits(buffer, value);
1092        }
1093    }
1094
1095    /**
1096     * <p>Inner class to output the twelve hour field.</p>
1097     */
1098    private static class TwelveHourField implements NumberRule {
1099        private final NumberRule mRule;
1100
1101        /**
1102         * Constructs an instance of {@code TwelveHourField} with the specified
1103         * {@code NumberRule}.
1104         *
1105         * @param rule the rule
1106         */
1107        TwelveHourField(final NumberRule rule) {
1108            mRule = rule;
1109        }
1110
1111        /**
1112         * {@inheritDoc}
1113         */
1114        @Override
1115        public int estimateLength() {
1116            return mRule.estimateLength();
1117        }
1118
1119        /**
1120         * {@inheritDoc}
1121         */
1122        @Override
1123        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
1124            int value = calendar.get(Calendar.HOUR);
1125            if (value == 0) {
1126                value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
1127            }
1128            mRule.appendTo(buffer, value);
1129        }
1130
1131        /**
1132         * {@inheritDoc}
1133         */
1134        @Override
1135        public void appendTo(final Appendable buffer, final int value) throws IOException {
1136            mRule.appendTo(buffer, value);
1137        }
1138    }
1139
1140    /**
1141     * <p>Inner class to output the twenty four hour field.</p>
1142     */
1143    private static class TwentyFourHourField implements NumberRule {
1144        private final NumberRule mRule;
1145
1146        /**
1147         * Constructs an instance of {@code TwentyFourHourField} with the specified
1148         * {@code NumberRule}.
1149         *
1150         * @param rule the rule
1151         */
1152        TwentyFourHourField(final NumberRule rule) {
1153            mRule = rule;
1154        }
1155
1156        /**
1157         * {@inheritDoc}
1158         */
1159        @Override
1160        public int estimateLength() {
1161            return mRule.estimateLength();
1162        }
1163
1164        /**
1165         * {@inheritDoc}
1166         */
1167        @Override
1168        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
1169            int value = calendar.get(Calendar.HOUR_OF_DAY);
1170            if (value == 0) {
1171                value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
1172            }
1173            mRule.appendTo(buffer, value);
1174        }
1175
1176        /**
1177         * {@inheritDoc}
1178         */
1179        @Override
1180        public void appendTo(final Appendable buffer, final int value) throws IOException {
1181            mRule.appendTo(buffer, value);
1182        }
1183    }
1184
1185    /**
1186     * <p>Inner class to output the numeric day in week.</p>
1187     */
1188    private static class DayInWeekField implements NumberRule {
1189        private final NumberRule mRule;
1190
1191        DayInWeekField(final NumberRule rule) {
1192            mRule = rule;
1193        }
1194
1195        @Override
1196        public int estimateLength() {
1197            return mRule.estimateLength();
1198        }
1199
1200        @Override
1201        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
1202            int value = calendar.get(Calendar.DAY_OF_WEEK);
1203            mRule.appendTo(buffer, value != Calendar.SUNDAY ? value - 1 : 7);
1204        }
1205
1206        @Override
1207        public void appendTo(final Appendable buffer, final int value) throws IOException {
1208            mRule.appendTo(buffer, value);
1209        }
1210    }
1211
1212    /**
1213     * <p>Inner class to output the numeric day in week.</p>
1214     */
1215    private static class WeekYear implements NumberRule {
1216        private final NumberRule mRule;
1217
1218        WeekYear(final NumberRule rule) {
1219            mRule = rule;
1220        }
1221
1222        @Override
1223        public int estimateLength() {
1224            return mRule.estimateLength();
1225        }
1226
1227        @Override
1228        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
1229            mRule.appendTo(buffer, CalendarReflection.getWeekYear(calendar));
1230        }
1231
1232        @Override
1233        public void appendTo(final Appendable buffer, final int value) throws IOException {
1234            mRule.appendTo(buffer, value);
1235        }
1236    }
1237
1238    //-----------------------------------------------------------------------
1239
1240    private static final ConcurrentMap<TimeZoneDisplayKey, String> cTimeZoneDisplayCache =
1241        new ConcurrentHashMap<TimeZoneDisplayKey, String>(7);
1242    /**
1243     * <p>Gets the time zone display name, using a cache for performance.</p>
1244     *
1245     * @param tz  the zone to query
1246     * @param daylight  true if daylight savings
1247     * @param style  the style to use {@code TimeZone.LONG} or {@code TimeZone.SHORT}
1248     * @param locale  the locale to use
1249     * @return the textual name of the time zone
1250     */
1251    static String getTimeZoneDisplay(final TimeZone tz, final boolean daylight, final int style, final Locale locale) {
1252        final TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale);
1253        String value = cTimeZoneDisplayCache.get(key);
1254        if (value == null) {
1255            // This is a very slow call, so cache the results.
1256            value = tz.getDisplayName(daylight, style, locale);
1257            final String prior = cTimeZoneDisplayCache.putIfAbsent(key, value);
1258            if (prior != null) {
1259                value= prior;
1260            }
1261        }
1262        return value;
1263    }
1264
1265    /**
1266     * <p>Inner class to output a time zone name.</p>
1267     */
1268    private static class TimeZoneNameRule implements Rule {
1269        private final Locale mLocale;
1270        private final int mStyle;
1271        private final String mStandard;
1272        private final String mDaylight;
1273
1274        /**
1275         * Constructs an instance of {@code TimeZoneNameRule} with the specified properties.
1276         *
1277         * @param timeZone the time zone
1278         * @param locale the locale
1279         * @param style the style
1280         */
1281        TimeZoneNameRule(final TimeZone timeZone, final Locale locale, final int style) {
1282            mLocale = locale;
1283            mStyle = style;
1284            
1285            mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
1286            mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
1287        }
1288
1289        /**
1290         * {@inheritDoc}
1291         */
1292        @Override
1293        public int estimateLength() {
1294            // We have no access to the Calendar object that will be passed to
1295            // appendTo so base estimate on the TimeZone passed to the
1296            // constructor
1297            return Math.max(mStandard.length(), mDaylight.length());
1298        }
1299
1300        /**
1301         * {@inheritDoc}
1302         */
1303        @Override
1304        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
1305            final TimeZone zone = calendar.getTimeZone();
1306            if (calendar.get(Calendar.DST_OFFSET) != 0) {
1307                buffer.append(getTimeZoneDisplay(zone, true, mStyle, mLocale));
1308            } else {
1309                buffer.append(getTimeZoneDisplay(zone, false, mStyle, mLocale));
1310            }
1311        }
1312    }
1313
1314    /**
1315     * <p>Inner class to output a time zone as a number {@code +/-HHMM}
1316     * or {@code +/-HH:MM}.</p>
1317     */
1318    private static class TimeZoneNumberRule implements Rule {
1319        static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true);
1320        static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false);
1321        
1322        final boolean mColon;
1323
1324        /**
1325         * Constructs an instance of {@code TimeZoneNumberRule} with the specified properties.
1326         *
1327         * @param colon add colon between HH and MM in the output if {@code true}
1328         */
1329        TimeZoneNumberRule(final boolean colon) {
1330            mColon = colon;
1331        }
1332
1333        /**
1334         * {@inheritDoc}
1335         */
1336        @Override
1337        public int estimateLength() {
1338            return 5;
1339        }
1340
1341        /**
1342         * {@inheritDoc}
1343         */
1344        @Override
1345        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
1346            
1347            int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
1348
1349            if (offset < 0) {
1350                buffer.append('-');
1351                offset = -offset;
1352            } else {
1353                buffer.append('+');
1354            }
1355
1356            final int hours = offset / (60 * 60 * 1000);
1357            appendDigits(buffer, hours);
1358
1359            if (mColon) {
1360                buffer.append(':');
1361            }
1362
1363            final int minutes = offset / (60 * 1000) - 60 * hours;
1364            appendDigits(buffer, minutes);
1365        }
1366    }
1367
1368    /**
1369     * <p>Inner class to output a time zone as a number {@code +/-HHMM}
1370     * or {@code +/-HH:MM}.</p>
1371     */
1372    private static class Iso8601_Rule implements Rule {
1373        
1374        // Sign TwoDigitHours or Z
1375        static final Iso8601_Rule ISO8601_HOURS = new Iso8601_Rule(3);       
1376        // Sign TwoDigitHours Minutes or Z
1377        static final Iso8601_Rule ISO8601_HOURS_MINUTES = new Iso8601_Rule(5);
1378        // Sign TwoDigitHours : Minutes or Z
1379        static final Iso8601_Rule ISO8601_HOURS_COLON_MINUTES = new Iso8601_Rule(6);
1380
1381        /**
1382         * Factory method for Iso8601_Rules.
1383         *
1384         * @param tokenLen a token indicating the length of the TimeZone String to be formatted.
1385         * @return a Iso8601_Rule that can format TimeZone String of length {@code tokenLen}. If no such
1386         *          rule exists, an IllegalArgumentException will be thrown.
1387         */
1388        static Iso8601_Rule getRule(int tokenLen) {
1389            switch(tokenLen) {
1390            case 1:
1391                return Iso8601_Rule.ISO8601_HOURS;
1392            case 2:
1393                return Iso8601_Rule.ISO8601_HOURS_MINUTES;
1394            case 3:
1395                return Iso8601_Rule.ISO8601_HOURS_COLON_MINUTES;
1396            default:
1397                throw new IllegalArgumentException("invalid number of X");                    
1398            }
1399        }        
1400        
1401        final int length;
1402
1403        /**
1404         * Constructs an instance of {@code Iso8601_Rule} with the specified properties.
1405         *
1406         * @param length The number of characters in output (unless Z is output)
1407         */
1408        Iso8601_Rule(final int length) {
1409            this.length = length;
1410        }
1411
1412        /**
1413         * {@inheritDoc}
1414         */
1415        @Override
1416        public int estimateLength() {
1417            return length;
1418        }
1419
1420        /**
1421         * {@inheritDoc}
1422         */
1423        @Override
1424        public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
1425            int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
1426            if (offset == 0) {
1427                buffer.append("Z");
1428                return;
1429            }
1430            
1431            if (offset < 0) {
1432                buffer.append('-');
1433                offset = -offset;
1434            } else {
1435                buffer.append('+');
1436            }
1437
1438            final int hours = offset / (60 * 60 * 1000);
1439            appendDigits(buffer, hours);
1440
1441            if (length<5) {
1442                return;
1443            }
1444            
1445            if (length==6) {
1446                buffer.append(':');
1447            }
1448
1449            final int minutes = offset / (60 * 1000) - 60 * hours;
1450            appendDigits(buffer, minutes);
1451        }
1452    }
1453
1454    // ----------------------------------------------------------------------
1455    /**
1456     * <p>Inner class that acts as a compound key for time zone names.</p>
1457     */
1458    private static class TimeZoneDisplayKey {
1459        private final TimeZone mTimeZone;
1460        private final int mStyle;
1461        private final Locale mLocale;
1462
1463        /**
1464         * Constructs an instance of {@code TimeZoneDisplayKey} with the specified properties.
1465         *
1466         * @param timeZone the time zone
1467         * @param daylight adjust the style for daylight saving time if {@code true}
1468         * @param style the timezone style
1469         * @param locale the timezone locale
1470         */
1471        TimeZoneDisplayKey(final TimeZone timeZone,
1472                           final boolean daylight, final int style, final Locale locale) {
1473            mTimeZone = timeZone;
1474            if (daylight) {
1475                mStyle = style | 0x80000000;
1476            } else {
1477                mStyle = style;
1478            }
1479            mLocale = locale;
1480        }
1481
1482        /**
1483         * {@inheritDoc}
1484         */
1485        @Override
1486        public int hashCode() {
1487            return (mStyle * 31 + mLocale.hashCode() ) * 31 + mTimeZone.hashCode();
1488        }
1489
1490        /**
1491         * {@inheritDoc}
1492         */
1493        @Override
1494        public boolean equals(final Object obj) {
1495            if (this == obj) {
1496                return true;
1497            }
1498            if (obj instanceof TimeZoneDisplayKey) {
1499                final TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj;
1500                return
1501                    mTimeZone.equals(other.mTimeZone) &&
1502                    mStyle == other.mStyle &&
1503                    mLocale.equals(other.mLocale);
1504            }
1505            return false;
1506        }
1507    }
1508}