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