001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2000-2005 Julian Hyde
008// Copyright (C) 2005-2012 Pentaho and others
009// All Rights Reserved.
010//
011// jhyde, 2 November, 2000
012*/
013package mondrian.util;
014
015import mondrian.olap.Util;
016
017import java.io.PrintWriter;
018import java.math.BigDecimal;
019import java.math.BigInteger;
020import java.text.*;
021import java.util.*;
022
023/**
024 * <code>Format</code> formats numbers, strings and dates according to the
025 * same specification as Visual Basic's
026 * <code>format()</code> function.  This function is described in more detail
027 * <a href="http://www.apostate.com/programming/vb-format.html">here</a>.  We
028 * have made the following enhancements to this specification:<ul>
029 *
030 * <li>if the international currency symbol (&#x00a4;) occurs in a format
031 *   string, it is translated to the locale's currency symbol.</li>
032 *
033 * <li>the format string "Currency" is translated to the locale's currency
034 *   format string. Negative currency values appear in parentheses.</li>
035 *
036 * <li>the string "USD" (abbreviation for U.S. Dollars) may occur in a format
037 *   string.</li>
038 *
039 * </ul>
040 *
041 * <p>One format object can be used to format multiple values, thereby
042 * amortizing the time required to parse the format string.  Example:</p>
043 *
044 * <pre><code>
045 * double[] values;
046 * Format format = new Format("##,##0.###;(##,##0.###);;Nil");
047 * for (int i = 0; i < values.length; i++) {
048 *   System.out.println("Value #" + i + " is " + format.format(values[i]));
049 * }
050 * </code></pre>
051 *
052 * <p>Still to be implemented:<ul>
053 *
054 * <li>String formatting (fill from left/right)</li>
055 *
056 * <li>Use client's timezone for printing times.</li>
057 *
058 * </ul>
059 *
060 * @author jhyde
061 */
062public class Format {
063    private String formatString;
064    private BasicFormat format;
065    private FormatLocale locale;
066
067    /**
068     * Maximum number of entries in the format cache used by
069     * {@link #get(String, java.util.Locale)}.
070     */
071    public static final int CacheLimit = 1000;
072
073    /**
074     * Maps (formatString, locale) pairs to {@link Format} objects.
075     *
076     * <p>If the number of entries in the cache exceeds 1000,
077     */
078    private static final Map<String, Format> cache =
079        new LinkedHashMap<String, Format>() {
080            public boolean removeEldestEntry(Map.Entry<String, Format> entry) {
081                return size() > CacheLimit;
082            }
083        };
084
085    static final char thousandSeparator_en = ',';
086    static final char decimalPlaceholder_en = '.';
087    static final String dateSeparator_en = "/";
088    static final String timeSeparator_en = ":";
089    static final String currencySymbol_en = "$";
090    static final String currencyFormat_en = "$#,##0.00";
091    static final String[] daysOfWeekShort_en = {
092        "", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
093    };
094    static final String[] daysOfWeekLong_en = {
095        "", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
096        "Saturday"
097    };
098    static final String[] monthsShort_en = {
099        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
100        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "",
101    };
102    static final String[] monthsLong_en = {
103        "January", "February", "March", "April", "May", "June",
104        "July", "August", "September", "October", "November", "December", "",
105    };
106    static final char intlCurrencySymbol = '\u00a4';
107
108    /**
109     * Maps strings representing locales (for example, "en_US_Boston", "en_US",
110     * "en", or "" for the default) to a {@link Format.FormatLocale}.
111     */
112    private static final Map<String, FormatLocale> mapLocaleToFormatLocale =
113        new HashMap<String, FormatLocale>();
114
115    /**
116     * Cache of parsed format strings and their thousand separator
117     * tokens length. Used so we don't have to tokenize a format string
118     * over and over again.
119     */
120    private static final Map<String, ArrayStack<Integer>>
121        thousandSeparatorTokenMap = new HashMap<String, ArrayStack<Integer>>();
122
123    /**
124     * Locale for US English, also the default for English and for all
125     * locales.
126     */
127    static final FormatLocale locale_US = createLocale(
128        '\0', '\0', null, null, null, null, null, null, null, null,
129        Locale.US);
130
131    static class Token {
132        final int code;
133        final int flags;
134        final String token;
135        final FormatType formatType;
136
137        Token(int code, int flags, String token)
138        {
139            this.code = code;
140            this.flags = flags;
141            this.token = token;
142            this.formatType =
143                isNumeric() ? FormatType.NUMERIC
144                    : isDate() ? FormatType.DATE
145                        : isString() ? FormatType.STRING
146                        : null;
147        }
148
149        boolean compatibleWith(FormatType formatType)
150        {
151            return formatType == null
152                || this.formatType == null
153                || formatType == this.formatType;
154        }
155
156        boolean isSpecial()
157        {
158            return (flags & SPECIAL) == SPECIAL;
159        }
160
161        boolean isNumeric()
162        {
163            return (flags & NUMERIC) == NUMERIC;
164        }
165
166        boolean isDate()
167        {
168            return (flags & DATE) == DATE;
169        }
170
171        boolean isString()
172        {
173            return (flags & STRING) == STRING;
174        }
175
176        FormatType getFormatType() {
177            return formatType;
178        }
179
180        BasicFormat makeFormat(FormatLocale locale)
181        {
182            if (isDate()) {
183                return new DateFormat(code, token, locale, false);
184            } else if (isNumeric()) {
185                return new LiteralFormat(code, token);
186            } else {
187                return new LiteralFormat(token);
188            }
189        }
190    }
191
192    /**
193     * BasicFormat is the interface implemented by the classes which do all
194     * the work.  Whereas {@link Format} has only one method for formatting,
195     * {@link Format#format(Object)}, this class provides methods for several
196     * primitive types.  To make it easy to combine formatting objects, all
197     * methods write to a {@link PrintWriter}.
198     *
199     * <p>The base implementation of most of these methods throws; there
200     * is no requirement that a derived class implements all of these methods.
201     * It is up to {@link Format#parseFormatString} to ensure that, for
202     * example, the {@link #format(double,StringBuilder)} method is
203     * never called for {@link DateFormat}.
204     */
205    static class BasicFormat {
206        final int code;
207
208        BasicFormat() {
209            this(0);
210        }
211
212        BasicFormat(int code) {
213            this.code = code;
214        }
215
216        FormatType getFormatType() {
217            return null;
218        }
219
220        void formatNull(StringBuilder buf) {
221            // SSAS formats null values as the empty string. However, SQL Server
222            // Management Studio's pivot table formats them as "(null)", so many
223            // people believe that this is the server's behavior.
224        }
225
226        void format(double d, StringBuilder buf) {
227            throw new Error();
228        }
229
230        void format(long n, StringBuilder buf) {
231            throw new Error();
232        }
233
234        void format(String s, StringBuilder buf) {
235            throw new Error();
236        }
237
238        void format(Date date, StringBuilder buf) {
239            Calendar calendar = Calendar.getInstance(); // todo: use locale
240            calendar.setTime(date);
241            format(calendar, buf);
242        }
243
244        void format(Calendar calendar, StringBuilder buf) {
245            throw new Error();
246        }
247
248        /**
249         * Returns whether this format can handle a given value.
250         *
251         * <p>Usually returns true;
252         * one notable exception is a format for negative numbers which
253         * causes the number to be underflow to zero and therefore be
254         * ineligible for the negative format.
255         *
256         * @param n value
257         * @return Whether this format is applicable for a given value
258         */
259        boolean isApplicableTo(double n) {
260            return true;
261        }
262
263        /**
264         * Returns whether this format can handle a given value.
265         *
266         * <p>Usually returns true;
267         * one notable exception is a format for negative numbers which
268         * causes the number to be underflow to zero and therefore be
269         * ineligible for the negative format.
270         *
271         * @param n value
272         * @return Whether this format is applicable for a given value
273         */
274        boolean isApplicableTo(long n) {
275            return true;
276        }
277    }
278
279    /**
280     * AlternateFormat is an implementation of {@link Format.BasicFormat} which
281     * allows a different format to be used for different kinds of values.  If
282     * there are 4 formats, purposes are as follows:<ol>
283     * <li>positive numbers</li>
284     * <li>negative numbers</li>
285     * <li>zero</li>
286     * <li>null values</li>
287     * </ol>
288     *
289     * <p>If there are fewer than 4 formats, the first is used as a fall-back.
290     * See the <a href="http://apostate.com/vb-format-syntax">the
291     * visual basic format specification</a> for more details.
292     */
293    static class AlternateFormat extends BasicFormat {
294        final BasicFormat[] formats;
295        final JavaFormat javaFormat;
296
297        AlternateFormat(BasicFormat[] formats, FormatLocale locale)
298        {
299            this.formats = formats;
300            assert formats.length >= 1;
301            this.javaFormat = new JavaFormat(locale.locale);
302        }
303
304        void formatNull(StringBuilder buf) {
305            if (formats.length >= 4) {
306                formats[3].format(0, buf);
307            } else {
308                super.formatNull(buf);
309            }
310        }
311
312        void format(double n, StringBuilder buf) {
313            int i;
314            if (n == 0
315                && formats.length >= 3
316                && formats[2] != null)
317            {
318                i = 2;
319            } else if (n < 0) {
320                if (formats.length >= 2
321                    && formats[1] != null)
322                {
323                    if (formats[1].isApplicableTo(n)) {
324                        n = -n;
325                        i = 1;
326                    } else {
327                        // Does not fit into the negative mask, so use the
328                        // nil mask, if there is one. For example,
329                        // "#.0;(#.0);Nil" formats -0.0001 as "Nil".
330                        if (formats.length >= 3
331                            && formats[2] != null)
332                        {
333                            i = 2;
334                        } else {
335                            i = 0;
336                        }
337                    }
338                } else {
339                    i = 0;
340                    if (formats[0].isApplicableTo(n)) {
341                        if (!Bug.BugMondrian687Fixed) {
342                            // Special case for format strings with style,
343                            // like "|#|style='red'". JPivot expects the
344                            // '-' to immediately precede the digits, viz
345                            // "|-6|style='red'|", not "-|6|style='red'|".
346                            // This is not consistent with SSAS 2005, hence
347                            // the bug.
348                            //
349                            // But for other formats, we want '-' to precede
350                            // literals, viz '-$6' not '$-6'. This is SSAS
351                            // 2005's behavior too.
352                            int size = buf.length();
353                            buf.append('-');
354                            n = -n;
355                            formats[i].format(n, buf);
356                            if (buf.substring(size, size + 2).equals(
357                                    "-|"))
358                            {
359                                buf.setCharAt(size, '|');
360                                buf.setCharAt(size + 1, '-');
361                            }
362                            return;
363                        }
364                        buf.append('-');
365                        n = -n;
366                    } else {
367                        n = 0;
368                    }
369                }
370            } else {
371                i = 0;
372            }
373            formats[i].format(n, buf);
374        }
375
376        void format(long n, StringBuilder buf) {
377            int i;
378            if (n == 0
379                && formats.length >= 3
380                && formats[2] != null)
381            {
382                i = 2;
383            } else if (n < 0) {
384                if (formats.length >= 2
385                    && formats[1] != null)
386                {
387                    if (formats[1].isApplicableTo(n)) {
388                        n = -n;
389                        i = 1;
390                    } else {
391                        // Does not fit into the negative mask, so use the
392                        // nil mask, if there is one. For example,
393                        // "#.0;(#.0);Nil" formats -0.0001 as "Nil".
394                        if (formats.length >= 3
395                            && formats[2] != null)
396                        {
397                            i = 2;
398                        } else {
399                            i = 0;
400                        }
401                    }
402                } else {
403                    i = 0;
404                    if (formats[0].isApplicableTo(n)) {
405                        if (!Bug.BugMondrian687Fixed) {
406                            // Special case for format strings with style,
407                            // like "|#|style='red'". JPivot expects the
408                            // '-' to immediately precede the digits, viz
409                            // "|-6|style='red'|", not "-|6|style='red'|".
410                            // This is not consistent with SSAS 2005, hence
411                            // the bug.
412                            //
413                            // But for other formats, we want '-' to precede
414                            // literals, viz '-$6' not '$-6'. This is SSAS
415                            // 2005's behavior too.
416                            final int size = buf.length();
417                            buf.append('-');
418                            n = -n;
419                            formats[i].format(n, buf);
420                            if (buf.substring(size, size + 2).equals(
421                                    "-|"))
422                            {
423                                buf.setCharAt(size, '|');
424                                buf.setCharAt(size + 1, '-');
425                            }
426                            return;
427                        }
428                        buf.append('-');
429                        n = -n;
430                    } else {
431                        n = 0;
432                    }
433                }
434            } else {
435                i = 0;
436            }
437            formats[i].format(n, buf);
438        }
439
440        void format(String s, StringBuilder buf) {
441            // since it is not a number, ignore all format strings
442            buf.append(s);
443        }
444
445        void format(Calendar calendar, StringBuilder buf) {
446            // We're passing a date to a numeric format string. Convert it to
447            // the number of days since 1900.
448            BigDecimal bigDecimal = daysSince1900(calendar);
449
450            // since it is not a number, ignore all format strings
451            format(bigDecimal.doubleValue(), buf);
452        }
453
454        private static BigDecimal daysSince1900(Calendar calendar) {
455            final long dayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
456            final long year = calendar.get(Calendar.YEAR);
457            long yearForLeap = year;
458            if (calendar.get(Calendar.MONTH) < 2) {
459                --yearForLeap;
460            }
461            final long leapDays =
462                (yearForLeap - 1900) / 4
463                - (yearForLeap - 1900) / 100
464                + (yearForLeap - 2000) / 400;
465            final long days =
466                (year - 1900) * 365
467                + leapDays
468                + dayOfYear
469                + 2; // kludge factor to agree with Excel
470            final long millis =
471                calendar.get(Calendar.HOUR_OF_DAY) * 3600000
472                + calendar.get(Calendar.MINUTE) * 60000
473                + calendar.get(Calendar.SECOND) * 1000
474                + calendar.get(Calendar.MILLISECOND);
475            return BigDecimal.valueOf(days).add(
476                BigDecimal.valueOf(millis).divide(
477                    BigDecimal.valueOf(86400000),
478                    8,
479                    BigDecimal.ROUND_FLOOR));
480        }
481    }
482
483    /**
484     * LiteralFormat is an implementation of {@link Format.BasicFormat} which
485     * prints a constant value, regardless of the value to be formatted.
486     *
487     * @see CompoundFormat
488     */
489    static class LiteralFormat extends BasicFormat
490    {
491        String s;
492
493        LiteralFormat(String s)
494        {
495            this(FORMAT_LITERAL, s);
496        }
497
498        LiteralFormat(int code, String s)
499        {
500            super(code);
501            this.s = s;
502        }
503
504        void format(double d, StringBuilder buf) {
505            buf.append(s);
506        }
507
508        void format(long n, StringBuilder buf) {
509            buf.append(s);
510        }
511
512        void format(String str, StringBuilder buf) {
513            buf.append(s);
514        }
515
516        void format(Date date, StringBuilder buf) {
517            buf.append(s);
518        }
519
520        void format(Calendar calendar, StringBuilder buf) {
521            buf.append(s);
522        }
523    }
524
525    /**
526     * CompoundFormat is an implementation of {@link Format.BasicFormat} where
527     * each value is formatted by applying a sequence of format elements.  Each
528     * format element is itself a format.
529     *
530     * @see AlternateFormat
531     */
532    static class CompoundFormat extends BasicFormat
533    {
534        final BasicFormat[] formats;
535        CompoundFormat(BasicFormat[] formats)
536        {
537            this.formats = formats;
538            assert formats.length >= 2;
539        }
540
541        void format(double v, StringBuilder buf) {
542            for (int i = 0; i < formats.length; i++) {
543                formats[i].format(v, buf);
544            }
545        }
546
547        void format(long v, StringBuilder buf) {
548            for (int i = 0; i < formats.length; i++) {
549                formats[i].format(v, buf);
550            }
551        }
552
553        void format(String v, StringBuilder buf) {
554            for (int i = 0; i < formats.length; i++) {
555                formats[i].format(v, buf);
556            }
557        }
558
559        void format(Date v, StringBuilder buf) {
560            for (int i = 0; i < formats.length; i++) {
561                formats[i].format(v, buf);
562            }
563        }
564
565        void format(Calendar v, StringBuilder buf) {
566            for (int i = 0; i < formats.length; i++) {
567                formats[i].format(v, buf);
568            }
569        }
570
571        boolean isApplicableTo(double n) {
572            for (int i = 0; i < formats.length; i++) {
573                if (!formats[i].isApplicableTo(n)) {
574                    return false;
575                }
576            }
577            return true;
578        }
579    }
580
581    /**
582     * JavaFormat is an implementation of {@link Format.BasicFormat} which
583     * prints values using Java's default formatting for their type.
584     * <code>null</code> values appear as an empty string.
585     */
586    static class JavaFormat extends BasicFormat
587    {
588        private final NumberFormat numberFormat;
589        private final java.text.DateFormat dateFormat;
590
591        JavaFormat(Locale locale)
592        {
593            this.numberFormat = NumberFormat.getNumberInstance(locale);
594            this.dateFormat =
595                java.text.DateFormat.getDateTimeInstance(
596                    java.text.DateFormat.SHORT,
597                    java.text.DateFormat.MEDIUM,
598                    locale);
599        }
600
601        // No need to override format(Object,PrintWriter) or
602        // format(Date,PrintWriter).
603
604        void format(double d, StringBuilder buf) {
605            // NOTE (jhyde, 2006/12/1): We'd use
606            // NumberFormat(double,StringBuilder,FieldPosition) if it existed.
607            buf.append(numberFormat.format(d));
608        }
609
610        void format(long n, StringBuilder buf) {
611            // NOTE (jhyde, 2006/12/1): We'd use
612            // NumberFormat(long,StringBuilder,FieldPosition) if it existed.
613            buf.append(numberFormat.format(n));
614        }
615
616        void format(String s, StringBuilder buf) {
617            buf.append(s);
618        }
619
620        void format(Calendar calendar, StringBuilder buf) {
621            // NOTE (jhyde, 2006/12/1): We'd use
622            // NumberFormat(Date,StringBuilder,FieldPosition) if it existed.
623            buf.append(dateFormat.format(calendar.getTime()));
624        }
625    }
626
627    /**
628     * FallbackFormat catches un-handled datatypes and prints the original
629     * format string.  Better than giving an error.  Abstract base class for
630     * NumericFormat and DateFormat.
631     */
632    static abstract class FallbackFormat extends BasicFormat
633    {
634        final String token;
635
636        FallbackFormat(int code, String token)
637        {
638            super(code);
639            this.token = token;
640        }
641
642        void format(double d, StringBuilder buf) {
643            buf.append(token);
644        }
645
646        void format(long n, StringBuilder buf) {
647            buf.append(token);
648        }
649
650        void format(String s, StringBuilder buf) {
651            buf.append(token);
652        }
653
654        void format(Calendar calendar, StringBuilder buf) {
655            buf.append(token);
656        }
657    }
658
659    /**
660     * NumericFormat is an implementation of {@link Format.BasicFormat} which
661     * prints numbers with a given number of decimal places, leading zeroes, in
662     * exponential notation, etc.
663     *
664     * <p>It is implemented using {@link MondrianFloatingDecimal}.
665     */
666    static class NumericFormat extends JavaFormat
667    {
668        final FormatLocale locale;
669        final int digitsLeftOfPoint;
670        final int zeroesLeftOfPoint;
671        final int digitsRightOfPoint;
672        final int zeroesRightOfPoint;
673        final int digitsRightOfExp;
674        final int zeroesRightOfExp;
675
676        /**
677         * Number of decimal places to shift the number left before
678         * formatting it: 2 means multiply by 100; -3 means divide by
679         * 1000.
680         */
681        int decimalShift;
682        final char expChar;
683        final boolean expSign;
684        final boolean useDecimal;
685        final boolean useThouSep;
686
687        final ArrayStack<Integer> cachedThousandSeparatorPositions;
688
689        NumericFormat(
690            String token,
691            FormatLocale locale,
692            int expFormat,
693            int digitsLeftOfPoint, int zeroesLeftOfPoint,
694            int digitsRightOfPoint, int zeroesRightOfPoint,
695            int digitsRightOfExp, int zeroesRightOfExp,
696            boolean useDecimal, boolean useThouSep,
697            String formatString)
698        {
699            super(locale.locale);
700            this.locale = locale;
701            switch (expFormat) {
702            case FORMAT_E_MINUS_UPPER:
703                this.expChar = 'E';
704                this.expSign = false;
705                break;
706            case FORMAT_E_PLUS_UPPER:
707                this.expChar = 'E';
708                this.expSign = true;
709                break;
710            case FORMAT_E_MINUS_LOWER:
711                this.expChar = 'e';
712                this.expSign = false;
713                break;
714            case FORMAT_E_PLUS_LOWER:
715                this.expChar = 'e';
716                this.expSign = true;
717                break;
718            default:
719                this.expChar = 0;
720                this.expSign = false;
721            }
722            this.digitsLeftOfPoint = digitsLeftOfPoint;
723            this.zeroesLeftOfPoint = zeroesLeftOfPoint;
724            this.digitsRightOfPoint = digitsRightOfPoint;
725            this.zeroesRightOfPoint = zeroesRightOfPoint;
726            this.digitsRightOfExp = digitsRightOfExp;
727            this.zeroesRightOfExp = zeroesRightOfExp;
728            this.useDecimal = useDecimal;
729            this.useThouSep = useThouSep;
730            this.decimalShift = 0; // set later
731
732            // Check if we're dealing with a format macro token rather than
733            // an actual format string.
734            formatString = MacroToken.expand(locale, formatString);
735
736            if (thousandSeparatorTokenMap.containsKey(formatString)) {
737                cachedThousandSeparatorPositions =
738                    thousandSeparatorTokenMap.get(formatString);
739            } else {
740                // To provide backwards compatibility, we apply the old
741                // formatting rules if there are less than 2 thousand
742                // separators in the format string.
743                String formatStringBuffer = formatString;
744
745                // If the format includes a negative format part, we strip it.
746                final int semiPos =
747                    formatStringBuffer.indexOf(getFormatToken(FORMAT_SEMI));
748                if (semiPos > 0) {
749                    formatStringBuffer =
750                        formatStringBuffer.substring(0, semiPos);
751                }
752
753                final int nbThousandSeparators =
754                    countOccurrences(
755                        formatStringBuffer,
756                        getFormatToken(FORMAT_THOUSEP).charAt(0));
757                cachedThousandSeparatorPositions = new ArrayStack<Integer>();
758                if (nbThousandSeparators > 1) {
759                    // Extract the whole part of the format string
760                    final int decimalPos =
761                        formatStringBuffer.indexOf(
762                            getFormatToken(FORMAT_DECIMAL));
763                    final int endIndex =
764                        decimalPos == -1
765                            ? formatStringBuffer.length()
766                            : decimalPos;
767                        final String wholeFormat =
768                            formatStringBuffer.substring(0, endIndex);
769
770                        // Tokenize it so we can analyze it's structure
771                        final StringTokenizer st =
772                            new StringTokenizer(
773                                wholeFormat,
774                                String.valueOf(
775                                    getFormatToken(FORMAT_THOUSEP)));
776
777                        // We ignore the first token.
778                        // ie: #,###,###
779                        st.nextToken();
780
781                        // Now we build a list of the token lengths in
782                        // reverse order. The last one in the reversed
783                        // list will be re-applied if the number is
784                        // longer than the format string.
785                        while (st.hasMoreTokens()) {
786                            cachedThousandSeparatorPositions.push(
787                                st.nextToken().length());
788                        }
789                } else if (nbThousandSeparators == 1) {
790                    // Use old style formatting.
791                    cachedThousandSeparatorPositions.add(3);
792                }
793                thousandSeparatorTokenMap.put(
794                    formatString, cachedThousandSeparatorPositions);
795            }
796        }
797
798        FormatType getFormatType() {
799            return FormatType.NUMERIC;
800        }
801
802        private ArrayStack<Integer> getThousandSeparatorPositions() {
803            // Defensive copy
804            return new ArrayStack<Integer>(cachedThousandSeparatorPositions);
805        }
806
807        private int countOccurrences(final String s, final char c) {
808            final char[] chars = s.toCharArray();
809            int count = 0;
810            for (int i = 0; i < chars.length; i++) {
811                if (chars[i] == c) {
812                    count++;
813                }
814            }
815            return count;
816        }
817
818        void format(double n, StringBuilder buf)
819        {
820            MondrianFloatingDecimal fd = new MondrianFloatingDecimal(n);
821            shift(fd, decimalShift);
822            final int formatDigitsRightOfPoint =
823                zeroesRightOfPoint + digitsRightOfPoint;
824            if (n == 0.0 || (n < 0 && !shows(fd, formatDigitsRightOfPoint))) {
825                // Underflow of negative number. Make it zero, so there is no
826                // '-' sign.
827                fd = new MondrianFloatingDecimal(0);
828            }
829            formatFd0(
830                fd,
831                buf,
832                zeroesLeftOfPoint,
833                locale.decimalPlaceholder,
834                zeroesRightOfPoint,
835                formatDigitsRightOfPoint,
836                expChar,
837                expSign,
838                zeroesRightOfExp,
839                useThouSep ? locale.thousandSeparator : '\0',
840                useDecimal,
841                getThousandSeparatorPositions());
842        }
843
844        boolean isApplicableTo(double n) {
845            if (n >= 0) {
846                return true;
847            }
848            MondrianFloatingDecimal fd = new MondrianFloatingDecimal(n);
849            shift(fd, decimalShift);
850            final int formatDigitsRightOfPoint =
851                zeroesRightOfPoint + digitsRightOfPoint;
852            return shows(fd, formatDigitsRightOfPoint);
853        }
854
855        private static boolean shows(
856            MondrianFloatingDecimal fd,
857            int formatDigitsRightOfPoint)
858        {
859            final int i0 = - fd.decExponent - formatDigitsRightOfPoint;
860            if (i0 < 0) {
861                return true;
862            }
863            if (i0 > 0) {
864                return false;
865            }
866            if (fd.digits[0] >= '5') {
867                return true;
868            }
869            return false;
870        }
871
872        void format(long n, StringBuilder buf)
873        {
874            MondrianFloatingDecimal fd =
875                new MondrianFloatingDecimal(n);
876            shift(fd, decimalShift);
877            formatFd0(
878                fd,
879                buf,
880                zeroesLeftOfPoint,
881                locale.decimalPlaceholder,
882                zeroesRightOfPoint,
883                zeroesRightOfPoint + digitsRightOfPoint,
884                expChar,
885                expSign,
886                zeroesRightOfExp,
887                useThouSep ? locale.thousandSeparator : '\0',
888                useDecimal,
889                getThousandSeparatorPositions());
890        }
891    }
892
893    /**
894     * DateFormat is an element of a {@link Format.CompoundFormat} which has a
895     * value when applied to a {@link Calendar} object.  (Values of type {@link
896     * Date} are automatically converted into {@link Calendar}s when you call
897     * {@link Format.BasicFormat#format(Date, StringBuilder)} calls
898     * to format other kinds of values give a runtime error.)
899     *
900     * <p>In a typical use of this class, a format string such as "m/d/yy" is
901     * parsed into DateFormat objects for "m", "d", and "yy", and {@link
902     * LiteralFormat} objects for "/".  A {@link Format.CompoundFormat} object
903     * is created to bind them together.
904     */
905    static class DateFormat extends FallbackFormat
906    {
907        FormatLocale locale;
908        boolean twelveHourClock;
909
910        DateFormat(
911            int code, String s, FormatLocale locale, boolean twelveHourClock)
912        {
913            super(code, s);
914            this.locale = locale;
915            this.twelveHourClock = twelveHourClock;
916        }
917
918        FormatType getFormatType() {
919            return FormatType.DATE;
920        }
921
922        void setTwelveHourClock(boolean twelveHourClock)
923        {
924            this.twelveHourClock = twelveHourClock;
925        }
926
927        void format(Calendar calendar, StringBuilder buf)
928        {
929            format(code, calendar, buf);
930        }
931
932        private void format(
933            int code,
934            Calendar calendar,
935            StringBuilder buf)
936        {
937            switch (code) {
938            case FORMAT_C:
939            {
940                boolean dateSet = !(
941                    calendar.get(Calendar.DAY_OF_YEAR) == 0
942                    && calendar.get(Calendar.YEAR) == 0);
943                boolean timeSet = !(
944                    calendar.get(Calendar.SECOND) == 0
945                    &&  calendar.get(Calendar.MINUTE) == 0
946                    && calendar.get(Calendar.HOUR) == 0);
947                if (dateSet) {
948                    format(FORMAT_DDDDD, calendar, buf);
949                }
950                if (dateSet && timeSet) {
951                    buf.append(' ');
952                }
953                if (timeSet) {
954                    format(FORMAT_TTTTT, calendar, buf);
955                }
956                break;
957            }
958            case FORMAT_D:
959            {
960                int d = calendar.get(Calendar.DAY_OF_MONTH);
961                buf.append(d);
962                break;
963            }
964            case FORMAT_DD:
965            {
966                int d = calendar.get(Calendar.DAY_OF_MONTH);
967                if (d < 10) {
968                    buf.append('0');
969                }
970                buf.append(d);
971                break;
972            }
973            case FORMAT_DDD:
974            {
975                int dow = calendar.get(Calendar.DAY_OF_WEEK);
976                buf.append(locale.daysOfWeekShort[dow]); // e.g. Sun
977                break;
978            }
979            case FORMAT_DDDD:
980            {
981                int dow = calendar.get(Calendar.DAY_OF_WEEK);
982                buf.append(locale.daysOfWeekLong[dow]); // e.g. Sunday
983                break;
984            }
985            case FORMAT_DDDDD:
986            {
987                // Officially, we should use the system's short date
988                // format. But for now, we always print using the default
989                // format, m/d/yy.
990                format(FORMAT_M, calendar, buf);
991                buf.append(locale.dateSeparator);
992                format(FORMAT_D, calendar, buf);
993                buf.append(locale.dateSeparator);
994                format(FORMAT_YY, calendar, buf);
995                break;
996            }
997            case FORMAT_DDDDDD:
998            {
999                format(FORMAT_MMMM_UPPER, calendar, buf);
1000                buf.append(" ");
1001                format(FORMAT_DD, calendar, buf);
1002                buf.append(", ");
1003                format(FORMAT_YYYY, calendar, buf);
1004                break;
1005            }
1006            case FORMAT_W:
1007            {
1008                int dow = calendar.get(Calendar.DAY_OF_WEEK);
1009                buf.append(dow);
1010                break;
1011            }
1012            case FORMAT_WW:
1013            {
1014                int woy = calendar.get(Calendar.WEEK_OF_YEAR);
1015                buf.append(woy);
1016                break;
1017            }
1018            case FORMAT_M:
1019            {
1020                int m = calendar.get(Calendar.MONTH) + 1; // 0-based
1021                buf.append(m);
1022                break;
1023            }
1024            case FORMAT_MM:
1025            {
1026                int mm = calendar.get(Calendar.MONTH) + 1; // 0-based
1027                if (mm < 10) {
1028                    buf.append('0');
1029                }
1030                buf.append(mm);
1031                break;
1032            }
1033            case FORMAT_MMM_LOWER:
1034            case FORMAT_MMM_UPPER:
1035            {
1036                int m = calendar.get(Calendar.MONTH); // 0-based
1037                buf.append(locale.monthsShort[m]); // e.g. Jan
1038                break;
1039            }
1040            case FORMAT_MMMM_LOWER:
1041            case FORMAT_MMMM_UPPER:
1042            {
1043                int m = calendar.get(Calendar.MONTH); // 0-based
1044                buf.append(locale.monthsLong[m]); // e.g. January
1045                break;
1046            }
1047            case FORMAT_Q:
1048            {
1049                int m = calendar.get(Calendar.MONTH);
1050                // 0(Jan) -> q1, 1(Feb) -> q1, 2(Mar) -> q1, 3(Apr) -> q2
1051                int q = m / 3 + 1;
1052                buf.append(q);
1053                break;
1054            }
1055            case FORMAT_Y:
1056            {
1057                int doy = calendar.get(Calendar.DAY_OF_YEAR);
1058                buf.append(doy);
1059                break;
1060            }
1061            case FORMAT_YY:
1062            {
1063                int y = calendar.get(Calendar.YEAR) % 100;
1064                if (y < 10) {
1065                    buf.append('0');
1066                }
1067                buf.append(y);
1068                break;
1069            }
1070            case FORMAT_YYYY:
1071            {
1072                int y = calendar.get(Calendar.YEAR);
1073                buf.append(y);
1074                break;
1075            }
1076            case FORMAT_H:
1077            {
1078                int h = calendar.get(
1079                    twelveHourClock ? Calendar.HOUR : Calendar.HOUR_OF_DAY);
1080                buf.append(h);
1081                break;
1082            }
1083            case FORMAT_HH:
1084            {
1085                int h = calendar.get(
1086                    twelveHourClock ? Calendar.HOUR : Calendar.HOUR_OF_DAY);
1087                if (h < 10) {
1088                    buf.append('0');
1089                }
1090                buf.append(h);
1091                break;
1092            }
1093            case FORMAT_N:
1094            {
1095                int n = calendar.get(Calendar.MINUTE);
1096                buf.append(n);
1097                break;
1098            }
1099            case FORMAT_NN:
1100            {
1101                int n = calendar.get(Calendar.MINUTE);
1102                if (n < 10) {
1103                    buf.append('0');
1104                }
1105                buf.append(n);
1106                break;
1107            }
1108            case FORMAT_S:
1109            {
1110                int s = calendar.get(Calendar.SECOND);
1111                buf.append(s);
1112                break;
1113            }
1114            case FORMAT_SS:
1115            {
1116                int s = calendar.get(Calendar.SECOND);
1117                if (s < 10) {
1118                    buf.append('0');
1119                }
1120                buf.append(s);
1121                break;
1122            }
1123            case FORMAT_TTTTT:
1124            {
1125                // Officially, we should use the system's time format. But
1126                // for now, we always print using the default format, h:mm:ss.
1127                format(FORMAT_H, calendar, buf);
1128                buf.append(locale.timeSeparator);
1129                format(FORMAT_NN, calendar, buf);
1130                buf.append(locale.timeSeparator);
1131                format(FORMAT_SS, calendar, buf);
1132                break;
1133            }
1134            case FORMAT_AMPM:
1135            case FORMAT_UPPER_AM_SOLIDUS_PM:
1136            {
1137                boolean isAm = calendar.get(Calendar.AM_PM) == Calendar.AM;
1138                buf.append(isAm ? "AM" : "PM");
1139                break;
1140            }
1141            case FORMAT_LOWER_AM_SOLIDUS_PM:
1142            {
1143                boolean isAm = calendar.get(Calendar.AM_PM) == Calendar.AM;
1144                buf.append(isAm ? "am" : "pm");
1145                break;
1146            }
1147            case FORMAT_UPPER_A_SOLIDUS_P:
1148            {
1149                boolean isAm = calendar.get(Calendar.AM_PM) == Calendar.AM;
1150                buf.append(isAm ? "A" : "P");
1151                break;
1152            }
1153            case FORMAT_LOWER_A_SOLIDUS_P:
1154            {
1155                boolean isAm = calendar.get(Calendar.AM_PM) == Calendar.AM;
1156                buf.append(isAm ? "a" : "p");
1157                break;
1158            }
1159            default:
1160                throw new Error();
1161            }
1162        }
1163    }
1164
1165    /**
1166     * A FormatLocale contains all information necessary to format objects
1167     * based upon the locale of the end-user.  Use {@link Format#createLocale}
1168     * to make one.
1169     */
1170    public static class FormatLocale
1171    {
1172        char thousandSeparator;
1173        char decimalPlaceholder;
1174        String dateSeparator;
1175        String timeSeparator;
1176        String currencySymbol;
1177        String currencyFormat;
1178        String[] daysOfWeekShort;
1179        String[] daysOfWeekLong;
1180        String[] monthsShort;
1181        String[] monthsLong;
1182        private final Locale locale;
1183
1184        private FormatLocale(
1185            char thousandSeparator,
1186            char decimalPlaceholder,
1187            String dateSeparator,
1188            String timeSeparator,
1189            String currencySymbol,
1190            String currencyFormat,
1191            String[] daysOfWeekShort,
1192            String[] daysOfWeekLong,
1193            String[] monthsShort,
1194            String[] monthsLong,
1195            Locale locale)
1196        {
1197            this.locale = locale;
1198            if (thousandSeparator == '\0') {
1199                thousandSeparator = thousandSeparator_en;
1200            }
1201            this.thousandSeparator = thousandSeparator;
1202            if (decimalPlaceholder == '\0') {
1203                decimalPlaceholder = decimalPlaceholder_en;
1204            }
1205            this.decimalPlaceholder = decimalPlaceholder;
1206            if (dateSeparator == null) {
1207                dateSeparator = dateSeparator_en;
1208            }
1209            this.dateSeparator = dateSeparator;
1210            if (timeSeparator == null) {
1211                timeSeparator = timeSeparator_en;
1212            }
1213            this.timeSeparator = timeSeparator;
1214            if (currencySymbol == null) {
1215                currencySymbol = currencySymbol_en;
1216            }
1217            this.currencySymbol = currencySymbol;
1218            if (currencyFormat == null) {
1219                currencyFormat = currencyFormat_en;
1220            }
1221            this.currencyFormat = currencyFormat;
1222            if (daysOfWeekShort == null) {
1223                daysOfWeekShort = daysOfWeekShort_en;
1224            }
1225            this.daysOfWeekShort = daysOfWeekShort;
1226            if (daysOfWeekLong == null) {
1227                daysOfWeekLong = daysOfWeekLong_en;
1228            }
1229            this.daysOfWeekLong = daysOfWeekLong;
1230            if (monthsShort == null) {
1231                monthsShort = monthsShort_en;
1232            }
1233            this.monthsShort = monthsShort;
1234            if (monthsLong == null) {
1235                monthsLong = monthsLong_en;
1236            }
1237            this.monthsLong = monthsLong;
1238            if (daysOfWeekShort.length != 8
1239                || daysOfWeekLong.length != 8
1240                || monthsShort.length != 13
1241                || monthsLong.length != 13)
1242            {
1243                throw new IllegalArgumentException(
1244                    "Format: day or month array has incorrect length");
1245            }
1246        }
1247    }
1248
1249    private static class StringFormat extends BasicFormat
1250    {
1251        final StringCase stringCase;
1252        final String literal;
1253        final JavaFormat javaFormat;
1254
1255        StringFormat(StringCase stringCase, String literal, Locale locale) {
1256            assert stringCase != null;
1257            this.stringCase = stringCase;
1258            this.literal = literal;
1259            this.javaFormat = new JavaFormat(locale);
1260        }
1261
1262        @Override
1263        void format(String s, StringBuilder buf) {
1264            switch (stringCase) {
1265            case UPPER:
1266                s = s.toUpperCase();
1267                break;
1268            case LOWER:
1269                s = s.toLowerCase();
1270                break;
1271            }
1272            buf.append(s);
1273        }
1274
1275        void format(double d, StringBuilder buf) {
1276            final int x = buf.length();
1277            javaFormat.format(d, buf);
1278            String s = buf.substring(x);
1279            buf.setLength(x);
1280            format(s, buf);
1281        }
1282
1283        void format(long n, StringBuilder buf) {
1284            final int x = buf.length();
1285            javaFormat.format(n, buf);
1286            String s = buf.substring(x);
1287            buf.setLength(x);
1288            format(s, buf);
1289        }
1290
1291        void format(Date date, StringBuilder buf) {
1292            final int x = buf.length();
1293            javaFormat.format(date, buf);
1294            String s = buf.substring(x);
1295            buf.setLength(x);
1296            format(s, buf);
1297        }
1298
1299        void format(Calendar calendar, StringBuilder buf) {
1300            final int x = buf.length();
1301            javaFormat.format(calendar, buf);
1302            String s = buf.substring(x);
1303            buf.setLength(x);
1304            format(s, buf);
1305        }
1306    }
1307
1308    private enum StringCase {
1309        UPPER,
1310        LOWER
1311    }
1312
1313    /** Types of Format. */
1314    private static final int GENERAL = 0;
1315    private static final int DATE = 1;
1316    private static final int NUMERIC = 2;
1317    private static final int STRING = 4;
1318
1319    /** A Format is flagged SPECIAL if it needs special processing
1320     * during parsing. */
1321    private static final int SPECIAL = 8;
1322
1323    /** Values for {@link Format.BasicFormat#code}. */
1324    private static final int FORMAT_NULL = 0;
1325    private static final int FORMAT_C = 3;
1326    private static final int FORMAT_D = 4;
1327    private static final int FORMAT_DD = 5;
1328    private static final int FORMAT_DDD = 6;
1329    private static final int FORMAT_DDDD = 7;
1330    private static final int FORMAT_DDDDD = 8;
1331    private static final int FORMAT_DDDDDD = 9;
1332    private static final int FORMAT_W = 10;
1333    private static final int FORMAT_WW = 11;
1334    private static final int FORMAT_M = 12;
1335    private static final int FORMAT_MM = 13;
1336    private static final int FORMAT_MMM_UPPER = 14;
1337    private static final int FORMAT_MMMM_UPPER = 15;
1338    private static final int FORMAT_Q = 16;
1339    private static final int FORMAT_Y = 17;
1340    private static final int FORMAT_YY = 18;
1341    private static final int FORMAT_YYYY = 19;
1342    private static final int FORMAT_H = 20;
1343    private static final int FORMAT_HH = 21;
1344    private static final int FORMAT_N = 22;
1345    private static final int FORMAT_NN = 23;
1346    private static final int FORMAT_S = 24;
1347    private static final int FORMAT_SS = 25;
1348    private static final int FORMAT_TTTTT = 26;
1349    private static final int FORMAT_UPPER_AM_SOLIDUS_PM = 27;
1350    private static final int FORMAT_LOWER_AM_SOLIDUS_PM = 28;
1351    private static final int FORMAT_UPPER_A_SOLIDUS_P = 29;
1352    private static final int FORMAT_LOWER_A_SOLIDUS_P = 30;
1353    private static final int FORMAT_AMPM = 31;
1354    private static final int FORMAT_0 = 32;
1355    private static final int FORMAT_POUND = 33;
1356    private static final int FORMAT_DECIMAL = 34;
1357    private static final int FORMAT_PERCENT = 35;
1358    private static final int FORMAT_THOUSEP = 36;
1359    private static final int FORMAT_TIMESEP = 37;
1360    private static final int FORMAT_DATESEP = 38;
1361    private static final int FORMAT_E_MINUS_UPPER = 39;
1362    private static final int FORMAT_E_PLUS_UPPER = 40;
1363    private static final int FORMAT_E_MINUS_LOWER = 41;
1364    private static final int FORMAT_E_PLUS_LOWER = 42;
1365    private static final int FORMAT_LITERAL = 43;
1366    private static final int FORMAT_BACKSLASH = 44;
1367    private static final int FORMAT_QUOTE = 45;
1368    private static final int FORMAT_CHARACTER_OR_SPACE = 46;
1369    private static final int FORMAT_CHARACTER_OR_NOTHING = 47;
1370    private static final int FORMAT_LOWER = 48;
1371    private static final int FORMAT_UPPER = 49;
1372    private static final int FORMAT_FILL_FROM_LEFT = 50;
1373    private static final int FORMAT_SEMI = 51;
1374    private static final int FORMAT_GENERAL_NUMBER = 52;
1375    private static final int FORMAT_GENERAL_DATE = 53;
1376    private static final int FORMAT_INTL_CURRENCY = 54;
1377    private static final int FORMAT_MMM_LOWER = 55;
1378    private static final int FORMAT_MMMM_LOWER = 56;
1379    private static final int FORMAT_USD = 57;
1380
1381    private static final Map<Integer, String> formatTokenToFormatString =
1382        new HashMap<Integer, String>();
1383
1384    private static Token nfe(
1385        int code, int flags, String token, String purpose, String description)
1386    {
1387        Util.discard(purpose);
1388        Util.discard(description);
1389        formatTokenToFormatString.put(code, token);
1390        return new Token(code, flags, token);
1391    }
1392
1393    public static List<Token> getTokenList()
1394    {
1395        return Collections.unmodifiableList(Arrays.asList(tokens));
1396    }
1397
1398    /**
1399     * Returns the format token as a string representation
1400     * which corresponds to a given token code.
1401     * @param code The code of the token to obtain.
1402     * @return The string representation of that token.
1403     */
1404    public static String getFormatToken(int code)
1405    {
1406        return formatTokenToFormatString.get(code);
1407    }
1408
1409    private static final Token[] tokens = {
1410        nfe(
1411            FORMAT_NULL,
1412            NUMERIC,
1413            null,
1414            "No formatting",
1415            "Display the number with no formatting."),
1416        nfe(
1417            FORMAT_C,
1418            DATE,
1419            "C",
1420            null,
1421            "Display the date as ddddd and display the time as t t t t t, in "
1422            + "that order. Display only date information if there is no "
1423            + "fractional part to the date serial number; display only time "
1424            + "information if there is no integer portion."),
1425        nfe(
1426            FORMAT_D,
1427            DATE,
1428            "d",
1429            null,
1430            "Display the day as a number without a leading zero (1 - 31)."),
1431        nfe(
1432            FORMAT_DD,
1433            DATE,
1434            "dd",
1435            null,
1436            "Display the day as a number with a leading zero (01 - 31)."),
1437        nfe(
1438            FORMAT_DDD,
1439            DATE,
1440            "Ddd",
1441            null,
1442            "Display the day as an abbreviation (Sun - Sat)."),
1443        nfe(
1444            FORMAT_DDDD,
1445            DATE,
1446            "dddd",
1447            null,
1448            "Display the day as a full name (Sunday - Saturday)."),
1449        nfe(
1450            FORMAT_DDDDD,
1451            DATE,
1452            "ddddd",
1453            null,
1454            "Display the date as a complete date (including day, month, and "
1455            + "year), formatted according to your system's short date format "
1456            + "setting. The default short date format is m/d/yy."),
1457        nfe(
1458            FORMAT_DDDDDD,
1459            DATE,
1460            "dddddd",
1461            null,
1462            "Display a date serial number as a complete date (including day, "
1463            + "month, and year) formatted according to the long date setting "
1464            + "recognized by your system. The default long date format is mmmm "
1465            + "dd, yyyy."),
1466        nfe(
1467            FORMAT_W,
1468            DATE,
1469            "w",
1470            null,
1471            "Display the day of the week as a number (1 for Sunday through 7 "
1472            + "for Saturday)."),
1473        nfe(
1474            FORMAT_WW,
1475            DATE,
1476            "ww",
1477            null,
1478            "Display the week of the year as a number (1 - 53)."),
1479        nfe(
1480            FORMAT_M,
1481            DATE | SPECIAL,
1482            "m",
1483            null,
1484            "Display the month as a number without a leading zero (1 - 12). If "
1485            + "m immediately follows h or hh, the minute rather than the month "
1486            + "is displayed."),
1487        nfe(
1488            FORMAT_MM,
1489            DATE | SPECIAL,
1490            "mm",
1491            null,
1492            "Display the month as a number with a leading zero (01 - 12). If m "
1493            + "immediately follows h or hh, the minute rather than the month "
1494            + "is displayed."),
1495        nfe(
1496            FORMAT_MMM_LOWER,
1497            DATE,
1498            "mmm",
1499            null,
1500            "Display the month as an abbreviation (Jan - Dec)."),
1501        nfe(
1502            FORMAT_MMMM_LOWER,
1503            DATE,
1504            "mmmm",
1505            null,
1506            "Display the month as a full month name (January - December)."),
1507        nfe(
1508            FORMAT_MMM_UPPER,
1509            DATE,
1510            "MMM",
1511            null,
1512            "Display the month as an abbreviation (Jan - Dec)."),
1513        nfe(
1514            FORMAT_MMMM_UPPER,
1515            DATE,
1516            "MMMM",
1517            null,
1518            "Display the month as a full month name (January - December)."),
1519        nfe(
1520            FORMAT_Q,
1521            DATE,
1522            "q",
1523            null,
1524            "Display the quarter of the year as a number (1 - 4)."),
1525        nfe(
1526            FORMAT_Y,
1527            DATE,
1528            "y",
1529            null,
1530            "Display the day of the year as a number (1 - 366)."),
1531        nfe(
1532            FORMAT_YY,
1533            DATE,
1534            "yy",
1535            null,
1536            "Display the year as a 2-digit number (00 - 99)."),
1537        nfe(
1538            FORMAT_YYYY,
1539            DATE,
1540            "yyyy",
1541            null,
1542            "Display the year as a 4-digit number (100 - 9999)."),
1543        nfe(
1544            FORMAT_H,
1545            DATE,
1546            "h",
1547            null,
1548            "Display the hour as a number without leading zeros (0 - 23)."),
1549        nfe(
1550            FORMAT_HH,
1551            DATE,
1552            "hh",
1553            null,
1554            "Display the hour as a number with leading zeros (00 - 23)."),
1555        nfe(
1556            FORMAT_N,
1557            DATE,
1558            "n",
1559            null,
1560            "Display the minute as a number without leading zeros (0 - 59)."),
1561        nfe(
1562            FORMAT_NN,
1563            DATE,
1564            "nn",
1565            null,
1566            "Display the minute as a number with leading zeros (00 - 59)."),
1567        nfe(
1568            FORMAT_S,
1569            DATE,
1570            "s",
1571            null,
1572            "Display the second as a number without leading zeros (0 - 59)."),
1573        nfe(
1574            FORMAT_SS,
1575            DATE,
1576            "ss",
1577            null,
1578            "Display the second as a number with leading zeros (00 - 59)."),
1579        nfe(
1580            FORMAT_TTTTT,
1581            DATE,
1582            "ttttt",
1583            null,
1584            "Display a time as a complete time (including hour, minute, and "
1585            + "second), formatted using the time separator defined by the time "
1586            + "format recognized by your system. A leading zero is displayed "
1587            + "if the leading zero option is selected and the time is before "
1588            + "10:00 A.M. or P.M. The default time format is h:mm:ss."),
1589        nfe(
1590            FORMAT_UPPER_AM_SOLIDUS_PM,
1591            DATE,
1592            "AM/PM",
1593            null,
1594            "Use the 12-hour clock and display an uppercase AM with any hour "
1595            + "before noon; display an uppercase PM with any hour between noon and 11:59 P.M."),
1596        nfe(
1597            FORMAT_LOWER_AM_SOLIDUS_PM,
1598            DATE,
1599            "am/pm",
1600            null,
1601            "Use the 12-hour clock and display a lowercase AM with any hour "
1602            + "before noon; display a lowercase PM with any hour between noon "
1603            + "and 11:59 P.M."),
1604        nfe(
1605            FORMAT_UPPER_A_SOLIDUS_P,
1606            DATE,
1607            "A/P",
1608            null,
1609            "Use the 12-hour clock and display an uppercase A with any hour "
1610            + "before noon; display an uppercase P with any hour between noon "
1611            + "and 11:59 P.M."),
1612        nfe(
1613            FORMAT_LOWER_A_SOLIDUS_P,
1614            DATE,
1615            "a/p",
1616            null,
1617            "Use the 12-hour clock and display a lowercase A with any hour "
1618            + "before noon; display a lowercase P with any hour between noon "
1619            + "and 11:59 P.M."),
1620        nfe(
1621            FORMAT_AMPM,
1622            DATE,
1623            "AMPM",
1624            null,
1625            "Use the 12-hour clock and display the AM string literal as "
1626            + "defined by your system with any hour before noon; display the "
1627            + "PM string literal as defined by your system with any hour "
1628            + "between noon and 11:59 P.M. AMPM can be either uppercase or "
1629            + "lowercase, but the case of the string displayed matches the "
1630            + "string as defined by your system settings. The default format "
1631            + "is AM/PM."),
1632        nfe(
1633            FORMAT_0,
1634            NUMERIC | SPECIAL,
1635            "0",
1636            "Digit placeholder",
1637            "Display a digit or a zero. If the expression has a digit in the "
1638            + "position where the 0 appears in the format string, display it; "
1639            + "otherwise, display a zero in that position. If the number has "
1640            + "fewer digits than there are zeros (on either side of the "
1641            + "decimal) in the format expression, display leading or trailing "
1642            + "zeros. If the number has more digits to the right of the "
1643            + "decimal separator than there are zeros to the right of the "
1644            + "decimal separator in the format expression, round the number to "
1645            + "as many decimal places as there are zeros. If the number has "
1646            + "more digits to the left of the decimal separator than there are "
1647            + "zeros to the left of the decimal separator in the format "
1648            + "expression, display the extra digits without modification."),
1649        nfe(
1650            FORMAT_POUND,
1651            NUMERIC | SPECIAL,
1652            "#",
1653            "Digit placeholder",
1654            "Display a digit or nothing. If the expression has a digit in the "
1655            + "position where the # appears in the format string, display it; "
1656            + "otherwise, display nothing in that position.  This symbol works "
1657            + "like the 0 digit placeholder, except that leading and trailing "
1658            + "zeros aren't displayed if the number has the same or fewer "
1659            + "digits than there are # characters on either side of the "
1660            + "decimal separator in the format expression."),
1661        nfe(
1662            FORMAT_DECIMAL,
1663            NUMERIC | SPECIAL,
1664            ".",
1665            "Decimal placeholder",
1666            "In some locales, a comma is used as the decimal separator. The "
1667            + "decimal placeholder determines how many digits are displayed to "
1668            + "the left and right of the decimal separator. If the format "
1669            + "expression contains only number signs to the left of this "
1670            + "symbol, numbers smaller than 1 begin with a decimal separator. "
1671            + "If you always want a leading zero displayed with fractional "
1672            + "numbers, use 0 as the first digit placeholder to the left of "
1673            + "the decimal separator instead. The actual character used as a "
1674            + "decimal placeholder in the formatted output depends on the "
1675            + "Number Format recognized by your system."),
1676        nfe(
1677            FORMAT_PERCENT,
1678            NUMERIC,
1679            "%",
1680            "Percent placeholder",
1681            "The expression is multiplied by 100. The percent character (%) is "
1682            + "inserted in the position where it appears in the format "
1683            + "string."),
1684        nfe(
1685            FORMAT_THOUSEP,
1686            NUMERIC | SPECIAL,
1687            ",",
1688            "Thousand separator",
1689            "In some locales, a period is used as a thousand separator. The "
1690            + "thousand separator separates thousands from hundreds within a "
1691            + "number that has four or more places to the left of the decimal "
1692            + "separator. Standard use of the thousand separator is specified "
1693            + "if the format contains a thousand separator surrounded by digit "
1694            + "placeholders (0 or #). Two adjacent thousand separators or a "
1695            + "thousand separator immediately to the left of the decimal "
1696            + "separator (whether or not a decimal is specified) means \"scale "
1697            + "the number by dividing it by 1000, rounding as needed.\"  You "
1698            + "can scale large numbers using this technique. For example, you "
1699            + "can use the format string \"##0,,\" to represent 100 million as "
1700            + "100. Numbers smaller than 1 million are displayed as 0. Two "
1701            + "adjacent thousand separators in any position other than "
1702            + "immediately to the left of the decimal separator are treated "
1703            + "simply as specifying the use of a thousand separator. The "
1704            + "actual character used as the thousand separator in the "
1705            + "formatted output depends on the Number Format recognized by "
1706            + "your system."),
1707        nfe(
1708            FORMAT_TIMESEP,
1709            DATE | SPECIAL,
1710            ":",
1711            "Time separator",
1712            "In some locales, other characters may be used to represent the "
1713            + "time separator. The time separator separates hours, minutes, "
1714            + "and seconds when time values are formatted. The actual "
1715            + "character used as the time separator in formatted output is "
1716            + "determined by your system settings."),
1717        nfe(
1718            FORMAT_DATESEP,
1719            DATE | SPECIAL,
1720            "/",
1721            "Date separator",
1722            "In some locales, other characters may be used to represent the "
1723            + "date separator. The date separator separates the day, month, "
1724            + "and year when date values are formatted. The actual character "
1725            + "used as the date separator in formatted output is determined by "
1726            + "your system settings."),
1727        nfe(
1728            FORMAT_E_MINUS_UPPER,
1729            NUMERIC | SPECIAL,
1730            "E-",
1731            "Scientific format",
1732            "If the format expression contains at least one digit placeholder "
1733            + "(0 or #) to the right of E-, E+, e-, or e+, the number is "
1734            + "displayed in scientific format and E or e is inserted between "
1735            + "the number and its exponent. The number of digit placeholders "
1736            + "to the right determines the number of digits in the exponent. "
1737            + "Use E- or e- to place a minus sign next to negative exponents. "
1738            + "Use E+ or e+ to place a minus sign next to negative exponents "
1739            + "and a plus sign next to positive exponents."),
1740        nfe(
1741            FORMAT_E_PLUS_UPPER,
1742            NUMERIC | SPECIAL,
1743            "E+",
1744            "Scientific format",
1745            "See E-."),
1746        nfe(
1747            FORMAT_E_MINUS_LOWER,
1748            NUMERIC | SPECIAL,
1749            "e-",
1750            "Scientific format",
1751            "See E-."),
1752        nfe(
1753            FORMAT_E_PLUS_LOWER,
1754            NUMERIC | SPECIAL,
1755            "e+",
1756            "Scientific format",
1757            "See E-."),
1758        nfe(
1759            FORMAT_LITERAL,
1760            GENERAL,
1761            "-",
1762            "Display a literal character",
1763            "To display a character other than one of those listed, precede it "
1764            + "with a backslash (\\) or enclose it in double quotation marks "
1765            + "(\" \")."),
1766        nfe(
1767            FORMAT_LITERAL,
1768            GENERAL,
1769            "+",
1770            "Display a literal character",
1771            "See -."),
1772        nfe(
1773            FORMAT_LITERAL,
1774            GENERAL,
1775            "$",
1776            "Display a literal character",
1777            "See -."),
1778        nfe(
1779            FORMAT_LITERAL,
1780            GENERAL,
1781            "(",
1782            "Display a literal character",
1783            "See -."),
1784        nfe(
1785            FORMAT_LITERAL,
1786            GENERAL,
1787            ")",
1788            "Display a literal character",
1789            "See -."),
1790        nfe(
1791            FORMAT_LITERAL,
1792            GENERAL,
1793            " ",
1794            "Display a literal character",
1795            "See -."),
1796        nfe(
1797            FORMAT_BACKSLASH,
1798            GENERAL | SPECIAL,
1799            "\\",
1800            "Display the next character in the format string",
1801            "Many characters in the format expression have a special meaning "
1802            + "and can't be displayed as literal characters unless they are "
1803            + "preceded by a backslash. The backslash itself isn't displayed. "
1804            + "Using a backslash is the same as enclosing the next character "
1805            + "in double quotation marks. To display a backslash, use two "
1806            + "backslashes (\\).  Examples of characters that can't be "
1807            + "displayed as literal characters are the date- and "
1808            + "time-formatting characters (a, c, d, h, m, n, p, q, s, t, w, y, "
1809            + "and /:), the numeric-formatting characters (#, 0, %, E, e, "
1810            + "comma, and period), and the string-formatting characters (@, &, "
1811            + "<, >, and !)."),
1812        nfe(
1813            FORMAT_QUOTE,
1814            GENERAL | SPECIAL,
1815            "\"",
1816            "Display the string inside the double quotation marks",
1817            "To include a string in format from within code, you must use "
1818            + "Chr(34) to enclose the text (34 is the character code for a "
1819            + "double quotation mark)."),
1820        nfe(
1821            FORMAT_CHARACTER_OR_SPACE,
1822            STRING,
1823            "@",
1824            "Character placeholder",
1825            "Display a character or a space. If the string has a character in "
1826            + "the position where the @ appears in the format string, display "
1827            + "it; otherwise, display a space in that position. Placeholders "
1828            + "are filled from right to left unless there is an ! character in "
1829            + "the format string. See below."),
1830        nfe(
1831            FORMAT_CHARACTER_OR_NOTHING,
1832            STRING,
1833            "&",
1834            "Character placeholder",
1835            "Display a character or nothing. If the string has a character in "
1836            + "the position where the & appears, display it; otherwise, "
1837            + "display nothing. Placeholders are filled from right to left "
1838            + "unless there is an ! character in the format string. See "
1839            + "below."),
1840        nfe(
1841            FORMAT_LOWER,
1842            STRING | SPECIAL,
1843            "<",
1844            "Force lowercase",
1845            "Display all characters in lowercase format."),
1846        nfe(
1847            FORMAT_UPPER,
1848            STRING | SPECIAL,
1849            ">",
1850            "Force uppercase",
1851            "Display all characters in uppercase format."),
1852        nfe(
1853            FORMAT_FILL_FROM_LEFT,
1854            STRING | SPECIAL,
1855            "!",
1856            "Force left to right fill of placeholders",
1857            "The default is to fill from right to left."),
1858        nfe(
1859            FORMAT_SEMI,
1860            GENERAL | SPECIAL,
1861            ";",
1862            "Separates format strings for different kinds of values",
1863            "If there is one section, the format expression applies to all "
1864            + "values. If there are two sections, the first section applies "
1865            + "to positive values and zeros, the second to negative values. If "
1866            + "there are three sections, the first section applies to positive "
1867            + "values, the second to negative values, and the third to zeros. "
1868            + "If there are four sections, the first section applies to "
1869            + "positive values, the second to negative values, the third to "
1870            + "zeros, and the fourth to Null values."),
1871        nfe(
1872            FORMAT_INTL_CURRENCY,
1873            NUMERIC | SPECIAL,
1874            intlCurrencySymbol + "",
1875            null,
1876            "Display the locale's currency symbol."),
1877        nfe(
1878            FORMAT_USD,
1879            GENERAL,
1880            "USD",
1881            null,
1882            "Display USD (U.S. Dollars)."),
1883        nfe(
1884            FORMAT_GENERAL_NUMBER,
1885            NUMERIC | SPECIAL,
1886            "General Number",
1887            null,
1888            "Shows numbers as entered."),
1889        nfe(
1890            FORMAT_GENERAL_DATE,
1891            DATE | SPECIAL,
1892            "General Date",
1893            null,
1894            "Shows date and time if expression contains both. If expression is "
1895            + "only a date or a time, the missing information is not "
1896            + "displayed."),
1897    };
1898
1899    // Named formats.  todo: Supply the translation strings.
1900    private enum MacroToken {
1901        CURRENCY(
1902            "Currency",
1903            null,
1904            "Shows currency values according to the locale's CurrencyFormat.  "
1905            + "Negative numbers are inside parentheses."),
1906        FIXED(
1907            "Fixed", "0", "Shows at least one digit."),
1908        STANDARD(
1909            "Standard", "#,##0", "Uses a thousands separator."),
1910        PERCENT(
1911            "Percent",
1912            "0.00%",
1913            "Multiplies the value by 100 with a percent sign at the end."),
1914        SCIENTIFIC(
1915            "Scientific", "0.00e+00", "Uses standard scientific notation."),
1916        LONG_DATE(
1917            "Long Date",
1918            "dddd, mmmm dd, yyyy",
1919            "Uses the Long Date format specified in the Regional Settings "
1920            + "dialog box of the Microsoft Windows Control Panel."),
1921        MEDIUM_DATE(
1922            "Medium Date",
1923            "dd-mmm-yy",
1924            "Uses the dd-mmm-yy format (for example, 03-Apr-93)"),
1925        SHORT_DATE(
1926            "Short Date",
1927            "m/d/yy",
1928            "Uses the Short Date format specified in the Regional Settings "
1929            + "dialog box of the Windows Control Panel."),
1930        LONG_TIME(
1931            "Long Time",
1932            "h:mm:ss AM/PM",
1933            "Shows the hour, minute, second, and \"AM\" or \"PM\" using the "
1934            + "h:mm:ss format."),
1935        MEDIUM_TIME(
1936            "Medium Time",
1937            "h:mm AM/PM",
1938            "Shows the hour, minute, and \"AM\" or \"PM\" using the \"hh:mm "
1939            + "AM/PM\" format."),
1940        SHORT_TIME(
1941            "Short Time",
1942            "hh:mm",
1943            "Shows the hour and minute using the hh:mm format."),
1944        YES_NO(
1945            "Yes/No",
1946            "\\Y\\e\\s;\\Y\\e\\s;\\N\\o;\\N\\o",
1947            "Any nonzero numeric value (usually - 1) is Yes. Zero is No."),
1948        TRUE_FALSE(
1949            "True/False",
1950            "\\T\\r\\u\\e;\\T\\r\\u\\e;\\F\\a\\l\\s\\e;\\F\\a\\l\\s\\e",
1951            "Any nonzero numeric value (usually - 1) is True. Zero is False."),
1952        ON_OFF(
1953            "On/Off",
1954            "\\O\\n;\\O\\n;\\O\\f\\f;\\O\\f\\f",
1955            "Any nonzero numeric value (usually - 1) is On. Zero is Off.");
1956
1957        /**
1958         * Maps macro token names with their related object. Used
1959         * to fast-resolve a macro token without iterating.
1960         */
1961        private static final Map<String, MacroToken> MAP =
1962            new HashMap<String, MacroToken>();
1963
1964        static {
1965            for (MacroToken macroToken : values()) {
1966                MAP.put(macroToken.token, macroToken);
1967            }
1968        }
1969
1970        MacroToken(String token, String translation, String description) {
1971            this.token = token;
1972            this.translation = translation;
1973            this.description = description;
1974            assert name().equals(
1975                token
1976                    .replace(',', '_')
1977                    .replace(' ', '_')
1978                    .replace('/', '_')
1979                    .toUpperCase());
1980            assert (translation == null) == name().equals("CURRENCY");
1981        }
1982
1983        final String token;
1984        final String translation;
1985        final String description;
1986
1987        static String expand(FormatLocale locale, String formatString) {
1988            final MacroToken macroToken = MAP.get(formatString);
1989            if (macroToken == null) {
1990                return formatString;
1991            }
1992            if (macroToken == MacroToken.CURRENCY) {
1993                // e.g. "$#,##0.00;($#,##0.00)"
1994                return locale.currencyFormat
1995                    + ";("  + locale.currencyFormat + ")";
1996            } else {
1997                return macroToken.translation;
1998            }
1999        }
2000    }
2001
2002    /**
2003     * Constructs a <code>Format</code> in a specific locale.
2004     *
2005     * @param formatString the format string; see
2006     *   <a href="http://www.apostate.com/programming/vb-format.html">this
2007     *   description</a> for more details
2008     * @param locale The locale
2009     */
2010    public Format(String formatString, Locale locale)
2011    {
2012        this(formatString, getBestFormatLocale(locale));
2013    }
2014
2015    /**
2016     * Constructs a <code>Format</code> in a specific locale.
2017     *
2018     * @param formatString the format string; see
2019     *   <a href="http://www.apostate.com/programming/vb-format.html">this
2020     *   description</a> for more details
2021     * @param locale The locale
2022     *
2023     * @see FormatLocale
2024     * @see #createLocale
2025     */
2026    public Format(String formatString, FormatLocale locale)
2027    {
2028        if (formatString == null) {
2029            formatString = "";
2030        }
2031        this.formatString = formatString;
2032        if (locale == null) {
2033            locale = locale_US;
2034        }
2035        this.locale = locale;
2036
2037        List<BasicFormat> alternateFormatList = new ArrayList<BasicFormat>();
2038        FormatType[] formatType = {null};
2039        while (formatString.length() > 0) {
2040            formatString = parseFormatString(
2041                formatString, alternateFormatList, formatType);
2042        }
2043
2044        // If the format string is empty, use a Java format.
2045        // Later entries in the formats list default to the first (e.g.
2046        // "#.00;;Nil"), but the first entry must be set.
2047        if (alternateFormatList.size() == 0
2048            || alternateFormatList.get(0) == null)
2049        {
2050            format = new JavaFormat(locale.locale);
2051        } else if (alternateFormatList.size() == 1
2052                   && (formatType[0] == FormatType.DATE
2053                       || formatType[0] == FormatType.STRING))
2054        {
2055            format = alternateFormatList.get(0);
2056        } else {
2057            BasicFormat[] alternateFormats =
2058                alternateFormatList.toArray(
2059                    new BasicFormat[alternateFormatList.size()]);
2060            format = new AlternateFormat(alternateFormats, locale);
2061        }
2062    }
2063
2064    /**
2065     * Constructs a <code>Format</code> in a specific locale, or retrieves
2066     * one from the cache if one already exists.
2067     *
2068     * <p>If the number of entries in the cache exceeds {@link #CacheLimit},
2069     * replaces the eldest entry in the cache.
2070     *
2071     * @param formatString the format string; see
2072     *   <a href="http://www.apostate.com/programming/vb-format.html">this
2073     *   description</a> for more details
2074     *
2075     * @return format for given format string in given locale
2076     */
2077    public static Format get(String formatString, Locale locale) {
2078        String key = formatString + "@@@" + locale;
2079        Format format = cache.get(key);
2080        if (format == null) {
2081            synchronized (cache) {
2082                format = cache.get(key);
2083                if (format == null) {
2084                    format = new Format(formatString, locale);
2085                    cache.put(key, format);
2086                }
2087            }
2088        }
2089        return format;
2090    }
2091
2092    /**
2093     * Create a {@link FormatLocale} object characterized by the given
2094     * properties.
2095     *
2096     * @param thousandSeparator the character used to separate thousands in
2097     *   numbers, or ',' by default.  For example, 12345 is '12,345 in English,
2098     *   '12.345 in French.
2099     * @param decimalPlaceholder the character placed between the integer and
2100     *   the fractional part of decimal numbers, or '.' by default.  For
2101     *   example, 12.34 is '12.34' in English, '12,34' in French.
2102     * @param dateSeparator the character placed between the year, month and
2103     *   day of a date such as '12/07/2001', or '/' by default.
2104     * @param timeSeparator the character placed between the hour, minute and
2105     *   second value of a time such as '1:23:45 AM', or ':' by default.
2106     * @param daysOfWeekShort Short forms of the days of the week.
2107     *            The array is 1-based, because position
2108     *            {@link Calendar#SUNDAY} (= 1) must hold Sunday, etc.
2109     *            The array must have 8 elements.
2110     *            For example {"", "Sun", "Mon", ..., "Sat"}.
2111     * @param daysOfWeekLong Long forms of the days of the week.
2112     *            The array is 1-based, because position
2113     *            {@link Calendar#SUNDAY} must hold Sunday, etc.
2114     *            The array must have 8 elements.
2115     *            For example {"", "Sunday", ..., "Saturday"}.
2116     * @param monthsShort Short forms of the months of the year.
2117     *            The array is 0-based, because position
2118     *            {@link Calendar#JANUARY} (= 0) holds January, etc.
2119     *            For example {"Jan", ..., "Dec", ""}.
2120     * @param monthsLong Long forms of the months of the year.
2121     *            The array is 0-based, because position
2122     *            {@link Calendar#JANUARY} (= 0) holds January, etc.
2123     *            For example {"January", ..., "December", ""}.
2124     * @param locale if this is not null, register that the constructed
2125     *     <code>FormatLocale</code> is the default for <code>locale</code>
2126     */
2127    public static FormatLocale createLocale(
2128        char thousandSeparator,
2129        char decimalPlaceholder,
2130        String dateSeparator,
2131        String timeSeparator,
2132        String currencySymbol,
2133        String currencyFormat,
2134        String[] daysOfWeekShort,
2135        String[] daysOfWeekLong,
2136        String[] monthsShort,
2137        String[] monthsLong,
2138        Locale locale)
2139    {
2140        FormatLocale formatLocale = new FormatLocale(
2141            thousandSeparator, decimalPlaceholder, dateSeparator,
2142            timeSeparator, currencySymbol, currencyFormat, daysOfWeekShort,
2143            daysOfWeekLong, monthsShort, monthsLong, locale);
2144        if (locale != null) {
2145            registerFormatLocale(formatLocale, locale);
2146        }
2147        return formatLocale;
2148    }
2149
2150    public static FormatLocale createLocale(Locale locale)
2151    {
2152        final DecimalFormatSymbols decimalSymbols =
2153            new DecimalFormatSymbols(locale);
2154        final DateFormatSymbols dateSymbols = new DateFormatSymbols(locale);
2155
2156        Calendar calendar = Calendar.getInstance(locale);
2157        calendar.set(1969, Calendar.DECEMBER, 31, 0, 0, 0);
2158        final Date date = calendar.getTime();
2159
2160        final java.text.DateFormat dateFormat =
2161            java.text.DateFormat.getDateInstance(
2162                java.text.DateFormat.SHORT, locale);
2163        final String dateValue = dateFormat.format(date); // "12/31/69"
2164        String dateSeparator = dateValue.substring(2, 3); // "/"
2165
2166        final java.text.DateFormat timeFormat =
2167            java.text.DateFormat.getTimeInstance(
2168                java.text.DateFormat.SHORT, locale);
2169        final String timeValue = timeFormat.format(date); // "12:00:00"
2170        String timeSeparator = timeValue.substring(2, 3); // ":"
2171
2172        // Deduce the locale's currency format.
2173        // For example, US is "$#,###.00"; France is "#,###-00FF".
2174        final NumberFormat currencyFormat =
2175            NumberFormat.getCurrencyInstance(locale);
2176        final String currencyValue = currencyFormat.format(123456.78);
2177        String currencyLeft =
2178            currencyValue.substring(0, currencyValue.indexOf("1"));
2179        String currencyRight =
2180            currencyValue.substring(currencyValue.indexOf("8") + 1);
2181        StringBuilder buf = new StringBuilder();
2182        buf.append(currencyLeft);
2183        int minimumIntegerDigits = currencyFormat.getMinimumIntegerDigits();
2184        for (int i = Math.max(minimumIntegerDigits, 4) - 1; i >= 0; --i) {
2185            buf.append(i < minimumIntegerDigits ? '0' : '#');
2186            if (i % 3 == 0 && i > 0) {
2187                buf.append(',');
2188            }
2189        }
2190        if (currencyFormat.getMaximumFractionDigits() > 0) {
2191            buf.append('.');
2192            appendTimes(buf, '0', currencyFormat.getMinimumFractionDigits());
2193            appendTimes(
2194                buf, '#',
2195                currencyFormat.getMaximumFractionDigits()
2196                - currencyFormat.getMinimumFractionDigits());
2197        }
2198        buf.append(currencyRight);
2199        String currencyFormatString = buf.toString();
2200
2201        // If the locale passed is only a language, Java cannot
2202        // resolve the currency symbol and will instead return
2203        // u00a4 (The international currency symbol). For those cases,
2204        // we use the default system locale currency symbol.
2205        String currencySymbol = decimalSymbols.getCurrencySymbol();
2206        if (currencySymbol.equals(Format.intlCurrencySymbol + "")) {
2207            final DecimalFormatSymbols defaultDecimalSymbols =
2208                new DecimalFormatSymbols(Locale.getDefault());
2209            currencySymbol = defaultDecimalSymbols.getCurrencySymbol();
2210        }
2211
2212        return createLocale(
2213            decimalSymbols.getGroupingSeparator(),
2214            decimalSymbols.getDecimalSeparator(),
2215            dateSeparator,
2216            timeSeparator,
2217            currencySymbol,
2218            currencyFormatString,
2219            dateSymbols.getShortWeekdays(),
2220            dateSymbols.getWeekdays(),
2221            dateSymbols.getShortMonths(),
2222            dateSymbols.getMonths(),
2223            locale);
2224    }
2225
2226    private static void appendTimes(StringBuilder buf, char c, int i) {
2227        while (i-- > 0) {
2228            buf.append(c);
2229        }
2230    }
2231
2232    /**
2233     * Returns the {@link FormatLocale} which precisely matches {@link Locale},
2234     * if any, or null if there is none.
2235     */
2236    public static FormatLocale getFormatLocale(Locale locale)
2237    {
2238        if (locale == null) {
2239            locale = Locale.US;
2240        }
2241        String key = locale.toString();
2242        return mapLocaleToFormatLocale.get(key);
2243    }
2244
2245    /**
2246     * Returns the best {@link FormatLocale} for a given {@link Locale}.
2247     * Never returns null, even if <code>locale</code> is null.
2248     */
2249    public static synchronized FormatLocale getBestFormatLocale(Locale locale)
2250    {
2251        FormatLocale formatLocale;
2252        if (locale == null) {
2253            return locale_US;
2254        }
2255        String key = locale.toString();
2256        // Look in the cache first.
2257        formatLocale = mapLocaleToFormatLocale.get(key);
2258        if (formatLocale == null) {
2259            // Not in the cache, so ask the factory.
2260            formatLocale = getFormatLocaleUsingFactory(locale);
2261            if (formatLocale == null) {
2262                formatLocale = locale_US;
2263            }
2264            // Add to cache.
2265            mapLocaleToFormatLocale.put(key, formatLocale);
2266        }
2267        return formatLocale;
2268    }
2269
2270    private static FormatLocale getFormatLocaleUsingFactory(Locale locale)
2271    {
2272        FormatLocale formatLocale;
2273        // Lookup full locale, e.g. "en-US-Boston"
2274        if (!locale.getVariant().equals("")) {
2275            formatLocale = createLocale(locale);
2276            if (formatLocale != null) {
2277                return formatLocale;
2278            }
2279            locale = new Locale(locale.getLanguage(), locale.getCountry());
2280        }
2281        // Lookup language and country, e.g. "en-US"
2282        if (!locale.getCountry().equals("")) {
2283            formatLocale = createLocale(locale);
2284            if (formatLocale != null) {
2285                return formatLocale;
2286            }
2287            locale = new Locale(locale.getLanguage());
2288        }
2289        // Lookup language, e.g. "en"
2290        formatLocale = createLocale(locale);
2291        if (formatLocale != null) {
2292            return formatLocale;
2293        }
2294        return null;
2295    }
2296
2297    /**
2298     * Registers a {@link FormatLocale} to a given {@link Locale}. Returns the
2299     * previous mapping.
2300     */
2301    public static FormatLocale registerFormatLocale(
2302        FormatLocale formatLocale, Locale locale)
2303    {
2304        String key = locale.toString(); // e.g. "en_us_Boston"
2305        return mapLocaleToFormatLocale.put(key, formatLocale);
2306    }
2307
2308    // Values for variable numberState below.
2309    static final int NOT_IN_A_NUMBER = 0;
2310    static final int LEFT_OF_POINT = 1;
2311    static final int RIGHT_OF_POINT = 2;
2312    static final int RIGHT_OF_EXP = 3;
2313
2314    /**
2315     * Reads formatString up to the first semi-colon, or to the end if there
2316     * are no semi-colons.  Adds a format to alternateFormatList, and returns
2317     * the remains of formatString.
2318     */
2319    private String parseFormatString(
2320        String formatString,
2321        List<BasicFormat> alternateFormatList,
2322        FormatType[] formatTypeOut)
2323    {
2324        // Cache the original value
2325        final String originalFormatString = formatString;
2326
2327        // Where we are in a numeric format.
2328        int numberState = NOT_IN_A_NUMBER;
2329        StringBuilder ignored = new StringBuilder();
2330        String prevIgnored = null;
2331        boolean haveSeenNumber = false;
2332        int digitsLeftOfPoint = 0,
2333            digitsRightOfPoint = 0,
2334            digitsRightOfExp = 0,
2335            zeroesLeftOfPoint = 0,
2336            zeroesRightOfPoint = 0,
2337            zeroesRightOfExp = 0;
2338        boolean useDecimal = false,
2339            useThouSep = false,
2340            fillFromRight = true;
2341
2342        // Whether to print numbers in decimal or exponential format.  Valid
2343        // values are FORMAT_NULL, FORMAT_E_PLUS_LOWER, FORMAT_E_MINUS_LOWER,
2344        // FORMAT_E_PLUS_UPPER, FORMAT_E_MINUS_UPPER.
2345        int expFormat = FORMAT_NULL;
2346
2347        // todo: Parse the string for ;s
2348
2349        // Look for the format string in the table of named formats.
2350        formatString = MacroToken.expand(locale, formatString);
2351
2352        // Add a semi-colon to the end of the string so the end of the string
2353        // looks like the end of an alternate.
2354        if (!formatString.endsWith(";")) {
2355            formatString = formatString + ";";
2356        }
2357
2358        // Scan through the format string for format elements.
2359        List<BasicFormat> formatList = new ArrayList<BasicFormat>();
2360        List<Integer> thousands = new ArrayList<Integer>();
2361        int decimalShift = 0;
2362        loop:
2363        while (formatString.length() > 0) {
2364            BasicFormat format = null;
2365            String newFormatString;
2366            final Token token = findToken(formatString, formatTypeOut[0]);
2367            if (token != null) {
2368                String matched = token.token;
2369                newFormatString = formatString.substring(matched.length());
2370                if (token.isSpecial()) {
2371                    switch (token.code) {
2372                    case FORMAT_SEMI:
2373                        break loop;
2374
2375                    case FORMAT_POUND:
2376                        switch (numberState) {
2377                        case NOT_IN_A_NUMBER:
2378                            numberState = LEFT_OF_POINT;
2379                            // fall through
2380                        case LEFT_OF_POINT:
2381                            digitsLeftOfPoint++;
2382                            break;
2383                        case RIGHT_OF_POINT:
2384                            digitsRightOfPoint++;
2385                            break;
2386                        case RIGHT_OF_EXP:
2387                            digitsRightOfExp++;
2388                            break;
2389                        default:
2390                            throw new Error();
2391                        }
2392                        break;
2393
2394                    case FORMAT_0:
2395                        switch (numberState) {
2396                        case NOT_IN_A_NUMBER:
2397                            numberState = LEFT_OF_POINT;
2398                            // fall through
2399                        case LEFT_OF_POINT:
2400                            zeroesLeftOfPoint++;
2401                            break;
2402                        case RIGHT_OF_POINT:
2403                            zeroesRightOfPoint++;
2404                            break;
2405                        case RIGHT_OF_EXP:
2406                            zeroesRightOfExp++;
2407                            break;
2408                        default:
2409                            throw new Error();
2410                        }
2411                        break;
2412
2413                    case FORMAT_M:
2414                    case FORMAT_MM:
2415                    {
2416                        // "m" and "mm" mean minute if immediately after
2417                        // "h" or "hh"; month otherwise.
2418                        boolean theyMeantMinute = false;
2419                        int j = formatList.size() - 1;
2420                        while (j >= 0) {
2421                            BasicFormat prevFormat = formatList.get(j);
2422                            if (prevFormat instanceof LiteralFormat) {
2423                                // ignore boilerplate
2424                                j--;
2425                            } else if (prevFormat.code == FORMAT_H
2426                                       || prevFormat.code == FORMAT_HH)
2427                            {
2428                                theyMeantMinute = true;
2429                                break;
2430                            } else {
2431                                theyMeantMinute = false;
2432                                break;
2433                            }
2434                        }
2435                        if (theyMeantMinute) {
2436                            format = new DateFormat(
2437                                (token.code == FORMAT_M
2438                                    ? FORMAT_N
2439                                    : FORMAT_NN),
2440                                matched,
2441                                locale,
2442                                false);
2443                        } else {
2444                            format = token.makeFormat(locale);
2445                        }
2446                        break;
2447                    }
2448
2449                    case FORMAT_DECIMAL:
2450                    {
2451                        if (numberState == LEFT_OF_POINT) {
2452                            decimalShift =
2453                                fixThousands(
2454                                    thousands, formatString, decimalShift);
2455                        }
2456                        numberState = RIGHT_OF_POINT;
2457                        useDecimal = true;
2458                        break;
2459                    }
2460
2461                    case FORMAT_THOUSEP:
2462                    {
2463                        if (numberState == LEFT_OF_POINT) {
2464                            // e.g. "#,##"
2465                            useThouSep = true;
2466                            thousands.add(formatString.length());
2467                        } else {
2468                            // e.g. "ddd, mmm dd, yyy"
2469                            format = token.makeFormat(locale);
2470                        }
2471                        break;
2472                    }
2473
2474                    case FORMAT_TIMESEP:
2475                    {
2476                        format = new LiteralFormat(locale.timeSeparator);
2477                        break;
2478                    }
2479
2480                    case FORMAT_DATESEP:
2481                    {
2482                        format = new LiteralFormat(locale.dateSeparator);
2483                        break;
2484                    }
2485
2486                    case FORMAT_BACKSLASH:
2487                    {
2488                        // Display the next character in the format string.
2489                        String s;
2490                        if (formatString.length() == 1) {
2491                            // Backslash is the last character in the
2492                            // string.
2493                            s = "";
2494                            newFormatString = "";
2495                        } else {
2496                            s = formatString.substring(1, 2);
2497                            newFormatString = formatString.substring(2);
2498                        }
2499                        format = new LiteralFormat(s);
2500                        break;
2501                    }
2502
2503                    case FORMAT_E_MINUS_UPPER:
2504                    case FORMAT_E_PLUS_UPPER:
2505                    case FORMAT_E_MINUS_LOWER:
2506                    case FORMAT_E_PLUS_LOWER:
2507                    {
2508                        if (numberState == LEFT_OF_POINT) {
2509                            decimalShift =
2510                                fixThousands(
2511                                    thousands, formatString, decimalShift);
2512                        }
2513                        numberState = RIGHT_OF_EXP;
2514                        expFormat = token.code;
2515                        if (zeroesLeftOfPoint == 0
2516                            && zeroesRightOfPoint == 0)
2517                        {
2518                            // We need a mantissa, so that format(123.45,
2519                            // "E+") gives "1E+2", not "0E+2" or "E+2".
2520                            zeroesLeftOfPoint = 1;
2521                        }
2522                        break;
2523                    }
2524
2525                    case FORMAT_QUOTE:
2526                    {
2527                        // Display the string inside the double quotation
2528                        // marks.
2529                        String s;
2530                        int j = formatString.indexOf("\"", 1);
2531                        if (j == -1) {
2532                            // The string did not contain a closing quote.
2533                            // Use the whole string.
2534                            s = formatString.substring(1);
2535                            newFormatString = "";
2536                        } else {
2537                            // Take the string inside the quotes.
2538                            s = formatString.substring(1, j);
2539                            newFormatString = formatString.substring(
2540                                j + 1);
2541                        }
2542                        format = new LiteralFormat(s);
2543                        break;
2544                    }
2545
2546                    case FORMAT_UPPER:
2547                    {
2548                        format =
2549                            new StringFormat(
2550                                StringCase.UPPER, ">", locale.locale);
2551                        break;
2552                    }
2553
2554                    case FORMAT_LOWER:
2555                    {
2556                        format =
2557                            new StringFormat(
2558                                StringCase.LOWER, "<", locale.locale);
2559                        break;
2560                    }
2561
2562                    case FORMAT_FILL_FROM_LEFT:
2563                    {
2564                        fillFromRight = false;
2565                        break;
2566                    }
2567
2568                    case FORMAT_GENERAL_NUMBER:
2569                    {
2570                        format = new JavaFormat(locale.locale);
2571                        break;
2572                    }
2573
2574                    case FORMAT_GENERAL_DATE:
2575                    {
2576                        format = new JavaFormat(locale.locale);
2577                        break;
2578                    }
2579
2580                    case FORMAT_INTL_CURRENCY:
2581                    {
2582                        format = new LiteralFormat(locale.currencySymbol);
2583                        break;
2584                    }
2585
2586                    default:
2587                        throw new Error();
2588                    }
2589                    if (formatTypeOut[0] == null) {
2590                        formatTypeOut[0] = token.getFormatType();
2591                    }
2592                    if (format == null) {
2593                        // If the special-case code does not set format,
2594                        // we should not create a format element.  (The
2595                        // token probably caused some flag to be set.)
2596                        ignored.append(matched);
2597                    } else {
2598                        prevIgnored = ignored.toString();
2599                        ignored.setLength(0);
2600                    }
2601                } else {
2602                    format = token.makeFormat(locale);
2603                }
2604            } else {
2605                // None of the standard format elements matched.  Make the
2606                // current character into a literal.
2607                format = new LiteralFormat(
2608                    formatString.substring(0, 1));
2609                newFormatString = formatString.substring(1);
2610            }
2611
2612            if (format != null) {
2613                if (numberState != NOT_IN_A_NUMBER) {
2614                    // Having seen a few number tokens, we're looking at a
2615                    // non-number token.  Create the number first.
2616                    if (numberState == LEFT_OF_POINT) {
2617                        decimalShift =
2618                            fixThousands(
2619                                thousands, formatString, decimalShift);
2620                    }
2621                    NumericFormat numericFormat = new NumericFormat(
2622                        prevIgnored, locale, expFormat, digitsLeftOfPoint,
2623                        zeroesLeftOfPoint, digitsRightOfPoint,
2624                        zeroesRightOfPoint, digitsRightOfExp, zeroesRightOfExp,
2625                        useDecimal, useThouSep, originalFormatString);
2626                    formatList.add(numericFormat);
2627                    numberState = NOT_IN_A_NUMBER;
2628                    haveSeenNumber = true;
2629                }
2630
2631                formatList.add(format);
2632                if (formatTypeOut[0] == null) {
2633                    formatTypeOut[0] = format.getFormatType();
2634                }
2635            }
2636
2637            formatString = newFormatString;
2638        }
2639
2640        if (numberState != NOT_IN_A_NUMBER) {
2641            // We're still in a number.  Create a number format.
2642            if (numberState == LEFT_OF_POINT) {
2643                decimalShift =
2644                    fixThousands(
2645                        thousands, formatString, decimalShift);
2646            }
2647            NumericFormat numericFormat = new NumericFormat(
2648                prevIgnored, locale, expFormat, digitsLeftOfPoint,
2649                zeroesLeftOfPoint, digitsRightOfPoint, zeroesRightOfPoint,
2650                digitsRightOfExp, zeroesRightOfExp, useDecimal, useThouSep,
2651                originalFormatString);
2652            formatList.add(numericFormat);
2653            numberState = NOT_IN_A_NUMBER;
2654            haveSeenNumber = true;
2655        }
2656
2657        if (formatString.startsWith(";")) {
2658            formatString = formatString.substring(1);
2659        }
2660
2661        // If they used some symbol like 'AM/PM' in the format string, tell all
2662        // date formats to use twelve hour clock.  Likewise, figure out the
2663        // multiplier implied by their use of "%" or ",".
2664        boolean twelveHourClock = false;
2665        for (int i = 0; i < formatList.size(); i++) {
2666            switch (formatList.get(i).code) {
2667            case FORMAT_UPPER_AM_SOLIDUS_PM:
2668            case FORMAT_LOWER_AM_SOLIDUS_PM:
2669            case FORMAT_UPPER_A_SOLIDUS_P:
2670            case FORMAT_LOWER_A_SOLIDUS_P:
2671            case FORMAT_AMPM:
2672                twelveHourClock = true;
2673                break;
2674
2675            case FORMAT_PERCENT:
2676                // If "%" occurs, the number should be multiplied by 100.
2677                decimalShift += 2;
2678                break;
2679
2680            case FORMAT_THOUSEP:
2681                // If there is a thousands separator (",") immediately to the
2682                // left of the point, or at the end of the number, divide the
2683                // number by 1000.  (Or by 1000^n if there are more than one.)
2684                if (haveSeenNumber
2685                    && i + 1 < formatList.size())
2686                {
2687                    final BasicFormat nextFormat = formatList.get(i + 1);
2688                    if (nextFormat.code != FORMAT_THOUSEP
2689                        && nextFormat.code != FORMAT_0
2690                        && nextFormat.code != FORMAT_POUND)
2691                    {
2692                        for (int j = i;
2693                            j >= 0 && formatList.get(j).code == FORMAT_THOUSEP;
2694                            j--)
2695                        {
2696                            decimalShift -= 3;
2697                            formatList.remove(j); // ignore
2698                            --i;
2699                        }
2700                    }
2701                }
2702                break;
2703
2704            default:
2705            }
2706        }
2707
2708        if (twelveHourClock) {
2709            for (int i = 0; i < formatList.size(); i++) {
2710                if (formatList.get(i) instanceof DateFormat) {
2711                    ((DateFormat) formatList.get(i)).setTwelveHourClock(true);
2712                }
2713            }
2714        }
2715
2716        if (decimalShift != 0) {
2717            for (int i = 0; i < formatList.size(); i++) {
2718                if (formatList.get(i) instanceof NumericFormat) {
2719                    ((NumericFormat) formatList.get(i)).decimalShift =
2720                        decimalShift;
2721                }
2722            }
2723        }
2724
2725        // Merge adjacent literal formats.
2726        //
2727        // Must do this AFTER adjusting for percent. Otherwise '%' and following
2728        // '|' might be merged into a plain literal, and '%' would lose its
2729        // special powers.
2730        for (int i = 0; i < formatList.size(); ++i) {
2731            if (i > 0
2732                && formatList.get(i) instanceof LiteralFormat
2733                && formatList.get(i - 1) instanceof LiteralFormat)
2734            {
2735                formatList.set(
2736                    i - 1,
2737                    new LiteralFormat(
2738                        ((LiteralFormat) formatList.get(i - 1)).s
2739                        + ((LiteralFormat) formatList.get(i)).s));
2740                formatList.remove(i);
2741                --i;
2742            }
2743        }
2744
2745        // Create a CompoundFormat containing all of the format elements.
2746        // This is the end of an alternate - or of the whole format string.
2747        // Push the current list of formats onto the list of alternates.
2748
2749        BasicFormat alternateFormat;
2750        switch (formatList.size()) {
2751        case 0:
2752            alternateFormat = null;
2753            break;
2754        case 1:
2755            alternateFormat = formatList.get(0);
2756            break;
2757        default:
2758            alternateFormat =
2759                new CompoundFormat(
2760                    formatList.toArray(
2761                        new BasicFormat[formatList.size()]));
2762            break;
2763        }
2764        alternateFormatList.add(alternateFormat);
2765        return formatString;
2766    }
2767
2768    private Token findToken(String formatString, FormatType formatType) {
2769        for (int i = tokens.length - 1; i > 0; i--) {
2770            final Token token = tokens[i];
2771            if (formatString.startsWith(token.token)
2772                && token.compatibleWith(formatType))
2773            {
2774                return token;
2775            }
2776        }
2777        return null;
2778    }
2779
2780    private int fixThousands(
2781        List<Integer> thousands, String formatString, int shift)
2782    {
2783        int offset = formatString.length() + 1;
2784        for (int i = thousands.size() - 1; i >= 0; i--) {
2785            Integer integer = thousands.get(i);
2786            thousands.set(i, integer - offset);
2787            ++offset;
2788        }
2789        while (thousands.size() > 0
2790            && thousands.get(thousands.size() - 1) == 0)
2791        {
2792            shift -= 3;
2793            thousands.remove(thousands.size() - 1);
2794        }
2795        return shift;
2796    }
2797
2798    public String format(Object o)
2799    {
2800        StringBuilder buf = new StringBuilder();
2801        format(o, buf);
2802        return buf.toString();
2803    }
2804
2805    private StringBuilder format(Object o, StringBuilder buf) {
2806        if (o == null) {
2807            format.formatNull(buf);
2808        } else {
2809            // For final classes, it is more efficient to switch using
2810            // class equality than using 'instanceof'.
2811            Class<? extends Object> clazz = o.getClass();
2812            if (clazz == Double.class) {
2813                format.format((Double) o, buf);
2814            } else if (clazz == Float.class) {
2815                format.format((Float) o, buf);
2816            } else if (clazz == Integer.class) {
2817                format.format((Integer) o, buf);
2818            } else if (clazz == Long.class) {
2819                format.format((Long) o, buf);
2820            } else if (clazz == Short.class) {
2821                format.format((Short) o, buf);
2822            } else if (clazz == Byte.class) {
2823                format.format((Byte) o, buf);
2824            } else if (o instanceof BigDecimal) {
2825                format.format(
2826                    ((BigDecimal) o).doubleValue(), buf);
2827            } else if (o instanceof BigInteger) {
2828                format.format(
2829                    ((BigInteger) o).longValue(), buf);
2830            } else if (clazz == String.class) {
2831                format.format((String) o, buf);
2832            } else if (o instanceof java.util.Date) {
2833                // includes java.sql.Date, java.sql.Time and java.sql.Timestamp
2834                format.format((Date) o, buf);
2835            } else if (o instanceof Calendar) {
2836                format.format((Calendar) o, buf);
2837            } else {
2838                buf.append(o.toString());
2839            }
2840        }
2841        return buf;
2842    }
2843
2844    public String getFormatString()
2845    {
2846        return formatString;
2847    }
2848
2849    private static void shift(
2850        MondrianFloatingDecimal fd,
2851        int i)
2852    {
2853        if (fd.isExceptional
2854            || fd.nDigits == 1 && fd.digits[0] == '0')
2855        {
2856            ; // don't multiply zero
2857        } else {
2858            fd.decExponent += i;
2859        }
2860    }
2861
2862    /** Formats a floating decimal to a given buffer. */
2863    private static void formatFd0(
2864        MondrianFloatingDecimal fd,
2865        StringBuilder buf,
2866        int minDigitsLeftOfDecimal,
2867        char decimalChar, // '.' or ','
2868        int minDigitsRightOfDecimal,
2869        int maxDigitsRightOfDecimal,
2870        char expChar, // 'E' or 'e'
2871        boolean expSign, // whether to print '+' if exp is positive
2872        int minExpDigits, // minimum digits in exponent
2873        char thousandChar, // ',' or '.', or 0
2874        boolean useDecimal,
2875        ArrayStack<Integer> thousandSeparatorPositions)
2876    {
2877        // char result[] = new char[nDigits + 10]; // crashes for 1.000.000,00
2878        // the result length does *not* depend from nDigits
2879        //  it is : decExponent
2880        //         +maxDigitsRightOfDecimal
2881        //          + 10  (for decimal point and sign or -Infinity)
2882        //         +decExponent/3 (for the thousand separators)
2883        int resultLen =
2884            10 + Math.abs(fd.decExponent) * 4 / 3 + maxDigitsRightOfDecimal;
2885        char result[] = new char[resultLen];
2886        int i = formatFd1(
2887            fd,
2888            result,
2889            0,
2890            minDigitsLeftOfDecimal,
2891            decimalChar,
2892            minDigitsRightOfDecimal,
2893            maxDigitsRightOfDecimal,
2894            expChar,
2895            expSign,
2896            minExpDigits,
2897            thousandChar,
2898            useDecimal,
2899            thousandSeparatorPositions);
2900        buf.append(result, 0, i);
2901    }
2902
2903    /** Formats a floating decimal to a given char array. */
2904    private static int formatFd1(
2905        MondrianFloatingDecimal fd,
2906        char result[],
2907        int i,
2908        int minDigitsLeftOfDecimal,
2909        char decimalChar, // '.' or ','
2910        int minDigitsRightOfDecimal,
2911        int maxDigitsRightOfDecimal,
2912        char expChar, // 'E' or 'e'
2913        boolean expSign, // whether to print '+' if exp is positive
2914        int minExpDigits, // minimum digits in exponent
2915        char thousandChar, // ',' or '.' or 0
2916        boolean useDecimal,
2917        ArrayStack<Integer> thousandSeparatorPositions)
2918    {
2919        if (expChar != 0) {
2920            // Print the digits left of the 'E'.
2921            int oldExp = fd.decExponent;
2922            fd.decExponent = Math.min(minDigitsLeftOfDecimal, fd.nDigits);
2923            boolean oldIsNegative = fd.isNegative;
2924            fd.isNegative = false;
2925            i = formatFd2(
2926                fd,
2927                result,
2928                i,
2929                minDigitsLeftOfDecimal,
2930                decimalChar,
2931                minDigitsRightOfDecimal,
2932                maxDigitsRightOfDecimal,
2933                '\0',
2934                useDecimal,
2935                thousandSeparatorPositions);
2936            fd.decExponent = oldExp;
2937            fd.isNegative = oldIsNegative;
2938
2939            result[i++] = expChar;
2940            // Print the digits right of the 'E'.
2941            return fd.formatExponent(result, i, expSign, minExpDigits);
2942        } else {
2943            return formatFd2(
2944                fd,
2945                result,
2946                i,
2947                minDigitsLeftOfDecimal,
2948                decimalChar,
2949                minDigitsRightOfDecimal,
2950                maxDigitsRightOfDecimal,
2951                thousandChar,
2952                useDecimal,
2953                thousandSeparatorPositions);
2954        }
2955    }
2956
2957    static int formatFd2(
2958        MondrianFloatingDecimal fd,
2959        char result[],
2960        int i,
2961        int minDigitsLeftOfDecimal,
2962        char decimalChar, // '.' or ','
2963        int minDigitsRightOfDecimal,
2964        int maxDigitsRightOfDecimal,
2965        char thousandChar, // ',' or '.' or 0
2966        boolean useDecimal,
2967        ArrayStack<Integer> thousandSeparatorPositions)
2968    {
2969        if (fd.isNegative) {
2970            result[i++] = '-';
2971        }
2972        if (fd.isExceptional) {
2973            System.arraycopy(fd.digits, 0, result, i, fd.nDigits);
2974            return i + fd.nDigits;
2975        }
2976        // Build a new array of digits, padded with 0s at either end.  For
2977        // example, here is the array we would build for 1234.56.
2978        //
2979        // |  0     0     1     2     3  .  4     5     6     0     0   |
2980        // |           |- nDigits=6 -----------------------|            |
2981        // |           |- decExponent=3 -|                              |
2982        // |- minDigitsLeftOfDecimal=5 --|                              |
2983        // |                             |- minDigitsRightOfDecimal=5 --|
2984        // |- wholeDigits=5 -------------|- fractionDigits=5 -----------|
2985        // |- totalDigits=10 -------------------------------------------|
2986        // |                             |- maxDigitsRightOfDecimal=5 --|
2987        int wholeDigits = Math.max(fd.decExponent, minDigitsLeftOfDecimal),
2988            fractionDigits = Math.max(
2989                fd.nDigits - fd.decExponent, minDigitsRightOfDecimal),
2990            totalDigits = wholeDigits + fractionDigits;
2991        char[] digits2 = new char[totalDigits];
2992        for (int j = 0; j < totalDigits; j++) {
2993            digits2[j] = '0';
2994        }
2995        for (int j = 0; j < fd.nDigits; j++) {
2996            digits2[wholeDigits - fd.decExponent + j] = fd.digits[j];
2997        }
2998
2999        // Now round.  Suppose that we want to round 1234.56 to 1 decimal
3000        // place (that is, maxDigitsRightOfDecimal = 1).  Then lastDigit
3001        // initially points to '5'.  We find out that we need to round only
3002        // when we see that the next digit ('6') is non-zero.
3003        //
3004        // |  0     0     1     2     3  .  4     5     6     0     0   |
3005        // |                             |  ^   |                       |
3006        // |                                maxDigitsRightOfDecimal=1   |
3007        int lastDigit = wholeDigits + maxDigitsRightOfDecimal;
3008        if (lastDigit < totalDigits) {
3009            // We need to truncate -- also round if the trailing digits are
3010            // 5000... or greater.
3011            int m = totalDigits;
3012            while (true) {
3013                m--;
3014                if (m < 0) {
3015                    // The entire number was 9s.  Re-allocate, so we can
3016                    // prepend a '1'.
3017                    wholeDigits++;
3018                    totalDigits++;
3019                    lastDigit++;
3020                    char[] old = digits2;
3021                    digits2 = new char[totalDigits];
3022                    digits2[0] = '1';
3023                    System.arraycopy(old, 0, digits2, 1, old.length);
3024                    break;
3025                } else if (m == lastDigit) {
3026                    char d = digits2[m];
3027                    digits2[m] = '0';
3028                    if (d < '5') {
3029                        break; // no need to round
3030                    }
3031                } else if (m > lastDigit) {
3032                    digits2[m] = '0';
3033                } else if (digits2[m] == '9') {
3034                    digits2[m] = '0';
3035                    // do not break - we have to carry
3036                } else {
3037                    digits2[m]++;
3038                    break; // nothing to carry
3039                }
3040            }
3041        }
3042
3043        // Find the first non-zero digit and the last non-zero digit.
3044        int firstNonZero = wholeDigits,
3045            firstTrailingZero = 0;
3046        for (int j = 0; j < totalDigits; j++) {
3047            if (digits2[j] != '0') {
3048                if (j < firstNonZero) {
3049                    firstNonZero = j;
3050                }
3051                firstTrailingZero = j + 1;
3052            }
3053        }
3054
3055        int firstDigitToPrint = firstNonZero;
3056        if (firstDigitToPrint > wholeDigits - minDigitsLeftOfDecimal) {
3057            firstDigitToPrint = wholeDigits - minDigitsLeftOfDecimal;
3058        }
3059        int lastDigitToPrint = firstTrailingZero;
3060        if (lastDigitToPrint > wholeDigits + maxDigitsRightOfDecimal) {
3061            lastDigitToPrint = wholeDigits + maxDigitsRightOfDecimal;
3062        }
3063        if (lastDigitToPrint < wholeDigits + minDigitsRightOfDecimal) {
3064            lastDigitToPrint = wholeDigits + minDigitsRightOfDecimal;
3065        }
3066
3067        if (thousandChar != '\0'
3068            && thousandSeparatorPositions.size() > 0)
3069        {
3070            // Now print the number. That will happen backwards, so we
3071            // store it temporarily and then invert.
3072            ArrayStack<Character> formattedWholeDigits =
3073                new ArrayStack<Character>();
3074            // We need to keep track of how many digits we printed in the
3075            // current token.
3076            int nbInserted = 0;
3077            for (int j = wholeDigits - 1; j >= firstDigitToPrint; j--) {
3078                // Check if we need to insert another thousand separator
3079                if (nbInserted % thousandSeparatorPositions.peek() == 0
3080                    && nbInserted > 0)
3081                {
3082                    formattedWholeDigits.push(thousandChar);
3083                    nbInserted = 0;
3084                    // The last format token is kept because we re-apply it
3085                    // until the end of the digits.
3086                    if (thousandSeparatorPositions.size() > 1) {
3087                        thousandSeparatorPositions.pop();
3088                    }
3089                }
3090                // Insert the next digit.
3091                formattedWholeDigits.push(digits2[j]);
3092                nbInserted++;
3093            }
3094            // We're done. Invert the print out and add it to
3095            // the result array.
3096            while (formattedWholeDigits.size() > 0) {
3097                result[i++] = formattedWholeDigits.pop();
3098            }
3099        } else {
3100            // There are no thousand separators. Just put the
3101            // digits in the results array.
3102            for (int j = firstDigitToPrint; j < wholeDigits; j++) {
3103                result[i++] = digits2[j];
3104            }
3105        }
3106
3107        if (wholeDigits < lastDigitToPrint
3108            || (useDecimal
3109            && wholeDigits == lastDigitToPrint))
3110        {
3111            result[i++] = decimalChar;
3112        }
3113        for (int j = wholeDigits; j < lastDigitToPrint; j++) {
3114            result[i++] = digits2[j];
3115        }
3116        return i;
3117    }
3118
3119    private enum FormatType {
3120        STRING,
3121        DATE,
3122        NUMERIC
3123    }
3124
3125    private static class DummyDecimalFormat extends DecimalFormat {
3126        private FieldPosition pos;
3127
3128        public StringBuffer format(
3129            double number,
3130            StringBuffer result,
3131            FieldPosition fieldPosition)
3132        {
3133            pos = fieldPosition;
3134            return result;
3135        }
3136    }
3137
3138    /** Specification for MondrianFloatingDecimal. */
3139    private static class MondrianFloatingDecimalSpec {
3140        boolean     isExceptional;
3141        boolean     isNegative;
3142        int         decExponent;
3143        char        digits[];
3144        int         nDigits;
3145
3146        /** Creates a floating decimal with a given value. */
3147        MondrianFloatingDecimalSpec(double n) {
3148        }
3149
3150        /**
3151         * Appends {@link #decExponent} to result string. Returns i plus the
3152         * number of chars written.
3153         *
3154         * <p>Implementation may assume that exponent has 3 or fewer digits.</p>
3155         *
3156         * <p>For example, given {@code decExponent} = 2,
3157         * {@code formatExponent(result, 5, true, 2)}
3158         * will write '0' into result[5]
3159         * and '2' into result[6] and return 7.</p>
3160         *
3161         * @param result Result buffer
3162         * @param i Initial offset into result buffer
3163         * @param expSign Whether to print a '+' sign if exponent is positive
3164         *                (always prints '-' if negative)
3165         * @param minExpDigits Minimum number of digits to write
3166         * @return Offset into result buffer after writing chars
3167         */
3168        int formatExponent(
3169            char[] result,
3170            int i,
3171            boolean expSign,
3172            int minExpDigits)
3173        {
3174            return i;
3175        }
3176
3177        /**
3178         * Handles an exceptional number. If {@link #isExceptional} is false,
3179         * does nothing. If {@link #isExceptional} is true, appends the contents
3180         * of {@link #digits} to result starting from i and returns the
3181         * incremented i.
3182         *
3183         * @param result Result buffer
3184         * @param i Initial offset into result buffer
3185         * @return Offset into result buffer after writing chars
3186         */
3187        int handleExceptional(char[] result, int i) {
3188            return i;
3189        }
3190
3191        /**
3192         * Handles a negative number. If {@link #isNegative}, appends '-' to
3193         * result at i and returns i + 1; otherwise does nothing and returns i.
3194         *
3195         * @param result Result buffer
3196         * @param i Initial offset into result buffer
3197         * @return Offset into result buffer after writing chars
3198         */
3199        int handleNegative(char[] result, int i) {
3200            return i;
3201        }
3202    }
3203}
3204
3205// End Format.java