1fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert// © 2017 and later: Unicode, Inc. and others.
2fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert// License & terms of use: http://www.unicode.org/copyright.html#License
3fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertpackage com.ibm.icu.impl.number;
4fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
5fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertimport com.ibm.icu.impl.number.Padder.PadPosition;
6fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
7fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert/** Implements a recursive descent parser for decimal format patterns. */
8fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertpublic class PatternStringParser {
9fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
10fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public static final int IGNORE_ROUNDING_NEVER = 0;
11fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public static final int IGNORE_ROUNDING_IF_CURRENCY = 1;
12fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public static final int IGNORE_ROUNDING_ALWAYS = 2;
13fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
14fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    /**
15fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * Runs the recursive descent parser on the given pattern string, returning a data structure with raw information
16fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * about the pattern string.
17fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *
18fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * <p>
19fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * To obtain a more useful form of the data, consider using {@link #parseToProperties} instead.
20fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *
21fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * @param patternString
22fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *            The LDML decimal format pattern (Excel-style pattern) to parse.
23fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * @return The results of the parse.
24fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     */
25fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public static ParsedPatternInfo parseToPatternInfo(String patternString) {
26fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        ParserState state = new ParserState(patternString);
27fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        ParsedPatternInfo result = new ParsedPatternInfo(patternString);
28fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        consumePattern(state, result);
29fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        return result;
30fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
31fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
32fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    /**
33fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * Parses a pattern string into a new property bag.
34fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *
35fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * @param pattern
36fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *            The pattern string, like "#,##0.00"
37fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * @param ignoreRounding
38fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *            Whether to leave out rounding information (minFrac, maxFrac, and rounding increment) when parsing the
39fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *            pattern. This may be desirable if a custom rounding mode, such as CurrencyUsage, is to be used
40fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *            instead. One of {@link PatternStringParser#IGNORE_ROUNDING_ALWAYS},
41fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *            {@link PatternStringParser#IGNORE_ROUNDING_IF_CURRENCY}, or
42fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *            {@link PatternStringParser#IGNORE_ROUNDING_NEVER}.
43fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * @return A property bag object.
44fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * @throws IllegalArgumentException
45fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *             If there is a syntax error in the pattern string.
46fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     */
47fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public static DecimalFormatProperties parseToProperties(String pattern, int ignoreRounding) {
48fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        DecimalFormatProperties properties = new DecimalFormatProperties();
49fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        parseToExistingPropertiesImpl(pattern, properties, ignoreRounding);
50fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        return properties;
51fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
52fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
53fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public static DecimalFormatProperties parseToProperties(String pattern) {
54fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        return parseToProperties(pattern, PatternStringParser.IGNORE_ROUNDING_NEVER);
55fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
56fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
57fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    /**
58fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * Parses a pattern string into an existing property bag. All properties that can be encoded into a pattern string
59fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * will be overwritten with either their default value or with the value coming from the pattern string. Properties
60fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * that cannot be encoded into a pattern string, such as rounding mode, are not modified.
61fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *
62fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * @param pattern
63fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *            The pattern string, like "#,##0.00"
64fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * @param properties
65fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *            The property bag object to overwrite.
66fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * @param ignoreRounding
67fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *            See {@link #parseToProperties(String pattern, int ignoreRounding)}.
68fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * @throws IllegalArgumentException
69fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     *             If there was a syntax error in the pattern string.
70fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     */
71fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public static void parseToExistingProperties(String pattern, DecimalFormatProperties properties,
72fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            int ignoreRounding) {
73fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        parseToExistingPropertiesImpl(pattern, properties, ignoreRounding);
74fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
75fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
76fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public static void parseToExistingProperties(String pattern, DecimalFormatProperties properties) {
77fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        parseToExistingProperties(pattern, properties, PatternStringParser.IGNORE_ROUNDING_NEVER);
78fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
79fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
80fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    /**
81fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     * Contains raw information about the parsed decimal format pattern string.
82fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert     */
83fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public static class ParsedPatternInfo implements AffixPatternProvider {
84fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public String pattern;
85fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public ParsedSubpatternInfo positive;
86fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public ParsedSubpatternInfo negative;
87fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
88fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        private ParsedPatternInfo(String pattern) {
89fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            this.pattern = pattern;
90fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
91fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
92fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        @Override
93fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public char charAt(int flags, int index) {
94fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            long endpoints = getEndpoints(flags);
95fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            int left = (int) (endpoints & 0xffffffff);
96fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            int right = (int) (endpoints >>> 32);
97fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            if (index < 0 || index >= right - left) {
98fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                throw new IndexOutOfBoundsException();
99fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
100fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return pattern.charAt(left + index);
101fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
102fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
103fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        @Override
104fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public int length(int flags) {
105fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return getLengthFromEndpoints(getEndpoints(flags));
106fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
107fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
108fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public static int getLengthFromEndpoints(long endpoints) {
109fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            int left = (int) (endpoints & 0xffffffff);
110fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            int right = (int) (endpoints >>> 32);
111fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return right - left;
112fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
113fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
114fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public String getString(int flags) {
115fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            long endpoints = getEndpoints(flags);
116fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            int left = (int) (endpoints & 0xffffffff);
117fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            int right = (int) (endpoints >>> 32);
118fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            if (left == right) {
119fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                return "";
120fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
121fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return pattern.substring(left, right);
122fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
123fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
124fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        private long getEndpoints(int flags) {
125fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            boolean prefix = (flags & Flags.PREFIX) != 0;
126fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            boolean isNegative = (flags & Flags.NEGATIVE_SUBPATTERN) != 0;
127fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            boolean padding = (flags & Flags.PADDING) != 0;
128fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            if (isNegative && padding) {
129fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                return negative.paddingEndpoints;
130fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            } else if (padding) {
131fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                return positive.paddingEndpoints;
132fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            } else if (prefix && isNegative) {
133fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                return negative.prefixEndpoints;
134fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            } else if (prefix) {
135fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                return positive.prefixEndpoints;
136fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            } else if (isNegative) {
137fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                return negative.suffixEndpoints;
138fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            } else {
139fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                return positive.suffixEndpoints;
140fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
141fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
142fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
143fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        @Override
144fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public boolean positiveHasPlusSign() {
145fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return positive.hasPlusSign;
146fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
147fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
148fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        @Override
149fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public boolean hasNegativeSubpattern() {
150fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return negative != null;
151fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
152fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
153fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        @Override
154fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public boolean negativeHasMinusSign() {
155fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return negative.hasMinusSign;
156fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
157fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
158fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        @Override
159fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public boolean hasCurrencySign() {
160fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return positive.hasCurrencySign || (negative != null && negative.hasCurrencySign);
161fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
162fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
163fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        @Override
164fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public boolean containsSymbolType(int type) {
165fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return AffixUtils.containsType(pattern, type);
166fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
167fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
168fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
169fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public static class ParsedSubpatternInfo {
170fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public long groupingSizes = 0x0000ffffffff0000L;
171fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public int integerLeadingHashSigns = 0;
172fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public int integerTrailingHashSigns = 0;
173fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public int integerNumerals = 0;
174fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public int integerAtSigns = 0;
175fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public int integerTotal = 0; // for convenience
176fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public int fractionNumerals = 0;
177fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public int fractionHashSigns = 0;
178fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public int fractionTotal = 0; // for convenience
179fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public boolean hasDecimal = false;
180fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public int widthExceptAffixes = 0;
181fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public PadPosition paddingLocation = null;
182fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public DecimalQuantity_DualStorageBCD rounding = null;
183fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public boolean exponentHasPlusSign = false;
184fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public int exponentZeros = 0;
185fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public boolean hasPercentSign = false;
186fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public boolean hasPerMilleSign = false;
187fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public boolean hasCurrencySign = false;
188fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public boolean hasMinusSign = false;
189fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public boolean hasPlusSign = false;
190fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
191fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public long prefixEndpoints = 0;
192fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public long suffixEndpoints = 0;
193fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public long paddingEndpoints = 0;
194fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
195fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
196fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    /////////////////////////////////////////////////////
197fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    /// BEGIN RECURSIVE DESCENT PARSER IMPLEMENTATION ///
198fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    /////////////////////////////////////////////////////
199fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
200fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    /** An internal class used for tracking the cursor during parsing of a pattern string. */
201fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static class ParserState {
202fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        final String pattern;
203fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        int offset;
204fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
205fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        ParserState(String pattern) {
206fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            this.pattern = pattern;
207fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            this.offset = 0;
208fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
209fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
210fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        int peek() {
211fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            if (offset == pattern.length()) {
212fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                return -1;
213fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            } else {
214fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                return pattern.codePointAt(offset);
215fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
216fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
217fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
218fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        int next() {
219fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            int codePoint = peek();
220fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            offset += Character.charCount(codePoint);
221fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return codePoint;
222fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
223fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
224fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        IllegalArgumentException toParseException(String message) {
225fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            StringBuilder sb = new StringBuilder();
226fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            sb.append("Malformed pattern for ICU DecimalFormat: \"");
227fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            sb.append(pattern);
228fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            sb.append("\": ");
229fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            sb.append(message);
230fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            sb.append(" at position ");
231fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            sb.append(offset);
232fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return new IllegalArgumentException(sb.toString());
233fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
234fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
235fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
236fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static void consumePattern(ParserState state, ParsedPatternInfo result) {
237fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // pattern := subpattern (';' subpattern)?
238fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        result.positive = new ParsedSubpatternInfo();
239fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        consumeSubpattern(state, result.positive);
240fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (state.peek() == ';') {
241fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            state.next(); // consume the ';'
242fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            // Don't consume the negative subpattern if it is empty (trailing ';')
243fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            if (state.peek() != -1) {
244fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.negative = new ParsedSubpatternInfo();
245fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                consumeSubpattern(state, result.negative);
246fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
247fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
248fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (state.peek() != -1) {
249fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            throw state.toParseException("Found unquoted special character");
250fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
251fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
252fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
253fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static void consumeSubpattern(ParserState state, ParsedSubpatternInfo result) {
254fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // subpattern := literals? number exponent? literals?
255fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        consumePadding(state, result, PadPosition.BEFORE_PREFIX);
256fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        result.prefixEndpoints = consumeAffix(state, result);
257fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        consumePadding(state, result, PadPosition.AFTER_PREFIX);
258fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        consumeFormat(state, result);
259fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        consumeExponent(state, result);
260fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        consumePadding(state, result, PadPosition.BEFORE_SUFFIX);
261fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        result.suffixEndpoints = consumeAffix(state, result);
262fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        consumePadding(state, result, PadPosition.AFTER_SUFFIX);
263fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
264fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
265fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static void consumePadding(ParserState state, ParsedSubpatternInfo result, PadPosition paddingLocation) {
266fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (state.peek() != '*') {
267fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return;
268fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
269fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (result.paddingLocation != null) {
270fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            throw state.toParseException("Cannot have multiple pad specifiers");
271fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
272fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        result.paddingLocation = paddingLocation;
273fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        state.next(); // consume the '*'
274fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        result.paddingEndpoints |= state.offset;
275fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        consumeLiteral(state);
276fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        result.paddingEndpoints |= ((long) state.offset) << 32;
277fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
278fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
279fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static long consumeAffix(ParserState state, ParsedSubpatternInfo result) {
280fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // literals := { literal }
281fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        long endpoints = state.offset;
282fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        outer: while (true) {
283fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            switch (state.peek()) {
284fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '#':
285fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '@':
286fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case ';':
287fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '*':
288fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '.':
289fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case ',':
290fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '0':
291fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '1':
292fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '2':
293fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '3':
294fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '4':
295fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '5':
296fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '6':
297fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '7':
298fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '8':
299fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '9':
300fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case -1:
301fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                // Characters that cannot appear unquoted in a literal
302fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                break outer;
303fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
304fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '%':
305fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.hasPercentSign = true;
306fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                break;
307fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
308fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '‰':
309fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.hasPerMilleSign = true;
310fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                break;
311fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
312fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '¤':
313fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.hasCurrencySign = true;
314fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                break;
315fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
316fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '-':
317fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.hasMinusSign = true;
318fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                break;
319fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
320fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '+':
321fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.hasPlusSign = true;
322fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                break;
323fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
324fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            consumeLiteral(state);
325fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
326fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        endpoints |= ((long) state.offset) << 32;
327fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        return endpoints;
328fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
329fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
330fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static void consumeLiteral(ParserState state) {
331fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (state.peek() == -1) {
332fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            throw state.toParseException("Expected unquoted literal but found EOL");
333fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else if (state.peek() == '\'') {
334fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            state.next(); // consume the starting quote
335fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            while (state.peek() != '\'') {
336fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                if (state.peek() == -1) {
337fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    throw state.toParseException("Expected quoted literal but found EOL");
338fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                } else {
339fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    state.next(); // consume a quoted character
340fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                }
341fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
342fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            state.next(); // consume the ending quote
343fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else {
344fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            // consume a non-quoted literal character
345fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            state.next();
346fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
347fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
348fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
349fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static void consumeFormat(ParserState state, ParsedSubpatternInfo result) {
350fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        consumeIntegerFormat(state, result);
351fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (state.peek() == '.') {
352fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            state.next(); // consume the decimal point
353fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            result.hasDecimal = true;
354fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            result.widthExceptAffixes += 1;
355fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            consumeFractionFormat(state, result);
356fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
357fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
358fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
359fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static void consumeIntegerFormat(ParserState state, ParsedSubpatternInfo result) {
360fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        outer: while (true) {
361fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            switch (state.peek()) {
362fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case ',':
363fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.widthExceptAffixes += 1;
364fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.groupingSizes <<= 16;
365fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                break;
366fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
367fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '#':
368fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                if (result.integerNumerals > 0) {
369fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    throw state.toParseException("# cannot follow 0 before decimal point");
370fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                }
371fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.widthExceptAffixes += 1;
372fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.groupingSizes += 1;
373fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                if (result.integerAtSigns > 0) {
374fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    result.integerTrailingHashSigns += 1;
375fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                } else {
376fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    result.integerLeadingHashSigns += 1;
377fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                }
378fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.integerTotal += 1;
379fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                break;
380fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
381fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '@':
382fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                if (result.integerNumerals > 0) {
383fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    throw state.toParseException("Cannot mix 0 and @");
384fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                }
385fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                if (result.integerTrailingHashSigns > 0) {
386fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    throw state.toParseException("Cannot nest # inside of a run of @");
387fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                }
388fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.widthExceptAffixes += 1;
389fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.groupingSizes += 1;
390fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.integerAtSigns += 1;
391fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.integerTotal += 1;
392fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                break;
393fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
394fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '0':
395fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '1':
396fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '2':
397fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '3':
398fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '4':
399fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '5':
400fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '6':
401fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '7':
402fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '8':
403fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '9':
404fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                if (result.integerAtSigns > 0) {
405fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    throw state.toParseException("Cannot mix @ and 0");
406fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                }
407fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.widthExceptAffixes += 1;
408fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.groupingSizes += 1;
409fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.integerNumerals += 1;
410fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.integerTotal += 1;
411fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                if (state.peek() != '0' && result.rounding == null) {
412fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    result.rounding = new DecimalQuantity_DualStorageBCD();
413fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                }
414fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                if (result.rounding != null) {
415fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    result.rounding.appendDigit((byte) (state.peek() - '0'), 0, true);
416fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                }
417fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                break;
418fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
419fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            default:
420fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                break outer;
421fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
422fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            state.next(); // consume the symbol
423fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
424fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
425fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // Disallow patterns with a trailing ',' or with two ',' next to each other
426fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        short grouping1 = (short) (result.groupingSizes & 0xffff);
427fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        short grouping2 = (short) ((result.groupingSizes >>> 16) & 0xffff);
428fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        short grouping3 = (short) ((result.groupingSizes >>> 32) & 0xffff);
429fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (grouping1 == 0 && grouping2 != -1) {
430fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            throw state.toParseException("Trailing grouping separator is invalid");
431fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
432fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (grouping2 == 0 && grouping3 != -1) {
433fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            throw state.toParseException("Grouping width of zero is invalid");
434fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
435fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
436fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
437fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static void consumeFractionFormat(ParserState state, ParsedSubpatternInfo result) {
438fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        int zeroCounter = 0;
439fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        while (true) {
440fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            switch (state.peek()) {
441fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '#':
442fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.widthExceptAffixes += 1;
443fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.fractionHashSigns += 1;
444fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.fractionTotal += 1;
445fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                zeroCounter++;
446fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                break;
447fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
448fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '0':
449fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '1':
450fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '2':
451fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '3':
452fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '4':
453fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '5':
454fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '6':
455fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '7':
456fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '8':
457fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            case '9':
458fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                if (result.fractionHashSigns > 0) {
459fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    throw state.toParseException("0 cannot follow # after decimal point");
460fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                }
461fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.widthExceptAffixes += 1;
462fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.fractionNumerals += 1;
463fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                result.fractionTotal += 1;
464fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                if (state.peek() == '0') {
465fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    zeroCounter++;
466fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                } else {
467fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    if (result.rounding == null) {
468fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                        result.rounding = new DecimalQuantity_DualStorageBCD();
469fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    }
470fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    result.rounding.appendDigit((byte) (state.peek() - '0'), zeroCounter, false);
471fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    zeroCounter = 0;
472fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                }
473fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                break;
474fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
475fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            default:
476fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                return;
477fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
478fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            state.next(); // consume the symbol
479fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
480fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
481fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
482fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static void consumeExponent(ParserState state, ParsedSubpatternInfo result) {
483fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (state.peek() != 'E') {
484fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return;
485fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
486fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if ((result.groupingSizes & 0xffff0000L) != 0xffff0000L) {
487fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            throw state.toParseException("Cannot have grouping separator in scientific notation");
488fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
489fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        state.next(); // consume the E
490fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        result.widthExceptAffixes++;
491fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (state.peek() == '+') {
492fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            state.next(); // consume the +
493fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            result.exponentHasPlusSign = true;
494fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            result.widthExceptAffixes++;
495fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
496fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        while (state.peek() == '0') {
497fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            state.next(); // consume the 0
498fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            result.exponentZeros += 1;
499fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            result.widthExceptAffixes++;
500fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
501fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
502fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
503fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    ///////////////////////////////////////////////////
504fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    /// END RECURSIVE DESCENT PARSER IMPLEMENTATION ///
505fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    ///////////////////////////////////////////////////
506fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
507fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static void parseToExistingPropertiesImpl(String pattern, DecimalFormatProperties properties, int ignoreRounding) {
508fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (pattern == null || pattern.length() == 0) {
509fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            // Backwards compatibility requires that we reset to the default values.
510fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            // TODO: Only overwrite the properties that "saveToProperties" normally touches?
511fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.clear();
512fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return;
513fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
514fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
515fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // TODO: Use thread locals here?
516fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        ParsedPatternInfo patternInfo = parseToPatternInfo(pattern);
517fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        patternInfoToProperties(properties, patternInfo, ignoreRounding);
518fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
519fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
520fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    /** Finalizes the temporary data stored in the ParsedPatternInfo to the Properties. */
521fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static void patternInfoToProperties(DecimalFormatProperties properties, ParsedPatternInfo patternInfo,
522fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            int _ignoreRounding) {
523fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // Translate from PatternParseResult to Properties.
524fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // Note that most data from "negative" is ignored per the specification of DecimalFormat.
525fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
526fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        ParsedSubpatternInfo positive = patternInfo.positive;
527fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
528fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        boolean ignoreRounding;
529fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (_ignoreRounding == PatternStringParser.IGNORE_ROUNDING_NEVER) {
530fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            ignoreRounding = false;
531fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else if (_ignoreRounding == PatternStringParser.IGNORE_ROUNDING_IF_CURRENCY) {
532fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            ignoreRounding = positive.hasCurrencySign;
533fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else {
534fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            assert _ignoreRounding == PatternStringParser.IGNORE_ROUNDING_ALWAYS;
535fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            ignoreRounding = true;
536fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
537fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
538fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // Grouping settings
539fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        short grouping1 = (short) (positive.groupingSizes & 0xffff);
540fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        short grouping2 = (short) ((positive.groupingSizes >>> 16) & 0xffff);
541fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        short grouping3 = (short) ((positive.groupingSizes >>> 32) & 0xffff);
542fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (grouping2 != -1) {
543fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setGroupingSize(grouping1);
544fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else {
545fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setGroupingSize(-1);
546fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
547fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (grouping3 != -1) {
548fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setSecondaryGroupingSize(grouping2);
549fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else {
550fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setSecondaryGroupingSize(-1);
551fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
552fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
553fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // For backwards compatibility, require that the pattern emit at least one min digit.
554fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        int minInt, minFrac;
555fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (positive.integerTotal == 0 && positive.fractionTotal > 0) {
556fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            // patterns like ".##"
557fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            minInt = 0;
558fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            minFrac = Math.max(1, positive.fractionNumerals);
559fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else if (positive.integerNumerals == 0 && positive.fractionNumerals == 0) {
560fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            // patterns like "#.##"
561fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            minInt = 1;
562fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            minFrac = 0;
563fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else {
564fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            minInt = positive.integerNumerals;
565fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            minFrac = positive.fractionNumerals;
566fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
567fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
568fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // Rounding settings
569fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // Don't set basic rounding when there is a currency sign; defer to CurrencyUsage
570fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (positive.integerAtSigns > 0) {
571fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setMinimumFractionDigits(-1);
572fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setMaximumFractionDigits(-1);
573fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setRoundingIncrement(null);
574fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setMinimumSignificantDigits(positive.integerAtSigns);
575fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setMaximumSignificantDigits(positive.integerAtSigns + positive.integerTrailingHashSigns);
576fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else if (positive.rounding != null) {
577fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            if (!ignoreRounding) {
578fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setMinimumFractionDigits(minFrac);
579fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setMaximumFractionDigits(positive.fractionTotal);
580fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setRoundingIncrement(positive.rounding.toBigDecimal().setScale(positive.fractionNumerals));
581fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            } else {
582fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setMinimumFractionDigits(-1);
583fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setMaximumFractionDigits(-1);
584fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setRoundingIncrement(null);
585fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
586fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setMinimumSignificantDigits(-1);
587fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setMaximumSignificantDigits(-1);
588fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else {
589fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            if (!ignoreRounding) {
590fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setMinimumFractionDigits(minFrac);
591fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setMaximumFractionDigits(positive.fractionTotal);
592fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setRoundingIncrement(null);
593fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            } else {
594fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setMinimumFractionDigits(-1);
595fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setMaximumFractionDigits(-1);
596fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setRoundingIncrement(null);
597fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
598fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setMinimumSignificantDigits(-1);
599fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setMaximumSignificantDigits(-1);
600fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
601fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
602fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // If the pattern ends with a '.' then force the decimal point.
603fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (positive.hasDecimal && positive.fractionTotal == 0) {
604fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setDecimalSeparatorAlwaysShown(true);
605fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else {
606fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setDecimalSeparatorAlwaysShown(false);
607fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
608fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
609fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // Scientific notation settings
610fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (positive.exponentZeros > 0) {
611fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setExponentSignAlwaysShown(positive.exponentHasPlusSign);
612fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setMinimumExponentDigits(positive.exponentZeros);
613fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            if (positive.integerAtSigns == 0) {
614fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                // patterns without '@' can define max integer digits, used for engineering notation
615fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setMinimumIntegerDigits(positive.integerNumerals);
616fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setMaximumIntegerDigits(positive.integerTotal);
617fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            } else {
618fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                // patterns with '@' cannot define max integer digits
619fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setMinimumIntegerDigits(1);
620fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setMaximumIntegerDigits(-1);
621fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
622fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else {
623fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setExponentSignAlwaysShown(false);
624fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setMinimumExponentDigits(-1);
625fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setMinimumIntegerDigits(minInt);
626fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setMaximumIntegerDigits(-1);
627fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
628fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
629fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // Compute the affix patterns (required for both padding and affixes)
630fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        String posPrefix = patternInfo.getString(AffixPatternProvider.Flags.PREFIX);
631fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        String posSuffix = patternInfo.getString(0);
632fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
633fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // Padding settings
634fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (positive.paddingLocation != null) {
635fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            // The width of the positive prefix and suffix templates are included in the padding
636fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            int paddingWidth = positive.widthExceptAffixes + AffixUtils.estimateLength(posPrefix)
637fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    + AffixUtils.estimateLength(posSuffix);
638fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setFormatWidth(paddingWidth);
639fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            String rawPaddingString = patternInfo.getString(AffixPatternProvider.Flags.PADDING);
640fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            if (rawPaddingString.length() == 1) {
641fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setPadString(rawPaddingString);
642fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            } else if (rawPaddingString.length() == 2) {
643fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                if (rawPaddingString.charAt(0) == '\'') {
644fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    properties.setPadString("'");
645fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                } else {
646fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    properties.setPadString(rawPaddingString);
647fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                }
648fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            } else {
649fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                properties.setPadString(rawPaddingString.substring(1, rawPaddingString.length() - 1));
650fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
651fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            assert positive.paddingLocation != null;
652fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setPadPosition(positive.paddingLocation);
653fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else {
654fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setFormatWidth(-1);
655fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setPadString(null);
656fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setPadPosition(null);
657fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
658fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
659fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // Set the affixes
660fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // Always call the setter, even if the prefixes are empty, especially in the case of the
661fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // negative prefix pattern, to prevent default values from overriding the pattern.
662fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        properties.setPositivePrefixPattern(posPrefix);
663fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        properties.setPositiveSuffixPattern(posSuffix);
664fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (patternInfo.negative != null) {
665fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setNegativePrefixPattern(patternInfo
666fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    .getString(AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN | AffixPatternProvider.Flags.PREFIX));
667fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setNegativeSuffixPattern(patternInfo.getString(AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN));
668fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else {
669fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setNegativePrefixPattern(null);
670fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setNegativeSuffixPattern(null);
671fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
672fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
673fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // Set the magnitude multiplier
674fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (positive.hasPercentSign) {
675fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setMagnitudeMultiplier(2);
676fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else if (positive.hasPerMilleSign) {
677fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setMagnitudeMultiplier(3);
678fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        } else {
679fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            properties.setMagnitudeMultiplier(0);
680fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
681fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
682fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert}
683