package jef.tools.support;
import java.util.HashMap;
import java.util.Map;
/**
* This class transfers an integer number into a string :
*
* <br/>
*
* <pre>
* {@code
* // an example :
* NumberText ns = NumberText.getInstance(NumberText.Lang.EnglishWithDash);
* ns.getText(123) // one hundred and twenty-three
* ns.getOrdinalText(320) // three hundred and twentieth
* }
* </pre>
*/
public abstract class NumberText {
private NumberText() {
}
/**
* Exports a {@code NumberText} implementation instance, based on a natural
* language argument. {@see Lang}
*
* @param lang
* @return a NumberText instance.
*/
public static NumberText getInstance(Lang lang) {
return lang.instance();
}
public static NumberText getInstance() {
return Lang.ChineseSimplified.instance();
}
/**
* Transfers an integer number into a String, specifically in which language
* depends on the implementation.
* <p />
* e.g. in EnglishWithDash,
* <p />
* 100 -> one hundred <br />
* -976083854 -> minus nine hundred and seventy-six million and eighty-three
* thousand eight hundred and fifty-four
*
* @param number
* the integer number to be transfered
* @return the result String
*/
public final String getText(long number) {
return getText(Long.toString(number));
}
/**
* Transfers an integer number into a String, specifically in which language
* depends on the implementation.
* <p />
* e.g. in EnglishWithDash,
* <p />
* 100 -> one hundred <br />
* -976083854 -> minus nine hundred and seventy-six million and eighty-three
* thousand eight hundred and fifty-four
*
* @param number
* the integer number to be transfered
* @return the result String
*/
public abstract String getText(String number);
/**
* Transfers an integer number into a String of its ordinal representation,
* specifically in which language depends on the implementation.
* <p />
* e.g. in EnglishWithDash,
* <p />
* 100 -> one hundredth <br />
* 8331125 -> eight million three hundred and thirty-one thousand one
* hundred and twenty-fifth
*
* @param number
* the integer number to be transfered
* @return the result String
*/
public final String getOrdinalText(long number) {
return getOrdinalText(Long.toString(number));
}
/**
* <p>
* Transfers an integer number into a String of its ordinal representation,
* specifically in which language depends on the implementation.
* </p>
*
* <p>
* e.g. in EnglishWithDash, <br />
* <br />
* 100 -> one hundredth <br />
* 8331125 -> eight million three hundred and thirty-one thousand one
* hundred and twenty-fifth
* </p>
*
* @param number
* the integer number to be transfered
* @return the result String
*/
public abstract String getOrdinalText(String number);
/**
* This enumeration type is typically named under a natural language name,
* and is to mark a specific implementation name; it is used as an argument
* to call the factory method
* {@link NumberText#getInstance(NumberText.Lang)}.
*/
public static enum Lang {
English(NumberTextEnglishCleanSpaceOnly.INSTANCE),
EnglishWithDash(NumberTextEnglish.INSTANCE),
ChineseSimplified(NumberTextChinese.SIMPLIFIED),
ChineseTraditional(NumberTextChinese.TRADITIONAL), ;
private final NumberText instance;
private Lang(NumberText instance) {
this.instance = instance;
}
private NumberText instance() {
if (instance == null)
throw new UnsupportedOperationException("Language not supported yet : " + this);
return instance;
}
}
abstract int limit();
void checkNumber(String number) {
if (!number.matches("-?\\d+"))
throw new NumberFormatException();
int length = number.length();
if (number.startsWith("-"))
length--;
if (length > limit())
throw new UnsupportedOperationException("The current " + NumberText.class.getSimpleName() + "can only handle numbers up to (+/-)10^" + limit() + ".");
}
/*----------------------------------------------------------------------------
* EnglishWithDash Implementation
---------------------------------------------------------------------------*/
private static class NumberTextEnglish extends NumberText {
private static final NumberText INSTANCE = new NumberTextEnglish();
static enum Connect {
Minus("minus"), Hundred("hundred"), And("and"), AfterMinus(" "), AfterNumber(" "), AfterPower(" "), AfterHundred(" "), AfterAnd(" "), AfterTen("-"), ;
final String display;
Connect(String display) {
this.display = display;
}
private static boolean isConnect(char c) {
return c == ' ' || c == '-';
}
}
static enum Power {
Thousand("thousand"), // 10 ^ 3
Million("million"), // 10 ^ 6
Billion("billion"), // 10 ^ 9
Trillion("trillion"), // 10 ^ 12
Quadrillion("quadrillion"), // 10 ^ 15
Quintillion("quintillion"), // 10 ^ 18 (enough for Long.MAX_VALUE)
Sextillion("sextillion"), // 10 ^ 21
Septillion("septillion"), // 10 ^ 24
Octillion("octillion"), // 10 ^ 27
Nonillion("nonillion"), // 10 ^ 30
Decillion("decillion"), // 10 ^ 33
Undecillion("undecillion"), // 10 ^ 36
Duodecillion("duodecillion"), // 10 ^ 39
Tredecillion("tredecillion"), // 10 ^ 42
Quattuordecillion("quattuordecillion"), // 10 ^ 45
Quindecillion("quindecillion"), // 10 ^ 48
Sexdecillion("sexdecillion"), // 10 ^ 51
Septendecillion("septendecillion"), // 10 ^ 54
Octodecillion("octodecillion"), // 10 ^ 57
Novemdecillion("novemdecillion"), // 10 ^ 60
Vigintillion("vigintillion"), // 10 ^ 63
;
final String display;
Power(String display) {
this.display = display;
}
}
static enum Digit {
Zero("zero", "zeroth", "ten", ""), One("one", "first", "eleven", "ten"), Two("two", "second", "twelve", "twenty"), Three("three", "third", "thirteen", "thirty"), Four("four", "fourth", "fourteen", "fourty"), Five("five", "fifth", "fifteen", "fifty"), Six("six",
"sixth", "sixteen", "sixty"), Seven("seven", "seventh", "seventeen", "seventy"), Eight("eight", "eighth", "eighteen", "eighty"), Nine("nine", "nineth", "nineteen", "ninety"), ;
final String display, displayOrdinal, plusTen, multiTen;
Digit(String display, String displayOrdinal, String plusTen, String multiTen) {
this.display = display;
this.displayOrdinal = displayOrdinal;
this.plusTen = plusTen;
this.multiTen = multiTen;
}
}
private static final Map<String, String> _Ordinals;
static {
_Ordinals = new HashMap<String, String>();
for (Digit d : Digit.values())
_Ordinals.put(d.display, d.displayOrdinal);
}
@Override
int limit() {
return 63;
}
@Override
public String getText(String number) {
checkNumber(number);
StringBuilder builder = new StringBuilder();
buildText(builder, number);
return builder.toString();
}
@Override
public String getOrdinalText(String number) {
checkNumber(number);
StringBuilder builder = new StringBuilder();
buildText(builder, number);
replaceLastTokenWithOrdinal(builder);
return builder.toString();
}
private void buildText(StringBuilder builder, String number) {
assert builder != null;
if (number.startsWith("-")) {
builder.append(getConnectDisplay(Connect.Minus)).append(getConnectDisplay(Connect.AfterMinus));
number = number.substring(1);
}
int power = 0;
while (number.length() > (power + 1) * 3)
power++;
while (power > 0) {
boolean modified = extendToken(builder, number, power * 3);
if (modified)
builder.append(getConnectDisplay(Connect.AfterNumber)).append(getPowerDisplay(Power.values()[power - 1]));
power--;
}
extendToken(builder, number, 0);
}
private boolean extendToken(StringBuilder builder, String number, int suffix) {
assert builder != null && suffix < number.length();
int len = number.length() - suffix;
int hundreds = len > 2 ? (int) (number.charAt(len - 3) - '0') : -1;
int tens = len > 1 ? (int) (number.charAt(len - 2) - '0') : -1;
int inds = (int) (number.charAt(len - 1) - '0');
if (hundreds <= 0 && tens <= 0 && inds <= 0 && suffix > 0)
return false;
else if (len > 3)
builder.append(getConnectDisplay(Connect.AfterPower));
if (hundreds == 0) {
if (len > 3 && (tens > 0 || inds > 0))
builder.append(getConnectDisplay(Connect.And)).append(getConnectDisplay(Connect.AfterAnd));
} else if (hundreds > 0) {
builder.append(getDigitName(Digit.values()[hundreds])).append(getConnectDisplay(Connect.AfterNumber)).append(getConnectDisplay(Connect.Hundred));
if (tens > 0 || inds > 0)
builder.append(getConnectDisplay(Connect.AfterHundred)).append(getConnectDisplay(Connect.And)).append(getConnectDisplay(Connect.AfterAnd));
}
if (tens > 1) {
builder.append(getDigitMultiTen(Digit.values()[tens]));
if (inds > 0)
builder.append(getConnectDisplay(Connect.AfterTen));
}
if (tens == 1)
builder.append(getDigitPlusTen(Digit.values()[inds]));
else if (inds > 0 || number.length() == 1)
builder.append(getDigitName(Digit.values()[inds]));
return true;
}
private void replaceLastTokenWithOrdinal(StringBuilder builder) {
assert builder != null && builder.length() > 0;
int suffix = builder.length() - 1;
while (suffix >= 0 && !isConnect(builder.charAt(suffix)))
suffix--;
String lastToken = builder.substring(suffix + 1);
builder.delete(suffix + 1, builder.length()).append(toOrdinal(lastToken));
}
String getPowerDisplay(Power power) {
assert power != null;
return power.display;
}
String getConnectDisplay(Connect connect) {
assert connect != null;
return connect.display;
}
String getDigitName(Digit digit) {
assert digit != null;
return digit.display;
}
String toOrdinal(String name) {
assert name != null && !name.isEmpty();
String result = _Ordinals.get(name);
if (result == null) {
if (name.charAt(name.length() - 1) == 'y')
result = name.substring(0, name.length() - 1) + "ieth";
else
result = name + "th";
}
return result;
}
String getDigitPlusTen(Digit digit) {
assert digit != null;
return digit.plusTen;
}
String getDigitMultiTen(Digit digit) {
assert digit != null;
return digit.multiTen;
}
boolean isConnect(char c) {
return Connect.isConnect(c);
}
}
/*----------------------------------------------------------------------------
* EnglishWithDash with only Clean Space Connectors
---------------------------------------------------------------------------*/
private static class NumberTextEnglishCleanSpaceOnly extends NumberTextEnglish {
private static final NumberText INSTANCE = new NumberTextEnglishCleanSpaceOnly();
@Override
String getConnectDisplay(Connect connect) {
return connect == Connect.AfterTen ? " " : super.getConnectDisplay(connect);
}
}
/*----------------------------------------------------------------------------
* ChineseSimplified Implementation
---------------------------------------------------------------------------*/
private static class NumberTextChinese extends NumberText {
private static final NumberText SIMPLIFIED = new NumberTextChinese(Type.Simplified);
private static final NumberText TRADITIONAL = new NumberTextChinese(Type.Traditional);
static enum Type {
Simplified, Traditional;
}
static enum Connect {
Di("第", "第"), Fu("负", "負"), Ling("零", "零"), Shi("十", "拾"), Bai("百", "佰"), Qian("千", "仟"), ;
final String display, displayTraditional;
Connect(String display, String displayTraditional) {
this.display = display;
this.displayTraditional = displayTraditional;
}
}
static enum Power {
Wan("万", "萬"), // 10^4
Yi("亿", "億"), // 10^8
Zhao("兆", "兆"), // 10^12
Jing("京", "京"), // 10^16 (enough for Long.MAX_VALUE)
Gai("垓", "垓"), // 10^20
Zi("秭", "秭"), // 10^24
Rang("穰", "穰"), // 10^28
Gou("沟", "溝"), // 10^32
Jian("涧", "澗"), // 10^36
Zheng("正", "正"), // 10^40
Zai("载", "載"), // 10^44
;
final String display, displayTraditional;
Power(String display, String displayTraditional) {
this.display = display;
this.displayTraditional = displayTraditional;
}
}
static enum Digit {
Ling("零", "零"), // just to occupy this position
Yi("一", "壹"), Er("二", "贰"), San("三", "叁"), Si("四", "肆"), Wu("五", "伍"), Liu("六", "陆"), Qi("七", "柒"), Ba("八", "捌"), Jiu("九", "玖"), ;
final String display, displayTraditional;
Digit(String display, String displayTraditional) {
this.display = display;
this.displayTraditional = displayTraditional;
}
}
private final Type type;
private NumberTextChinese(Type type) {
assert type != null;
this.type = type;
}
@Override
int limit() {
return 44;
}
@Override
public String getText(String number) {
checkNumber(number);
StringBuilder builder = new StringBuilder();
buildText(builder, number);
return builder.toString();
}
@Override
public String getOrdinalText(String number) {
checkNumber(number);
StringBuilder builder = new StringBuilder().append(Connect.Di);
buildText(builder, number);
return builder.toString();
}
private void buildText(StringBuilder builder, String number) {
assert builder != null;
if (number.startsWith("-")) {
builder.append(getConnectDisplay(Connect.Fu));
number = number.substring(1);
}
int power = 0;
while (number.length() > (power + 1) * 4)
power++;
while (power > 0) {
if (extendToken(builder, number, power * 4))
builder.append(getPowerDisplay(Power.values()[power - 1]));
power--;
}
extendToken(builder, number, 0);
}
private boolean extendToken(StringBuilder builder, String number, int suffix) {
assert builder != null && number.length() > suffix;
int len = number.length() - suffix;
int qian = len > 3 ? (int) (number.charAt(len - 4) - '0') : -1;
int bai = len > 2 ? (int) (number.charAt(len - 3) - '0') : -1;
int shi = len > 1 ? (int) (number.charAt(len - 2) - '0') : -1;
int ind = (int) (number.charAt(len - 1) - '0');
boolean nonZero = false; // true if any of the digits is not zero
if (qian == 0) {
if (bai > 0 || shi > 0 || ind > 0)
builder.append(getConnectDisplay(Connect.Ling));
} else if (qian > 0) {
builder.append(getDigitDisplay(Digit.values()[qian])).append(getConnectDisplay(Connect.Qian));
nonZero = true;
}
if (bai == 0) {
if (qian > 0 && (shi > 0 || ind > 0))
builder.append(getConnectDisplay(Connect.Ling));
} else if (bai > 0) {
builder.append(getDigitDisplay(Digit.values()[bai])).append(getConnectDisplay(Connect.Bai));
nonZero = true;
}
if (shi == 0) {
if (bai > 0 && ind > 0)
builder.append(getConnectDisplay(Connect.Ling));
} else if (shi > 0) {
if (number.length() > 2 || shi != 1)
builder.append(getDigitDisplay(Digit.values()[shi]));
builder.append(getConnectDisplay(Connect.Shi));
nonZero = true;
}
if (ind == 0) {
boolean addZero = len == 1;
for (int i = 1; addZero && i <= suffix; i++) {
if (number.charAt(i) != '0')
addZero = false;
}
if (addZero)
builder.append(getConnectDisplay(Connect.Ling));
} else {
builder.append(getDigitDisplay(Digit.values()[ind]));
nonZero = true;
}
return nonZero;
}
String getConnectDisplay(Connect connect) {
assert connect != null;
return type == Type.Simplified ? connect.display : connect.displayTraditional;
}
String getPowerDisplay(Power power) {
assert power != null;
return type == Type.Simplified ? power.display : power.displayTraditional;
}
String getDigitDisplay(Digit digit) {
assert digit != null;
return type == Type.Simplified ? digit.display : digit.displayTraditional;
}
}
}