1717 lines
68 KiB
Java
1717 lines
68 KiB
Java
/* GENERATED SOURCE. DO NOT MODIFY. */
|
|
// © 2018 and later: Unicode, Inc. and others.
|
|
// License & terms of use: http://www.unicode.org/copyright.html
|
|
package android.icu.number;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.math.RoundingMode;
|
|
import java.util.Set;
|
|
|
|
import android.icu.impl.CacheBase;
|
|
import android.icu.impl.PatternProps;
|
|
import android.icu.impl.SoftCache;
|
|
import android.icu.impl.StringSegment;
|
|
import android.icu.impl.number.MacroProps;
|
|
import android.icu.impl.number.RoundingUtils;
|
|
import android.icu.number.NumberFormatter.DecimalSeparatorDisplay;
|
|
import android.icu.number.NumberFormatter.GroupingStrategy;
|
|
import android.icu.number.NumberFormatter.RoundingPriority;
|
|
import android.icu.number.NumberFormatter.SignDisplay;
|
|
import android.icu.number.NumberFormatter.TrailingZeroDisplay;
|
|
import android.icu.number.NumberFormatter.UnitWidth;
|
|
import android.icu.text.DecimalFormatSymbols;
|
|
import android.icu.text.NumberingSystem;
|
|
import android.icu.util.BytesTrie;
|
|
import android.icu.util.CharsTrie;
|
|
import android.icu.util.CharsTrieBuilder;
|
|
import android.icu.util.Currency;
|
|
import android.icu.util.Currency.CurrencyUsage;
|
|
import android.icu.util.MeasureUnit;
|
|
import android.icu.util.NoUnit;
|
|
import android.icu.util.StringTrieBuilder;
|
|
|
|
/**
|
|
* @author sffc
|
|
*
|
|
*/
|
|
class NumberSkeletonImpl {
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// NOTE: For examples of how to add a new stem to the number skeleton parser, see: //
|
|
// https://github.com/unicode-org/icu/commit/a2a7982216b2348070dc71093775ac7195793d73 //
|
|
// and //
|
|
// https://github.com/unicode-org/icu/commit/6fe86f3934a8a5701034f648a8f7c5087e84aa28 //
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* 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_FRACTION_PRECISION,
|
|
STATE_PRECISION,
|
|
|
|
// Section 2: An option is required:
|
|
STATE_INCREMENT_PRECISION,
|
|
STATE_MEASURE_UNIT,
|
|
STATE_PER_MEASURE_UNIT,
|
|
STATE_IDENTIFIER_UNIT,
|
|
STATE_UNIT_USAGE,
|
|
STATE_CURRENCY_UNIT,
|
|
STATE_INTEGER_WIDTH,
|
|
STATE_NUMBERING_SYSTEM,
|
|
STATE_SCALE,
|
|
}
|
|
|
|
/**
|
|
* 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_SCIENTIFIC,
|
|
STEM_ENGINEERING,
|
|
STEM_NOTATION_SIMPLE,
|
|
STEM_BASE_UNIT,
|
|
STEM_PERCENT,
|
|
STEM_PERMILLE,
|
|
STEM_PERCENT_100, // concise-only
|
|
STEM_PRECISION_INTEGER,
|
|
STEM_PRECISION_UNLIMITED,
|
|
STEM_PRECISION_CURRENCY_STANDARD,
|
|
STEM_PRECISION_CURRENCY_CASH,
|
|
STEM_ROUNDING_MODE_CEILING,
|
|
STEM_ROUNDING_MODE_FLOOR,
|
|
STEM_ROUNDING_MODE_DOWN,
|
|
STEM_ROUNDING_MODE_UP,
|
|
STEM_ROUNDING_MODE_HALF_EVEN,
|
|
STEM_ROUNDING_MODE_HALF_DOWN,
|
|
STEM_ROUNDING_MODE_HALF_UP,
|
|
STEM_ROUNDING_MODE_UNNECESSARY,
|
|
STEM_INTEGER_WIDTH_TRUNC,
|
|
STEM_GROUP_OFF,
|
|
STEM_GROUP_MIN2,
|
|
STEM_GROUP_AUTO,
|
|
STEM_GROUP_ON_ALIGNED,
|
|
STEM_GROUP_THOUSANDS,
|
|
STEM_LATIN,
|
|
STEM_UNIT_WIDTH_NARROW,
|
|
STEM_UNIT_WIDTH_SHORT,
|
|
STEM_UNIT_WIDTH_FULL_NAME,
|
|
STEM_UNIT_WIDTH_ISO_CODE,
|
|
STEM_UNIT_WIDTH_FORMAL,
|
|
STEM_UNIT_WIDTH_VARIANT,
|
|
STEM_UNIT_WIDTH_HIDDEN,
|
|
STEM_SIGN_AUTO,
|
|
STEM_SIGN_ALWAYS,
|
|
STEM_SIGN_NEVER,
|
|
STEM_SIGN_ACCOUNTING,
|
|
STEM_SIGN_ACCOUNTING_ALWAYS,
|
|
STEM_SIGN_EXCEPT_ZERO,
|
|
STEM_SIGN_ACCOUNTING_EXCEPT_ZERO,
|
|
STEM_SIGN_NEGATIVE,
|
|
STEM_SIGN_ACCOUNTING_NEGATIVE,
|
|
STEM_DECIMAL_AUTO,
|
|
STEM_DECIMAL_ALWAYS,
|
|
|
|
// Section 2: Stems that DO require an option:
|
|
STEM_PRECISION_INCREMENT,
|
|
STEM_MEASURE_UNIT,
|
|
STEM_PER_MEASURE_UNIT,
|
|
STEM_UNIT,
|
|
STEM_UNIT_USAGE,
|
|
STEM_CURRENCY,
|
|
STEM_INTEGER_WIDTH,
|
|
STEM_NUMBERING_SYSTEM,
|
|
STEM_SCALE,
|
|
};
|
|
|
|
/** Default wildcard char, accepted on input and printed in output */
|
|
static final char WILDCARD_CHAR = '*';
|
|
|
|
/** Alternative wildcard char, accept on input but not printed in output */
|
|
static final char ALT_WILDCARD_CHAR = '+';
|
|
|
|
/** Checks whether the char is a wildcard on input */
|
|
static boolean isWildcardChar(char c) {
|
|
return c == WILDCARD_CHAR || c == ALT_WILDCARD_CHAR;
|
|
}
|
|
|
|
/** For mapping from ordinal back to StemEnum in Java. */
|
|
static final StemEnum[] STEM_ENUM_VALUES = StemEnum.values();
|
|
|
|
/** 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", 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("precision-integer", StemEnum.STEM_PRECISION_INTEGER.ordinal());
|
|
b.add("precision-unlimited", StemEnum.STEM_PRECISION_UNLIMITED.ordinal());
|
|
b.add("precision-currency-standard", StemEnum.STEM_PRECISION_CURRENCY_STANDARD.ordinal());
|
|
b.add("precision-currency-cash", StemEnum.STEM_PRECISION_CURRENCY_CASH.ordinal());
|
|
b.add("rounding-mode-ceiling", StemEnum.STEM_ROUNDING_MODE_CEILING.ordinal());
|
|
b.add("rounding-mode-floor", StemEnum.STEM_ROUNDING_MODE_FLOOR.ordinal());
|
|
b.add("rounding-mode-down", StemEnum.STEM_ROUNDING_MODE_DOWN.ordinal());
|
|
b.add("rounding-mode-up", StemEnum.STEM_ROUNDING_MODE_UP.ordinal());
|
|
b.add("rounding-mode-half-even", StemEnum.STEM_ROUNDING_MODE_HALF_EVEN.ordinal());
|
|
b.add("rounding-mode-half-down", StemEnum.STEM_ROUNDING_MODE_HALF_DOWN.ordinal());
|
|
b.add("rounding-mode-half-up", StemEnum.STEM_ROUNDING_MODE_HALF_UP.ordinal());
|
|
b.add("rounding-mode-unnecessary", StemEnum.STEM_ROUNDING_MODE_UNNECESSARY.ordinal());
|
|
b.add("integer-width-trunc", StemEnum.STEM_INTEGER_WIDTH_TRUNC.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-formal", StemEnum.STEM_UNIT_WIDTH_FORMAL.ordinal());
|
|
b.add("unit-width-variant", StemEnum.STEM_UNIT_WIDTH_VARIANT.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("sign-negative", StemEnum.STEM_SIGN_NEGATIVE.ordinal());
|
|
b.add("sign-accounting-negative", StemEnum.STEM_SIGN_ACCOUNTING_NEGATIVE.ordinal());
|
|
b.add("decimal-auto", StemEnum.STEM_DECIMAL_AUTO.ordinal());
|
|
b.add("decimal-always", StemEnum.STEM_DECIMAL_ALWAYS.ordinal());
|
|
|
|
// Section 2:
|
|
b.add("precision-increment", StemEnum.STEM_PRECISION_INCREMENT.ordinal());
|
|
b.add("measure-unit", StemEnum.STEM_MEASURE_UNIT.ordinal());
|
|
b.add("per-measure-unit", StemEnum.STEM_PER_MEASURE_UNIT.ordinal());
|
|
b.add("unit", StemEnum.STEM_UNIT.ordinal());
|
|
b.add("usage", StemEnum.STEM_UNIT_USAGE.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());
|
|
b.add("scale", StemEnum.STEM_SCALE.ordinal());
|
|
|
|
// Section 3 (concise tokens):
|
|
b.add("K", StemEnum.STEM_COMPACT_SHORT.ordinal());
|
|
b.add("KK", StemEnum.STEM_COMPACT_LONG.ordinal());
|
|
b.add("%", StemEnum.STEM_PERCENT.ordinal());
|
|
b.add("%x100", StemEnum.STEM_PERCENT_100.ordinal());
|
|
b.add(",_", StemEnum.STEM_GROUP_OFF.ordinal());
|
|
b.add(",?", StemEnum.STEM_GROUP_MIN2.ordinal());
|
|
b.add(",!", StemEnum.STEM_GROUP_ON_ALIGNED.ordinal());
|
|
b.add("+!", StemEnum.STEM_SIGN_ALWAYS.ordinal());
|
|
b.add("+_", StemEnum.STEM_SIGN_NEVER.ordinal());
|
|
b.add("()", StemEnum.STEM_SIGN_ACCOUNTING.ordinal());
|
|
b.add("()!", StemEnum.STEM_SIGN_ACCOUNTING_ALWAYS.ordinal());
|
|
b.add("+?", StemEnum.STEM_SIGN_EXCEPT_ZERO.ordinal());
|
|
b.add("()?", StemEnum.STEM_SIGN_ACCOUNTING_EXCEPT_ZERO.ordinal());
|
|
b.add("+-", StemEnum.STEM_SIGN_NEGATIVE.ordinal());
|
|
b.add("()-", StemEnum.STEM_SIGN_ACCOUNTING_NEGATIVE.ordinal());
|
|
|
|
// Build the CharsTrie
|
|
// TODO: Use SLOW or FAST here?
|
|
return b.buildCharSequence(StringTrieBuilder.Option.FAST).toString();
|
|
}
|
|
|
|
/**
|
|
* 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:
|
|
throw new AssertionError();
|
|
}
|
|
}
|
|
|
|
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:
|
|
throw new AssertionError();
|
|
}
|
|
}
|
|
|
|
private static Precision precision(StemEnum stem) {
|
|
switch (stem) {
|
|
case STEM_PRECISION_INTEGER:
|
|
return Precision.integer();
|
|
case STEM_PRECISION_UNLIMITED:
|
|
return Precision.unlimited();
|
|
case STEM_PRECISION_CURRENCY_STANDARD:
|
|
return Precision.currency(CurrencyUsage.STANDARD);
|
|
case STEM_PRECISION_CURRENCY_CASH:
|
|
return Precision.currency(CurrencyUsage.CASH);
|
|
default:
|
|
throw new AssertionError();
|
|
}
|
|
}
|
|
|
|
private static RoundingMode roundingMode(StemEnum stem) {
|
|
switch (stem) {
|
|
case STEM_ROUNDING_MODE_CEILING:
|
|
return RoundingMode.CEILING;
|
|
case STEM_ROUNDING_MODE_FLOOR:
|
|
return RoundingMode.FLOOR;
|
|
case STEM_ROUNDING_MODE_DOWN:
|
|
return RoundingMode.DOWN;
|
|
case STEM_ROUNDING_MODE_UP:
|
|
return RoundingMode.UP;
|
|
case STEM_ROUNDING_MODE_HALF_EVEN:
|
|
return RoundingMode.HALF_EVEN;
|
|
case STEM_ROUNDING_MODE_HALF_DOWN:
|
|
return RoundingMode.HALF_DOWN;
|
|
case STEM_ROUNDING_MODE_HALF_UP:
|
|
return RoundingMode.HALF_UP;
|
|
case STEM_ROUNDING_MODE_UNNECESSARY:
|
|
return RoundingMode.UNNECESSARY;
|
|
default:
|
|
throw new AssertionError();
|
|
}
|
|
}
|
|
|
|
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; // for objects, throw; for enums, 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_FORMAL:
|
|
return UnitWidth.FORMAL;
|
|
case STEM_UNIT_WIDTH_VARIANT:
|
|
return UnitWidth.VARIANT;
|
|
case STEM_UNIT_WIDTH_HIDDEN:
|
|
return UnitWidth.HIDDEN;
|
|
default:
|
|
return null; // for objects, throw; for enums, 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;
|
|
case STEM_SIGN_NEGATIVE:
|
|
return SignDisplay.NEGATIVE;
|
|
case STEM_SIGN_ACCOUNTING_NEGATIVE:
|
|
return SignDisplay.ACCOUNTING_NEGATIVE;
|
|
default:
|
|
return null; // for objects, throw; for enums, 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; // for objects, throw; for enums, return null
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 roundingMode(RoundingMode value, StringBuilder sb) {
|
|
switch (value) {
|
|
case CEILING:
|
|
sb.append("rounding-mode-ceiling");
|
|
break;
|
|
case FLOOR:
|
|
sb.append("rounding-mode-floor");
|
|
break;
|
|
case DOWN:
|
|
sb.append("rounding-mode-down");
|
|
break;
|
|
case UP:
|
|
sb.append("rounding-mode-up");
|
|
break;
|
|
case HALF_EVEN:
|
|
sb.append("rounding-mode-half-even");
|
|
break;
|
|
case HALF_DOWN:
|
|
sb.append("rounding-mode-half-down");
|
|
break;
|
|
case HALF_UP:
|
|
sb.append("rounding-mode-half-up");
|
|
break;
|
|
case UNNECESSARY:
|
|
sb.append("rounding-mode-unnecessary");
|
|
break;
|
|
default:
|
|
throw new AssertionError();
|
|
}
|
|
}
|
|
|
|
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 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 FORMAL:
|
|
sb.append("unit-width-formal");
|
|
break;
|
|
case VARIANT:
|
|
sb.append("unit-width-variant");
|
|
break;
|
|
case HIDDEN:
|
|
sb.append("unit-width-hidden");
|
|
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;
|
|
case NEGATIVE:
|
|
sb.append("sign-negative");
|
|
break;
|
|
case ACCOUNTING_NEGATIVE:
|
|
sb.append("sign-accounting-negative");
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
///// 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 create(skeletonString);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Gets the number formatter for the given number skeleton string from the cache, creating it if it
|
|
* does not exist in the cache.
|
|
*
|
|
* @param skeletonString
|
|
* A number skeleton string, possibly not in its shortest form.
|
|
* @return An UnlocalizedNumberFormatter with behavior defined by the given skeleton string.
|
|
*/
|
|
public static UnlocalizedNumberFormatter getOrCreate(String skeletonString) {
|
|
// TODO: This does not currently check the cache for the normalized form of the skeleton.
|
|
// A new cache implementation would be required for that to work.
|
|
return cache.getInstance(skeletonString, null);
|
|
}
|
|
|
|
/**
|
|
* Creates a NumberFormatter corresponding to the given skeleton string.
|
|
*
|
|
* @param skeletonString
|
|
* A number skeleton string, possibly not in its shortest form.
|
|
* @return An UnlocalizedNumberFormatter with behavior defined by the given skeleton string.
|
|
*/
|
|
public static UnlocalizedNumberFormatter create(String skeletonString) {
|
|
MacroProps macros = parseSkeleton(skeletonString);
|
|
return NumberFormatter.with().macros(macros);
|
|
}
|
|
|
|
/**
|
|
* Create a skeleton string corresponding to the given NumberFormatter.
|
|
*
|
|
* @param macros
|
|
* The NumberFormatter options object.
|
|
* @return A skeleton string in normalized form.
|
|
*/
|
|
public static String generate(MacroProps macros) {
|
|
StringBuilder sb = new StringBuilder();
|
|
generateSkeleton(macros, sb);
|
|
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);
|
|
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);
|
|
boolean isOptionSeparator = (cp == '/');
|
|
|
|
if (!isTokenSeparator && !isOptionSeparator) {
|
|
// Non-separator token; consume it.
|
|
offset += Character.charCount(cp);
|
|
if (stem == ParseState.STATE_NULL) {
|
|
// We are currently consuming a stem.
|
|
// Go to the next state in the stem trie.
|
|
stemTrie.nextForCodePoint(cp);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// We are looking at a token or option separator.
|
|
// If the segment is nonempty, parse it and reset the segment.
|
|
// Otherwise, make sure it is a valid repeating separator.
|
|
if (offset != 0) {
|
|
segment.setLength(offset);
|
|
if (stem == ParseState.STATE_NULL) {
|
|
// The first separator after the start of a token. Parse it as a stem.
|
|
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();
|
|
|
|
// Consume the segment:
|
|
segment.adjustOffset(offset);
|
|
offset = 0;
|
|
|
|
} 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.
|
|
}
|
|
|
|
// 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 STATE_INCREMENT_PRECISION:
|
|
case STATE_MEASURE_UNIT:
|
|
case STATE_PER_MEASURE_UNIT:
|
|
case STATE_IDENTIFIER_UNIT:
|
|
case STATE_UNIT_USAGE:
|
|
case STATE_CURRENCY_UNIT:
|
|
case STATE_INTEGER_WIDTH:
|
|
case STATE_NUMBERING_SYSTEM:
|
|
case STATE_SCALE:
|
|
segment.setLength(Character.charCount(cp)); // for error message
|
|
throw new SkeletonSyntaxException("Stem requires an option", segment);
|
|
default:
|
|
break;
|
|
}
|
|
stem = ParseState.STATE_NULL;
|
|
}
|
|
|
|
// Consume the separator:
|
|
segment.adjustOffset(Character.charCount(cp));
|
|
}
|
|
assert stem == ParseState.STATE_NULL;
|
|
return macros;
|
|
}
|
|
|
|
/**
|
|
* Given that the current segment represents a 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.precision, segment);
|
|
BlueprintHelpers.parseFractionStem(segment, macros);
|
|
return ParseState.STATE_FRACTION_PRECISION;
|
|
case '@':
|
|
checkNull(macros.precision, segment);
|
|
BlueprintHelpers.parseDigitsStem(segment, macros);
|
|
return ParseState.STATE_PRECISION;
|
|
case 'E':
|
|
checkNull(macros.notation, segment);
|
|
BlueprintHelpers.parseScientificStem(segment, macros);
|
|
return ParseState.STATE_NULL;
|
|
case '0':
|
|
checkNull(macros.integerWidth, segment);
|
|
BlueprintHelpers.parseIntegerStem(segment, macros);
|
|
return ParseState.STATE_NULL;
|
|
}
|
|
|
|
// Now look at the stemsTrie, which is already be pointing at our stem.
|
|
BytesTrie.Result stemResult = stemTrie.current();
|
|
|
|
if (stemResult != BytesTrie.Result.INTERMEDIATE_VALUE
|
|
&& stemResult != BytesTrie.Result.FINAL_VALUE) {
|
|
throw new SkeletonSyntaxException("Unknown stem", segment);
|
|
}
|
|
|
|
StemEnum stem = STEM_ENUM_VALUES[stemTrie.getValue()];
|
|
switch (stem) {
|
|
|
|
// Stems with meaning on their own, not requiring an option:
|
|
|
|
case STEM_COMPACT_SHORT:
|
|
case STEM_COMPACT_LONG:
|
|
case STEM_SCIENTIFIC:
|
|
case STEM_ENGINEERING:
|
|
case STEM_NOTATION_SIMPLE:
|
|
checkNull(macros.notation, segment);
|
|
macros.notation = StemToObject.notation(stem);
|
|
switch (stem) {
|
|
case STEM_SCIENTIFIC:
|
|
case STEM_ENGINEERING:
|
|
return ParseState.STATE_SCIENTIFIC; // allows for scientific options
|
|
default:
|
|
return ParseState.STATE_NULL;
|
|
}
|
|
|
|
case STEM_BASE_UNIT:
|
|
case STEM_PERCENT:
|
|
case STEM_PERMILLE:
|
|
checkNull(macros.unit, segment);
|
|
macros.unit = StemToObject.unit(stem);
|
|
return ParseState.STATE_NULL;
|
|
|
|
case STEM_PERCENT_100:
|
|
checkNull(macros.scale, segment);
|
|
checkNull(macros.unit, segment);
|
|
macros.scale = Scale.powerOfTen(2);
|
|
macros.unit = NoUnit.PERCENT;
|
|
return ParseState.STATE_NULL;
|
|
|
|
case STEM_PRECISION_INTEGER:
|
|
case STEM_PRECISION_UNLIMITED:
|
|
case STEM_PRECISION_CURRENCY_STANDARD:
|
|
case STEM_PRECISION_CURRENCY_CASH:
|
|
checkNull(macros.precision, segment);
|
|
macros.precision = StemToObject.precision(stem);
|
|
switch (stem) {
|
|
case STEM_PRECISION_INTEGER:
|
|
return ParseState.STATE_FRACTION_PRECISION; // allows for "precision-integer/@##"
|
|
default:
|
|
return ParseState.STATE_PRECISION;
|
|
}
|
|
|
|
case STEM_ROUNDING_MODE_CEILING:
|
|
case STEM_ROUNDING_MODE_FLOOR:
|
|
case STEM_ROUNDING_MODE_DOWN:
|
|
case STEM_ROUNDING_MODE_UP:
|
|
case STEM_ROUNDING_MODE_HALF_EVEN:
|
|
case STEM_ROUNDING_MODE_HALF_DOWN:
|
|
case STEM_ROUNDING_MODE_HALF_UP:
|
|
case STEM_ROUNDING_MODE_UNNECESSARY:
|
|
checkNull(macros.roundingMode, segment);
|
|
macros.roundingMode = StemToObject.roundingMode(stem);
|
|
return ParseState.STATE_NULL;
|
|
|
|
case STEM_INTEGER_WIDTH_TRUNC:
|
|
checkNull(macros.integerWidth, segment);
|
|
macros.integerWidth = IntegerWidth.zeroFillTo(0).truncateAt(0);
|
|
return ParseState.STATE_NULL;
|
|
|
|
case STEM_GROUP_OFF:
|
|
case STEM_GROUP_MIN2:
|
|
case STEM_GROUP_AUTO:
|
|
case STEM_GROUP_ON_ALIGNED:
|
|
case STEM_GROUP_THOUSANDS:
|
|
checkNull(macros.grouping, segment);
|
|
macros.grouping = StemToObject.groupingStrategy(stem);
|
|
return ParseState.STATE_NULL;
|
|
|
|
case STEM_LATIN:
|
|
checkNull(macros.symbols, segment);
|
|
macros.symbols = NumberingSystem.LATIN;
|
|
return ParseState.STATE_NULL;
|
|
|
|
case STEM_UNIT_WIDTH_NARROW:
|
|
case STEM_UNIT_WIDTH_SHORT:
|
|
case STEM_UNIT_WIDTH_FULL_NAME:
|
|
case STEM_UNIT_WIDTH_ISO_CODE:
|
|
case STEM_UNIT_WIDTH_FORMAL:
|
|
case STEM_UNIT_WIDTH_VARIANT:
|
|
case STEM_UNIT_WIDTH_HIDDEN:
|
|
checkNull(macros.unitWidth, segment);
|
|
macros.unitWidth = StemToObject.unitWidth(stem);
|
|
return ParseState.STATE_NULL;
|
|
|
|
case STEM_SIGN_AUTO:
|
|
case STEM_SIGN_ALWAYS:
|
|
case STEM_SIGN_NEVER:
|
|
case STEM_SIGN_ACCOUNTING:
|
|
case STEM_SIGN_ACCOUNTING_ALWAYS:
|
|
case STEM_SIGN_EXCEPT_ZERO:
|
|
case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO:
|
|
case STEM_SIGN_NEGATIVE:
|
|
case STEM_SIGN_ACCOUNTING_NEGATIVE:
|
|
checkNull(macros.sign, segment);
|
|
macros.sign = StemToObject.signDisplay(stem);
|
|
return ParseState.STATE_NULL;
|
|
|
|
case STEM_DECIMAL_AUTO:
|
|
case STEM_DECIMAL_ALWAYS:
|
|
checkNull(macros.decimal, segment);
|
|
macros.decimal = StemToObject.decimalSeparatorDisplay(stem);
|
|
return ParseState.STATE_NULL;
|
|
|
|
// Stems requiring an option:
|
|
|
|
case STEM_PRECISION_INCREMENT:
|
|
checkNull(macros.precision, segment);
|
|
return ParseState.STATE_INCREMENT_PRECISION;
|
|
|
|
case STEM_MEASURE_UNIT:
|
|
checkNull(macros.unit, segment);
|
|
return ParseState.STATE_MEASURE_UNIT;
|
|
|
|
case STEM_PER_MEASURE_UNIT:
|
|
// In C++, STEM_CURRENCY's checks mark perUnit as "seen". Here we do
|
|
// the inverse: checking that macros.unit is not set to a currency.
|
|
if (macros.unit instanceof Currency) {
|
|
throw new SkeletonSyntaxException("Duplicated setting", segment);
|
|
}
|
|
checkNull(macros.perUnit, segment);
|
|
return ParseState.STATE_PER_MEASURE_UNIT;
|
|
|
|
case STEM_UNIT:
|
|
checkNull(macros.unit, segment);
|
|
checkNull(macros.perUnit, segment);
|
|
return ParseState.STATE_IDENTIFIER_UNIT;
|
|
|
|
case STEM_UNIT_USAGE:
|
|
checkNull(macros.usage, segment);
|
|
return ParseState.STATE_UNIT_USAGE;
|
|
|
|
case STEM_CURRENCY:
|
|
checkNull(macros.unit, segment);
|
|
checkNull(macros.perUnit, segment);
|
|
return ParseState.STATE_CURRENCY_UNIT;
|
|
|
|
case STEM_INTEGER_WIDTH:
|
|
checkNull(macros.integerWidth, segment);
|
|
return ParseState.STATE_INTEGER_WIDTH;
|
|
|
|
case STEM_NUMBERING_SYSTEM:
|
|
checkNull(macros.symbols, segment);
|
|
return ParseState.STATE_NUMBERING_SYSTEM;
|
|
|
|
case STEM_SCALE:
|
|
checkNull(macros.scale, segment);
|
|
return ParseState.STATE_SCALE;
|
|
|
|
default:
|
|
throw new AssertionError();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 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_IDENTIFIER_UNIT:
|
|
BlueprintHelpers.parseIdentifierUnitOption(segment, macros);
|
|
return ParseState.STATE_NULL;
|
|
case STATE_UNIT_USAGE:
|
|
BlueprintHelpers.parseUnitUsageOption(segment, macros);
|
|
return ParseState.STATE_NULL;
|
|
case STATE_INCREMENT_PRECISION:
|
|
BlueprintHelpers.parseIncrementOption(segment, macros);
|
|
return ParseState.STATE_PRECISION;
|
|
case STATE_INTEGER_WIDTH:
|
|
BlueprintHelpers.parseIntegerWidthOption(segment, macros);
|
|
return ParseState.STATE_NULL;
|
|
case STATE_NUMBERING_SYSTEM:
|
|
BlueprintHelpers.parseNumberingSystemOption(segment, macros);
|
|
return ParseState.STATE_NULL;
|
|
case STATE_SCALE:
|
|
BlueprintHelpers.parseScaleOption(segment, macros);
|
|
return ParseState.STATE_NULL;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
///// Non-required options: /////
|
|
|
|
// Scientific options
|
|
switch (stem) {
|
|
case STATE_SCIENTIFIC:
|
|
if (BlueprintHelpers.parseExponentWidthOption(segment, macros)) {
|
|
return ParseState.STATE_SCIENTIFIC;
|
|
}
|
|
if (BlueprintHelpers.parseExponentSignOption(segment, macros)) {
|
|
return ParseState.STATE_SCIENTIFIC;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Frac-sig option
|
|
switch (stem) {
|
|
case STATE_FRACTION_PRECISION:
|
|
if (BlueprintHelpers.parseFracSigOption(segment, macros)) {
|
|
return ParseState.STATE_PRECISION;
|
|
}
|
|
// If the fracSig option was not found, try normal precision options.
|
|
stem = ParseState.STATE_PRECISION;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Trailing zeros option
|
|
switch (stem) {
|
|
case STATE_PRECISION:
|
|
if (BlueprintHelpers.parseTrailingZeroOption(segment, macros)) {
|
|
return ParseState.STATE_NULL;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Unknown option
|
|
throw new SkeletonSyntaxException("Invalid option", segment);
|
|
}
|
|
|
|
///// MAIN SKELETON GENERATION FUNCTION /////
|
|
|
|
/**
|
|
* Main skeleton generator function. Appends the normalized skeleton for the MacroProps to the given
|
|
* StringBuilder.
|
|
*/
|
|
private static void generateSkeleton(MacroProps macros, StringBuilder sb) {
|
|
// Supported options
|
|
if (macros.notation != null && GeneratorHelpers.notation(macros, sb)) {
|
|
sb.append(' ');
|
|
}
|
|
if (macros.unit != null && GeneratorHelpers.unit(macros, sb)) {
|
|
sb.append(' ');
|
|
}
|
|
if (macros.usage != null && GeneratorHelpers.usage(macros, sb)) {
|
|
sb.append(' ');
|
|
}
|
|
if (macros.precision != null && GeneratorHelpers.precision(macros, sb)) {
|
|
sb.append(' ');
|
|
}
|
|
if (macros.roundingMode != null && GeneratorHelpers.roundingMode(macros, sb)) {
|
|
sb.append(' ');
|
|
}
|
|
if (macros.grouping != null && GeneratorHelpers.grouping(macros, sb)) {
|
|
sb.append(' ');
|
|
}
|
|
if (macros.integerWidth != null && GeneratorHelpers.integerWidth(macros, sb)) {
|
|
sb.append(' ');
|
|
}
|
|
if (macros.symbols != null && GeneratorHelpers.symbols(macros, sb)) {
|
|
sb.append(' ');
|
|
}
|
|
if (macros.unitWidth != null && GeneratorHelpers.unitWidth(macros, sb)) {
|
|
sb.append(' ');
|
|
}
|
|
if (macros.sign != null && GeneratorHelpers.sign(macros, sb)) {
|
|
sb.append(' ');
|
|
}
|
|
if (macros.decimal != null && GeneratorHelpers.decimal(macros, sb)) {
|
|
sb.append(' ');
|
|
}
|
|
if (macros.scale != null && GeneratorHelpers.scale(macros, sb)) {
|
|
sb.append(' ');
|
|
}
|
|
|
|
// Unsupported options
|
|
if (macros.padder != null) {
|
|
throw new UnsupportedOperationException(
|
|
"Cannot generate number skeleton with custom padder");
|
|
}
|
|
if (macros.unitDisplayCase != null && !macros.unitDisplayCase.isEmpty()) {
|
|
throw new UnsupportedOperationException(
|
|
"Cannot generate number skeleton with custom unit display case");
|
|
}
|
|
if (macros.affixProvider != null) {
|
|
throw new UnsupportedOperationException(
|
|
"Cannot generate number skeleton with custom affix provider");
|
|
}
|
|
if (macros.rules != null) {
|
|
throw new UnsupportedOperationException(
|
|
"Cannot generate number skeleton with custom plural rules");
|
|
}
|
|
|
|
// Remove the trailing space
|
|
if (sb.length() > 0) {
|
|
sb.setLength(sb.length() - 1);
|
|
}
|
|
}
|
|
|
|
///// BLUEPRINT HELPER FUNCTIONS /////
|
|
|
|
/**
|
|
* Utility class for methods for processing stems and options that cannot be interpreted literally.
|
|
*/
|
|
static final class BlueprintHelpers {
|
|
|
|
/** @return Whether we successfully found and parsed an exponent width option. */
|
|
private static boolean parseExponentWidthOption(StringSegment segment, MacroProps macros) {
|
|
if (!isWildcardChar(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;
|
|
}
|
|
|
|
private static void generateExponentWidthOption(int minExponentDigits, StringBuilder sb) {
|
|
sb.append(WILDCARD_CHAR);
|
|
appendMultiple(sb, 'e', minExponentDigits);
|
|
}
|
|
|
|
/** @return Whether we successfully found and parsed an exponent sign option. */
|
|
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 parseCurrencyOption(StringSegment segment, MacroProps macros) {
|
|
String currencyCode = segment.subSequence(0, segment.length()).toString();
|
|
Currency currency;
|
|
try {
|
|
currency = Currency.getInstance(currencyCode);
|
|
} catch (IllegalArgumentException e) {
|
|
// Not 3 ascii chars
|
|
throw new SkeletonSyntaxException("Invalid currency", segment, e);
|
|
}
|
|
macros.unit = currency;
|
|
}
|
|
|
|
private static void generateCurrencyOption(Currency currency, StringBuilder sb) {
|
|
sb.append(currency.getCurrencyCode());
|
|
}
|
|
|
|
// "measure-unit/" is deprecated in favour of "unit/".
|
|
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;
|
|
}
|
|
}
|
|
throw new SkeletonSyntaxException("Unknown measure unit", segment);
|
|
}
|
|
|
|
// "per-measure-unit/" is deprecated in favour of "unit/".
|
|
private static void parseMeasurePerUnitOption(StringSegment segment, MacroProps macros) {
|
|
// A little bit of a hack: save 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;
|
|
}
|
|
|
|
/**
|
|
* Parses unit identifiers like "meter-per-second" and "foot-and-inch", as
|
|
* specified via a "unit/" concise skeleton.
|
|
*/
|
|
private static void parseIdentifierUnitOption(StringSegment segment, MacroProps macros) {
|
|
try {
|
|
macros.unit = MeasureUnit.forIdentifier(segment.asString());
|
|
} catch (IllegalArgumentException e) {
|
|
throw new SkeletonSyntaxException("Invalid unit stem", segment);
|
|
}
|
|
}
|
|
|
|
private static void parseUnitUsageOption(StringSegment segment, MacroProps macros) {
|
|
macros.usage = segment.asString();
|
|
// We do not do any validation of the usage string: it depends on the
|
|
// unitPreferenceData in the units resources.
|
|
}
|
|
|
|
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 (isWildcardChar(segment.charAt(offset))) {
|
|
maxFrac = -1;
|
|
offset++;
|
|
} else {
|
|
maxFrac = minFrac;
|
|
for (; offset < segment.length(); offset++) {
|
|
if (segment.charAt(offset) == '#') {
|
|
maxFrac++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
maxFrac = minFrac;
|
|
}
|
|
if (offset < segment.length()) {
|
|
throw new SkeletonSyntaxException("Invalid fraction stem", segment);
|
|
}
|
|
// Use the public APIs to enforce bounds checking
|
|
if (maxFrac == -1) {
|
|
if (minFrac == 0) {
|
|
macros.precision = Precision.unlimited();
|
|
} else {
|
|
macros.precision = Precision.minFraction(minFrac);
|
|
}
|
|
} else {
|
|
macros.precision = Precision.minMaxFraction(minFrac, maxFrac);
|
|
}
|
|
}
|
|
|
|
private static void generateFractionStem(int minFrac, int maxFrac, StringBuilder sb) {
|
|
if (minFrac == 0 && maxFrac == 0) {
|
|
sb.append("precision-integer");
|
|
return;
|
|
}
|
|
sb.append('.');
|
|
appendMultiple(sb, '0', minFrac);
|
|
if (maxFrac == -1) {
|
|
sb.append(WILDCARD_CHAR);
|
|
} else {
|
|
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 (isWildcardChar(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.precision = Precision.minSignificantDigits(minSig);
|
|
} else {
|
|
macros.precision = Precision.minMaxSignificantDigits(minSig, maxSig);
|
|
}
|
|
}
|
|
|
|
private static void generateDigitsStem(int minSig, int maxSig, StringBuilder sb) {
|
|
appendMultiple(sb, '@', minSig);
|
|
if (maxSig == -1) {
|
|
sb.append(WILDCARD_CHAR);
|
|
} else {
|
|
appendMultiple(sb, '#', maxSig - minSig);
|
|
}
|
|
}
|
|
|
|
private static void parseScientificStem(StringSegment segment, MacroProps macros) {
|
|
assert(segment.charAt(0) == 'E');
|
|
block:
|
|
{
|
|
int offset = 1;
|
|
if (segment.length() == offset) {
|
|
break block;
|
|
}
|
|
boolean isEngineering = false;
|
|
if (segment.charAt(offset) == 'E') {
|
|
isEngineering = true;
|
|
offset++;
|
|
if (segment.length() == offset) {
|
|
break block;
|
|
}
|
|
}
|
|
SignDisplay signDisplay = SignDisplay.AUTO;
|
|
if (segment.charAt(offset) == '+') {
|
|
offset++;
|
|
if (segment.length() == offset) {
|
|
break block;
|
|
}
|
|
if (segment.charAt(offset) == '!') {
|
|
signDisplay = SignDisplay.ALWAYS;
|
|
} else if (segment.charAt(offset) == '?') {
|
|
signDisplay = SignDisplay.EXCEPT_ZERO;
|
|
} else {
|
|
// NOTE: Other sign displays are not included because they aren't useful in this context
|
|
break block;
|
|
}
|
|
offset++;
|
|
if (segment.length() == offset) {
|
|
break block;
|
|
}
|
|
}
|
|
int minDigits = 0;
|
|
for (; offset < segment.length(); offset++) {
|
|
if (segment.charAt(offset) != '0') {
|
|
break block;
|
|
}
|
|
minDigits++;
|
|
}
|
|
macros.notation = (isEngineering ? Notation.engineering() : Notation.scientific())
|
|
.withExponentSignDisplay(signDisplay)
|
|
.withMinExponentDigits(minDigits);
|
|
return;
|
|
}
|
|
throw new SkeletonSyntaxException("Invalid scientific stem", segment);
|
|
}
|
|
|
|
private static void parseIntegerStem(StringSegment segment, MacroProps macros) {
|
|
assert(segment.charAt(0) == '0');
|
|
int offset = 1;
|
|
for (; offset < segment.length(); offset++) {
|
|
if (segment.charAt(offset) != '0') {
|
|
offset--;
|
|
break;
|
|
}
|
|
}
|
|
if (offset < segment.length()) {
|
|
throw new SkeletonSyntaxException("Invalid integer stem", segment);
|
|
}
|
|
macros.integerWidth = IntegerWidth.zeroFillTo(offset);
|
|
return;
|
|
}
|
|
|
|
/** @return Whether we successfully found and parsed a frac-sig option. */
|
|
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;
|
|
}
|
|
}
|
|
if (offset < segment.length()) {
|
|
if (isWildcardChar(segment.charAt(offset))) {
|
|
// @+, @@+, @@@+
|
|
maxSig = -1;
|
|
offset++;
|
|
} else {
|
|
// @#, @##, @###
|
|
// @@#, @@##, @@@#
|
|
maxSig = minSig;
|
|
for (; offset < segment.length(); offset++) {
|
|
if (segment.charAt(offset) == '#') {
|
|
maxSig++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// @, @@, @@@
|
|
maxSig = minSig;
|
|
}
|
|
FractionPrecision oldRounder = (FractionPrecision) macros.precision;
|
|
if (offset < segment.length()) {
|
|
RoundingPriority priority;
|
|
if (maxSig == -1) {
|
|
throw new SkeletonSyntaxException(
|
|
"Invalid digits option: Wildcard character not allowed with the priority annotation", segment);
|
|
}
|
|
if (segment.codePointAt(offset) == 'r') {
|
|
priority = RoundingPriority.RELAXED;
|
|
offset++;
|
|
} else if (segment.codePointAt(offset) == 's') {
|
|
priority = RoundingPriority.STRICT;
|
|
offset++;
|
|
} else {
|
|
assert offset < segment.length();
|
|
priority = RoundingPriority.RELAXED; // make compiler happy (uninitialized variable)
|
|
}
|
|
if (offset < segment.length()) {
|
|
throw new SkeletonSyntaxException(
|
|
"Invalid digits option for fraction rounder", segment);
|
|
}
|
|
macros.precision = oldRounder.withSignificantDigits(minSig, maxSig, priority);
|
|
} else if (maxSig == -1) {
|
|
// withMinDigits
|
|
macros.precision = oldRounder.withMinDigits(minSig);
|
|
} else if (minSig == 1) {
|
|
// withMaxDigits
|
|
macros.precision = oldRounder.withMaxDigits(maxSig);
|
|
} else {
|
|
throw new SkeletonSyntaxException(
|
|
"Invalid digits option: Priority annotation required", segment);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** @return Whether we successfully found and parsed a trailing zero option. */
|
|
private static boolean parseTrailingZeroOption(StringSegment segment, MacroProps macros) {
|
|
if (segment.contentEquals("w")) {
|
|
macros.precision = macros.precision.trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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.precision = Precision.increment(increment);
|
|
}
|
|
|
|
private static void generateIncrementOption(BigDecimal increment, StringBuilder sb) {
|
|
sb.append(increment.toPlainString());
|
|
}
|
|
|
|
private static void parseIntegerWidthOption(StringSegment segment, MacroProps macros) {
|
|
int offset = 0;
|
|
int minInt = 0;
|
|
int maxInt;
|
|
if (isWildcardChar(segment.charAt(0))) {
|
|
maxInt = -1;
|
|
offset++;
|
|
} else {
|
|
maxInt = 0;
|
|
}
|
|
for (; offset < segment.length(); offset++) {
|
|
if (maxInt != -1 && 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);
|
|
}
|
|
}
|
|
|
|
private static void generateIntegerWidthOption(int minInt, int maxInt, StringBuilder sb) {
|
|
if (maxInt == -1) {
|
|
sb.append(WILDCARD_CHAR);
|
|
} else {
|
|
appendMultiple(sb, '#', maxInt - minInt);
|
|
}
|
|
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);
|
|
}
|
|
macros.symbols = ns;
|
|
}
|
|
|
|
private static void generateNumberingSystemOption(NumberingSystem ns, StringBuilder sb) {
|
|
sb.append(ns.getName());
|
|
}
|
|
|
|
private static void parseScaleOption(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 bd;
|
|
try {
|
|
bd = new BigDecimal(str);
|
|
} catch (NumberFormatException e) {
|
|
throw new SkeletonSyntaxException("Invalid scale", segment, e);
|
|
}
|
|
// NOTE: If bd is a power of ten, the Scale API optimizes it for us.
|
|
macros.scale = Scale.byBigDecimal(bd);
|
|
}
|
|
|
|
private static void generateScaleOption(Scale scale, StringBuilder sb) {
|
|
BigDecimal bd = scale.arbitrary;
|
|
if (bd == null) {
|
|
bd = BigDecimal.ONE;
|
|
}
|
|
bd = bd.scaleByPowerOfTen(scale.magnitude);
|
|
sb.append(bd.toPlainString());
|
|
}
|
|
}
|
|
|
|
///// STEM GENERATION HELPER FUNCTIONS /////
|
|
|
|
/**
|
|
* Utility class for methods for generating a token corresponding to each macro-prop. Each method
|
|
* returns whether or not a token was written to the string builder.
|
|
*/
|
|
static final class GeneratorHelpers {
|
|
|
|
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 {
|
|
assert macros.notation instanceof SimpleNotation;
|
|
// Default value is not shown in normalized form
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static boolean unit(MacroProps macros, StringBuilder sb) {
|
|
MeasureUnit unit = macros.unit;
|
|
if (macros.perUnit != null) {
|
|
if (macros.unit instanceof Currency || macros.perUnit instanceof Currency) {
|
|
throw new UnsupportedOperationException(
|
|
"Cannot generate number skeleton with currency unit and per-unit");
|
|
}
|
|
unit = unit.product(macros.perUnit.reciprocal());
|
|
}
|
|
if (unit instanceof Currency) {
|
|
sb.append("currency/");
|
|
BlueprintHelpers.generateCurrencyOption((Currency)unit, sb);
|
|
return true;
|
|
} else if (unit.equals(MeasureUnit.PERCENT)) {
|
|
sb.append("percent");
|
|
return true;
|
|
} else if (unit.equals(MeasureUnit.PERMILLE)) {
|
|
sb.append("permille");
|
|
return true;
|
|
} else {
|
|
sb.append("unit/");
|
|
sb.append(unit.getIdentifier());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private static boolean usage(MacroProps macros, StringBuilder sb) {
|
|
if (macros.usage != null && macros.usage.length() > 0) {
|
|
sb.append("usage/");
|
|
sb.append(macros.usage);
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static boolean precision(MacroProps macros, StringBuilder sb) {
|
|
if (macros.precision instanceof Precision.InfiniteRounderImpl) {
|
|
sb.append("precision-unlimited");
|
|
} else if (macros.precision instanceof Precision.FractionRounderImpl) {
|
|
Precision.FractionRounderImpl impl = (Precision.FractionRounderImpl) macros.precision;
|
|
BlueprintHelpers.generateFractionStem(impl.minFrac, impl.maxFrac, sb);
|
|
} else if (macros.precision instanceof Precision.SignificantRounderImpl) {
|
|
Precision.SignificantRounderImpl impl = (Precision.SignificantRounderImpl) macros.precision;
|
|
BlueprintHelpers.generateDigitsStem(impl.minSig, impl.maxSig, sb);
|
|
} else if (macros.precision instanceof Precision.FracSigRounderImpl) {
|
|
Precision.FracSigRounderImpl impl = (Precision.FracSigRounderImpl) macros.precision;
|
|
BlueprintHelpers.generateFractionStem(impl.minFrac, impl.maxFrac, sb);
|
|
sb.append('/');
|
|
if (impl.retain) {
|
|
if (impl.priority == RoundingPriority.RELAXED) {
|
|
BlueprintHelpers.generateDigitsStem(impl.maxSig, -1, sb);
|
|
} else {
|
|
BlueprintHelpers.generateDigitsStem(1, impl.maxSig, sb);
|
|
}
|
|
} else {
|
|
BlueprintHelpers.generateDigitsStem(impl.minSig, impl.maxSig, sb);
|
|
if (impl.priority == RoundingPriority.RELAXED) {
|
|
sb.append('r');
|
|
} else {
|
|
sb.append('s');
|
|
}
|
|
}
|
|
} else if (macros.precision instanceof Precision.IncrementRounderImpl) {
|
|
Precision.IncrementRounderImpl impl = (Precision.IncrementRounderImpl) macros.precision;
|
|
sb.append("precision-increment/");
|
|
BlueprintHelpers.generateIncrementOption(impl.increment, sb);
|
|
} else {
|
|
assert macros.precision instanceof Precision.CurrencyRounderImpl;
|
|
Precision.CurrencyRounderImpl impl = (Precision.CurrencyRounderImpl) macros.precision;
|
|
if (impl.usage == CurrencyUsage.STANDARD) {
|
|
sb.append("precision-currency-standard");
|
|
} else {
|
|
sb.append("precision-currency-cash");
|
|
}
|
|
}
|
|
|
|
if (macros.precision.trailingZeroDisplay == TrailingZeroDisplay.HIDE_IF_WHOLE) {
|
|
sb.append("/w");
|
|
}
|
|
|
|
// NOTE: Always return true for rounding because the default value depends on other options.
|
|
return true;
|
|
}
|
|
|
|
private static boolean roundingMode(MacroProps macros, StringBuilder sb) {
|
|
if (macros.roundingMode == RoundingUtils.DEFAULT_ROUNDING_MODE) {
|
|
return false; // Default value
|
|
}
|
|
EnumToStemString.roundingMode(macros.roundingMode, sb);
|
|
return true;
|
|
}
|
|
|
|
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 {
|
|
throw new UnsupportedOperationException(
|
|
"Cannot generate number skeleton with custom Grouper");
|
|
}
|
|
}
|
|
|
|
private static boolean integerWidth(MacroProps macros, StringBuilder sb) {
|
|
if (macros.integerWidth.equals(IntegerWidth.DEFAULT)) {
|
|
return false; // Default
|
|
}
|
|
if (macros.integerWidth.minInt == 0 && macros.integerWidth.maxInt == 0) {
|
|
sb.append("integer-width-trunc");
|
|
return true;
|
|
}
|
|
sb.append("integer-width/");
|
|
BlueprintHelpers.generateIntegerWidthOption(macros.integerWidth.minInt,
|
|
macros.integerWidth.maxInt,
|
|
sb);
|
|
return true;
|
|
}
|
|
|
|
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 {
|
|
assert macros.symbols instanceof DecimalFormatSymbols;
|
|
throw new UnsupportedOperationException(
|
|
"Cannot generate number skeleton with custom DecimalFormatSymbols");
|
|
}
|
|
}
|
|
|
|
private static boolean unitWidth(MacroProps macros, StringBuilder sb) {
|
|
if (macros.unitWidth == UnitWidth.SHORT) {
|
|
return false; // Default value
|
|
}
|
|
EnumToStemString.unitWidth(macros.unitWidth, sb);
|
|
return true;
|
|
}
|
|
|
|
private static boolean sign(MacroProps macros, StringBuilder sb) {
|
|
if (macros.sign == SignDisplay.AUTO) {
|
|
return false; // Default value
|
|
}
|
|
EnumToStemString.signDisplay(macros.sign, sb);
|
|
return true;
|
|
}
|
|
|
|
private static boolean decimal(MacroProps macros, StringBuilder sb) {
|
|
if (macros.decimal == DecimalSeparatorDisplay.AUTO) {
|
|
return false; // Default value
|
|
}
|
|
EnumToStemString.decimalSeparatorDisplay(macros.decimal, sb);
|
|
return true;
|
|
}
|
|
|
|
private static boolean scale(MacroProps macros, StringBuilder sb) {
|
|
if (!macros.scale.isValid()) {
|
|
return false; // Default value
|
|
}
|
|
sb.append("scale/");
|
|
BlueprintHelpers.generateScaleOption(macros.scale, sb);
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
///// OTHER UTILITY FUNCTIONS /////
|
|
|
|
private static void checkNull(Object value, CharSequence content) {
|
|
if (value != null) {
|
|
throw new SkeletonSyntaxException("Duplicated setting", content);
|
|
}
|
|
}
|
|
|
|
private static void appendMultiple(StringBuilder sb, int cp, int count) {
|
|
for (int i = 0; i < count; i++) {
|
|
sb.appendCodePoint(cp);
|
|
}
|
|
}
|
|
}
|