public static String toString(Object o) {
return o == null ? "null" : o.toString();
}
+
+ /**
+ * This implementation is equivalent to Java 8+ {@link Math#addExact(int, int)}
+ * @param x the first value
+ * @param y the second value
+ * @return the result
+ */
+ public static int addExact(int x, int y) {
+ int r = x + y;
+ // HD 2-12 Overflow iff both arguments have the opposite sign of the result
+ if (((x ^ r) & (y ^ r)) < 0) {
+ throw new ArithmeticException("integer overflow");
+ }
+ return r;
+ }
}
}
/**
- * Sames as {@link #unescape}, but only calculates the code point count. More efficient than
- * {@link #unescape} if you only need the length but not the string itself.
+ * Sames as {@link #unescape}, but only calculates the length or code point count. More efficient
+ * than {@link #unescape} if you only need the length but not the string itself.
*
* @param affixPattern
* The original string to be unescaped.
+ * @param lengthOrCount
+ * true to count length (UTF-16 code units); false to count code points
* @param provider
* An object to generate locale symbols.
* @return The number of code points in the unescaped string.
*/
- public static int unescapedCodePointCount(CharSequence affixPattern, SymbolProvider provider) {
+ public static int unescapedCount(
+ CharSequence affixPattern,
+ boolean lengthOrCount,
+ SymbolProvider provider) {
int length = 0;
long tag = 0L;
while (hasNext(tag, affixPattern)) {
tag = nextToken(tag, affixPattern);
int typeOrCp = getTypeOrCp(tag);
if (typeOrCp == TYPE_CURRENCY_OVERFLOW) {
+ // U+FFFD is one char
length += 1;
} else if (typeOrCp < 0) {
CharSequence symbol = provider.getSymbol(typeOrCp);
- length += Character.codePointCount(symbol, 0, symbol.length());
+ length += lengthOrCount ? symbol.length()
+ : Character.codePointCount(symbol, 0, symbol.length());
} else {
- length += 1;
+ length += lengthOrCount ? Character.charCount(typeOrCp) : 1;
}
}
return length;
return new String(chars);
}
- /**
- * Appends a new affix pattern with all symbols and code points in the given "ignorables" UnicodeSet
- * trimmed from the beginning and end. Similar to calling unescape with a symbol provider that always
- * returns the empty string.
- *
- * <p>
- * Accepts and returns a StringBuilder, allocating it only if necessary.
- */
- public static StringBuilder trimSymbolsAndIgnorables(
- CharSequence affixPattern,
- UnicodeSet ignorables,
- StringBuilder sb) {
- assert affixPattern != null;
- long tag = 0L;
- int trailingIgnorables = 0;
- while (hasNext(tag, affixPattern)) {
- tag = nextToken(tag, affixPattern);
- int typeOrCp = getTypeOrCp(tag);
- if (typeOrCp >= 0) {
- if (!ignorables.contains(typeOrCp)) {
- if (sb == null) {
- // Lazy-initialize the StringBuilder
- sb = new StringBuilder();
- }
- sb.appendCodePoint(typeOrCp);
- trailingIgnorables = 0;
- } else if (sb != null && sb.length() > 0) {
- sb.appendCodePoint(typeOrCp);
- trailingIgnorables += Character.charCount(typeOrCp);
- }
- }
- }
- if (trailingIgnorables > 0) {
- sb.setLength(sb.length() - trailingIgnorables);
- }
- return sb;
- }
-
/**
* Returns whether the given affix pattern contains only symbols and ignorables as defined by the
* given ignorables set.
return true;
}
+ /**
+ * Iterates over the affix pattern, calling the TokenConsumer for each token.
+ */
public static void iterateWithConsumer(CharSequence affixPattern, TokenConsumer consumer) {
assert affixPattern != null;
long tag = 0L;
* (never negative), or -1 if there were no more tokens in the affix pattern.
* @see #hasNext
*/
- public static long nextToken(long tag, CharSequence patternString) {
+ private static long nextToken(long tag, CharSequence patternString) {
int offset = getOffset(tag);
int state = getState(tag);
for (; offset < patternString.length();) {
* The affix pattern.
* @return true if there are more tokens to consume; false otherwise.
*/
- public static boolean hasNext(long tag, CharSequence string) {
+ private static boolean hasNext(long tag, CharSequence string) {
assert tag >= 0;
int state = getState(tag);
int offset = getOffset(tag);
* @return If less than zero, a symbol type corresponding to one of the <code>TYPE_</code> constants,
* such as {@link #TYPE_MINUS_SIGN}. If greater than or equal to zero, a literal code point.
*/
- public static int getTypeOrCp(long tag) {
+ private static int getTypeOrCp(long tag) {
assert tag >= 0;
int type = getType(tag);
return (type == TYPE_CODEPOINT) ? getCodePoint(tag) : -type;
return tag;
}
- static int getOffset(long tag) {
+ private static int getOffset(long tag) {
return (int) (tag & 0xffffffff);
}
- static int getType(long tag) {
+ private static int getType(long tag) {
return (int) ((tag >>> 32) & 0xf);
}
- static int getState(long tag) {
+ private static int getState(long tag) {
return (int) ((tag >>> 36) & 0xf);
}
- static int getCodePoint(long tag) {
+ private static int getCodePoint(long tag) {
return (int) (tag >>> 40);
}
}
*/
public void roundToInfinity();
- /**
- * Truncates the decimals from this DecimalQuantity. Equivalent to calling roundToMagnitude(0, FLOOR)
- */
- void truncate();
-
/**
* Multiply the internal value.
*
import java.text.FieldPosition;
import com.ibm.icu.impl.StandardPlural;
+import com.ibm.icu.impl.Utility;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.Operand;
import com.ibm.icu.text.UFieldPosition;
@Override
public void adjustMagnitude(int delta) {
if (precision != 0) {
- // TODO: Math.addExact is not in 1.6 or 1.7
- scale = Math.addExact(scale, delta);
- origDelta = Math.addExact(origDelta, delta);
+ scale = Utility.addExact(scale, delta);
+ origDelta = Utility.addExact(origDelta, delta);
}
}
}
}
- @Override
- public void truncate() {
- if (scale < 0) {
- shiftRight(-scale);
- scale = 0;
- compact();
- }
- }
-
/**
* Appends a digit, optionally with one or more leading zeros, to the end of the value represented by
* this DecimalQuantity.
Currency currency,
UnitWidth unitWidth,
PluralRules rules) {
- // assert (rules != null) == needsPlurals();
+ assert (rules != null) == needsPlurals();
this.symbols = symbols;
this.currency = currency;
this.unitWidth = unitWidth;
@Override
public int getPrefixLength() {
- // Enter and exit CharSequence Mode to get the length.
+ // Render the affix to get the length
prepareAffix(true);
- int result = AffixUtils.unescapedCodePointCount(currentAffix, this); // prefix length
+ int result = AffixUtils.unescapedCount(currentAffix, true, this); // prefix length
return result;
}
@Override
public int getCodePointCount() {
- // Enter and exit CharSequence Mode to get the length.
+ // Render the affixes to get the length
prepareAffix(true);
- int result = AffixUtils.unescapedCodePointCount(currentAffix, this); // prefix length
+ int result = AffixUtils.unescapedCount(currentAffix, false, this); // prefix length
prepareAffix(false);
- result += AffixUtils.unescapedCodePointCount(currentAffix, this); // suffix length
+ result += AffixUtils.unescapedCount(currentAffix, false, this); // suffix length
return result;
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
-import java.util.Objects;
import com.ibm.icu.impl.StandardPlural;
+import com.ibm.icu.impl.Utility;
import com.ibm.icu.impl.number.AffixPatternProvider;
import com.ibm.icu.impl.number.AffixUtils;
import com.ibm.icu.impl.number.PatternStringUtils;
if (signum == 1) {
posPrefix = prefix;
posSuffix = suffix;
- } else if (Objects.equals(prefix, posPrefix) && Objects.equals(suffix, posSuffix)) {
+ } else if (Utility.equals(prefix, posPrefix) && Utility.equals(suffix, posSuffix)) {
// Skip adding these matchers (we already have equivalents)
continue;
}
matchers.add(getInstance(prefix, suffix, flags));
if (includeUnpaired && prefix != null && suffix != null) {
// The following if statements are designed to prevent adding two identical matchers.
- if (signum == 1 || !Objects.equals(prefix, posPrefix)) {
+ if (signum == 1 || !Utility.equals(prefix, posPrefix)) {
matchers.add(getInstance(prefix, null, flags));
}
- if (signum == 1 || !Objects.equals(suffix, posSuffix)) {
+ if (signum == 1 || !Utility.equals(suffix, posSuffix)) {
matchers.add(getInstance(null, suffix, flags));
}
}
return false;
}
AffixMatcher other = (AffixMatcher) _other;
- return Objects.equals(prefix, other.prefix)
- && Objects.equals(suffix, other.suffix)
+ return Utility.equals(prefix, other.prefix)
+ && Utility.equals(suffix, other.suffix)
&& flags == other.flags;
}
@Override
public int hashCode() {
- return Objects.hashCode(prefix) ^ Objects.hashCode(suffix) ^ flags;
+ return Utility.hashCode(prefix) ^ Utility.hashCode(suffix) ^ flags;
}
@Override
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.number.DecimalQuantity;
-import com.ibm.icu.impl.number.RoundingUtils;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.Operand;
import com.ibm.icu.text.UFieldPosition;
// noop
}
- @Override
- public void truncate() {
- roundToMagnitude(0, RoundingUtils.mathContextUnlimited(RoundingMode.FLOOR));
- }
-
/**
* Multiply the internal number by the specified multiplicand. This method forces the internal
* representation into a BigDecimal. If you are multiplying by a power of 10, use {@link
Object[][] cases = {
{ "", false, 0, "" },
{ "abc", false, 3, "abc" },
+ { "📺", false, 1, "📺" },
{ "-", false, 1, "−" },
{ "-!", false, 2, "−!" },
{ "+", false, 1, "\u061C+" },
String actual = unescapeWithDefaults(input);
assertEquals("Output on <" + input + ">", output, actual);
- int ulength = AffixUtils.unescapedCodePointCount(input, DEFAULT_SYMBOL_PROVIDER);
+ int ulength = AffixUtils.unescapedCount(input, true, DEFAULT_SYMBOL_PROVIDER);
assertEquals("Unescaped length on <" + input + ">", output.length(), ulength);
+
+ int ucpcount = AffixUtils.unescapedCount(input, false, DEFAULT_SYMBOL_PROVIDER);
+ assertEquals("Unescaped length on <" + input + ">",
+ output.codePointCount(0, output.length()),
+ ucpcount);
}
}
@Test
public void testWithoutSymbolsOrIgnorables() {
- String[][] cases = {
- { "", "" },
- { "-", "" },
- { " ", "" },
- { "'-'", "-" },
- { " a + b ", "a b" },
- { "-a+b%c‰d¤e¤¤f¤¤¤g¤¤¤¤h¤¤¤¤¤i", "abcdefghi" }, };
+ Object[][] cases = {
+ { "", true },
+ { "-", true },
+ { " ", true },
+ { "'-'", false },
+ { " a + b ", false },
+ { "-a+b%c‰d¤e¤¤f¤¤¤g¤¤¤¤h¤¤¤¤¤i", false }, };
UnicodeSet ignorables = new UnicodeSet("[:whitespace:]");
- StringBuilder sb = new StringBuilder();
- for (String[] cas : cases) {
- String input = cas[0];
- String expected = cas[1];
- sb.setLength(0);
- AffixUtils.trimSymbolsAndIgnorables(input, ignorables, sb);
- assertEquals("Removing symbols from: " + input, expected, sb.toString());
+ for (Object[] cas : cases) {
+ String input = (String) cas[0];
+ boolean expected = (Boolean) cas[1];
assertEquals("Contains only symbols and ignorables: " + input,
- sb.length() == 0,
+ expected,
AffixUtils.containsOnlySymbolsAndIgnorables(input, ignorables));
}
}