You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

NumberConverter.java 58KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.complexscripts.util;
  19. import java.util.ArrayList;
  20. import java.util.List;
  21. // CSOFF: LineLengthCheck
  22. /**
  23. * <p>Implementation of Number to String Conversion algorithm specified by
  24. * XSL Transformations (XSLT) Version 2.0, W3C Recommendation, 23 January 2007.</p>
  25. *
  26. * <p>This algorithm differs from that specified in XSLT 1.0 in the following
  27. * ways:</p>
  28. * <ul>
  29. * <li>input numbers are greater than or equal to zero rather than greater than zero;</li>
  30. * <li>introduces format tokens { w, W, Ww };</li>
  31. * <li>introduces ordinal parameter to generate ordinal numbers;</li>
  32. * </ul>
  33. *
  34. * <p>Implementation Defaults and Limitations</p>
  35. * <ul>
  36. * <li>If language parameter is unspecified (null or empty string), then the value
  37. * of DEFAULT_LANGUAGE is used, which is defined below as "eng" (English).</li>
  38. * <li>Only English, French, and Spanish word numerals are supported, and only if less than one trillion (1,000,000,000,000).</li>
  39. * <li>Ordinal word numerals are supported for French and Spanish only when less than or equal to ten (10).</li>
  40. * </ul>
  41. *
  42. * <p>Implementation Notes</p>
  43. * <ul>
  44. * <li>In order to handle format tokens outside the Unicode BMP, all processing is
  45. * done in Unicode Scalar Values represented with Integer and Integer[]
  46. * types. Without affecting behavior, this may be subsequently optimized to
  47. * use int and int[] types.</li>
  48. * <li>In order to communicate various sub-parameters, including ordinalization, a <em>features</em>
  49. * is employed, which consists of comma separated name and optional value tokens, where name and value
  50. * are separated by an equals '=' sign.</li>
  51. * <li>Ordinal numbers are selected by specifying a word based format token in combination with a 'ordinal' feature with no value, in which case
  52. * the features 'male' and 'female' may be used to specify gender for gender sensitive languages. For example, the feature string "ordinal,female"
  53. * selects female ordinals.</li>
  54. * </ul>
  55. *
  56. * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
  57. */
  58. public class NumberConverter {
  59. /** alphabetical */
  60. public static final int LETTER_VALUE_ALPHABETIC = 1;
  61. /** traditional */
  62. public static final int LETTER_VALUE_TRADITIONAL = 2;
  63. /** no token type */
  64. private static final int TOKEN_NONE = 0;
  65. /** alhphanumeric token type */
  66. private static final int TOKEN_ALPHANUMERIC = 1;
  67. /** nonalphanumeric token type */
  68. private static final int TOKEN_NONALPHANUMERIC = 2;
  69. /** default token */
  70. private static final Integer[] DEFAULT_TOKEN = new Integer[] { (int) '1' };
  71. /** default separator */
  72. private static final Integer[] DEFAULT_SEPARATOR = new Integer[] { (int) '.' };
  73. /** default language */
  74. private static final String DEFAULT_LANGUAGE = "eng";
  75. /** prefix token */
  76. private Integer[] prefix;
  77. /** suffix token */
  78. private Integer[] suffix;
  79. /** sequence of tokens, as parsed from format */
  80. private Integer[][] tokens;
  81. /** sequence of separators, as parsed from format */
  82. private Integer[][] separators;
  83. /** grouping separator */
  84. private int groupingSeparator;
  85. /** grouping size */
  86. private int groupingSize;
  87. /** letter value */
  88. private int letterValue;
  89. /** letter value system */
  90. private String features;
  91. /** language */
  92. private String language;
  93. /** country */
  94. private String country;
  95. /**
  96. * Construct parameterized number converter.
  97. * @param format format for the page number (may be null or empty, which is treated as null)
  98. * @param groupingSeparator grouping separator (if zero, then no grouping separator applies)
  99. * @param groupingSize grouping size (if zero or negative, then no grouping size applies)
  100. * @param letterValue letter value (must be one of the above letter value enumeration values)
  101. * @param features features (feature sub-parameters)
  102. * @param language (may be null or empty, which is treated as null)
  103. * @param country (may be null or empty, which is treated as null)
  104. * @throws IllegalArgumentException if format is not a valid UTF-16 string (e.g., has unpaired surrogate)
  105. */
  106. public NumberConverter(String format, int groupingSeparator, int groupingSize, int letterValue, String features, String language, String country)
  107. throws IllegalArgumentException {
  108. this.groupingSeparator = groupingSeparator;
  109. this.groupingSize = groupingSize;
  110. this.letterValue = letterValue;
  111. this.features = features;
  112. this.language = (language != null) ? language.toLowerCase() : null;
  113. this.country = (country != null) ? country.toLowerCase() : null;
  114. parseFormatTokens(format);
  115. }
  116. /**
  117. * Convert a number to string according to conversion parameters.
  118. * @param number number to conver
  119. * @return string representing converted number
  120. */
  121. public String convert(long number) {
  122. List<Long> numbers = new ArrayList<Long>();
  123. numbers.add(number);
  124. return convert(numbers);
  125. }
  126. /**
  127. * Convert list of numbers to string according to conversion parameters.
  128. * @param numbers list of numbers to convert
  129. * @return string representing converted list of numbers
  130. */
  131. public String convert(List<Long> numbers) {
  132. List<Integer> scalars = new ArrayList<Integer>();
  133. if (prefix != null) {
  134. appendScalars(scalars, prefix);
  135. }
  136. convertNumbers(scalars, numbers);
  137. if (suffix != null) {
  138. appendScalars(scalars, suffix);
  139. }
  140. return scalarsToString(scalars);
  141. }
  142. private void parseFormatTokens(String format) throws IllegalArgumentException {
  143. List<Integer[]> tokens = new ArrayList<Integer[]>();
  144. List<Integer[]> separators = new ArrayList<Integer[]>();
  145. if ((format == null) || (format.length() == 0)) {
  146. format = "1";
  147. }
  148. int tokenType = TOKEN_NONE;
  149. List<Integer> token = new ArrayList<Integer>();
  150. Integer[] ca = UTF32.toUTF32(format, 0, true);
  151. for (int i = 0, n = ca.length; i < n; i++) {
  152. int c = ca[i];
  153. int tokenTypeNew = isAlphaNumeric(c) ? TOKEN_ALPHANUMERIC : TOKEN_NONALPHANUMERIC;
  154. if (tokenTypeNew != tokenType) {
  155. if (token.size() > 0) {
  156. if (tokenType == TOKEN_ALPHANUMERIC) {
  157. tokens.add(token.toArray(new Integer [ token.size() ]));
  158. } else {
  159. separators.add(token.toArray(new Integer [ token.size() ]));
  160. }
  161. token.clear();
  162. }
  163. tokenType = tokenTypeNew;
  164. }
  165. token.add(c);
  166. }
  167. if (token.size() > 0) {
  168. if (tokenType == TOKEN_ALPHANUMERIC) {
  169. tokens.add(token.toArray(new Integer [ token.size() ]));
  170. } else {
  171. separators.add(token.toArray(new Integer [ token.size() ]));
  172. }
  173. }
  174. if (!separators.isEmpty()) {
  175. this.prefix = separators.remove(0);
  176. }
  177. if (!separators.isEmpty()) {
  178. this.suffix = separators.remove(separators.size() - 1);
  179. }
  180. this.separators = separators.toArray(new Integer [ separators.size() ] []);
  181. this.tokens = tokens.toArray(new Integer [ tokens.size() ] []);
  182. }
  183. private static boolean isAlphaNumeric(int c) {
  184. switch (Character.getType(c)) {
  185. case Character.DECIMAL_DIGIT_NUMBER: // Nd
  186. case Character.LETTER_NUMBER: // Nl
  187. case Character.OTHER_NUMBER: // No
  188. case Character.UPPERCASE_LETTER: // Lu
  189. case Character.LOWERCASE_LETTER: // Ll
  190. case Character.TITLECASE_LETTER: // Lt
  191. case Character.MODIFIER_LETTER: // Lm
  192. case Character.OTHER_LETTER: // Lo
  193. return true;
  194. default:
  195. return false;
  196. }
  197. }
  198. private void convertNumbers(List<Integer> scalars, List<Long> numbers) {
  199. Integer[] tknLast = DEFAULT_TOKEN;
  200. int tknIndex = 0;
  201. int tknCount = tokens.length;
  202. int sepIndex = 0;
  203. int sepCount = separators.length;
  204. int numIndex = 0;
  205. for (Long number : numbers) {
  206. Integer[] sep = null;
  207. Integer[] tkn;
  208. if (tknIndex < tknCount) {
  209. if (numIndex > 0) {
  210. if (sepIndex < sepCount) {
  211. sep = separators [ sepIndex++ ];
  212. } else {
  213. sep = DEFAULT_SEPARATOR;
  214. }
  215. }
  216. tkn = tokens [ tknIndex++ ];
  217. } else {
  218. tkn = tknLast;
  219. }
  220. appendScalars(scalars, convertNumber(number, sep, tkn));
  221. tknLast = tkn;
  222. numIndex++;
  223. }
  224. }
  225. private Integer[] convertNumber(long number, Integer[] separator, Integer[] token) {
  226. List<Integer> sl = new ArrayList<Integer>();
  227. if (separator != null) {
  228. appendScalars(sl, separator);
  229. }
  230. if (token != null) {
  231. appendScalars(sl, formatNumber(number, token));
  232. }
  233. return sl.toArray(new Integer [ sl.size() ]);
  234. }
  235. private Integer[] formatNumber(long number, Integer[] token) {
  236. Integer[] fn = null;
  237. assert token.length > 0;
  238. if (number < 0) {
  239. throw new IllegalArgumentException("number must be non-negative");
  240. } else if (token.length == 1) {
  241. int s = token[0].intValue();
  242. switch (s) {
  243. case (int) '1':
  244. fn = formatNumberAsDecimal(number, (int) '1', 1);
  245. break;
  246. case (int) 'W':
  247. case (int) 'w':
  248. fn = formatNumberAsWord(number, (s == (int) 'W') ? Character.UPPERCASE_LETTER : Character.LOWERCASE_LETTER);
  249. break;
  250. case (int) 'A': // handled as numeric sequence
  251. case (int) 'a': // handled as numeric sequence
  252. case (int) 'I': // handled as numeric special
  253. case (int) 'i': // handled as numeric special
  254. default:
  255. if (isStartOfDecimalSequence(s)) {
  256. fn = formatNumberAsDecimal(number, s, 1);
  257. } else if (isStartOfAlphabeticSequence(s)) {
  258. fn = formatNumberAsSequence(number, s, getSequenceBase(s), null);
  259. } else if (isStartOfNumericSpecial(s)) {
  260. fn = formatNumberAsSpecial(number, s);
  261. } else {
  262. fn = null;
  263. }
  264. break;
  265. }
  266. } else if ((token.length == 2) && (token[0] == (int) 'W') && (token[1] == (int) 'w')) {
  267. fn = formatNumberAsWord(number, Character.TITLECASE_LETTER);
  268. } else if (isPaddedOne(token)) {
  269. int s = token [ token.length - 1 ].intValue();
  270. fn = formatNumberAsDecimal(number, s, token.length);
  271. } else {
  272. throw new IllegalArgumentException("invalid format token: \"" + UTF32.fromUTF32(token) + "\"");
  273. }
  274. if (fn == null) {
  275. fn = formatNumber(number, DEFAULT_TOKEN);
  276. }
  277. assert fn != null;
  278. return fn;
  279. }
  280. /**
  281. * Format NUMBER as decimal using characters denoting digits that start at ONE,
  282. * adding one or more (zero) padding characters as needed to fill out field WIDTH.
  283. * @param number to be formatted
  284. * @param one unicode scalar value denoting numeric value 1
  285. * @param width non-negative integer denoting field width of number, possible including padding
  286. * @return formatted number as array of unicode scalars
  287. */
  288. private Integer[] formatNumberAsDecimal(long number, int one, int width) {
  289. assert Character.getNumericValue(one) == 1;
  290. assert Character.getNumericValue(one - 1) == 0;
  291. assert Character.getNumericValue(one + 8) == 9;
  292. List<Integer> sl = new ArrayList<Integer>();
  293. int zero = one - 1;
  294. while (number > 0) {
  295. long digit = number % 10;
  296. sl.add(0, zero + (int) digit);
  297. number = number / 10;
  298. }
  299. while (width > sl.size()) {
  300. sl.add(0, zero);
  301. }
  302. if ((groupingSize != 0) && (groupingSeparator != 0)) {
  303. sl = performGrouping(sl, groupingSize, groupingSeparator);
  304. }
  305. return sl.toArray(new Integer [ sl.size() ]);
  306. }
  307. private static List<Integer> performGrouping(List<Integer> sl, int groupingSize, int groupingSeparator) {
  308. assert groupingSize > 0;
  309. assert groupingSeparator != 0;
  310. if (sl.size() > groupingSize) {
  311. List<Integer> gl = new ArrayList<Integer>();
  312. for (int i = 0, n = sl.size(), g = 0; i < n; i++) {
  313. int k = n - i - 1;
  314. if (g == groupingSize) {
  315. gl.add(0, groupingSeparator);
  316. g = 1;
  317. } else {
  318. g++;
  319. }
  320. gl.add(0, sl.get(k));
  321. }
  322. return gl;
  323. } else {
  324. return sl;
  325. }
  326. }
  327. /**
  328. * Format NUMBER as using sequence of characters that start at ONE, and
  329. * having BASE radix.
  330. * @param number to be formatted
  331. * @param one unicode scalar value denoting start of sequence (numeric value 1)
  332. * @param base number of elements in sequence
  333. * @param map if non-null, then maps sequences indices to unicode scalars
  334. * @return formatted number as array of unicode scalars
  335. */
  336. private Integer[] formatNumberAsSequence(long number, int one, int base, int[] map) {
  337. assert base > 1;
  338. assert (map == null) || (map.length >= base);
  339. List<Integer> sl = new ArrayList<Integer>();
  340. if (number == 0) {
  341. return null;
  342. } else {
  343. long n = number;
  344. while (n > 0) {
  345. int d = (int) ((n - 1) % (long) base);
  346. int s = (map != null) ? map [ d ] : (one + d);
  347. sl.add(0, s);
  348. n = (n - 1) / base;
  349. }
  350. return sl.toArray(new Integer [ sl.size() ]);
  351. }
  352. }
  353. /**
  354. * Format NUMBER as using special system that starts at ONE.
  355. * @param number to be formatted
  356. * @param one unicode scalar value denoting start of system (numeric value 1)
  357. * @return formatted number as array of unicode scalars
  358. */
  359. private Integer[] formatNumberAsSpecial(long number, int one) {
  360. SpecialNumberFormatter f = getSpecialFormatter(one, letterValue, features, language, country);
  361. if (f != null) {
  362. return f.format(number, one, letterValue, features, language, country);
  363. } else {
  364. return null;
  365. }
  366. }
  367. /**
  368. * Format NUMBER as word according to TYPE, which must be either
  369. * Character.UPPERCASE_LETTER, Character.LOWERCASE_LETTER, or
  370. * Character.TITLECASE_LETTER. Makes use of this.language to
  371. * determine language of word.
  372. * @param number to be formatted
  373. * @param caseType unicode character type for case conversion
  374. * @return formatted number as array of unicode scalars
  375. */
  376. private Integer[] formatNumberAsWord(long number, int caseType) {
  377. SpecialNumberFormatter f = null;
  378. if (isLanguage("eng")) {
  379. f = new EnglishNumberAsWordFormatter(caseType);
  380. } else if (isLanguage("spa")) {
  381. f = new SpanishNumberAsWordFormatter(caseType);
  382. } else if (isLanguage("fra")) {
  383. f = new FrenchNumberAsWordFormatter(caseType);
  384. } else {
  385. f = new EnglishNumberAsWordFormatter(caseType);
  386. }
  387. return f.format(number, 0, letterValue, features, language, country);
  388. }
  389. private boolean isLanguage(String iso3Code) {
  390. if (language == null) {
  391. return false;
  392. } else if (language.equals(iso3Code)) {
  393. return true;
  394. } else {
  395. return isSameLanguage(iso3Code, language);
  396. }
  397. }
  398. private static String[][] equivalentLanguages = {
  399. { "eng", "en" },
  400. { "fra", "fre", "fr" },
  401. { "spa", "es" },
  402. };
  403. private static boolean isSameLanguage(String i3c, String lc) {
  404. for (String[] el : equivalentLanguages) {
  405. assert el.length >= 2;
  406. if (el[0].equals(i3c)) {
  407. for (int i = 0, n = el.length; i < n; i++) {
  408. if (el[i].equals(lc)) {
  409. return true;
  410. }
  411. }
  412. return false;
  413. }
  414. }
  415. return false;
  416. }
  417. private static boolean hasFeature(String features, String feature) {
  418. if (features != null) {
  419. assert feature != null;
  420. assert feature.length() != 0;
  421. String[] fa = features.split(",");
  422. for (String f : fa) {
  423. String[] fp = f.split("=");
  424. assert fp.length > 0;
  425. String fn = fp[0];
  426. String fv = (fp.length > 1) ? fp[1] : "";
  427. if (fn.equals(feature)) {
  428. return true;
  429. }
  430. }
  431. }
  432. return false;
  433. }
  434. /* not yet used
  435. private static String getFeatureValue ( String features, String feature ) {
  436. if ( features != null ) {
  437. assert feature != null;
  438. assert feature.length() != 0;
  439. String[] fa = features.split(",");
  440. for ( String f : fa ) {
  441. String[] fp = f.split("=");
  442. assert fp.length > 0;
  443. String fn = fp[0];
  444. String fv = ( fp.length > 1 ) ? fp[1] : "";
  445. if ( fn.equals ( feature ) ) {
  446. return fv;
  447. }
  448. }
  449. }
  450. return "";
  451. }
  452. */
  453. private static void appendScalars(List<Integer> scalars, Integer[] sa) {
  454. for (Integer s : sa) {
  455. scalars.add(s);
  456. }
  457. }
  458. private static String scalarsToString(List<Integer> scalars) {
  459. Integer[] sa = scalars.toArray(new Integer [ scalars.size() ]);
  460. return UTF32.fromUTF32(sa);
  461. }
  462. private static boolean isPaddedOne(Integer[] token) {
  463. if (getDecimalValue(token [ token.length - 1 ]) != 1) {
  464. return false;
  465. } else {
  466. for (int i = 0, n = token.length - 1; i < n; i++) {
  467. if (getDecimalValue(token [ i ]) != 0) {
  468. return false;
  469. }
  470. }
  471. return true;
  472. }
  473. }
  474. private static int getDecimalValue(Integer scalar) {
  475. int s = scalar.intValue();
  476. if (Character.getType(s) == Character.DECIMAL_DIGIT_NUMBER) {
  477. return Character.getNumericValue(s);
  478. } else {
  479. return -1;
  480. }
  481. }
  482. private static boolean isStartOfDecimalSequence(int s) {
  483. return (Character.getNumericValue(s) == 1)
  484. && (Character.getNumericValue(s - 1) == 0)
  485. && (Character.getNumericValue(s + 8) == 9);
  486. }
  487. private static int[][] supportedAlphabeticSequences = {
  488. { 'A', 26 }, // A...Z
  489. { 'a', 26 }, // a...z
  490. };
  491. private static boolean isStartOfAlphabeticSequence(int s) {
  492. for (int[] ss : supportedAlphabeticSequences) {
  493. assert ss.length >= 2;
  494. if (ss[0] == s) {
  495. return true;
  496. }
  497. }
  498. return false;
  499. }
  500. private static int getSequenceBase(int s) {
  501. for (int[] ss : supportedAlphabeticSequences) {
  502. assert ss.length >= 2;
  503. if (ss[0] == s) {
  504. return ss[1];
  505. }
  506. }
  507. return 0;
  508. }
  509. private static int[][] supportedSpecials = {
  510. { 'I' }, // latin - uppercase roman numerals
  511. { 'i' }, // latin - lowercase roman numerals
  512. { '\u0391' }, // greek - uppercase isopsephry numerals
  513. { '\u03B1' }, // greek - lowercase isopsephry numerals
  514. { '\u05D0' }, // hebrew - gematria numerals
  515. { '\u0623' }, // arabic - abjadi numberals
  516. { '\u0627' }, // arabic - either abjadi or hijai alphabetic sequence
  517. { '\u0E01' }, // thai - default alphabetic sequence
  518. { '\u3042' }, // kana - hiragana (gojuon) - default alphabetic sequence
  519. { '\u3044' }, // kana - hiragana (iroha)
  520. { '\u30A2' }, // kana - katakana (gojuon) - default alphabetic sequence
  521. { '\u30A4' }, // kana - katakana (iroha)
  522. };
  523. private static boolean isStartOfNumericSpecial(int s) {
  524. for (int[] ss : supportedSpecials) {
  525. assert ss.length >= 1;
  526. if (ss[0] == s) {
  527. return true;
  528. }
  529. }
  530. return false;
  531. }
  532. private SpecialNumberFormatter getSpecialFormatter(int one, int letterValue, String features, String language, String country) {
  533. if (one == (int) 'I') {
  534. return new RomanNumeralsFormatter();
  535. } else if (one == (int) 'i') {
  536. return new RomanNumeralsFormatter();
  537. } else if (one == (int) '\u0391') {
  538. return new IsopsephryNumeralsFormatter();
  539. } else if (one == (int) '\u03B1') {
  540. return new IsopsephryNumeralsFormatter();
  541. } else if (one == (int) '\u05D0') {
  542. return new GematriaNumeralsFormatter();
  543. } else if (one == (int) '\u0623') {
  544. return new ArabicNumeralsFormatter();
  545. } else if (one == (int) '\u0627') {
  546. return new ArabicNumeralsFormatter();
  547. } else if (one == (int) '\u0E01') {
  548. return new ThaiNumeralsFormatter();
  549. } else if (one == (int) '\u3042') {
  550. return new KanaNumeralsFormatter();
  551. } else if (one == (int) '\u3044') {
  552. return new KanaNumeralsFormatter();
  553. } else if (one == (int) '\u30A2') {
  554. return new KanaNumeralsFormatter();
  555. } else if (one == (int) '\u30A4') {
  556. return new KanaNumeralsFormatter();
  557. } else {
  558. return null;
  559. }
  560. }
  561. private static Integer[] toUpperCase(Integer[] sa) {
  562. assert sa != null;
  563. for (int i = 0, n = sa.length; i < n; i++) {
  564. Integer s = sa [ i ];
  565. sa [ i ] = Character.toUpperCase(s);
  566. }
  567. return sa;
  568. }
  569. private static Integer[] toLowerCase(Integer[] sa) {
  570. assert sa != null;
  571. for (int i = 0, n = sa.length; i < n; i++) {
  572. Integer s = sa [ i ];
  573. sa [ i ] = Character.toLowerCase(s);
  574. }
  575. return sa;
  576. }
  577. /* not yet used
  578. private static Integer[] toTitleCase ( Integer[] sa ) {
  579. assert sa != null;
  580. if ( sa.length > 0 ) {
  581. sa [ 0 ] = Character.toTitleCase ( sa [ 0 ] );
  582. }
  583. return sa;
  584. }
  585. */
  586. private static List<String> convertWordCase(List<String> words, int caseType) {
  587. List<String> wl = new ArrayList<String>();
  588. for (String w : words) {
  589. wl.add(convertWordCase(w, caseType));
  590. }
  591. return wl;
  592. }
  593. private static String convertWordCase(String word, int caseType) {
  594. if (caseType == Character.UPPERCASE_LETTER) {
  595. return word.toUpperCase();
  596. } else if (caseType == Character.LOWERCASE_LETTER) {
  597. return word.toLowerCase();
  598. } else if (caseType == Character.TITLECASE_LETTER) {
  599. StringBuffer sb = new StringBuffer();
  600. for (int i = 0, n = word.length(); i < n; i++) {
  601. String s = word.substring(i, i + 1);
  602. if (i == 0) {
  603. sb.append(s.toUpperCase());
  604. } else {
  605. sb.append(s.toLowerCase());
  606. }
  607. }
  608. return sb.toString();
  609. } else {
  610. return word;
  611. }
  612. }
  613. private static String joinWords(List<String> words, String separator) {
  614. StringBuffer sb = new StringBuffer();
  615. for (String w : words) {
  616. if (sb.length() > 0) {
  617. sb.append(separator);
  618. }
  619. sb.append(w);
  620. }
  621. return sb.toString();
  622. }
  623. /**
  624. * Special number formatter.
  625. */
  626. interface SpecialNumberFormatter {
  627. /**
  628. * Format number with special numeral system.
  629. * @param number to be formatted
  630. * @param one unicode scalar value denoting numeric value 1
  631. * @param letterValue letter value (must be one of the above letter value enumeration values)
  632. * @param features features (feature sub-parameters)
  633. * @param language denotes applicable language
  634. * @param country denotes applicable country
  635. * @return formatted number as array of unicode scalars
  636. */
  637. Integer[] format(long number, int one, int letterValue, String features, String language, String country);
  638. }
  639. /**
  640. * English Word Numerals
  641. */
  642. private static String[] englishWordOnes = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
  643. private static String[] englishWordTeens = { "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" };
  644. private static String[] englishWordTens = { "", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety" };
  645. private static String[] englishWordOthers = { "hundred", "thousand", "million", "billion" };
  646. private static String[] englishWordOnesOrd = { "none", "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth" };
  647. private static String[] englishWordTeensOrd = { "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth" };
  648. private static String[] englishWordTensOrd = { "", "tenth", "twentieth", "thirtieth", "fortieth", "fiftieth", "sixtieth", "seventieth", "eightieth", "ninetith" };
  649. private static String[] englishWordOthersOrd = { "hundredth", "thousandth", "millionth", "billionth" };
  650. private static class EnglishNumberAsWordFormatter implements SpecialNumberFormatter {
  651. private int caseType = Character.UPPERCASE_LETTER;
  652. EnglishNumberAsWordFormatter(int caseType) {
  653. this.caseType = caseType;
  654. }
  655. public Integer[] format(long number, int one, int letterValue, String features, String language, String country) {
  656. List<String> wl = new ArrayList<String>();
  657. if (number >= 1000000000000L) {
  658. return null;
  659. } else {
  660. boolean ordinal = hasFeature(features, "ordinal");
  661. if (number == 0) {
  662. wl.add(englishWordOnes [ 0 ]);
  663. } else if (ordinal && (number < 10)) {
  664. wl.add(englishWordOnesOrd [ (int) number ]);
  665. } else {
  666. int ones = (int) (number % 1000);
  667. int thousands = (int) ((number / 1000) % 1000);
  668. int millions = (int) ((number / 1000000) % 1000);
  669. int billions = (int) ((number / 1000000000) % 1000);
  670. if (billions > 0) {
  671. wl = formatOnesInThousand(wl, billions);
  672. if (ordinal && ((number % 1000000000) == 0)) {
  673. wl.add(englishWordOthersOrd[3]);
  674. } else {
  675. wl.add(englishWordOthers[3]);
  676. }
  677. }
  678. if (millions > 0) {
  679. wl = formatOnesInThousand(wl, millions);
  680. if (ordinal && ((number % 1000000) == 0)) {
  681. wl.add(englishWordOthersOrd[2]);
  682. } else {
  683. wl.add(englishWordOthers[2]);
  684. }
  685. }
  686. if (thousands > 0) {
  687. wl = formatOnesInThousand(wl, thousands);
  688. if (ordinal && ((number % 1000) == 0)) {
  689. wl.add(englishWordOthersOrd[1]);
  690. } else {
  691. wl.add(englishWordOthers[1]);
  692. }
  693. }
  694. if (ones > 0) {
  695. wl = formatOnesInThousand(wl, ones, ordinal);
  696. }
  697. }
  698. wl = convertWordCase(wl, caseType);
  699. return UTF32.toUTF32(joinWords(wl, " "), 0, true);
  700. }
  701. }
  702. private List<String> formatOnesInThousand(List<String> wl, int number) {
  703. return formatOnesInThousand(wl, number, false);
  704. }
  705. private List<String> formatOnesInThousand(List<String> wl, int number, boolean ordinal) {
  706. assert number < 1000;
  707. int ones = number % 10;
  708. int tens = (number / 10) % 10;
  709. int hundreds = (number / 100) % 10;
  710. if (hundreds > 0) {
  711. wl.add(englishWordOnes [ hundreds ]);
  712. if (ordinal && ((number % 100) == 0)) {
  713. wl.add(englishWordOthersOrd[0]);
  714. } else {
  715. wl.add(englishWordOthers[0]);
  716. }
  717. }
  718. if (tens > 0) {
  719. if (tens == 1) {
  720. if (ordinal) {
  721. wl.add(englishWordTeensOrd [ ones ]);
  722. } else {
  723. wl.add(englishWordTeens [ ones ]);
  724. }
  725. } else {
  726. if (ordinal && (ones == 0)) {
  727. wl.add(englishWordTensOrd [ tens ]);
  728. } else {
  729. wl.add(englishWordTens [ tens ]);
  730. }
  731. if (ones > 0) {
  732. if (ordinal) {
  733. wl.add(englishWordOnesOrd [ ones ]);
  734. } else {
  735. wl.add(englishWordOnes [ ones ]);
  736. }
  737. }
  738. }
  739. } else if (ones > 0) {
  740. if (ordinal) {
  741. wl.add(englishWordOnesOrd [ ones ]);
  742. } else {
  743. wl.add(englishWordOnes [ ones ]);
  744. }
  745. }
  746. return wl;
  747. }
  748. }
  749. /**
  750. * French Word Numerals
  751. */
  752. private static String[] frenchWordOnes = { "z\u00e9ro", "un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf" };
  753. private static String[] frenchWordTeens = { "dix", "onze", "douze", "treize", "quatorze", "quinze", "seize", "dix-sept", "dix-huit", "dix-neuf" };
  754. private static String[] frenchWordTens = { "", "dix", "vingt", "trente", "quarante", "cinquante", "soixante", "soixante-dix", "quatre-vingt", "quatre-vingt-dix" };
  755. private static String[] frenchWordOthers = { "cent", "cents", "mille", "million", "millions", "milliard", "milliards" };
  756. private static String[] frenchWordOnesOrdMale = { "premier", "deuxi\u00e8me", "troisi\u00e8me", "quatri\u00e8me", "cinqui\u00e8me", "sixi\u00e8me", "septi\u00e8me", "huiti\u00e8me", "neuvi\u00e8me", "dixi\u00e8me" };
  757. private static String[] frenchWordOnesOrdFemale = { "premi\u00e8re", "deuxi\u00e8me", "troisi\u00e8me", "quatri\u00e8me", "cinqui\u00e8me", "sixi\u00e8me", "septi\u00e8me", "huiti\u00e8me", "neuvi\u00e8me", "dixi\u00e8me" };
  758. private static class FrenchNumberAsWordFormatter implements SpecialNumberFormatter {
  759. private int caseType = Character.UPPERCASE_LETTER;
  760. FrenchNumberAsWordFormatter(int caseType) {
  761. this.caseType = caseType;
  762. }
  763. public Integer[] format(long number, int one, int letterValue, String features, String language, String country) {
  764. List<String> wl = new ArrayList<String>();
  765. if (number >= 1000000000000L) {
  766. return null;
  767. } else {
  768. boolean ordinal = hasFeature(features, "ordinal");
  769. if (number == 0) {
  770. wl.add(frenchWordOnes [ 0 ]);
  771. } else if (ordinal && (number <= 10)) {
  772. boolean female = hasFeature(features, "female");
  773. if (female) {
  774. wl.add(frenchWordOnesOrdFemale [ (int) number ]);
  775. } else {
  776. wl.add(frenchWordOnesOrdMale [ (int) number ]);
  777. }
  778. } else {
  779. int ones = (int) (number % 1000);
  780. int thousands = (int) ((number / 1000) % 1000);
  781. int millions = (int) ((number / 1000000) % 1000);
  782. int billions = (int) ((number / 1000000000) % 1000);
  783. if (billions > 0) {
  784. wl = formatOnesInThousand(wl, billions);
  785. if (billions == 1) {
  786. wl.add(frenchWordOthers[5]);
  787. } else {
  788. wl.add(frenchWordOthers[6]);
  789. }
  790. }
  791. if (millions > 0) {
  792. wl = formatOnesInThousand(wl, millions);
  793. if (millions == 1) {
  794. wl.add(frenchWordOthers[3]);
  795. } else {
  796. wl.add(frenchWordOthers[4]);
  797. }
  798. }
  799. if (thousands > 0) {
  800. if (thousands > 1) {
  801. wl = formatOnesInThousand(wl, thousands);
  802. }
  803. wl.add(frenchWordOthers[2]);
  804. }
  805. if (ones > 0) {
  806. wl = formatOnesInThousand(wl, ones);
  807. }
  808. }
  809. wl = convertWordCase(wl, caseType);
  810. return UTF32.toUTF32(joinWords(wl, " "), 0, true);
  811. }
  812. }
  813. private List<String> formatOnesInThousand(List<String> wl, int number) {
  814. assert number < 1000;
  815. int ones = number % 10;
  816. int tens = (number / 10) % 10;
  817. int hundreds = (number / 100) % 10;
  818. if (hundreds > 0) {
  819. if (hundreds > 1) {
  820. wl.add(frenchWordOnes [ hundreds ]);
  821. }
  822. if ((hundreds > 1) && (tens == 0) && (ones == 0)) {
  823. wl.add(frenchWordOthers[1]);
  824. } else {
  825. wl.add(frenchWordOthers[0]);
  826. }
  827. }
  828. if (tens > 0) {
  829. if (tens == 1) {
  830. wl.add(frenchWordTeens [ ones ]);
  831. } else if (tens < 7) {
  832. if (ones == 1) {
  833. wl.add(frenchWordTens [ tens ]);
  834. wl.add("et");
  835. wl.add(frenchWordOnes [ ones ]);
  836. } else {
  837. StringBuffer sb = new StringBuffer();
  838. sb.append(frenchWordTens [ tens ]);
  839. if (ones > 0) {
  840. sb.append('-');
  841. sb.append(frenchWordOnes [ ones ]);
  842. }
  843. wl.add(sb.toString());
  844. }
  845. } else if (tens == 7) {
  846. if (ones == 1) {
  847. wl.add(frenchWordTens [ 6 ]);
  848. wl.add("et");
  849. wl.add(frenchWordTeens [ ones ]);
  850. } else {
  851. StringBuffer sb = new StringBuffer();
  852. sb.append(frenchWordTens [ 6 ]);
  853. sb.append('-');
  854. sb.append(frenchWordTeens [ ones ]);
  855. wl.add(sb.toString());
  856. }
  857. } else if (tens == 8) {
  858. StringBuffer sb = new StringBuffer();
  859. sb.append(frenchWordTens [ tens ]);
  860. if (ones > 0) {
  861. sb.append('-');
  862. sb.append(frenchWordOnes [ ones ]);
  863. } else {
  864. sb.append('s');
  865. }
  866. wl.add(sb.toString());
  867. } else if (tens == 9) {
  868. StringBuffer sb = new StringBuffer();
  869. sb.append(frenchWordTens [ 8 ]);
  870. sb.append('-');
  871. sb.append(frenchWordTeens [ ones ]);
  872. wl.add(sb.toString());
  873. }
  874. } else if (ones > 0) {
  875. wl.add(frenchWordOnes [ ones ]);
  876. }
  877. return wl;
  878. }
  879. }
  880. /**
  881. * Spanish Word Numerals
  882. */
  883. private static String[] spanishWordOnes = { "cero", "uno", "dos", "tres", "cuatro", "cinco", "seise", "siete", "ocho", "nueve" };
  884. private static String[] spanishWordTeens = { "diez", "once", "doce", "trece", "catorce", "quince", "diecis\u00e9is", "diecisiete", "dieciocho", "diecinueve" };
  885. private static String[] spanishWordTweens = { "veinte", "veintiuno", "veintid\u00f3s", "veintitr\u00e9s", "veinticuatro", "veinticinco", "veintis\u00e9is", "veintisiete", "veintiocho", "veintinueve" };
  886. private static String[] spanishWordTens = { "", "diez", "veinte", "treinta", "cuarenta", "cincuenta", "sesenta", "setenta", "ochenta", "noventa" };
  887. private static String[] spanishWordHundreds = { "", "ciento", "doscientos", "trescientos", "cuatrocientos", "quinientos", "seiscientos", "setecientos", "ochocientos", "novecientos" };
  888. private static String[] spanishWordOthers = { "un", "cien", "mil", "mill\u00f3n", "millones" };
  889. private static String[] spanishWordOnesOrdMale = { "ninguno", "primero", "segundo", "tercero", "cuarto", "quinto", "sexto", "s\u00e9ptimo", "octavo", "novento", "d\u00e9cimo" };
  890. private static String[] spanishWordOnesOrdFemale = { "ninguna", "primera", "segunda", "tercera", "cuarta", "quinta", "sexta", "s\u00e9ptima", "octava", "noventa", "d\u00e9cima" };
  891. private static class SpanishNumberAsWordFormatter implements SpecialNumberFormatter {
  892. private int caseType = Character.UPPERCASE_LETTER;
  893. SpanishNumberAsWordFormatter(int caseType) {
  894. this.caseType = caseType;
  895. }
  896. public Integer[] format(long number, int one, int letterValue, String features, String language, String country) {
  897. List<String> wl = new ArrayList<String>();
  898. if (number >= 1000000000000L) {
  899. return null;
  900. } else {
  901. boolean ordinal = hasFeature(features, "ordinal");
  902. if (number == 0) {
  903. wl.add(spanishWordOnes [ 0 ]);
  904. } else if (ordinal && (number <= 10)) {
  905. boolean female = hasFeature(features, "female");
  906. if (female) {
  907. wl.add(spanishWordOnesOrdFemale [ (int) number ]);
  908. } else {
  909. wl.add(spanishWordOnesOrdMale [ (int) number ]);
  910. }
  911. } else {
  912. int ones = (int) (number % 1000);
  913. int thousands = (int) ((number / 1000) % 1000);
  914. int millions = (int) ((number / 1000000) % 1000);
  915. int billions = (int) ((number / 1000000000) % 1000);
  916. if (billions > 0) {
  917. if (billions > 1) {
  918. wl = formatOnesInThousand(wl, billions);
  919. }
  920. wl.add(spanishWordOthers[2]);
  921. wl.add(spanishWordOthers[4]);
  922. }
  923. if (millions > 0) {
  924. if (millions == 1) {
  925. wl.add(spanishWordOthers[0]);
  926. } else {
  927. wl = formatOnesInThousand(wl, millions);
  928. }
  929. if (millions > 1) {
  930. wl.add(spanishWordOthers[4]);
  931. } else {
  932. wl.add(spanishWordOthers[3]);
  933. }
  934. }
  935. if (thousands > 0) {
  936. if (thousands > 1) {
  937. wl = formatOnesInThousand(wl, thousands);
  938. }
  939. wl.add(spanishWordOthers[2]);
  940. }
  941. if (ones > 0) {
  942. wl = formatOnesInThousand(wl, ones);
  943. }
  944. }
  945. wl = convertWordCase(wl, caseType);
  946. return UTF32.toUTF32(joinWords(wl, " "), 0, true);
  947. }
  948. }
  949. private List<String> formatOnesInThousand(List<String> wl, int number) {
  950. assert number < 1000;
  951. int ones = number % 10;
  952. int tens = (number / 10) % 10;
  953. int hundreds = (number / 100) % 10;
  954. if (hundreds > 0) {
  955. if ((hundreds == 1) && (tens == 0) && (ones == 0)) {
  956. wl.add(spanishWordOthers[1]);
  957. } else {
  958. wl.add(spanishWordHundreds [ hundreds ]);
  959. }
  960. }
  961. if (tens > 0) {
  962. if (tens == 1) {
  963. wl.add(spanishWordTeens [ ones ]);
  964. } else if (tens == 2) {
  965. wl.add(spanishWordTweens [ ones ]);
  966. } else {
  967. wl.add(spanishWordTens [ tens ]);
  968. if (ones > 0) {
  969. wl.add("y");
  970. wl.add(spanishWordOnes [ ones ]);
  971. }
  972. }
  973. } else if (ones > 0) {
  974. wl.add(spanishWordOnes [ ones ]);
  975. }
  976. return wl;
  977. }
  978. }
  979. /**
  980. * Roman (Latin) Numerals
  981. */
  982. private static int[] romanMapping = {
  983. 100000,
  984. 90000,
  985. 50000,
  986. 40000,
  987. 10000,
  988. 9000,
  989. 5000,
  990. 4000,
  991. 1000,
  992. 900,
  993. 500,
  994. 400,
  995. 100,
  996. 90,
  997. 50,
  998. 40,
  999. 10,
  1000. 9,
  1001. 8,
  1002. 7,
  1003. 6,
  1004. 5,
  1005. 4,
  1006. 3,
  1007. 2,
  1008. 1
  1009. };
  1010. private static String[] romanStandardForms = {
  1011. null,
  1012. null,
  1013. null,
  1014. null,
  1015. null,
  1016. null,
  1017. null,
  1018. null,
  1019. "m",
  1020. "cm",
  1021. "d",
  1022. "cd",
  1023. "c",
  1024. "xc",
  1025. "l",
  1026. "xl",
  1027. "x",
  1028. "ix",
  1029. null,
  1030. null,
  1031. null,
  1032. "v",
  1033. "iv",
  1034. null,
  1035. null,
  1036. "i"
  1037. };
  1038. private static String[] romanLargeForms = {
  1039. "\u2188",
  1040. "\u2182\u2188",
  1041. "\u2187",
  1042. "\u2182\u2187",
  1043. "\u2182",
  1044. "\u2180\u2182",
  1045. "\u2181",
  1046. "\u2180\u2181",
  1047. "m",
  1048. "cm",
  1049. "d",
  1050. "cd",
  1051. "c",
  1052. "xc",
  1053. "l",
  1054. "xl",
  1055. "x",
  1056. "ix",
  1057. null,
  1058. null,
  1059. null,
  1060. "v",
  1061. "iv",
  1062. null,
  1063. null,
  1064. "i"
  1065. };
  1066. private static String[] romanNumberForms = {
  1067. "\u2188",
  1068. "\u2182\u2188",
  1069. "\u2187",
  1070. "\u2182\u2187",
  1071. "\u2182",
  1072. "\u2180\u2182",
  1073. "\u2181",
  1074. "\u2180\u2181",
  1075. "\u216F",
  1076. "\u216D\u216F",
  1077. "\u216E",
  1078. "\u216D\u216E",
  1079. "\u216D",
  1080. "\u2169\u216D",
  1081. "\u216C",
  1082. "\u2169\u216C",
  1083. "\u2169",
  1084. "\u2168",
  1085. "\u2167",
  1086. "\u2166",
  1087. "\u2165",
  1088. "\u2164",
  1089. "\u2163",
  1090. "\u2162",
  1091. "\u2161",
  1092. "\u2160"
  1093. };
  1094. private static class RomanNumeralsFormatter implements SpecialNumberFormatter {
  1095. public Integer[] format(long number, int one, int letterValue, String features, String language, String country) {
  1096. List<Integer> sl = new ArrayList<Integer>();
  1097. if (number == 0) {
  1098. return null;
  1099. } else {
  1100. String[] forms;
  1101. int maxNumber;
  1102. if (hasFeature(features, "unicode-number-forms")) {
  1103. forms = romanNumberForms;
  1104. maxNumber = 199999;
  1105. } else if (hasFeature(features, "large")) {
  1106. forms = romanLargeForms;
  1107. maxNumber = 199999;
  1108. } else {
  1109. forms = romanStandardForms;
  1110. maxNumber = 4999;
  1111. }
  1112. if (number > maxNumber) {
  1113. return null;
  1114. } else {
  1115. while (number > 0) {
  1116. for (int i = 0, n = romanMapping.length; i < n; i++) {
  1117. int d = romanMapping [ i ];
  1118. if ((number >= d) && (forms [ i ] != null)) {
  1119. appendScalars(sl, UTF32.toUTF32(forms [ i ], 0, true));
  1120. number = number - d;
  1121. break;
  1122. }
  1123. }
  1124. }
  1125. if (one == (int) 'I') {
  1126. return toUpperCase(sl.toArray(new Integer [ sl.size() ]));
  1127. } else if (one == (int) 'i') {
  1128. return toLowerCase(sl.toArray(new Integer [ sl.size() ]));
  1129. } else {
  1130. return null;
  1131. }
  1132. }
  1133. }
  1134. }
  1135. }
  1136. /**
  1137. * Isopsephry (Greek) Numerals
  1138. */
  1139. private static class IsopsephryNumeralsFormatter implements SpecialNumberFormatter {
  1140. public Integer[] format(long number, int one, int letterValue, String features, String language, String country) {
  1141. return null;
  1142. }
  1143. }
  1144. /**
  1145. * Gematria (Hebrew) Numerals
  1146. */
  1147. private static int[] hebrewGematriaAlphabeticMap = {
  1148. // ones
  1149. 0x05D0, // ALEF
  1150. 0x05D1, // BET
  1151. 0x05D2, // GIMEL
  1152. 0x05D3, // DALET
  1153. 0x05D4, // HE
  1154. 0x05D5, // VAV
  1155. 0x05D6, // ZAYIN
  1156. 0x05D7, // HET
  1157. 0x05D8, // TET
  1158. // tens
  1159. 0x05D9, // YOD
  1160. 0x05DB, // KAF
  1161. 0x05DC, // LAMED
  1162. 0x05DE, // MEM
  1163. 0x05E0, // NUN
  1164. 0x05E1, // SAMEKH
  1165. 0x05E2, // AYIN
  1166. 0x05E4, // PE
  1167. 0x05E6, // TSADHI
  1168. // hundreds
  1169. 0x05E7, // QOF
  1170. 0x05E8, // RESH
  1171. 0x05E9, // SHIN
  1172. 0x05EA, // TAV
  1173. 0x05DA, // FINAL KAF
  1174. 0x05DD, // FINAL MEM
  1175. 0x05DF, // FINAL NUN
  1176. 0x05E3, // FINAL PE
  1177. 0x05E5, // FINAL TSADHI
  1178. };
  1179. private class GematriaNumeralsFormatter implements SpecialNumberFormatter {
  1180. public Integer[] format(long number, int one, int letterValue, String features, String language, String country) {
  1181. if (one == 0x05D0) {
  1182. if (letterValue == LETTER_VALUE_ALPHABETIC) {
  1183. return formatNumberAsSequence(number, one, hebrewGematriaAlphabeticMap.length, hebrewGematriaAlphabeticMap);
  1184. } else if (letterValue == LETTER_VALUE_TRADITIONAL) {
  1185. if ((number == 0) || (number > 1999)) {
  1186. return null;
  1187. } else {
  1188. return formatAsGematriaNumber(number, features, language, country);
  1189. }
  1190. } else {
  1191. return null;
  1192. }
  1193. } else {
  1194. return null;
  1195. }
  1196. }
  1197. private Integer[] formatAsGematriaNumber(long number, String features, String language, String country) {
  1198. List<Integer> sl = new ArrayList<Integer>();
  1199. assert hebrewGematriaAlphabeticMap.length == 27;
  1200. assert hebrewGematriaAlphabeticMap[0] == 0x05D0; // ALEF
  1201. assert hebrewGematriaAlphabeticMap[21] == 0x05EA; // TAV
  1202. assert number != 0;
  1203. assert number < 2000;
  1204. int[] map = hebrewGematriaAlphabeticMap;
  1205. int thousands = (int) ((number / 1000) % 10);
  1206. int hundreds = (int) ((number / 100) % 10);
  1207. int tens = (int) ((number / 10) % 10);
  1208. int ones = (int) ((number / 1) % 10);
  1209. if (thousands > 0) {
  1210. sl.add(map [ 0 + (thousands - 1) ]);
  1211. sl.add(0x05F3);
  1212. }
  1213. if (hundreds > 0) {
  1214. assert hundreds < 10;
  1215. if (hundreds < 5) {
  1216. sl.add(map [ 18 + (hundreds - 1) ]);
  1217. } else if (hundreds < 9) {
  1218. sl.add(map [ 18 + (4 - 1) ]);
  1219. sl.add(0x05F4);
  1220. sl.add(map [ 18 + (hundreds - 5) ]);
  1221. } else if (hundreds == 9) {
  1222. sl.add(map [ 18 + (4 - 1) ]);
  1223. sl.add(map [ 18 + (4 - 1) ]);
  1224. sl.add(0x05F4);
  1225. sl.add(map [ 18 + (hundreds - 9) ]);
  1226. }
  1227. }
  1228. if (number == 15) {
  1229. sl.add(map [ 9 - 1]);
  1230. sl.add(0x05F4);
  1231. sl.add(map [ 6 - 1]);
  1232. } else if (number == 16) {
  1233. sl.add(map [ 9 - 1 ]);
  1234. sl.add(0x05F4);
  1235. sl.add(map [ 7 - 1 ]);
  1236. } else {
  1237. if (tens > 0) {
  1238. assert tens < 10;
  1239. sl.add(map [ 9 + (tens - 1) ]);
  1240. }
  1241. if (ones > 0) {
  1242. assert ones < 10;
  1243. sl.add(map [ 0 + (ones - 1) ]);
  1244. }
  1245. }
  1246. return sl.toArray(new Integer [ sl.size() ]);
  1247. }
  1248. }
  1249. /**
  1250. * Arabic Numerals
  1251. */
  1252. private static int[] arabicAbjadiAlphabeticMap = {
  1253. // ones
  1254. 0x0623, // ALEF WITH HAMZA ABOVE
  1255. 0x0628, // BEH
  1256. 0x062C, // JEEM
  1257. 0x062F, // DAL
  1258. 0x0647, // HEH
  1259. 0x0648, // WAW
  1260. 0x0632, // ZAIN
  1261. 0x062D, // HAH
  1262. 0x0637, // TAH
  1263. // tens
  1264. 0x0649, // ALEF MAQSURA
  1265. 0x0643, // KAF
  1266. 0x0644, // LAM
  1267. 0x0645, // MEEM
  1268. 0x0646, // NOON
  1269. 0x0633, // SEEN
  1270. 0x0639, // AIN
  1271. 0x0641, // FEH
  1272. 0x0635, // SAD
  1273. // hundreds
  1274. 0x0642, // QAF
  1275. 0x0631, // REH
  1276. 0x0634, // SHEEN
  1277. 0x062A, // TEH
  1278. 0x062B, // THEH
  1279. 0x062E, // KHAH
  1280. 0x0630, // THAL
  1281. 0x0636, // DAD
  1282. 0x0638, // ZAH
  1283. // thousands
  1284. 0x063A, // GHAIN
  1285. };
  1286. private static int[] arabicHijaiAlphabeticMap = {
  1287. 0x0623, // ALEF WITH HAMZA ABOVE
  1288. 0x0628, // BEH
  1289. 0x062A, // TEH
  1290. 0x062B, // THEH
  1291. 0x062C, // JEEM
  1292. 0x062D, // HAH
  1293. 0x062E, // KHAH
  1294. 0x062F, // DAL
  1295. 0x0630, // THAL
  1296. 0x0631, // REH
  1297. 0x0632, // ZAIN
  1298. 0x0633, // SEEN
  1299. 0x0634, // SHEEN
  1300. 0x0635, // SAD
  1301. 0x0636, // DAD
  1302. 0x0637, // TAH
  1303. 0x0638, // ZAH
  1304. 0x0639, // AIN
  1305. 0x063A, // GHAIN
  1306. 0x0641, // FEH
  1307. 0x0642, // QAF
  1308. 0x0643, // KAF
  1309. 0x0644, // LAM
  1310. 0x0645, // MEEM
  1311. 0x0646, // NOON
  1312. 0x0647, // HEH
  1313. 0x0648, // WAW
  1314. 0x0649, // ALEF MAQSURA
  1315. };
  1316. private class ArabicNumeralsFormatter implements SpecialNumberFormatter {
  1317. public Integer[] format(long number, int one, int letterValue, String features, String language, String country) {
  1318. if (one == 0x0627) {
  1319. int[] map;
  1320. if (letterValue == LETTER_VALUE_TRADITIONAL) {
  1321. map = arabicAbjadiAlphabeticMap;
  1322. } else if (letterValue == LETTER_VALUE_ALPHABETIC) {
  1323. map = arabicHijaiAlphabeticMap;
  1324. } else {
  1325. map = arabicAbjadiAlphabeticMap;
  1326. }
  1327. return formatNumberAsSequence(number, one, map.length, map);
  1328. } else if (one == 0x0623) {
  1329. if ((number == 0) || (number > 1999)) {
  1330. return null;
  1331. } else {
  1332. return formatAsAbjadiNumber(number, features, language, country);
  1333. }
  1334. } else {
  1335. return null;
  1336. }
  1337. }
  1338. private Integer[] formatAsAbjadiNumber(long number, String features, String language, String country) {
  1339. List<Integer> sl = new ArrayList<Integer>();
  1340. assert arabicAbjadiAlphabeticMap.length == 28;
  1341. assert arabicAbjadiAlphabeticMap[0] == 0x0623; // ALEF WITH HAMZA ABOVE
  1342. assert arabicAbjadiAlphabeticMap[27] == 0x063A; // GHAIN
  1343. assert number != 0;
  1344. assert number < 2000;
  1345. int[] map = arabicAbjadiAlphabeticMap;
  1346. int thousands = (int) ((number / 1000) % 10);
  1347. int hundreds = (int) ((number / 100) % 10);
  1348. int tens = (int) ((number / 10) % 10);
  1349. int ones = (int) ((number / 1) % 10);
  1350. if (thousands > 0) {
  1351. assert thousands < 2;
  1352. sl.add(map [ 27 + (thousands - 1) ]);
  1353. }
  1354. if (hundreds > 0) {
  1355. assert thousands < 10;
  1356. sl.add(map [ 18 + (hundreds - 1) ]);
  1357. }
  1358. if (tens > 0) {
  1359. assert tens < 10;
  1360. sl.add(map [ 9 + (tens - 1) ]);
  1361. }
  1362. if (ones > 0) {
  1363. assert ones < 10;
  1364. sl.add(map [ 0 + (ones - 1) ]);
  1365. }
  1366. return sl.toArray(new Integer [ sl.size() ]);
  1367. }
  1368. }
  1369. /**
  1370. * Kana (Japanese) Numerals
  1371. */
  1372. private static int[] hiraganaGojuonAlphabeticMap = {
  1373. 0x3042, // A
  1374. 0x3044, // I
  1375. 0x3046, // U
  1376. 0x3048, // E
  1377. 0x304A, // O
  1378. 0x304B, // KA
  1379. 0x304D, // KI
  1380. 0x304F, // KU
  1381. 0x3051, // KE
  1382. 0x3053, // KO
  1383. 0x3055, // SA
  1384. 0x3057, // SI
  1385. 0x3059, // SU
  1386. 0x305B, // SE
  1387. 0x305D, // SO
  1388. 0x305F, // TA
  1389. 0x3061, // TI
  1390. 0x3064, // TU
  1391. 0x3066, // TE
  1392. 0x3068, // TO
  1393. 0x306A, // NA
  1394. 0x306B, // NI
  1395. 0x306C, // NU
  1396. 0x306D, // NE
  1397. 0x306E, // NO
  1398. 0x306F, // HA
  1399. 0x3072, // HI
  1400. 0x3075, // HU
  1401. 0x3078, // HE
  1402. 0x307B, // HO
  1403. 0x307E, // MA
  1404. 0x307F, // MI
  1405. 0x3080, // MU
  1406. 0x3081, // ME
  1407. 0x3082, // MO
  1408. 0x3084, // YA
  1409. 0x3086, // YU
  1410. 0x3088, // YO
  1411. 0x3089, // RA
  1412. 0x308A, // RI
  1413. 0x308B, // RU
  1414. 0x308C, // RE
  1415. 0x308D, // RO
  1416. 0x308F, // WA
  1417. 0x3090, // WI
  1418. 0x3091, // WE
  1419. 0x3092, // WO
  1420. 0x3093, // N
  1421. };
  1422. private static int[] katakanaGojuonAlphabeticMap = {
  1423. 0x30A2, // A
  1424. 0x30A4, // I
  1425. 0x30A6, // U
  1426. 0x30A8, // E
  1427. 0x30AA, // O
  1428. 0x30AB, // KA
  1429. 0x30AD, // KI
  1430. 0x30AF, // KU
  1431. 0x30B1, // KE
  1432. 0x30B3, // KO
  1433. 0x30B5, // SA
  1434. 0x30B7, // SI
  1435. 0x30B9, // SU
  1436. 0x30BB, // SE
  1437. 0x30BD, // SO
  1438. 0x30BF, // TA
  1439. 0x30C1, // TI
  1440. 0x30C4, // TU
  1441. 0x30C6, // TE
  1442. 0x30C8, // TO
  1443. 0x30CA, // NA
  1444. 0x30CB, // NI
  1445. 0x30CC, // NU
  1446. 0x30CD, // NE
  1447. 0x30CE, // NO
  1448. 0x30CF, // HA
  1449. 0x30D2, // HI
  1450. 0x30D5, // HU
  1451. 0x30D8, // HE
  1452. 0x30DB, // HO
  1453. 0x30DE, // MA
  1454. 0x30DF, // MI
  1455. 0x30E0, // MU
  1456. 0x30E1, // ME
  1457. 0x30E2, // MO
  1458. 0x30E4, // YA
  1459. 0x30E6, // YU
  1460. 0x30E8, // YO
  1461. 0x30E9, // RA
  1462. 0x30EA, // RI
  1463. 0x30EB, // RU
  1464. 0x30EC, // RE
  1465. 0x30ED, // RO
  1466. 0x30EF, // WA
  1467. 0x30F0, // WI
  1468. 0x30F1, // WE
  1469. 0x30F2, // WO
  1470. 0x30F3, // N
  1471. };
  1472. private class KanaNumeralsFormatter implements SpecialNumberFormatter {
  1473. public Integer[] format(long number, int one, int letterValue, String features, String language, String country) {
  1474. if ((one == 0x3042) && (letterValue == LETTER_VALUE_ALPHABETIC)) {
  1475. return formatNumberAsSequence(number, one, hiraganaGojuonAlphabeticMap.length, hiraganaGojuonAlphabeticMap);
  1476. } else if ((one == 0x30A2) && (letterValue == LETTER_VALUE_ALPHABETIC)) {
  1477. return formatNumberAsSequence(number, one, katakanaGojuonAlphabeticMap.length, katakanaGojuonAlphabeticMap);
  1478. } else {
  1479. return null;
  1480. }
  1481. }
  1482. }
  1483. /**
  1484. * Thai Numerals
  1485. */
  1486. private static int[] thaiAlphabeticMap = {
  1487. 0x0E01,
  1488. 0x0E02,
  1489. 0x0E03,
  1490. 0x0E04,
  1491. 0x0E05,
  1492. 0x0E06,
  1493. 0x0E07,
  1494. 0x0E08,
  1495. 0x0E09,
  1496. 0x0E0A,
  1497. 0x0E0B,
  1498. 0x0E0C,
  1499. 0x0E0D,
  1500. 0x0E0E,
  1501. 0x0E0F,
  1502. 0x0E10,
  1503. 0x0E11,
  1504. 0x0E12,
  1505. 0x0E13,
  1506. 0x0E14,
  1507. 0x0E15,
  1508. 0x0E16,
  1509. 0x0E17,
  1510. 0x0E18,
  1511. 0x0E19,
  1512. 0x0E1A,
  1513. 0x0E1B,
  1514. 0x0E1C,
  1515. 0x0E1D,
  1516. 0x0E1E,
  1517. 0x0E1F,
  1518. 0x0E20,
  1519. 0x0E21,
  1520. 0x0E22,
  1521. 0x0E23,
  1522. // 0x0E24, // RU - not used in modern sequence
  1523. 0x0E25,
  1524. // 0x0E26, // LU - not used in modern sequence
  1525. 0x0E27,
  1526. 0x0E28,
  1527. 0x0E29,
  1528. 0x0E2A,
  1529. 0x0E2B,
  1530. 0x0E2C,
  1531. 0x0E2D,
  1532. 0x0E2E,
  1533. };
  1534. private class ThaiNumeralsFormatter implements SpecialNumberFormatter {
  1535. public Integer[] format(long number, int one, int letterValue, String features, String language, String country) {
  1536. if ((one == 0x0E01) && (letterValue == LETTER_VALUE_ALPHABETIC)) {
  1537. return formatNumberAsSequence(number, one, thaiAlphabeticMap.length, thaiAlphabeticMap);
  1538. } else {
  1539. return null;
  1540. }
  1541. }
  1542. }
  1543. }