*/
class NumberSkeletonImpl {
- static enum StemType {
- OTHER,
- COMPACT_NOTATION,
- SCIENTIFIC_NOTATION,
- SIMPLE_NOTATION,
- NO_UNIT,
- CURRENCY,
- MEASURE_UNIT,
- PER_MEASURE_UNIT,
- ROUNDER,
- FRACTION_ROUNDER,
- MAYBE_INCREMENT_ROUNDER,
- CURRENCY_ROUNDER,
- GROUPING,
- INTEGER_WIDTH,
- LATIN,
- NUMBERING_SYSTEM,
- UNIT_WIDTH,
- SIGN_DISPLAY,
- DECIMAL_DISPLAY
+ /**
+ * While parsing a skeleton, this enum records what type of option we expect to find next.
+ */
+ static enum ParseState {
+ // Section 0: We expect whitespace or a stem, but not an option:
+ STATE_NULL,
+
+ // Section 1: We might accept an option, but it is not required:
+ STATE_SCIENTIFIC,
+ STATE_ROUNDER,
+ STATE_FRACTION_ROUNDER,
+
+ // Section 2: An option is required:
+ STATE_INCREMENT_ROUNDER,
+ STATE_MEASURE_UNIT,
+ STATE_PER_MEASURE_UNIT,
+ STATE_CURRENCY_UNIT,
+ STATE_INTEGER_WIDTH,
+ STATE_NUMBERING_SYSTEM,
}
- static enum ActualStem {
+ /**
+ * All possible stem literals have an entry in the StemEnum. The enum name is the kebab case stem
+ * string literal written in upper snake case.
+ *
+ * @see StemToObject
+ * @see #SERIALIZED_STEM_TRIE
+ */
+ static enum StemEnum {
// Section 1: Stems that do not require an option:
STEM_COMPACT_SHORT,
STEM_COMPACT_LONG,
STEM_NUMBERING_SYSTEM,
};
- static final ActualStem[] ACTUAL_STEM_VALUES = ActualStem.values();
+ /** For mapping from ordinal back to StemEnum in Java. */
+ static final StemEnum[] STEM_ENUM_VALUES = StemEnum.values();
- private static Notation stemToNotation(ActualStem stem) {
- switch (stem) {
- case STEM_COMPACT_SHORT:
- return Notation.compactShort();
- case STEM_COMPACT_LONG:
- return Notation.compactLong();
- case STEM_SCIENTIFIC:
- return Notation.scientific();
- case STEM_ENGINEERING:
- return Notation.engineering();
- case STEM_NOTATION_SIMPLE:
- return Notation.simple();
- default:
- return null;
+ /**
+ * Utility class for methods that convert from StemEnum to corresponding objects or enums. This
+ * applies to only the "Section 1" stems, those that are well-defined without an option.
+ */
+ static final class StemToObject {
+
+ private static Notation notation(StemEnum stem) {
+ switch (stem) {
+ case STEM_COMPACT_SHORT:
+ return Notation.compactShort();
+ case STEM_COMPACT_LONG:
+ return Notation.compactLong();
+ case STEM_SCIENTIFIC:
+ return Notation.scientific();
+ case STEM_ENGINEERING:
+ return Notation.engineering();
+ case STEM_NOTATION_SIMPLE:
+ return Notation.simple();
+ default:
+ return null;
+ }
}
- }
- private static MeasureUnit stemToUnit(ActualStem stem) {
- switch (stem) {
- case STEM_BASE_UNIT:
- return NoUnit.BASE;
- case STEM_PERCENT:
- return NoUnit.PERCENT;
- case STEM_PERMILLE:
- return NoUnit.PERMILLE;
- default:
- return null;
+ private static MeasureUnit unit(StemEnum stem) {
+ switch (stem) {
+ case STEM_BASE_UNIT:
+ return NoUnit.BASE;
+ case STEM_PERCENT:
+ return NoUnit.PERCENT;
+ case STEM_PERMILLE:
+ return NoUnit.PERMILLE;
+ default:
+ return null;
+ }
}
- }
- private static Rounder stemToRounder(ActualStem stem) {
- switch (stem) {
- case STEM_ROUND_INTEGER:
- return Rounder.integer();
- case STEM_ROUND_UNLIMITED:
- return Rounder.unlimited();
- case STEM_ROUND_CURRENCY_STANDARD:
- return Rounder.currency(CurrencyUsage.STANDARD);
- case STEM_ROUND_CURRENCY_CASH:
- return Rounder.currency(CurrencyUsage.CASH);
- default:
- return null;
+ private static Rounder rounder(StemEnum stem) {
+ switch (stem) {
+ case STEM_ROUND_INTEGER:
+ return Rounder.integer();
+ case STEM_ROUND_UNLIMITED:
+ return Rounder.unlimited();
+ case STEM_ROUND_CURRENCY_STANDARD:
+ return Rounder.currency(CurrencyUsage.STANDARD);
+ case STEM_ROUND_CURRENCY_CASH:
+ return Rounder.currency(CurrencyUsage.CASH);
+ default:
+ return null;
+ }
}
- }
- private static GroupingStrategy stemToGroupingStrategy(ActualStem stem) {
- switch (stem) {
- case STEM_GROUP_OFF:
- return GroupingStrategy.OFF;
- case STEM_GROUP_MIN2:
- return GroupingStrategy.MIN2;
- case STEM_GROUP_AUTO:
- return GroupingStrategy.AUTO;
- case STEM_GROUP_ON_ALIGNED:
- return GroupingStrategy.ON_ALIGNED;
- case STEM_GROUP_THOUSANDS:
- return GroupingStrategy.THOUSANDS;
- default:
- return null;
+ private static GroupingStrategy groupingStrategy(StemEnum stem) {
+ switch (stem) {
+ case STEM_GROUP_OFF:
+ return GroupingStrategy.OFF;
+ case STEM_GROUP_MIN2:
+ return GroupingStrategy.MIN2;
+ case STEM_GROUP_AUTO:
+ return GroupingStrategy.AUTO;
+ case STEM_GROUP_ON_ALIGNED:
+ return GroupingStrategy.ON_ALIGNED;
+ case STEM_GROUP_THOUSANDS:
+ return GroupingStrategy.THOUSANDS;
+ default:
+ return null;
+ }
}
- }
- private static UnitWidth stemToUnitWidth(ActualStem stem) {
- switch (stem) {
- case STEM_UNIT_WIDTH_NARROW:
- return UnitWidth.NARROW;
- case STEM_UNIT_WIDTH_SHORT:
- return UnitWidth.SHORT;
- case STEM_UNIT_WIDTH_FULL_NAME:
- return UnitWidth.FULL_NAME;
- case STEM_UNIT_WIDTH_ISO_CODE:
- return UnitWidth.ISO_CODE;
- case STEM_UNIT_WIDTH_HIDDEN:
- return UnitWidth.HIDDEN;
- default:
- return null;
+ private static UnitWidth unitWidth(StemEnum stem) {
+ switch (stem) {
+ case STEM_UNIT_WIDTH_NARROW:
+ return UnitWidth.NARROW;
+ case STEM_UNIT_WIDTH_SHORT:
+ return UnitWidth.SHORT;
+ case STEM_UNIT_WIDTH_FULL_NAME:
+ return UnitWidth.FULL_NAME;
+ case STEM_UNIT_WIDTH_ISO_CODE:
+ return UnitWidth.ISO_CODE;
+ case STEM_UNIT_WIDTH_HIDDEN:
+ return UnitWidth.HIDDEN;
+ default:
+ return null;
+ }
}
- }
- private static SignDisplay stemToSignDisplay(ActualStem stem) {
- switch (stem) {
- case STEM_SIGN_AUTO:
- return SignDisplay.AUTO;
- case STEM_SIGN_ALWAYS:
- return SignDisplay.ALWAYS;
- case STEM_SIGN_NEVER:
- return SignDisplay.NEVER;
- case STEM_SIGN_ACCOUNTING:
- return SignDisplay.ACCOUNTING;
- case STEM_SIGN_ACCOUNTING_ALWAYS:
- return SignDisplay.ACCOUNTING_ALWAYS;
- case STEM_SIGN_EXCEPT_ZERO:
- return SignDisplay.EXCEPT_ZERO;
- case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO:
- return SignDisplay.ACCOUNTING_EXCEPT_ZERO;
- default:
- return null;
+ private static SignDisplay signDisplay(StemEnum stem) {
+ switch (stem) {
+ case STEM_SIGN_AUTO:
+ return SignDisplay.AUTO;
+ case STEM_SIGN_ALWAYS:
+ return SignDisplay.ALWAYS;
+ case STEM_SIGN_NEVER:
+ return SignDisplay.NEVER;
+ case STEM_SIGN_ACCOUNTING:
+ return SignDisplay.ACCOUNTING;
+ case STEM_SIGN_ACCOUNTING_ALWAYS:
+ return SignDisplay.ACCOUNTING_ALWAYS;
+ case STEM_SIGN_EXCEPT_ZERO:
+ return SignDisplay.EXCEPT_ZERO;
+ case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO:
+ return SignDisplay.ACCOUNTING_EXCEPT_ZERO;
+ default:
+ return null;
+ }
}
- }
- private static DecimalSeparatorDisplay stemToDecimalSeparatorDisplay(ActualStem stem) {
- switch (stem) {
- case STEM_DECIMAL_AUTO:
- return DecimalSeparatorDisplay.AUTO;
- case STEM_DECIMAL_ALWAYS:
- return DecimalSeparatorDisplay.ALWAYS;
- default:
- return null;
+ private static DecimalSeparatorDisplay decimalSeparatorDisplay(StemEnum stem) {
+ switch (stem) {
+ case STEM_DECIMAL_AUTO:
+ return DecimalSeparatorDisplay.AUTO;
+ case STEM_DECIMAL_ALWAYS:
+ return DecimalSeparatorDisplay.ALWAYS;
+ default:
+ return null;
+ }
}
}
- private static void groupingStrategyToStemString(GroupingStrategy value, StringBuilder sb) {
- switch (value) {
- case OFF:
- sb.append("group-off");
- break;
- case MIN2:
- sb.append("group-min2");
- break;
- case AUTO:
- sb.append("group-auto");
- break;
- case ON_ALIGNED:
- sb.append("group-on-aligned");
- break;
- case THOUSANDS:
- sb.append("group-thousands");
- break;
- default:
- throw new AssertionError();
+ /**
+ * Utility class for methods that convert from enums to stem strings. More complex object conversions
+ * take place in ObjectToStemString.
+ */
+ static final class EnumToStemString {
+
+ private static void groupingStrategy(GroupingStrategy value, StringBuilder sb) {
+ switch (value) {
+ case OFF:
+ sb.append("group-off");
+ break;
+ case MIN2:
+ sb.append("group-min2");
+ break;
+ case AUTO:
+ sb.append("group-auto");
+ break;
+ case ON_ALIGNED:
+ sb.append("group-on-aligned");
+ break;
+ case THOUSANDS:
+ sb.append("group-thousands");
+ break;
+ default:
+ throw new AssertionError();
+ }
}
- }
- private static void unitWidthToStemString(UnitWidth value, StringBuilder sb) {
- switch (value) {
- case NARROW:
- sb.append("unit-width-narrow");
- break;
- case SHORT:
- sb.append("unit-width-short");
- break;
- case FULL_NAME:
- sb.append("unit-width-full-name");
- break;
- case ISO_CODE:
- sb.append("unit-width-iso-code");
- break;
- case HIDDEN:
- sb.append("unit-width-hidden");
- break;
- default:
- throw new AssertionError();
+ private static void unitWidth(UnitWidth value, StringBuilder sb) {
+ switch (value) {
+ case NARROW:
+ sb.append("unit-width-narrow");
+ break;
+ case SHORT:
+ sb.append("unit-width-short");
+ break;
+ case FULL_NAME:
+ sb.append("unit-width-full-name");
+ break;
+ case ISO_CODE:
+ sb.append("unit-width-iso-code");
+ break;
+ case HIDDEN:
+ sb.append("unit-width-hidden");
+ break;
+ default:
+ throw new AssertionError();
+ }
}
- }
- private static void signDisplayToStemString(SignDisplay value, StringBuilder sb) {
- switch (value) {
- case AUTO:
- sb.append("sign-auto");
- break;
- case ALWAYS:
- sb.append("sign-always");
- break;
- case NEVER:
- sb.append("sign-never");
- break;
- case ACCOUNTING:
- sb.append("sign-accounting");
- break;
- case ACCOUNTING_ALWAYS:
- sb.append("sign-accounting-always");
- break;
- case EXCEPT_ZERO:
- sb.append("sign-except-zero");
- break;
- case ACCOUNTING_EXCEPT_ZERO:
- sb.append("sign-accounting-except-zero");
- break;
- default:
- throw new AssertionError();
+ private static void signDisplay(SignDisplay value, StringBuilder sb) {
+ switch (value) {
+ case AUTO:
+ sb.append("sign-auto");
+ break;
+ case ALWAYS:
+ sb.append("sign-always");
+ break;
+ case NEVER:
+ sb.append("sign-never");
+ break;
+ case ACCOUNTING:
+ sb.append("sign-accounting");
+ break;
+ case ACCOUNTING_ALWAYS:
+ sb.append("sign-accounting-always");
+ break;
+ case EXCEPT_ZERO:
+ sb.append("sign-except-zero");
+ break;
+ case ACCOUNTING_EXCEPT_ZERO:
+ sb.append("sign-accounting-except-zero");
+ break;
+ default:
+ throw new AssertionError();
+ }
}
- }
- private static void decimalSeparatorDisplayToStemString(DecimalSeparatorDisplay value, StringBuilder sb) {
- switch (value) {
- case AUTO:
- sb.append("decimal-auto");
- break;
- case ALWAYS:
- sb.append("decimal-always");
- break;
- default:
- throw new AssertionError();
+ private static void decimalSeparatorDisplay(DecimalSeparatorDisplay value, StringBuilder sb) {
+ switch (value) {
+ case AUTO:
+ sb.append("decimal-auto");
+ break;
+ case ALWAYS:
+ sb.append("decimal-always");
+ break;
+ default:
+ throw new AssertionError();
+ }
}
}
+ /** A data structure for mapping from stem strings to the stem enum. Built at startup. */
static final String SERIALIZED_STEM_TRIE = buildStemTrie();
static String buildStemTrie() {
CharsTrieBuilder b = new CharsTrieBuilder();
// Section 1:
- b.add("compact-short", ActualStem.STEM_COMPACT_SHORT.ordinal());
- b.add("compact-long", ActualStem.STEM_COMPACT_LONG.ordinal());
- b.add("scientific", ActualStem.STEM_SCIENTIFIC.ordinal());
- b.add("engineering", ActualStem.STEM_ENGINEERING.ordinal());
- b.add("notation-simple", ActualStem.STEM_NOTATION_SIMPLE.ordinal());
- b.add("base-unit", ActualStem.STEM_BASE_UNIT.ordinal());
- b.add("percent", ActualStem.STEM_PERCENT.ordinal());
- b.add("permille", ActualStem.STEM_PERMILLE.ordinal());
- b.add("round-integer", ActualStem.STEM_ROUND_INTEGER.ordinal());
- b.add("round-unlimited", ActualStem.STEM_ROUND_UNLIMITED.ordinal());
- b.add("round-currency-standard", ActualStem.STEM_ROUND_CURRENCY_STANDARD.ordinal());
- b.add("round-currency-cash", ActualStem.STEM_ROUND_CURRENCY_CASH.ordinal());
- b.add("group-off", ActualStem.STEM_GROUP_OFF.ordinal());
- b.add("group-min2", ActualStem.STEM_GROUP_MIN2.ordinal());
- b.add("group-auto", ActualStem.STEM_GROUP_AUTO.ordinal());
- b.add("group-on-aligned", ActualStem.STEM_GROUP_ON_ALIGNED.ordinal());
- b.add("group-thousands", ActualStem.STEM_GROUP_THOUSANDS.ordinal());
- b.add("latin", ActualStem.STEM_LATIN.ordinal());
- b.add("unit-width-narrow", ActualStem.STEM_UNIT_WIDTH_NARROW.ordinal());
- b.add("unit-width-short", ActualStem.STEM_UNIT_WIDTH_SHORT.ordinal());
- b.add("unit-width-full-name", ActualStem.STEM_UNIT_WIDTH_FULL_NAME.ordinal());
- b.add("unit-width-iso-code", ActualStem.STEM_UNIT_WIDTH_ISO_CODE.ordinal());
- b.add("unit-width-hidden", ActualStem.STEM_UNIT_WIDTH_HIDDEN.ordinal());
- b.add("sign-auto", ActualStem.STEM_SIGN_AUTO.ordinal());
- b.add("sign-always", ActualStem.STEM_SIGN_ALWAYS.ordinal());
- b.add("sign-never", ActualStem.STEM_SIGN_NEVER.ordinal());
- b.add("sign-accounting", ActualStem.STEM_SIGN_ACCOUNTING.ordinal());
- b.add("sign-accounting-always", ActualStem.STEM_SIGN_ACCOUNTING_ALWAYS.ordinal());
- b.add("sign-except-zero", ActualStem.STEM_SIGN_EXCEPT_ZERO.ordinal());
- b.add("sign-accounting-except-zero", ActualStem.STEM_SIGN_ACCOUNTING_EXCEPT_ZERO.ordinal());
- b.add("decimal-auto", ActualStem.STEM_DECIMAL_AUTO.ordinal());
- b.add("decimal-always", ActualStem.STEM_DECIMAL_ALWAYS.ordinal());
+ b.add("compact-short", StemEnum.STEM_COMPACT_SHORT.ordinal());
+ b.add("compact-long", StemEnum.STEM_COMPACT_LONG.ordinal());
+ b.add("scientific", StemEnum.STEM_SCIENTIFIC.ordinal());
+ b.add("engineering", StemEnum.STEM_ENGINEERING.ordinal());
+ b.add("notation-simple", StemEnum.STEM_NOTATION_SIMPLE.ordinal());
+ b.add("base-unit", StemEnum.STEM_BASE_UNIT.ordinal());
+ b.add("percent", StemEnum.STEM_PERCENT.ordinal());
+ b.add("permille", StemEnum.STEM_PERMILLE.ordinal());
+ b.add("round-integer", StemEnum.STEM_ROUND_INTEGER.ordinal());
+ b.add("round-unlimited", StemEnum.STEM_ROUND_UNLIMITED.ordinal());
+ b.add("round-currency-standard", StemEnum.STEM_ROUND_CURRENCY_STANDARD.ordinal());
+ b.add("round-currency-cash", StemEnum.STEM_ROUND_CURRENCY_CASH.ordinal());
+ b.add("group-off", StemEnum.STEM_GROUP_OFF.ordinal());
+ b.add("group-min2", StemEnum.STEM_GROUP_MIN2.ordinal());
+ b.add("group-auto", StemEnum.STEM_GROUP_AUTO.ordinal());
+ b.add("group-on-aligned", StemEnum.STEM_GROUP_ON_ALIGNED.ordinal());
+ b.add("group-thousands", StemEnum.STEM_GROUP_THOUSANDS.ordinal());
+ b.add("latin", StemEnum.STEM_LATIN.ordinal());
+ b.add("unit-width-narrow", StemEnum.STEM_UNIT_WIDTH_NARROW.ordinal());
+ b.add("unit-width-short", StemEnum.STEM_UNIT_WIDTH_SHORT.ordinal());
+ b.add("unit-width-full-name", StemEnum.STEM_UNIT_WIDTH_FULL_NAME.ordinal());
+ b.add("unit-width-iso-code", StemEnum.STEM_UNIT_WIDTH_ISO_CODE.ordinal());
+ b.add("unit-width-hidden", StemEnum.STEM_UNIT_WIDTH_HIDDEN.ordinal());
+ b.add("sign-auto", StemEnum.STEM_SIGN_AUTO.ordinal());
+ b.add("sign-always", StemEnum.STEM_SIGN_ALWAYS.ordinal());
+ b.add("sign-never", StemEnum.STEM_SIGN_NEVER.ordinal());
+ b.add("sign-accounting", StemEnum.STEM_SIGN_ACCOUNTING.ordinal());
+ b.add("sign-accounting-always", StemEnum.STEM_SIGN_ACCOUNTING_ALWAYS.ordinal());
+ b.add("sign-except-zero", StemEnum.STEM_SIGN_EXCEPT_ZERO.ordinal());
+ b.add("sign-accounting-except-zero", StemEnum.STEM_SIGN_ACCOUNTING_EXCEPT_ZERO.ordinal());
+ b.add("decimal-auto", StemEnum.STEM_DECIMAL_AUTO.ordinal());
+ b.add("decimal-always", StemEnum.STEM_DECIMAL_ALWAYS.ordinal());
// Section 2:
- b.add("round-increment", ActualStem.STEM_ROUND_INCREMENT.ordinal());
- b.add("measure-unit", ActualStem.STEM_MEASURE_UNIT.ordinal());
- b.add("per-measure-unit", ActualStem.STEM_PER_MEASURE_UNIT.ordinal());
- b.add("currency", ActualStem.STEM_CURRENCY.ordinal());
- b.add("integer-width", ActualStem.STEM_INTEGER_WIDTH.ordinal());
- b.add("numbering-system", ActualStem.STEM_NUMBERING_SYSTEM.ordinal());
+ b.add("round-increment", StemEnum.STEM_ROUND_INCREMENT.ordinal());
+ b.add("measure-unit", StemEnum.STEM_MEASURE_UNIT.ordinal());
+ b.add("per-measure-unit", StemEnum.STEM_PER_MEASURE_UNIT.ordinal());
+ b.add("currency", StemEnum.STEM_CURRENCY.ordinal());
+ b.add("integer-width", StemEnum.STEM_INTEGER_WIDTH.ordinal());
+ b.add("numbering-system", StemEnum.STEM_NUMBERING_SYSTEM.ordinal());
// TODO: Use SLOW or FAST here?
return b.buildCharSequence(StringTrieBuilder.Option.FAST).toString();
}
+ /** Kebab case versions of the rounding mode enum. */
static final String[] ROUNDING_MODE_STRINGS = {
"up",
"down",
"half-even",
"unnecessary" };
+ ///// ENTRYPOINT FUNCTIONS /////
+
+ /** Cache for parsed skeleton strings. */
private static final CacheBase<String, UnlocalizedNumberFormatter, Void> cache = new SoftCache<String, UnlocalizedNumberFormatter, Void>() {
@Override
protected UnlocalizedNumberFormatter createInstance(String skeletonString, Void unused) {
return sb.toString();
}
- /////
+ ///// MAIN PARSING FUNCTIONS /////
+ /**
+ * Converts from a skeleton string to a MacroProps. This method contains the primary parse loop.
+ */
private static MacroProps parseSkeleton(String skeletonString) {
// Add a trailing whitespace to the end of the skeleton string to make code cleaner.
skeletonString += " ";
MacroProps macros = new MacroProps();
StringSegment segment = new StringSegment(skeletonString, false);
CharsTrie stemTrie = new CharsTrie(SERIALIZED_STEM_TRIE, 0);
- StemType stem = null;
+ ParseState stem = ParseState.STATE_NULL;
int offset = 0;
+ // Primary skeleton parse loop:
while (offset < segment.length()) {
int cp = segment.codePointAt(offset);
boolean isTokenSeparator = PatternProps.isWhiteSpace(cp);
if (!isTokenSeparator && !isOptionSeparator) {
// Non-separator token; consume it.
offset += Character.charCount(cp);
- if (stem == null) {
+ if (stem == ParseState.STATE_NULL) {
// We are currently consuming a stem.
// Go to the next state in the stem trie.
stemTrie.nextForCodePoint(cp);
// Otherwise, make sure it is a valid repeating separator.
if (offset != 0) {
segment.setLength(offset);
- if (stem == null) {
+ if (stem == ParseState.STATE_NULL) {
// The first separator after the start of a token. Parse it as a stem.
- stem = parseStem2(segment, stemTrie, macros);
+ stem = parseStem(segment, stemTrie, macros);
stemTrie.reset();
} else {
// A separator after the first separator of a token. Parse it as an option.
stem = parseOption(stem, segment, macros);
}
segment.resetLength();
- segment.adjustOffset(offset + 1);
+
+ // Consume the segment:
+ segment.adjustOffset(offset);
offset = 0;
- } else if (stem != null) {
+ } else if (stem != ParseState.STATE_NULL) {
// A separator ('/' or whitespace) following an option separator ('/')
+ segment.setLength(Character.charCount(cp)); // for error message
throw new SkeletonSyntaxException("Unexpected separator character", segment);
} else {
// Two spaces in a row; this is OK.
- segment.adjustOffset(Character.charCount(cp));
}
- // Make sure we aren't in a state requiring an option, and then reset the state.
- if (isTokenSeparator && stem != null) {
+ // Does the current stem forbid options?
+ if (isOptionSeparator && stem == ParseState.STATE_NULL) {
+ segment.setLength(Character.charCount(cp)); // for error message
+ throw new SkeletonSyntaxException("Unexpected option separator", segment);
+ }
+
+ // Does the current stem require an option?
+ if (isTokenSeparator && stem != ParseState.STATE_NULL) {
switch (stem) {
- case MAYBE_INCREMENT_ROUNDER:
- case MEASURE_UNIT:
- case PER_MEASURE_UNIT:
- case CURRENCY:
- case INTEGER_WIDTH:
- case NUMBERING_SYSTEM:
+ case STATE_INCREMENT_ROUNDER:
+ case STATE_MEASURE_UNIT:
+ case STATE_PER_MEASURE_UNIT:
+ case STATE_CURRENCY_UNIT:
+ case STATE_INTEGER_WIDTH:
+ case STATE_NUMBERING_SYSTEM:
+ segment.setLength(Character.charCount(cp)); // for error message
throw new SkeletonSyntaxException("Stem requires an option", segment);
default:
break;
}
- stem = null;
+ stem = ParseState.STATE_NULL;
}
+
+ // Consume the separator:
+ segment.adjustOffset(Character.charCount(cp));
}
- assert stem == null;
+ assert stem == ParseState.STATE_NULL;
return macros;
}
- private static StemType parseStem2(StringSegment segment, CharsTrie stemTrie, MacroProps macros) {
+ /**
+ * Given that the current segment represents an stem, parse it and save the result.
+ *
+ * @return The next state after parsing this stem, corresponding to what subset of options to expect.
+ */
+ private static ParseState parseStem(StringSegment segment, CharsTrie stemTrie, MacroProps macros) {
// First check for "blueprint" stems, which start with a "signal char"
switch (segment.charAt(0)) {
case '.':
checkNull(macros.rounder, segment);
- parseFractionStem(segment, macros);
- return StemType.FRACTION_ROUNDER;
+ BlueprintHelpers.parseFractionStem(segment, macros);
+ return ParseState.STATE_FRACTION_ROUNDER;
case '@':
checkNull(macros.rounder, segment);
- parseDigitsStem(segment, macros);
- return StemType.OTHER;
+ BlueprintHelpers.parseDigitsStem(segment, macros);
+ return ParseState.STATE_NULL;
}
// Now look at the stemsTrie, which is already be pointing at our stem.
throw new SkeletonSyntaxException("Unknown stem", segment);
}
- ActualStem stemEnum = ACTUAL_STEM_VALUES[stemTrie.getValue()];
- switch (stemEnum) {
+ StemEnum stem = STEM_ENUM_VALUES[stemTrie.getValue()];
+ switch (stem) {
// Stems with meaning on their own, not requiring an option:
case STEM_ENGINEERING:
case STEM_NOTATION_SIMPLE:
checkNull(macros.notation, segment);
- macros.notation = stemToNotation(stemEnum);
- switch (stemEnum) {
+ macros.notation = StemToObject.notation(stem);
+ switch (stem) {
case STEM_SCIENTIFIC:
case STEM_ENGINEERING:
- return StemType.SCIENTIFIC_NOTATION; // allows for scientific options
+ return ParseState.STATE_SCIENTIFIC; // allows for scientific options
default:
- return StemType.OTHER;
+ return ParseState.STATE_NULL;
}
case STEM_BASE_UNIT:
case STEM_PERCENT:
case STEM_PERMILLE:
checkNull(macros.unit, segment);
- macros.unit = stemToUnit(stemEnum);
- return StemType.OTHER;
+ macros.unit = StemToObject.unit(stem);
+ return ParseState.STATE_NULL;
case STEM_ROUND_INTEGER:
case STEM_ROUND_UNLIMITED:
case STEM_ROUND_CURRENCY_STANDARD:
case STEM_ROUND_CURRENCY_CASH:
checkNull(macros.rounder, segment);
- macros.rounder = stemToRounder(stemEnum);
- switch (stemEnum) {
+ macros.rounder = StemToObject.rounder(stem);
+ switch (stem) {
case STEM_ROUND_INTEGER:
- return StemType.FRACTION_ROUNDER; // allows for "round-integer/@##"
+ return ParseState.STATE_FRACTION_ROUNDER; // allows for "round-integer/@##"
default:
- return StemType.ROUNDER; // allows for rounding mode options
+ return ParseState.STATE_ROUNDER; // allows for rounding mode options
}
case STEM_GROUP_OFF:
case STEM_GROUP_ON_ALIGNED:
case STEM_GROUP_THOUSANDS:
checkNull(macros.grouping, segment);
- macros.grouping = stemToGroupingStrategy(stemEnum);
- return StemType.OTHER;
+ macros.grouping = StemToObject.groupingStrategy(stem);
+ return ParseState.STATE_NULL;
case STEM_LATIN:
checkNull(macros.symbols, segment);
macros.symbols = NumberingSystem.LATIN;
- return StemType.OTHER;
+ return ParseState.STATE_NULL;
case STEM_UNIT_WIDTH_NARROW:
case STEM_UNIT_WIDTH_SHORT:
case STEM_UNIT_WIDTH_ISO_CODE:
case STEM_UNIT_WIDTH_HIDDEN:
checkNull(macros.unitWidth, segment);
- macros.unitWidth = stemToUnitWidth(stemEnum);
- return StemType.OTHER;
+ macros.unitWidth = StemToObject.unitWidth(stem);
+ return ParseState.STATE_NULL;
case STEM_SIGN_AUTO:
case STEM_SIGN_ALWAYS:
case STEM_SIGN_EXCEPT_ZERO:
case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO:
checkNull(macros.sign, segment);
- macros.sign = stemToSignDisplay(stemEnum);
- return StemType.OTHER;
+ macros.sign = StemToObject.signDisplay(stem);
+ return ParseState.STATE_NULL;
case STEM_DECIMAL_AUTO:
case STEM_DECIMAL_ALWAYS:
checkNull(macros.decimal, segment);
- macros.decimal = stemToDecimalSeparatorDisplay(stemEnum);
- return StemType.OTHER;
+ macros.decimal = StemToObject.decimalSeparatorDisplay(stem);
+ return ParseState.STATE_NULL;
// Stems requiring an option:
case STEM_ROUND_INCREMENT:
checkNull(macros.rounder, segment);
- return StemType.MAYBE_INCREMENT_ROUNDER;
+ return ParseState.STATE_INCREMENT_ROUNDER;
case STEM_MEASURE_UNIT:
checkNull(macros.unit, segment);
- return StemType.MEASURE_UNIT;
+ return ParseState.STATE_MEASURE_UNIT;
case STEM_PER_MEASURE_UNIT:
checkNull(macros.perUnit, segment);
- return StemType.PER_MEASURE_UNIT;
+ return ParseState.STATE_PER_MEASURE_UNIT;
case STEM_CURRENCY:
checkNull(macros.unit, segment);
- return StemType.CURRENCY;
+ return ParseState.STATE_CURRENCY_UNIT;
case STEM_INTEGER_WIDTH:
checkNull(macros.integerWidth, segment);
- return StemType.INTEGER_WIDTH;
+ return ParseState.STATE_INTEGER_WIDTH;
case STEM_NUMBERING_SYSTEM:
checkNull(macros.symbols, segment);
- return StemType.NUMBERING_SYSTEM;
+ return ParseState.STATE_NUMBERING_SYSTEM;
default:
throw new AssertionError();
}
}
- private static StemType parseOption(StemType stem, StringSegment segment, MacroProps macros) {
+ /**
+ * Given that the current segment represents an option, parse it and save the result.
+ *
+ * @return The next state after parsing this option, corresponding to what subset of options to
+ * expect next.
+ */
+ private static ParseState parseOption(ParseState stem, StringSegment segment, MacroProps macros) {
///// Required options: /////
switch (stem) {
- case CURRENCY:
- parseCurrencyOption(segment, macros);
- return StemType.OTHER;
- case MEASURE_UNIT:
- parseMeasureUnitOption(segment, macros);
- return StemType.OTHER;
- case PER_MEASURE_UNIT:
- parseMeasurePerUnitOption(segment, macros);
- return StemType.OTHER;
- case MAYBE_INCREMENT_ROUNDER:
- parseIncrementOption(segment, macros);
- return StemType.ROUNDER;
- case INTEGER_WIDTH:
- parseIntegerWidthOption(segment, macros);
- return StemType.OTHER;
- case NUMBERING_SYSTEM:
- parseNumberingSystemOption(segment, macros);
- return StemType.OTHER;
+ case STATE_CURRENCY_UNIT:
+ BlueprintHelpers.parseCurrencyOption(segment, macros);
+ return ParseState.STATE_NULL;
+ case STATE_MEASURE_UNIT:
+ BlueprintHelpers.parseMeasureUnitOption(segment, macros);
+ return ParseState.STATE_NULL;
+ case STATE_PER_MEASURE_UNIT:
+ BlueprintHelpers.parseMeasurePerUnitOption(segment, macros);
+ return ParseState.STATE_NULL;
+ case STATE_INCREMENT_ROUNDER:
+ BlueprintHelpers.parseIncrementOption(segment, macros);
+ return ParseState.STATE_ROUNDER;
+ case STATE_INTEGER_WIDTH:
+ BlueprintHelpers.parseIntegerWidthOption(segment, macros);
+ return ParseState.STATE_NULL;
+ case STATE_NUMBERING_SYSTEM:
+ BlueprintHelpers.parseNumberingSystemOption(segment, macros);
+ return ParseState.STATE_NULL;
}
///// Non-required options: /////
// Scientific options
switch (stem) {
- case SCIENTIFIC_NOTATION:
- if (parseExponentWidthOption(segment, macros)) {
- return StemType.SCIENTIFIC_NOTATION;
+ case STATE_SCIENTIFIC:
+ if (BlueprintHelpers.parseExponentWidthOption(segment, macros)) {
+ return ParseState.STATE_SCIENTIFIC;
}
- if (parseExponentSignOption(segment, macros)) {
- return StemType.SCIENTIFIC_NOTATION;
+ if (BlueprintHelpers.parseExponentSignOption(segment, macros)) {
+ return ParseState.STATE_SCIENTIFIC;
}
}
// Frac-sig option
switch (stem) {
- case FRACTION_ROUNDER:
- if (parseFracSigOption(segment, macros)) {
- return StemType.ROUNDER;
+ case STATE_FRACTION_ROUNDER:
+ if (BlueprintHelpers.parseFracSigOption(segment, macros)) {
+ return ParseState.STATE_ROUNDER;
}
}
// Rounding mode option
switch (stem) {
- case ROUNDER:
- case FRACTION_ROUNDER:
- case CURRENCY_ROUNDER:
- if (parseRoundingModeOption(segment, macros)) {
- return StemType.ROUNDER;
+ case STATE_ROUNDER:
+ case STATE_FRACTION_ROUNDER:
+ if (BlueprintHelpers.parseRoundingModeOption(segment, macros)) {
+ return ParseState.STATE_ROUNDER;
}
}
throw new SkeletonSyntaxException("Invalid option", segment);
}
+ ///// MAIN SKELETON GENERATION FUNCTION /////
+
private static void generateSkeleton(MacroProps macros, StringBuilder sb) {
// Supported options
- if (macros.notation != null && generateNotationValue(macros, sb)) {
+ if (macros.notation != null && GeneratorHelpers.notation(macros, sb)) {
sb.append(' ');
}
- if (macros.unit != null && generateUnitValue(macros, sb)) {
+ if (macros.unit != null && GeneratorHelpers.unit(macros, sb)) {
sb.append(' ');
}
- if (macros.perUnit != null && generatePerUnitValue(macros, sb)) {
+ if (macros.perUnit != null && GeneratorHelpers.perUnit(macros, sb)) {
sb.append(' ');
}
- if (macros.rounder != null && generateRoundingValue(macros, sb)) {
+ if (macros.rounder != null && GeneratorHelpers.rounding(macros, sb)) {
sb.append(' ');
}
- if (macros.grouping != null && generateGroupingValue(macros, sb)) {
+ if (macros.grouping != null && GeneratorHelpers.grouping(macros, sb)) {
sb.append(' ');
}
- if (macros.integerWidth != null && generateIntegerWidthValue(macros, sb)) {
+ if (macros.integerWidth != null && GeneratorHelpers.integerWidth(macros, sb)) {
sb.append(' ');
}
- if (macros.symbols != null && generateSymbolsValue(macros, sb)) {
+ if (macros.symbols != null && GeneratorHelpers.symbols(macros, sb)) {
sb.append(' ');
}
- if (macros.unitWidth != null && generateUnitWidthValue(macros, sb)) {
+ if (macros.unitWidth != null && GeneratorHelpers.unitWidth(macros, sb)) {
sb.append(' ');
}
- if (macros.sign != null && generateSignValue(macros, sb)) {
+ if (macros.sign != null && GeneratorHelpers.sign(macros, sb)) {
sb.append(' ');
}
- if (macros.decimal != null && generateDecimalValue(macros, sb)) {
+ if (macros.decimal != null && GeneratorHelpers.decimal(macros, sb)) {
sb.append(' ');
}
}
}
- /////
+ ///// BLUEPRINT HELPER FUNCTIONS (stem and options that cannot be interpreted literally) /////
- private static boolean parseExponentWidthOption(StringSegment segment, MacroProps macros) {
- if (segment.charAt(0) != '+') {
- return false;
- }
- int offset = 1;
- int minExp = 0;
- for (; offset < segment.length(); offset++) {
- if (segment.charAt(offset) == 'e') {
- minExp++;
- } else {
- break;
+ static final class BlueprintHelpers {
+ private static boolean parseExponentWidthOption(StringSegment segment, MacroProps macros) {
+ if (segment.charAt(0) != '+') {
+ return false;
}
+ int offset = 1;
+ int minExp = 0;
+ for (; offset < segment.length(); offset++) {
+ if (segment.charAt(offset) == 'e') {
+ minExp++;
+ } else {
+ break;
+ }
+ }
+ if (offset < segment.length()) {
+ return false;
+ }
+ // Use the public APIs to enforce bounds checking
+ macros.notation = ((ScientificNotation) macros.notation).withMinExponentDigits(minExp);
+ return true;
}
- if (offset < segment.length()) {
- return false;
- }
- // Use the public APIs to enforce bounds checking
- macros.notation = ((ScientificNotation) macros.notation).withMinExponentDigits(minExp);
- return true;
- }
- private static void generateExponentWidthOption(int minExponentDigits, StringBuilder sb) {
- sb.append('+');
- appendMultiple(sb, 'e', minExponentDigits);
- }
-
- private static boolean parseExponentSignOption(StringSegment segment, MacroProps macros) {
- // Get the sign display type out of the CharsTrie data structure.
- // TODO: Make this more efficient (avoid object allocation)? It shouldn't be very hot code.
- CharsTrie tempStemTrie = new CharsTrie(SERIALIZED_STEM_TRIE, 0);
- BytesTrie.Result result = tempStemTrie.next(segment, 0, segment.length());
- if (result != BytesTrie.Result.INTERMEDIATE_VALUE && result != BytesTrie.Result.FINAL_VALUE) {
- return false;
- }
- SignDisplay sign = stemToSignDisplay(ACTUAL_STEM_VALUES[tempStemTrie.getValue()]);
- if (sign == null) {
- return false;
+ private static void generateExponentWidthOption(int minExponentDigits, StringBuilder sb) {
+ sb.append('+');
+ appendMultiple(sb, 'e', minExponentDigits);
}
- macros.notation = ((ScientificNotation) macros.notation).withExponentSignDisplay(sign);
- return true;
- }
-
- private static void generateCurrencyOption(Currency currency, StringBuilder sb) {
- sb.append(currency.getCurrencyCode());
- }
- private static void parseCurrencyOption(StringSegment segment, MacroProps macros) {
- String currencyCode = segment.subSequence(0, segment.length()).toString();
- try {
- macros.unit = Currency.getInstance(currencyCode);
- } catch (IllegalArgumentException e) {
- throw new SkeletonSyntaxException("Invalid currency", segment, e);
+ private static boolean parseExponentSignOption(StringSegment segment, MacroProps macros) {
+ // Get the sign display type out of the CharsTrie data structure.
+ // TODO: Make this more efficient (avoid object allocation)? It shouldn't be very hot code.
+ CharsTrie tempStemTrie = new CharsTrie(SERIALIZED_STEM_TRIE, 0);
+ BytesTrie.Result result = tempStemTrie.next(segment, 0, segment.length());
+ if (result != BytesTrie.Result.INTERMEDIATE_VALUE
+ && result != BytesTrie.Result.FINAL_VALUE) {
+ return false;
+ }
+ SignDisplay sign = StemToObject.signDisplay(STEM_ENUM_VALUES[tempStemTrie.getValue()]);
+ if (sign == null) {
+ return false;
+ }
+ macros.notation = ((ScientificNotation) macros.notation).withExponentSignDisplay(sign);
+ return true;
}
- }
- private static void parseMeasureUnitOption(StringSegment segment, MacroProps macros) {
- // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric)
- // http://unicode.org/reports/tr35/#Validity_Data
- int firstHyphen = 0;
- while (firstHyphen < segment.length() && segment.charAt(firstHyphen) != '-') {
- firstHyphen++;
- }
- if (firstHyphen == segment.length()) {
- throw new SkeletonSyntaxException("Invalid measure unit option", segment);
- }
- String type = segment.subSequence(0, firstHyphen).toString();
- String subType = segment.subSequence(firstHyphen + 1, segment.length()).toString();
- Set<MeasureUnit> units = MeasureUnit.getAvailable(type);
- for (MeasureUnit unit : units) {
- if (subType.equals(unit.getSubtype())) {
- macros.unit = unit;
- return;
+ private static void parseCurrencyOption(StringSegment segment, MacroProps macros) {
+ String currencyCode = segment.subSequence(0, segment.length()).toString();
+ try {
+ macros.unit = Currency.getInstance(currencyCode);
+ } catch (IllegalArgumentException e) {
+ throw new SkeletonSyntaxException("Invalid currency", segment, e);
}
}
- throw new SkeletonSyntaxException("Unknown measure unit", segment);
- }
- private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) {
- sb.append(unit.getType() + "-" + unit.getSubtype());
- }
-
- private static void parseMeasurePerUnitOption(StringSegment segment, MacroProps macros) {
- // A little bit of a hack: safe the current unit (numerator), call the main measure unit parsing
- // code, put back the numerator unit, and put the new unit into per-unit.
- MeasureUnit numerator = macros.unit;
- parseMeasureUnitOption(segment, macros);
- macros.perUnit = macros.unit;
- macros.unit = numerator;
- }
+ private static void generateCurrencyOption(Currency currency, StringBuilder sb) {
+ sb.append(currency.getCurrencyCode());
+ }
- private static void parseFractionStem(StringSegment segment, MacroProps macros) {
- assert segment.charAt(0) == '.';
- int offset = 1;
- int minFrac = 0;
- int maxFrac;
- for (; offset < segment.length(); offset++) {
- if (segment.charAt(offset) == '0') {
- minFrac++;
- } else {
- break;
+ private static void parseMeasureUnitOption(StringSegment segment, MacroProps macros) {
+ // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric)
+ // http://unicode.org/reports/tr35/#Validity_Data
+ int firstHyphen = 0;
+ while (firstHyphen < segment.length() && segment.charAt(firstHyphen) != '-') {
+ firstHyphen++;
}
- }
- if (offset < segment.length()) {
- if (segment.charAt(offset) == '+') {
- maxFrac = -1;
- offset++;
- } else {
- maxFrac = minFrac;
- for (; offset < segment.length(); offset++) {
- if (segment.charAt(offset) == '#') {
- maxFrac++;
- } else {
- break;
- }
+ if (firstHyphen == segment.length()) {
+ throw new SkeletonSyntaxException("Invalid measure unit option", segment);
+ }
+ String type = segment.subSequence(0, firstHyphen).toString();
+ String subType = segment.subSequence(firstHyphen + 1, segment.length()).toString();
+ Set<MeasureUnit> units = MeasureUnit.getAvailable(type);
+ for (MeasureUnit unit : units) {
+ if (subType.equals(unit.getSubtype())) {
+ macros.unit = unit;
+ return;
}
}
- } else {
- maxFrac = minFrac;
- }
- if (offset < segment.length()) {
- throw new SkeletonSyntaxException("Invalid fraction stem", segment);
+ throw new SkeletonSyntaxException("Unknown measure unit", segment);
}
- // Use the public APIs to enforce bounds checking
- if (maxFrac == -1) {
- macros.rounder = Rounder.minFraction(minFrac);
- } else {
- macros.rounder = Rounder.minMaxFraction(minFrac, maxFrac);
- }
- }
- private static void generateFractionStem(int minFrac, int maxFrac, StringBuilder sb) {
- if (minFrac == 0 && maxFrac == 0) {
- sb.append("round-integer");
- return;
+ private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) {
+ sb.append(unit.getType() + "-" + unit.getSubtype());
}
- sb.append('.');
- appendMultiple(sb, '0', minFrac);
- if (maxFrac == -1) {
- sb.append('+');
- } else {
- appendMultiple(sb, '#', maxFrac - minFrac);
+
+ private static void parseMeasurePerUnitOption(StringSegment segment, MacroProps macros) {
+ // A little bit of a hack: safe the current unit (numerator), call the main measure unit
+ // parsing
+ // code, put back the numerator unit, and put the new unit into per-unit.
+ MeasureUnit numerator = macros.unit;
+ parseMeasureUnitOption(segment, macros);
+ macros.perUnit = macros.unit;
+ macros.unit = numerator;
}
- }
- private static void parseDigitsStem(StringSegment segment, MacroProps macros) {
- assert segment.charAt(0) == '@';
- int offset = 0;
- int minSig = 0;
- int maxSig;
- for (; offset < segment.length(); offset++) {
- if (segment.charAt(offset) == '@') {
- minSig++;
+ private static void parseFractionStem(StringSegment segment, MacroProps macros) {
+ assert segment.charAt(0) == '.';
+ int offset = 1;
+ int minFrac = 0;
+ int maxFrac;
+ for (; offset < segment.length(); offset++) {
+ if (segment.charAt(offset) == '0') {
+ minFrac++;
+ } else {
+ break;
+ }
+ }
+ if (offset < segment.length()) {
+ if (segment.charAt(offset) == '+') {
+ maxFrac = -1;
+ offset++;
+ } else {
+ maxFrac = minFrac;
+ for (; offset < segment.length(); offset++) {
+ if (segment.charAt(offset) == '#') {
+ maxFrac++;
+ } else {
+ break;
+ }
+ }
+ }
} else {
- break;
+ maxFrac = minFrac;
+ }
+ if (offset < segment.length()) {
+ throw new SkeletonSyntaxException("Invalid fraction stem", segment);
+ }
+ // Use the public APIs to enforce bounds checking
+ if (maxFrac == -1) {
+ macros.rounder = Rounder.minFraction(minFrac);
+ } else {
+ macros.rounder = Rounder.minMaxFraction(minFrac, maxFrac);
}
}
- if (offset < segment.length()) {
- if (segment.charAt(offset) == '+') {
- maxSig = -1;
- offset++;
+
+ private static void generateFractionStem(int minFrac, int maxFrac, StringBuilder sb) {
+ if (minFrac == 0 && maxFrac == 0) {
+ sb.append("round-integer");
+ return;
+ }
+ sb.append('.');
+ appendMultiple(sb, '0', minFrac);
+ if (maxFrac == -1) {
+ sb.append('+');
} else {
- maxSig = minSig;
- for (; offset < segment.length(); offset++) {
- if (segment.charAt(offset) == '#') {
- maxSig++;
- } else {
- break;
+ appendMultiple(sb, '#', maxFrac - minFrac);
+ }
+ }
+
+ private static void parseDigitsStem(StringSegment segment, MacroProps macros) {
+ assert segment.charAt(0) == '@';
+ int offset = 0;
+ int minSig = 0;
+ int maxSig;
+ for (; offset < segment.length(); offset++) {
+ if (segment.charAt(offset) == '@') {
+ minSig++;
+ } else {
+ break;
+ }
+ }
+ if (offset < segment.length()) {
+ if (segment.charAt(offset) == '+') {
+ maxSig = -1;
+ offset++;
+ } else {
+ maxSig = minSig;
+ for (; offset < segment.length(); offset++) {
+ if (segment.charAt(offset) == '#') {
+ maxSig++;
+ } else {
+ break;
+ }
}
}
+ } else {
+ maxSig = minSig;
+ }
+ if (offset < segment.length()) {
+ throw new SkeletonSyntaxException("Invalid significant digits stem", segment);
+ }
+ // Use the public APIs to enforce bounds checking
+ if (maxSig == -1) {
+ macros.rounder = Rounder.minDigits(minSig);
+ } else {
+ macros.rounder = Rounder.minMaxDigits(minSig, maxSig);
}
- } else {
- maxSig = minSig;
- }
- if (offset < segment.length()) {
- throw new SkeletonSyntaxException("Invalid significant digits stem", segment);
- }
- // Use the public APIs to enforce bounds checking
- if (maxSig == -1) {
- macros.rounder = Rounder.minDigits(minSig);
- } else {
- macros.rounder = Rounder.minMaxDigits(minSig, maxSig);
}
- }
- private static void generateDigitsStem(int minSig, int maxSig, StringBuilder sb) {
- appendMultiple(sb, '@', minSig);
- if (maxSig == -1) {
- sb.append('+');
- } else {
- appendMultiple(sb, '#', maxSig - minSig);
+ private static void generateDigitsStem(int minSig, int maxSig, StringBuilder sb) {
+ appendMultiple(sb, '@', minSig);
+ if (maxSig == -1) {
+ sb.append('+');
+ } else {
+ appendMultiple(sb, '#', maxSig - minSig);
+ }
}
- }
- private static boolean parseFracSigOption(StringSegment segment, MacroProps macros) {
- if (segment.charAt(0) != '@') {
- return false;
- }
- int offset = 0;
- int minSig = 0;
- int maxSig;
- for (; offset < segment.length(); offset++) {
- if (segment.charAt(offset) == '@') {
- minSig++;
+ private static boolean parseFracSigOption(StringSegment segment, MacroProps macros) {
+ if (segment.charAt(0) != '@') {
+ return false;
+ }
+ int offset = 0;
+ int minSig = 0;
+ int maxSig;
+ for (; offset < segment.length(); offset++) {
+ if (segment.charAt(offset) == '@') {
+ minSig++;
+ } else {
+ break;
+ }
+ }
+ // For the frac-sig option, there must be minSig or maxSig but not both.
+ // Valid: @+, @@+, @@@+
+ // Valid: @#, @##, @###
+ // Invalid: @, @@, @@@
+ // Invalid: @@#, @@##, @@@#
+ if (offset < segment.length()) {
+ if (segment.charAt(offset) == '+') {
+ maxSig = -1;
+ offset++;
+ } else if (minSig > 1) {
+ // @@#, @@##, @@@#
+ throw new SkeletonSyntaxException("Invalid digits option for fraction rounder",
+ segment);
+ } else {
+ maxSig = minSig;
+ for (; offset < segment.length(); offset++) {
+ if (segment.charAt(offset) == '#') {
+ maxSig++;
+ } else {
+ break;
+ }
+ }
+ }
} else {
- break;
+ // @, @@, @@@
+ throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment);
}
- }
- // For the frac-sig option, there must be minSig or maxSig but not both.
- // Valid: @+, @@+, @@@+
- // Valid: @#, @##, @###
- // Invalid: @, @@, @@@
- // Invalid: @@#, @@##, @@@#
- if (offset < segment.length()) {
- if (segment.charAt(offset) == '+') {
- maxSig = -1;
- offset++;
- } else if (minSig > 1) {
- // @@#, @@##, @@@#
+ if (offset < segment.length()) {
throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment);
+ }
+
+ FractionRounder oldRounder = (FractionRounder) macros.rounder;
+ if (maxSig == -1) {
+ macros.rounder = oldRounder.withMinDigits(minSig);
} else {
- maxSig = minSig;
- for (; offset < segment.length(); offset++) {
- if (segment.charAt(offset) == '#') {
- maxSig++;
- } else {
- break;
- }
- }
+ macros.rounder = oldRounder.withMaxDigits(maxSig);
}
- } else {
- // @, @@, @@@
- throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment);
- }
- if (offset < segment.length()) {
- throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment);
+ return true;
}
- FractionRounder oldRounder = (FractionRounder) macros.rounder;
- if (maxSig == -1) {
- macros.rounder = oldRounder.withMinDigits(minSig);
- } else {
- macros.rounder = oldRounder.withMaxDigits(maxSig);
+ private static void parseIncrementOption(StringSegment segment, MacroProps macros) {
+ // Call segment.subSequence() because segment.toString() doesn't create a clean string.
+ String str = segment.subSequence(0, segment.length()).toString();
+ BigDecimal increment;
+ try {
+ increment = new BigDecimal(str);
+ } catch (NumberFormatException e) {
+ throw new SkeletonSyntaxException("Invalid rounding increment", segment, e);
+ }
+ macros.rounder = Rounder.increment(increment);
}
- return true;
- }
- private static void parseIncrementOption(StringSegment segment, MacroProps macros) {
- // Call segment.subSequence() because segment.toString() doesn't create a clean string.
- String str = segment.subSequence(0, segment.length()).toString();
- BigDecimal increment;
- try {
- increment = new BigDecimal(str);
- } catch (NumberFormatException e) {
- throw new SkeletonSyntaxException("Invalid rounding increment", segment, e);
+ private static void generateIncrementOption(BigDecimal increment, StringBuilder sb) {
+ sb.append(increment.toPlainString());
}
- macros.rounder = Rounder.increment(increment);
- }
- private static void generateIncrementOption(BigDecimal increment, StringBuilder sb) {
- sb.append(increment.toPlainString());
- }
-
- private static boolean parseRoundingModeOption(StringSegment segment, MacroProps macros) {
- for (int rm = 0; rm < ROUNDING_MODE_STRINGS.length; rm++) {
- if (segment.equals(ROUNDING_MODE_STRINGS[rm])) {
- macros.rounder = macros.rounder.withMode(RoundingMode.valueOf(rm));
- return true;
+ private static boolean parseRoundingModeOption(StringSegment segment, MacroProps macros) {
+ for (int rm = 0; rm < ROUNDING_MODE_STRINGS.length; rm++) {
+ if (segment.equals(ROUNDING_MODE_STRINGS[rm])) {
+ macros.rounder = macros.rounder.withMode(RoundingMode.valueOf(rm));
+ return true;
+ }
}
+ return false;
}
- return false;
- }
- private static void generateRoundingModeOption(RoundingMode mode, StringBuilder sb) {
- String option = ROUNDING_MODE_STRINGS[mode.ordinal()];
- sb.append(option);
- }
+ private static void generateRoundingModeOption(RoundingMode mode, StringBuilder sb) {
+ String option = ROUNDING_MODE_STRINGS[mode.ordinal()];
+ sb.append(option);
+ }
- private static void parseIntegerWidthOption(StringSegment segment, MacroProps macros) {
- int offset = 0;
- int minInt = 0;
- int maxInt;
- if (segment.charAt(0) == '+') {
- maxInt = -1;
- offset++;
- } else {
- maxInt = 0;
- }
- for (; offset < segment.length(); offset++) {
- if (segment.charAt(offset) == '#') {
- maxInt++;
+ private static void parseIntegerWidthOption(StringSegment segment, MacroProps macros) {
+ int offset = 0;
+ int minInt = 0;
+ int maxInt;
+ if (segment.charAt(0) == '+') {
+ maxInt = -1;
+ offset++;
} else {
- break;
+ maxInt = 0;
}
- }
- if (offset < segment.length()) {
for (; offset < segment.length(); offset++) {
- if (segment.charAt(offset) == '0') {
- minInt++;
+ if (segment.charAt(offset) == '#') {
+ maxInt++;
} else {
break;
}
}
+ if (offset < segment.length()) {
+ for (; offset < segment.length(); offset++) {
+ if (segment.charAt(offset) == '0') {
+ minInt++;
+ } else {
+ break;
+ }
+ }
+ }
+ if (maxInt != -1) {
+ maxInt += minInt;
+ }
+ if (offset < segment.length()) {
+ throw new SkeletonSyntaxException("Invalid integer width stem", segment);
+ }
+ // Use the public APIs to enforce bounds checking
+ if (maxInt == -1) {
+ macros.integerWidth = IntegerWidth.zeroFillTo(minInt);
+ } else {
+ macros.integerWidth = IntegerWidth.zeroFillTo(minInt).truncateAt(maxInt);
+ }
}
- if (maxInt != -1) {
- maxInt += minInt;
- }
- if (offset < segment.length()) {
- throw new SkeletonSyntaxException("Invalid integer width stem", segment);
- }
- // Use the public APIs to enforce bounds checking
- if (maxInt == -1) {
- macros.integerWidth = IntegerWidth.zeroFillTo(minInt);
- } else {
- macros.integerWidth = IntegerWidth.zeroFillTo(minInt).truncateAt(maxInt);
+
+ private static void generateIntegerWidthOption(int minInt, int maxInt, StringBuilder sb) {
+ if (maxInt == -1) {
+ sb.append('+');
+ } else {
+ appendMultiple(sb, '#', maxInt - minInt);
+ }
+ appendMultiple(sb, '0', minInt);
}
- }
- private static void generateIntegerWidthOption(int minInt, int maxInt, StringBuilder sb) {
- if (maxInt == -1) {
- sb.append('+');
- } else {
- appendMultiple(sb, '#', maxInt - minInt);
+ private static void parseNumberingSystemOption(StringSegment segment, MacroProps macros) {
+ String nsName = segment.subSequence(0, segment.length()).toString();
+ NumberingSystem ns = NumberingSystem.getInstanceByName(nsName);
+ if (ns == null) {
+ throw new SkeletonSyntaxException("Unknown numbering system", segment);
+ }
+ macros.symbols = ns;
}
- appendMultiple(sb, '0', minInt);
- }
- private static void parseNumberingSystemOption(StringSegment segment, MacroProps macros) {
- String nsName = segment.subSequence(0, segment.length()).toString();
- NumberingSystem ns = NumberingSystem.getInstanceByName(nsName);
- if (ns == null) {
- throw new SkeletonSyntaxException("Unknown numbering system", segment);
+ private static void generateNumberingSystemOption(NumberingSystem ns, StringBuilder sb) {
+ sb.append(ns.getName());
}
- macros.symbols = ns;
}
- private static void generateNumberingSystemOption(NumberingSystem ns, StringBuilder sb) {
- sb.append(ns.getName());
- }
+ ///// STEM GENERATION HELPER FUNCTIONS /////
- /////
+ static final class GeneratorHelpers {
- private static boolean generateNotationValue(MacroProps macros, StringBuilder sb) {
- if (macros.notation instanceof CompactNotation) {
- if (macros.notation == Notation.compactLong()) {
- sb.append("compact-long");
+ private static boolean notation(MacroProps macros, StringBuilder sb) {
+ if (macros.notation instanceof CompactNotation) {
+ if (macros.notation == Notation.compactLong()) {
+ sb.append("compact-long");
+ return true;
+ } else if (macros.notation == Notation.compactShort()) {
+ sb.append("compact-short");
+ return true;
+ } else {
+ // Compact notation generated from custom data (not supported in skeleton)
+ // The other compact notations are literals
+ throw new UnsupportedOperationException(
+ "Cannot generate number skeleton with custom compact data");
+ }
+ } else if (macros.notation instanceof ScientificNotation) {
+ ScientificNotation impl = (ScientificNotation) macros.notation;
+ if (impl.engineeringInterval == 3) {
+ sb.append("engineering");
+ } else {
+ sb.append("scientific");
+ }
+ if (impl.minExponentDigits > 1) {
+ sb.append('/');
+ BlueprintHelpers.generateExponentWidthOption(impl.minExponentDigits, sb);
+ }
+ if (impl.exponentSignDisplay != SignDisplay.AUTO) {
+ sb.append('/');
+ EnumToStemString.signDisplay(impl.exponentSignDisplay, sb);
+ }
return true;
- } else if (macros.notation == Notation.compactShort()) {
- sb.append("compact-short");
+ } else {
+ assert macros.notation instanceof SimpleNotation;
+ // Default value is not shown in normalized form
+ return false;
+ }
+ }
+
+ private static boolean unit(MacroProps macros, StringBuilder sb) {
+ if (macros.unit instanceof Currency) {
+ sb.append("currency/");
+ BlueprintHelpers.generateCurrencyOption((Currency) macros.unit, sb);
return true;
+ } else if (macros.unit instanceof NoUnit) {
+ if (macros.unit == NoUnit.PERCENT) {
+ sb.append("percent");
+ return true;
+ } else if (macros.unit == NoUnit.PERMILLE) {
+ sb.append("permille");
+ return true;
+ } else {
+ assert macros.unit == NoUnit.BASE;
+ // Default value is not shown in normalized form
+ return false;
+ }
} else {
- // Compact notation generated from custom data (not supported in skeleton)
- // The other compact notations are literals
- throw new UnsupportedOperationException(
- "Cannot generate number skeleton with custom compact data");
+ sb.append("measure-unit/");
+ BlueprintHelpers.generateMeasureUnitOption(macros.unit, sb);
+ return true;
}
- } else if (macros.notation instanceof ScientificNotation) {
- ScientificNotation impl = (ScientificNotation) macros.notation;
- if (impl.engineeringInterval == 3) {
- sb.append("engineering");
+ }
+
+ private static boolean perUnit(MacroProps macros, StringBuilder sb) {
+ // Per-units are currently expected to be only MeasureUnits.
+ if (macros.unit instanceof Currency || macros.unit instanceof NoUnit) {
+ throw new UnsupportedOperationException(
+ "Cannot generate number skeleton with per-unit that is not a standard measure unit");
} else {
- sb.append("scientific");
+ sb.append("per-measure-unit/");
+ BlueprintHelpers.generateMeasureUnitOption(macros.perUnit, sb);
+ return true;
}
- if (impl.minExponentDigits > 1) {
+ }
+
+ private static boolean rounding(MacroProps macros, StringBuilder sb) {
+ if (macros.rounder instanceof Rounder.InfiniteRounderImpl) {
+ sb.append("round-unlimited");
+ } else if (macros.rounder instanceof Rounder.FractionRounderImpl) {
+ Rounder.FractionRounderImpl impl = (Rounder.FractionRounderImpl) macros.rounder;
+ BlueprintHelpers.generateFractionStem(impl.minFrac, impl.maxFrac, sb);
+ } else if (macros.rounder instanceof Rounder.SignificantRounderImpl) {
+ Rounder.SignificantRounderImpl impl = (Rounder.SignificantRounderImpl) macros.rounder;
+ BlueprintHelpers.generateDigitsStem(impl.minSig, impl.maxSig, sb);
+ } else if (macros.rounder instanceof Rounder.FracSigRounderImpl) {
+ Rounder.FracSigRounderImpl impl = (Rounder.FracSigRounderImpl) macros.rounder;
+ BlueprintHelpers.generateFractionStem(impl.minFrac, impl.maxFrac, sb);
sb.append('/');
- generateExponentWidthOption(impl.minExponentDigits, sb);
+ if (impl.minSig == -1) {
+ BlueprintHelpers.generateDigitsStem(1, impl.maxSig, sb);
+ } else {
+ BlueprintHelpers.generateDigitsStem(impl.minSig, -1, sb);
+ }
+ } else if (macros.rounder instanceof Rounder.IncrementRounderImpl) {
+ Rounder.IncrementRounderImpl impl = (Rounder.IncrementRounderImpl) macros.rounder;
+ sb.append("round-increment/");
+ BlueprintHelpers.generateIncrementOption(impl.increment, sb);
+ } else if (macros.rounder instanceof Rounder.InfiniteRounderImpl) {
+ sb.append("round-unlimited");
+ } else {
+ assert macros.rounder instanceof Rounder.CurrencyRounderImpl;
+ Rounder.CurrencyRounderImpl impl = (Rounder.CurrencyRounderImpl) macros.rounder;
+ if (impl.usage == CurrencyUsage.STANDARD) {
+ sb.append("round-currency-standard");
+ } else {
+ sb.append("round-currency-cash");
+ }
}
- if (impl.exponentSignDisplay != SignDisplay.AUTO) {
+
+ // Generate the options
+ if (macros.rounder.mathContext != Rounder.DEFAULT_MATH_CONTEXT) {
sb.append('/');
- signDisplayToStemString(impl.exponentSignDisplay, sb);
+ BlueprintHelpers.generateRoundingModeOption(macros.rounder.mathContext.getRoundingMode(),
+ sb);
}
+
+ // NOTE: Always return true for rounding because the default value depends on other options.
return true;
- } else {
- assert macros.notation instanceof SimpleNotation;
- // Default value is not shown in normalized form
- return false;
}
- }
- private static boolean generateUnitValue(MacroProps macros, StringBuilder sb) {
- if (macros.unit instanceof Currency) {
- sb.append("currency/");
- generateCurrencyOption((Currency) macros.unit, sb);
- return true;
- } else if (macros.unit instanceof NoUnit) {
- if (macros.unit == NoUnit.PERCENT) {
- sb.append("percent");
- return true;
- } else if (macros.unit == NoUnit.PERMILLE) {
- sb.append("permille");
+ private static boolean grouping(MacroProps macros, StringBuilder sb) {
+ if (macros.grouping instanceof GroupingStrategy) {
+ if (macros.grouping == GroupingStrategy.AUTO) {
+ return false; // Default value
+ }
+ EnumToStemString.groupingStrategy((GroupingStrategy) macros.grouping, sb);
return true;
} else {
- assert macros.unit == NoUnit.BASE;
- // Default value is not shown in normalized form
- return false;
+ throw new UnsupportedOperationException(
+ "Cannot generate number skeleton with custom Grouper");
}
- } else {
- sb.append("measure-unit/");
- generateMeasureUnitOption(macros.unit, sb);
- return true;
}
- }
- private static boolean generatePerUnitValue(MacroProps macros, StringBuilder sb) {
- // Per-units are currently expected to be only MeasureUnits.
- if (macros.unit instanceof Currency || macros.unit instanceof NoUnit) {
- throw new UnsupportedOperationException(
- "Cannot generate number skeleton with per-unit that is not a standard measure unit");
- } else {
- sb.append("per-measure-unit/");
- generateMeasureUnitOption(macros.perUnit, sb);
+ private static boolean integerWidth(MacroProps macros, StringBuilder sb) {
+ if (macros.integerWidth.equals(IntegerWidth.DEFAULT)) {
+ return false; // Default
+ }
+ sb.append("integer-width/");
+ BlueprintHelpers.generateIntegerWidthOption(macros.integerWidth.minInt,
+ macros.integerWidth.maxInt,
+ sb);
return true;
}
- }
- private static boolean generateRoundingValue(MacroProps macros, StringBuilder sb) {
- if (macros.rounder instanceof Rounder.InfiniteRounderImpl) {
- sb.append("round-unlimited");
- } else if (macros.rounder instanceof Rounder.FractionRounderImpl) {
- Rounder.FractionRounderImpl impl = (Rounder.FractionRounderImpl) macros.rounder;
- generateFractionStem(impl.minFrac, impl.maxFrac, sb);
- } else if (macros.rounder instanceof Rounder.SignificantRounderImpl) {
- Rounder.SignificantRounderImpl impl = (Rounder.SignificantRounderImpl) macros.rounder;
- generateDigitsStem(impl.minSig, impl.maxSig, sb);
- } else if (macros.rounder instanceof Rounder.FracSigRounderImpl) {
- Rounder.FracSigRounderImpl impl = (Rounder.FracSigRounderImpl) macros.rounder;
- generateFractionStem(impl.minFrac, impl.maxFrac, sb);
- sb.append('/');
- if (impl.minSig == -1) {
- generateDigitsStem(1, impl.maxSig, sb);
- } else {
- generateDigitsStem(impl.minSig, -1, sb);
- }
- } else if (macros.rounder instanceof Rounder.IncrementRounderImpl) {
- Rounder.IncrementRounderImpl impl = (Rounder.IncrementRounderImpl) macros.rounder;
- sb.append("round-increment/");
- generateIncrementOption(impl.increment, sb);
- } else if (macros.rounder instanceof Rounder.InfiniteRounderImpl) {
- sb.append("round-unlimited");
- } else {
- assert macros.rounder instanceof Rounder.CurrencyRounderImpl;
- Rounder.CurrencyRounderImpl impl = (Rounder.CurrencyRounderImpl) macros.rounder;
- if (impl.usage == CurrencyUsage.STANDARD) {
- sb.append("round-currency-standard");
+ private static boolean symbols(MacroProps macros, StringBuilder sb) {
+ if (macros.symbols instanceof NumberingSystem) {
+ NumberingSystem ns = (NumberingSystem) macros.symbols;
+ if (ns.getName().equals("latn")) {
+ sb.append("latin");
+ } else {
+ sb.append("numbering-system/");
+ BlueprintHelpers.generateNumberingSystemOption(ns, sb);
+ }
+ return true;
} else {
- sb.append("round-currency-cash");
+ assert macros.symbols instanceof DecimalFormatSymbols;
+ throw new UnsupportedOperationException(
+ "Cannot generate number skeleton with custom DecimalFormatSymbols");
}
}
- // Generate the options
- if (macros.rounder.mathContext != Rounder.DEFAULT_MATH_CONTEXT) {
- sb.append('/');
- generateRoundingModeOption(macros.rounder.mathContext.getRoundingMode(), sb);
- }
-
- // NOTE: Always return true for rounding because the default value depends on other options.
- return true;
- }
-
- private static boolean generateGroupingValue(MacroProps macros, StringBuilder sb) {
- if (macros.grouping instanceof GroupingStrategy) {
- if (macros.grouping == GroupingStrategy.AUTO) {
+ private static boolean unitWidth(MacroProps macros, StringBuilder sb) {
+ if (macros.unitWidth == UnitWidth.SHORT) {
return false; // Default value
}
- groupingStrategyToStemString((GroupingStrategy) macros.grouping, sb);
+ EnumToStemString.unitWidth(macros.unitWidth, sb);
return true;
- } else {
- throw new UnsupportedOperationException(
- "Cannot generate number skeleton with custom Grouper");
}
- }
- private static boolean generateIntegerWidthValue(MacroProps macros, StringBuilder sb) {
- if (macros.integerWidth.equals(IntegerWidth.DEFAULT)) {
- return false; // Default
- }
- sb.append("integer-width/");
- generateIntegerWidthOption(macros.integerWidth.minInt, macros.integerWidth.maxInt, sb);
- return true;
- }
-
- private static boolean generateSymbolsValue(MacroProps macros, StringBuilder sb) {
- if (macros.symbols instanceof NumberingSystem) {
- NumberingSystem ns = (NumberingSystem) macros.symbols;
- if (ns.getName().equals("latn")) {
- sb.append("latin");
- } else {
- sb.append("numbering-system/");
- generateNumberingSystemOption(ns, sb);
+ private static boolean sign(MacroProps macros, StringBuilder sb) {
+ if (macros.sign == SignDisplay.AUTO) {
+ return false; // Default value
}
+ EnumToStemString.signDisplay(macros.sign, sb);
return true;
- } else {
- assert macros.symbols instanceof DecimalFormatSymbols;
- throw new UnsupportedOperationException(
- "Cannot generate number skeleton with custom DecimalFormatSymbols");
- }
- }
-
- private static boolean generateUnitWidthValue(MacroProps macros, StringBuilder sb) {
- if (macros.unitWidth == UnitWidth.SHORT) {
- return false; // Default value
}
- unitWidthToStemString(macros.unitWidth, sb);
- return true;
- }
- private static boolean generateSignValue(MacroProps macros, StringBuilder sb) {
- if (macros.sign == SignDisplay.AUTO) {
- return false; // Default value
+ private static boolean decimal(MacroProps macros, StringBuilder sb) {
+ if (macros.decimal == DecimalSeparatorDisplay.AUTO) {
+ return false; // Default value
+ }
+ EnumToStemString.decimalSeparatorDisplay(macros.decimal, sb);
+ return true;
}
- signDisplayToStemString(macros.sign, sb);
- return true;
- }
- private static boolean generateDecimalValue(MacroProps macros, StringBuilder sb) {
- if (macros.decimal == DecimalSeparatorDisplay.AUTO) {
- return false; // Default value
- }
- decimalSeparatorDisplayToStemString(macros.decimal, sb);
- return true;
}
- /////
+ ///// OTHER UTILITY FUNCTIONS /////
private static void checkNull(Object value, CharSequence content) {
if (value != null) {