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.

GenerateBidiTestData.java 45KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265
  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.bidi;
  19. import java.io.BufferedReader;
  20. import java.io.File;
  21. import java.io.FileOutputStream;
  22. import java.io.FileWriter;
  23. import java.io.IOException;
  24. import java.io.InputStreamReader;
  25. import java.io.ObjectOutputStream;
  26. import java.io.PrintWriter;
  27. import java.net.URL;
  28. import java.text.CharacterIterator;
  29. import java.text.StringCharacterIterator;
  30. import java.util.ArrayList;
  31. import java.util.HashMap;
  32. import java.util.Iterator;
  33. import java.util.List;
  34. import java.util.Map;
  35. import java.util.TreeMap;
  36. import org.apache.fop.util.License;
  37. // CSOFF: LineLengthCheck
  38. /**
  39. * <p>Utility for generating a Java class and associated data files representing
  40. * bidirectional confomance test data from the Unicode Character Database and
  41. * Unicode BidiTest data files.</p>
  42. *
  43. * <p>This code is derived in part from GenerateBidiClassUtils.java.</p>
  44. *
  45. * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
  46. */
  47. public final class GenerateBidiTestData {
  48. // local constants
  49. private static final String PFX_TYPE = "@Type:";
  50. private static final String PFX_LEVELS = "@Levels:";
  51. private static final String PFX_REORDER = "@Reorder:";
  52. // command line options
  53. private static boolean ignoreDeprecatedTypeData;
  54. private static boolean verbose;
  55. // instrumentation
  56. private static int lineNumber;
  57. private static int numTypeRanges;
  58. private static int numLevelSpecs;
  59. private static int numTestSpecs;
  60. // compiled data
  61. private static int[][] td; // types data
  62. private static int[][] ld; // levels data
  63. // ensure non-instantiation
  64. private GenerateBidiTestData() {
  65. }
  66. /**
  67. * Generate a class managing bidi test data for Unicode characters.
  68. *
  69. * @param ucdFileName name (as URL) of file containing unicode character database data
  70. * @param bidiFileName name (as URL) of file containing bidi test data
  71. * @param outFileName name of the output class file
  72. * @throws Exception
  73. */
  74. private static void convertBidiTestData(String ucdFileName, String bidiFileName, String outFileName) throws Exception {
  75. // read type data from UCD if ignoring deprecated type data
  76. if (ignoreDeprecatedTypeData) {
  77. readBidiTypeData(ucdFileName);
  78. }
  79. // read bidi test data
  80. readBidiTestData(bidiFileName);
  81. // generate class
  82. PrintWriter out = new PrintWriter(new FileWriter(outFileName));
  83. License.writeJavaLicenseId(out);
  84. out.println();
  85. out.println("package org.apache.fop.complexscripts.bidi;");
  86. out.println();
  87. out.println("import java.io.IOException;");
  88. out.println("import java.io.InputStream;");
  89. out.println("import java.io.ObjectInputStream;");
  90. out.println();
  91. out.println("// CSOFF: WhitespaceAfterCheck");
  92. out.println();
  93. out.println("/*");
  94. out.println(" * !!! THIS IS A GENERATED FILE !!!");
  95. out.println(" * If updates to the source are needed, then:");
  96. out.println(" * - apply the necessary modifications to");
  97. out.println(" * 'src/codegen/unicode/java/org/apache/fop/text/bidi/GenerateBidiTestData.java'");
  98. out.println(" * - run 'ant codegen-unicode', which will generate a new BidiTestData.java");
  99. out.println(" * in 'test/java/org/apache/fop/complexscripts/bidi'");
  100. out.println(" * - commit BOTH changed files");
  101. out.println(" */");
  102. out.println();
  103. out.println("/** Bidirectional test data. */");
  104. out.println("public final class BidiTestData {");
  105. out.println();
  106. out.println(" private BidiTestData() {");
  107. out.println(" }");
  108. out.println();
  109. dumpData(out, outFileName);
  110. out.println(" public static final int NUM_TEST_SEQUENCES = " + numTestSpecs + ";");
  111. out.println();
  112. out.println(" public static int[] readTestData ( String prefix, int index ) {");
  113. out.println(" int[] data = null;");
  114. out.println(" InputStream is = null;");
  115. out.println(" Class btc = BidiTestData.class;");
  116. out.println(" String name = btc.getSimpleName() + \"$\" + prefix + index + \".ser\";");
  117. out.println(" try {");
  118. out.println(" if ( ( is = btc.getResourceAsStream ( name ) ) != null ) {");
  119. out.println(" ObjectInputStream ois = new ObjectInputStream ( is );");
  120. out.println(" data = (int[]) ois.readObject();");
  121. out.println(" ois.close();");
  122. out.println(" }");
  123. out.println(" } catch ( IOException e ) {");
  124. out.println(" data = null;");
  125. out.println(" } catch ( ClassNotFoundException e ) {");
  126. out.println(" data = null;");
  127. out.println(" } finally {");
  128. out.println(" if ( is != null ) {");
  129. out.println(" try { is.close(); } catch ( Exception e ) {}");
  130. out.println(" }");
  131. out.println(" }");
  132. out.println(" return data;");
  133. out.println(" }");
  134. out.println("}");
  135. out.flush();
  136. out.close();
  137. }
  138. /**
  139. * Read bidi type data.
  140. *
  141. * @param ucdFileName name (as URL) of unicode character database data
  142. */
  143. private static void readBidiTypeData(String ucdFileName) throws Exception {
  144. BufferedReader b = new BufferedReader(new InputStreamReader(new URL(ucdFileName).openStream()));
  145. String line;
  146. int n;
  147. // singleton map - derived from single char entry
  148. Map/*<Integer,List>*/ sm = new HashMap/*<Integer,List>*/();
  149. // interval map - derived from pair of block endpoint entries
  150. Map/*<String,int[3]>*/ im = new HashMap/*<String,int[3]>*/();
  151. if (verbose) {
  152. System.out.print("Reading bidi type data...");
  153. }
  154. for (lineNumber = 0; (line = b.readLine()) != null; ) {
  155. lineNumber++;
  156. if (line.length() == 0) {
  157. continue;
  158. } else if (line.startsWith("#")) {
  159. continue;
  160. } else {
  161. parseTypeProperties(line, sm, im);
  162. }
  163. }
  164. // extract type data list
  165. List tdl = processTypeData(sm, im, new ArrayList());
  166. // dump instrumentation
  167. if (verbose) {
  168. System.out.println();
  169. System.out.println("Read type ranges : " + numTypeRanges);
  170. System.out.println("Read lines : " + lineNumber);
  171. }
  172. td = (int[][]) tdl.toArray(new int [ tdl.size() ] []);
  173. }
  174. private static void parseTypeProperties(String line, Map/*<Integer,List>*/ sm, Map/*<String,int[3]>*/ im) {
  175. String[] sa = line.split(";");
  176. if (sa.length >= 5) {
  177. int uc = Integer.parseInt(sa[0], 16);
  178. int bc = parseBidiClassAny(sa[4]);
  179. if (bc >= 0) {
  180. String ucName = sa[1];
  181. if (isBlockStart(ucName)) {
  182. String ucBlock = getBlockName(ucName);
  183. if (!im.containsKey(ucBlock)) {
  184. im.put(ucBlock, new int[] { uc, -1, bc });
  185. } else {
  186. throw new IllegalArgumentException("duplicate start of block '" + ucBlock + "' at entry: " + line);
  187. }
  188. } else if (isBlockEnd(ucName)) {
  189. String ucBlock = getBlockName(ucName);
  190. if (im.containsKey(ucBlock)) {
  191. int[] ba = (int[]) im.get(ucBlock);
  192. assert ba.length == 3;
  193. if (ba[1] < 0) {
  194. ba[1] = uc;
  195. } else {
  196. throw new IllegalArgumentException("duplicate end of block '" + ucBlock + "' at entry: " + line);
  197. }
  198. } else {
  199. throw new IllegalArgumentException("missing start of block '" + ucBlock + "' at entry: " + line);
  200. }
  201. } else {
  202. Integer k = Integer.valueOf(bc);
  203. List sl;
  204. if (!sm.containsKey(k)) {
  205. sl = new ArrayList();
  206. sm.put(k, sl);
  207. } else {
  208. sl = (List) sm.get(k);
  209. }
  210. assert sl != null;
  211. sl.add(Integer.valueOf(uc));
  212. }
  213. } else {
  214. throw new IllegalArgumentException("invalid bidi class '" + sa[4] + "' at entry: " + line);
  215. }
  216. } else {
  217. throw new IllegalArgumentException("invalid unicode character database entry: " + line);
  218. }
  219. }
  220. private static boolean isBlockStart(String s) {
  221. return s.startsWith("<") && s.endsWith("First>");
  222. }
  223. private static boolean isBlockEnd(String s) {
  224. return s.startsWith("<") && s.endsWith("Last>");
  225. }
  226. private static String getBlockName(String s) {
  227. String[] sa = s.substring(1, s.length() - 1).split(",");
  228. assert (sa != null) && (sa.length > 0);
  229. return sa[0].trim();
  230. }
  231. private static List processTypeData(Map/*<Integer,List>*/ sm, Map/*<String,int[3]>*/ im, List tdl) {
  232. for (int i = BidiConstants.FIRST, k = BidiConstants.LAST; i <= k; i++) {
  233. Map/*<Integer,Integer>*/ rm = new TreeMap/*<Integer,Integer>*/();
  234. // populate intervals from singleton map
  235. List sl = (List) sm.get(Integer.valueOf(i));
  236. if (sl != null) {
  237. for (Iterator it = sl.iterator(); it.hasNext(); ) {
  238. Integer s = (Integer) it.next();
  239. int uc = s.intValue();
  240. rm.put(Integer.valueOf(uc), Integer.valueOf(uc + 1));
  241. }
  242. }
  243. // populate intervals from (block) interval map
  244. if (!im.isEmpty()) {
  245. for (Iterator it = im.values().iterator(); it.hasNext(); ) {
  246. int[] ba = (int[]) it.next();
  247. assert (ba != null) && (ba.length > 2);
  248. if (ba[2] == i) {
  249. rm.put(Integer.valueOf(ba[0]), Integer.valueOf(ba[1] + 1));
  250. }
  251. }
  252. }
  253. tdl.add(createTypeData(i, extractRanges(rm)));
  254. }
  255. return tdl;
  256. }
  257. private static List extractRanges(Map/*<Integer,Integer>*/ rm) {
  258. List ranges = new ArrayList();
  259. int sLast = 0;
  260. int eLast = 0;
  261. for (Iterator it = rm.entrySet().iterator(); it.hasNext(); ) {
  262. Map.Entry/*<Integer,Integer>*/ me = (Map.Entry/*<Integer,Integer>*/) it.next();
  263. int s = ((Integer) me.getKey()).intValue();
  264. int e = ((Integer) me.getValue()).intValue();
  265. if (s > eLast) {
  266. if (eLast > sLast) {
  267. ranges.add(new int[] { sLast, eLast });
  268. if (verbose) {
  269. if ((++numTypeRanges % 10) == 0) {
  270. System.out.print("#");
  271. }
  272. }
  273. }
  274. sLast = s;
  275. eLast = e;
  276. } else if ((s >= sLast) && (e >= eLast)) {
  277. eLast = e;
  278. }
  279. }
  280. if (eLast > sLast) {
  281. ranges.add(new int[] { sLast, eLast });
  282. if (verbose) {
  283. if ((++numTypeRanges % 10) == 0) {
  284. System.out.print("#");
  285. }
  286. }
  287. }
  288. return ranges;
  289. }
  290. /**
  291. * Read biditest data.
  292. *
  293. * @param bidiFileName name (as URL) of bidi test data
  294. */
  295. private static void readBidiTestData(String bidiFileName) throws Exception {
  296. BufferedReader b = new BufferedReader(new InputStreamReader(new URL(bidiFileName).openStream()));
  297. String line;
  298. int n;
  299. List tdl = new ArrayList();
  300. List ldl = new ArrayList();
  301. if (verbose) {
  302. System.out.print("Reading bidi test data...");
  303. }
  304. for (lineNumber = 0; (line = b.readLine()) != null; ) {
  305. lineNumber++;
  306. if (line.length() == 0) {
  307. continue;
  308. } else if (line.startsWith("#")) {
  309. continue;
  310. } else if (line.startsWith(PFX_TYPE) && !ignoreDeprecatedTypeData) {
  311. List lines = new ArrayList();
  312. if ((n = readType(line, b, lines)) < 0) {
  313. break;
  314. } else {
  315. lineNumber += n;
  316. tdl.add(parseType(lines));
  317. }
  318. } else if (line.startsWith(PFX_LEVELS)) {
  319. List lines = new ArrayList();
  320. if ((n = readLevels(line, b, lines)) < 0) {
  321. break;
  322. } else {
  323. lineNumber += n;
  324. ldl.add(parseLevels(lines));
  325. }
  326. }
  327. }
  328. // dump instrumentation
  329. if (verbose) {
  330. System.out.println();
  331. if (!ignoreDeprecatedTypeData) {
  332. System.out.println("Read type ranges : " + numTypeRanges);
  333. }
  334. System.out.println("Read level specs : " + numLevelSpecs);
  335. System.out.println("Read test specs : " + numTestSpecs);
  336. System.out.println("Read lines : " + lineNumber);
  337. }
  338. if (!ignoreDeprecatedTypeData) {
  339. td = (int[][]) tdl.toArray(new int [ tdl.size() ] []);
  340. }
  341. ld = (int[][]) ldl.toArray(new int [ ldl.size() ] []);
  342. }
  343. private static int readType(String line, BufferedReader b, List lines) throws IOException {
  344. lines.add(line);
  345. return 0;
  346. }
  347. private static int readLevels(String line, BufferedReader b, List lines) throws IOException {
  348. boolean done = false;
  349. int n = 0;
  350. lines.add(line);
  351. while (!done) {
  352. switch (testPrefix(b, PFX_LEVELS)) {
  353. case 0: // within current levels
  354. if ((line = b.readLine()) != null) {
  355. n++;
  356. if ((line.length() > 0) && !line.startsWith("#")) {
  357. lines.add(line);
  358. }
  359. } else {
  360. done = true;
  361. }
  362. break;
  363. case 1: // end of current levels
  364. case -1: // eof
  365. default:
  366. done = true;
  367. break;
  368. }
  369. }
  370. return n;
  371. }
  372. private static int testPrefix(BufferedReader b, String pfx) throws IOException {
  373. int rv = 0;
  374. int pfxLen = pfx.length();
  375. b.mark(pfxLen);
  376. for (int i = 0, n = pfxLen; i < n; i++) {
  377. int c = b.read();
  378. if (c < 0) {
  379. rv = -1;
  380. break;
  381. } else if (c != pfx.charAt(i)) {
  382. rv = 0;
  383. break;
  384. } else {
  385. rv = 1;
  386. }
  387. }
  388. b.reset();
  389. return rv;
  390. }
  391. private static int[] parseType(List lines) {
  392. if ((lines != null) && (lines.size() >= 1)) {
  393. String line = (String) lines.get(0);
  394. if (line.startsWith(PFX_TYPE)) {
  395. // @Type: BIDI_CLASS ':' LWSP CHARACTER_CLASS
  396. String[] sa = line.split(":");
  397. if (sa.length == 3) {
  398. String bcs = sa[1].trim();
  399. String crs = sa[2].trim();
  400. int bc = parseBidiClass(bcs);
  401. List rl = parseCharacterRanges(crs);
  402. return createTypeData(bc, rl);
  403. }
  404. }
  405. }
  406. return null;
  407. }
  408. private static int[] createTypeData(int bc, List ranges) {
  409. int[] data = new int [ 1 + (2 * ranges.size()) ];
  410. int k = 0;
  411. data [ k++ ] = bc;
  412. for (Iterator it = ranges.iterator(); it.hasNext(); ) {
  413. int[] r = (int[]) it.next();
  414. data [ k++ ] = r [ 0 ];
  415. data [ k++ ] = r [ 1 ];
  416. }
  417. return data;
  418. }
  419. private static int parseBidiClass(String bidiClass) {
  420. int bc = 0;
  421. if ("L".equals(bidiClass)) {
  422. bc = BidiConstants.L;
  423. } else if ("LRE".equals(bidiClass)) {
  424. bc = BidiConstants.LRE;
  425. } else if ("LRO".equals(bidiClass)) {
  426. bc = BidiConstants.LRO;
  427. } else if ("R".equals(bidiClass)) {
  428. bc = BidiConstants.R;
  429. } else if ("AL".equals(bidiClass)) {
  430. bc = BidiConstants.AL;
  431. } else if ("RLE".equals(bidiClass)) {
  432. bc = BidiConstants.RLE;
  433. } else if ("RLO".equals(bidiClass)) {
  434. bc = BidiConstants.RLO;
  435. } else if ("PDF".equals(bidiClass)) {
  436. bc = BidiConstants.PDF;
  437. } else if ("EN".equals(bidiClass)) {
  438. bc = BidiConstants.EN;
  439. } else if ("ES".equals(bidiClass)) {
  440. bc = BidiConstants.ES;
  441. } else if ("ET".equals(bidiClass)) {
  442. bc = BidiConstants.ET;
  443. } else if ("AN".equals(bidiClass)) {
  444. bc = BidiConstants.AN;
  445. } else if ("CS".equals(bidiClass)) {
  446. bc = BidiConstants.CS;
  447. } else if ("NSM".equals(bidiClass)) {
  448. bc = BidiConstants.NSM;
  449. } else if ("BN".equals(bidiClass)) {
  450. bc = BidiConstants.BN;
  451. } else if ("B".equals(bidiClass)) {
  452. bc = BidiConstants.B;
  453. } else if ("S".equals(bidiClass)) {
  454. bc = BidiConstants.S;
  455. } else if ("WS".equals(bidiClass)) {
  456. bc = BidiConstants.WS;
  457. } else if ("ON".equals(bidiClass)) {
  458. bc = BidiConstants.ON;
  459. } else {
  460. throw new IllegalArgumentException("unknown bidi class: " + bidiClass);
  461. }
  462. return bc;
  463. }
  464. private static int parseBidiClassAny(String bidiClass) {
  465. try {
  466. return parseBidiClass(bidiClass);
  467. } catch (IllegalArgumentException e) {
  468. return -1;
  469. }
  470. }
  471. private static List parseCharacterRanges(String charRanges) {
  472. List ranges = new ArrayList();
  473. CharacterIterator ci = new StringCharacterIterator(charRanges);
  474. // read initial list delimiter
  475. skipSpace(ci);
  476. if (!readStartOfList(ci)) {
  477. badRangeSpec("missing initial list delimiter", charRanges);
  478. }
  479. // read negation token if present
  480. boolean negated = false;
  481. skipSpace(ci);
  482. if (maybeReadNext(ci, '^')) {
  483. negated = true;
  484. }
  485. // read item
  486. int[] r;
  487. skipSpace(ci);
  488. if ((r = maybeReadItem(ci)) != null) {
  489. ranges.add(r);
  490. if (verbose) {
  491. if ((++numTypeRanges % 10) == 0) {
  492. System.out.print("#");
  493. }
  494. }
  495. } else {
  496. badRangeSpec("must contain at least one item", charRanges);
  497. }
  498. // read more items if present
  499. boolean more = true;
  500. while (more) {
  501. // read separator if present
  502. String s;
  503. skipSpace(ci);
  504. if ((s = maybeReadSeparator(ci)) != null) {
  505. if ((s.length() != 0) && !s.equals("||")) {
  506. badRangeSpec("invalid item separator \"" + s + "\"", charRanges);
  507. }
  508. }
  509. // read item
  510. skipSpace(ci);
  511. if ((r = maybeReadItem(ci)) != null) {
  512. ranges.add(r);
  513. if (verbose) {
  514. if ((++numTypeRanges % 10) == 0) {
  515. System.out.print("#");
  516. }
  517. }
  518. } else {
  519. more = false;
  520. }
  521. }
  522. // read terminating list delimiter
  523. skipSpace(ci);
  524. if (!readEndOfList(ci)) {
  525. badRangeSpec("missing terminating list delimiter", charRanges);
  526. }
  527. if (!atEnd(ci)) {
  528. badRangeSpec("extraneous content prior to end of line", ci);
  529. }
  530. if (negated) {
  531. ranges = complementRanges(ranges);
  532. }
  533. return removeSurrogates(ranges);
  534. }
  535. private static boolean atEnd(CharacterIterator ci) {
  536. return ci.getIndex() >= ci.getEndIndex();
  537. }
  538. private static boolean readStartOfList(CharacterIterator ci) {
  539. return maybeReadNext(ci, '[');
  540. }
  541. private static void skipSpace(CharacterIterator ci) {
  542. while (!atEnd(ci)) {
  543. char c = ci.current();
  544. if (!Character.isWhitespace(c)) {
  545. break;
  546. } else {
  547. ci.next();
  548. }
  549. }
  550. }
  551. private static boolean maybeReadNext(CharacterIterator ci, char next) {
  552. while (!atEnd(ci)) {
  553. char c = ci.current();
  554. if (c == next) {
  555. ci.next();
  556. return true;
  557. } else {
  558. break;
  559. }
  560. }
  561. return false;
  562. }
  563. private static int[] maybeReadItem(CharacterIterator ci) {
  564. // read first code point
  565. int p1 = -1;
  566. skipSpace(ci);
  567. if ((p1 = maybeReadCodePoint(ci)) < 0) {
  568. return null;
  569. }
  570. // read second code point if present
  571. int p2 = -1;
  572. skipSpace(ci);
  573. if (maybeReadNext(ci, '-')) {
  574. skipSpace(ci);
  575. if ((p2 = maybeReadCodePoint(ci)) < 0) {
  576. badRangeSpec("incomplete item range, requires second item", ci);
  577. }
  578. }
  579. if (p2 < 0) {
  580. return new int[] { p1, p1 + 1 }; // convert to half open interval [ P1, P1+1 )
  581. } else if (p1 <= p2) {
  582. return new int[] { p1, p2 + 1 }; // convert to half open interval [ P1, P2+2 )
  583. } else {
  584. badRangeSpec("invalid item range, second item must be greater than or equal to first item", ci);
  585. return null;
  586. }
  587. }
  588. private static int maybeReadCodePoint(CharacterIterator ci) {
  589. if (maybeReadNext(ci, '\\')) {
  590. if (maybeReadNext(ci, 'u')) {
  591. String s = maybeReadHexDigits(ci, 4);
  592. if (s != null) {
  593. return Integer.parseInt(s, 16);
  594. } else {
  595. badRangeSpec("incomplete escaped code point, requires 4 hex digits", ci);
  596. }
  597. } else if (maybeReadNext(ci, 'U')) {
  598. String s = maybeReadHexDigits(ci, 8);
  599. if (s != null) {
  600. return Integer.parseInt(s, 16);
  601. } else {
  602. badRangeSpec("incomplete escaped code point, requires 8 hex digits", ci);
  603. }
  604. } else {
  605. char c = ci.current();
  606. if (c == CharacterIterator.DONE) {
  607. badRangeSpec("incomplete escaped code point", ci);
  608. } else {
  609. ci.next();
  610. return (int) c;
  611. }
  612. }
  613. } else {
  614. char c = ci.current();
  615. if ((c == CharacterIterator.DONE) || (c == ']')) {
  616. return -1;
  617. } else {
  618. ci.next();
  619. return (int) c;
  620. }
  621. }
  622. return -1;
  623. }
  624. private static String maybeReadHexDigits(CharacterIterator ci, int numDigits) {
  625. StringBuffer sb = new StringBuffer();
  626. while ((numDigits < 0) || (sb.length() < numDigits)) {
  627. char c = ci.current();
  628. if (c != CharacterIterator.DONE) {
  629. if (isHexDigit(c)) {
  630. ci.next();
  631. sb.append(c);
  632. } else {
  633. break;
  634. }
  635. } else {
  636. break;
  637. }
  638. }
  639. if (((numDigits < 0) && (sb.length() > 0)) || (sb.length() == numDigits)) {
  640. return sb.toString();
  641. } else {
  642. return null;
  643. }
  644. }
  645. private static boolean isHexDigit(char c) {
  646. return ((c >= '0') && (c <= '9')) || ((c >= 'a') && (c <= 'f')) || ((c >= 'A') && (c <= 'F'));
  647. }
  648. private static String maybeReadSeparator(CharacterIterator ci) {
  649. if (maybeReadNext(ci, '|')) {
  650. if (maybeReadNext(ci, '|')) {
  651. return "||";
  652. } else {
  653. return "|";
  654. }
  655. } else {
  656. return "";
  657. }
  658. }
  659. private static boolean readEndOfList(CharacterIterator ci) {
  660. return maybeReadNext(ci, ']');
  661. }
  662. private static List complementRanges(List ranges) {
  663. Map/*<Integer,Integer>*/ rm = new TreeMap/*<Integer,Integer>*/();
  664. for (Iterator it = ranges.iterator(); it.hasNext(); ) {
  665. int[] r = (int[]) it.next();
  666. rm.put(Integer.valueOf(r[0]), Integer.valueOf(r[1]));
  667. }
  668. // add complement ranges save last
  669. int s;
  670. int e;
  671. int cs = 0;
  672. List compRanges = new ArrayList(rm.size() + 1);
  673. for (Iterator it = rm.entrySet().iterator(); it.hasNext(); ) {
  674. Map.Entry/*<Integer,Integer>*/ me = (Map.Entry/*<Integer,Integer>*/) it.next();
  675. s = ((Integer) me.getKey()).intValue();
  676. e = ((Integer) me.getValue()).intValue();
  677. if (s > cs) {
  678. compRanges.add(new int[] { cs, s });
  679. }
  680. cs = e;
  681. }
  682. // add trailing complement range
  683. if (cs < 0x110000) {
  684. compRanges.add(new int[] { cs, 0x110000 });
  685. }
  686. return compRanges;
  687. }
  688. private static final int[] SURROGATES = new int[] { 0xD800, 0xE000 };
  689. private static List removeSurrogates(List ranges) {
  690. List rsl = new ArrayList(ranges.size());
  691. for (Iterator it = ranges.iterator(); it.hasNext(); ) {
  692. int[] r = (int[]) it.next();
  693. if (intersectsRange(r, SURROGATES)) {
  694. rsl.addAll(removeRange(r, SURROGATES));
  695. } else {
  696. rsl.add(r);
  697. }
  698. }
  699. return rsl;
  700. }
  701. /**
  702. * Determine if range r2 intersects with range r1.
  703. */
  704. private static boolean intersectsRange(int[] r1, int[] r2) {
  705. if (r1[1] <= r2[0]) { // r1 precedes r2 or abuts r2 on right
  706. return false;
  707. } else if (r1[0] >= r2[1]) { // r2 precedes r1 or abuts r1 on left
  708. return false;
  709. } else if ((r1[0] < r2[0]) && (r1[1] > r2[1])) { // r1 encloses r2
  710. return true;
  711. } else if (r1[0] < r2[0]) { // r1 precedes and overlaps r2
  712. return true;
  713. } else if (r2[1] < r1[1]) { // r2 precedes and overlaps r1
  714. return true;
  715. } else { // r2 encloses r1
  716. return true;
  717. }
  718. }
  719. /**
  720. * Remove range r2 from range r1, leaving zero, one, or two
  721. * remaining ranges.
  722. */
  723. private static List removeRange(int[] r1, int[] r2) {
  724. List rl = new ArrayList();
  725. if (r1[1] <= r2[0]) { // r1 precedes r2 or abuts r2 on right
  726. rl.add(r1);
  727. } else if (r1[0] >= r2[1]) { // r2 precedes r1 or abuts r1 on left
  728. rl.add(r1);
  729. } else if ((r1[0] < r2[0]) && (r1[1] > r2[1])) { // r1 encloses r2
  730. rl.add(new int[] { r1[0], r2[0] });
  731. rl.add(new int[] { r2[1], r1[1] });
  732. } else if (r1[0] < r2[0]) { // r1 precedes and overlaps r2
  733. rl.add(new int[] { r1[0], r2[0] });
  734. } else if (r2[1] < r1[1]) { // r2 precedes and overlaps r1
  735. rl.add(new int[] { r2[1], r1[1] });
  736. }
  737. return rl;
  738. }
  739. private static void badRangeSpec(String reason, String charRanges) throws IllegalArgumentException {
  740. if (verbose) {
  741. System.out.println();
  742. }
  743. throw new IllegalArgumentException("bad range specification: " + reason + ": \"" + charRanges + "\"");
  744. }
  745. private static void badRangeSpec(String reason, CharacterIterator ci) throws IllegalArgumentException {
  746. if (verbose) {
  747. System.out.println();
  748. }
  749. throw new IllegalArgumentException("bad range specification: " + reason + ": starting at \"" + remainder(ci) + "\"");
  750. }
  751. private static String remainder(CharacterIterator ci) {
  752. StringBuffer sb = new StringBuffer();
  753. for (char c; (c = ci.current()) != CharacterIterator.DONE; ) {
  754. ci.next();
  755. sb.append(c);
  756. }
  757. return sb.toString();
  758. }
  759. /**
  760. * Parse levels segment, consisting of multiple lines as follows:
  761. *
  762. * LEVEL_SPEC \n
  763. * REORDER_SPEC \n
  764. * ( TEST_SPEC \n )+
  765. */
  766. private static int[] parseLevels(List lines) {
  767. int[] la = null; // levels array
  768. int[] ra = null; // reorder array
  769. List tal = new ArrayList();
  770. if ((lines != null) && (lines.size() >= 3)) {
  771. for (Iterator it = lines.iterator(); it.hasNext(); ) {
  772. String line = (String) it.next();
  773. if (line.startsWith(PFX_LEVELS)) {
  774. if (la == null) {
  775. la = parseLevelSpec(line);
  776. if (verbose) {
  777. if ((++numLevelSpecs % 10) == 0) {
  778. System.out.print("&");
  779. }
  780. }
  781. } else {
  782. throw new IllegalArgumentException("redundant levels array: \"" + line + "\"");
  783. }
  784. } else if (line.startsWith(PFX_REORDER)) {
  785. if (la == null) {
  786. throw new IllegalArgumentException("missing levels array before: \"" + line + "\"");
  787. } else if (ra == null) {
  788. ra = parseReorderSpec(line, la);
  789. } else {
  790. throw new IllegalArgumentException("redundant reorder array: \"" + line + "\"");
  791. }
  792. } else if ((la != null) && (ra != null)) {
  793. int[] ta = parseTestSpec(line, la);
  794. if (ta != null) {
  795. if (verbose) {
  796. if ((++numTestSpecs % 100) == 0) {
  797. System.out.print("!");
  798. }
  799. }
  800. tal.add(ta);
  801. }
  802. } else if (la == null) {
  803. throw new IllegalArgumentException("missing levels array before: \"" + line + "\"");
  804. } else if (ra == null) {
  805. throw new IllegalArgumentException("missing reorder array before: \"" + line + "\"");
  806. }
  807. }
  808. }
  809. if ((la != null) && (ra != null)) {
  810. return createLevelData(la, ra, tal);
  811. } else {
  812. return null;
  813. }
  814. }
  815. private static int[] createLevelData(int[] la, int[] ra, List tal) {
  816. int nl = la.length;
  817. int[] data = new int [ 1 + nl * 2 + ((nl + 1) * tal.size()) ];
  818. int k = 0;
  819. data [ k++ ] = nl;
  820. for (int i = 0, n = nl; i < n; i++) {
  821. data [ k++ ] = la [ i ];
  822. }
  823. int nr = ra.length;
  824. for (int i = 0, n = nr; i < n; i++) {
  825. data [ k++ ] = ra [ i ];
  826. }
  827. for (Iterator it = tal.iterator(); it.hasNext(); ) {
  828. int[] ta = (int[]) it.next();
  829. if (ta == null) {
  830. throw new IllegalStateException("null test array");
  831. } else if (ta.length == (nl + 1)) {
  832. for (int i = 0, n = ta.length; i < n; i++) {
  833. data [ k++ ] = ta [ i ];
  834. }
  835. } else {
  836. throw new IllegalStateException("test array length error, expected " + (nl + 1) + " entries, got " + ta.length + " entries");
  837. }
  838. }
  839. assert k == data.length;
  840. return data;
  841. }
  842. /**
  843. * Parse level specification, which follows the following syntax:
  844. *
  845. * @Levels: ( LWSP ( NUMBER | 'x' ) )+
  846. */
  847. private static int[] parseLevelSpec(String line) {
  848. CharacterIterator ci = new StringCharacterIterator(line);
  849. List ll = new ArrayList();
  850. // read prefix
  851. skipSpace(ci);
  852. if (!maybeReadToken(ci, PFX_LEVELS)) {
  853. badLevelSpec("missing prefix \"" + PFX_LEVELS + "\"", ci);
  854. }
  855. // read level values
  856. boolean more = true;
  857. while (more) {
  858. Integer l;
  859. skipSpace(ci);
  860. if ((l = maybeReadInteger(ci)) != null) {
  861. ll.add(l);
  862. } else if (maybeReadToken(ci, "x")) {
  863. ll.add(Integer.valueOf(-1));
  864. } else {
  865. more = false;
  866. }
  867. }
  868. // read to end of line
  869. skipSpace(ci);
  870. if (!atEnd(ci)) {
  871. badLevelSpec("extraneous content prior to end of line", ci);
  872. }
  873. if (ll.size() == 0) {
  874. badLevelSpec("must have at least one level value", ci);
  875. }
  876. return createLevelsArray(ll);
  877. }
  878. private static Integer maybeReadInteger(CharacterIterator ci) {
  879. // read optional minus sign if present
  880. boolean negative;
  881. if (maybeReadNext(ci, '-')) {
  882. negative = true;
  883. } else {
  884. negative = false;
  885. }
  886. // read digits
  887. StringBuffer sb = new StringBuffer();
  888. while (true) {
  889. char c = ci.current();
  890. if ((c != CharacterIterator.DONE) && isDigit(c)) {
  891. ci.next();
  892. sb.append(c);
  893. } else {
  894. break;
  895. }
  896. }
  897. if (sb.length() == 0) {
  898. return null;
  899. } else {
  900. int value = Integer.parseInt(sb.toString());
  901. if (negative) {
  902. value = -value;
  903. }
  904. return Integer.valueOf(value);
  905. }
  906. }
  907. private static boolean isDigit(char c) {
  908. return ((c >= '0') && (c <= '9'));
  909. }
  910. private static boolean maybeReadToken(CharacterIterator ci, String s) {
  911. int startIndex = ci.getIndex();
  912. for (int i = 0, n = s.length(); i < n; i++) {
  913. char c = s.charAt(i);
  914. if (ci.current() == c) {
  915. ci.next();
  916. } else {
  917. ci.setIndex(startIndex);
  918. return false;
  919. }
  920. }
  921. return true;
  922. }
  923. private static void badLevelSpec(String reason, CharacterIterator ci) throws IllegalArgumentException {
  924. if (verbose) {
  925. System.out.println();
  926. }
  927. throw new IllegalArgumentException("bad level specification: " + reason + ": starting at \"" + remainder(ci) + "\"");
  928. }
  929. private static int[] createLevelsArray(List levels) {
  930. int[] la = new int [ levels.size() ];
  931. int k = 0;
  932. for (Iterator it = levels.iterator(); it.hasNext(); ) {
  933. la [ k++ ] = ((Integer) it.next()).intValue();
  934. }
  935. return la;
  936. }
  937. /**
  938. * Parse reorder specification, which follows the following syntax:
  939. *
  940. * @Reorder: ( LWSP NUMBER )*
  941. */
  942. private static int[] parseReorderSpec(String line, int[] levels) {
  943. CharacterIterator ci = new StringCharacterIterator(line);
  944. List rl = new ArrayList();
  945. // read prefix
  946. skipSpace(ci);
  947. if (!maybeReadToken(ci, PFX_REORDER)) {
  948. badReorderSpec("missing prefix \"" + PFX_REORDER + "\"", ci);
  949. }
  950. // read reorder values
  951. boolean more = true;
  952. while (more) {
  953. skipSpace(ci);
  954. Integer l;
  955. if ((l = maybeReadInteger(ci)) != null) {
  956. rl.add(l);
  957. } else {
  958. more = false;
  959. }
  960. }
  961. // read to end of line
  962. skipSpace(ci);
  963. if (!atEnd(ci)) {
  964. badReorderSpec("extraneous content prior to end of line", ci);
  965. }
  966. return createReorderArray(rl, levels);
  967. }
  968. private static void badReorderSpec(String reason, CharacterIterator ci) throws IllegalArgumentException {
  969. if (verbose) {
  970. System.out.println();
  971. }
  972. throw new IllegalArgumentException("bad reorder specification: " + reason + ": starting at \"" + remainder(ci) + "\"");
  973. }
  974. private static int[] createReorderArray(List reorders, int[] levels) {
  975. int nr = reorders.size();
  976. int nl = levels.length;
  977. if (nr <= nl) {
  978. int[] ra = new int [ nl ];
  979. Iterator it = reorders.iterator();
  980. for (int i = 0, n = nl; i < n; i++) {
  981. int r = -1;
  982. if (levels [ i ] >= 0) {
  983. if (it.hasNext()) {
  984. r = ((Integer) it.next()).intValue();
  985. }
  986. }
  987. ra [ i ] = r;
  988. }
  989. return ra;
  990. } else {
  991. throw new IllegalArgumentException("excessive number of reorder array entries, expected no more than " + nl + ", but got " + nr + " entries");
  992. }
  993. }
  994. /**
  995. * Parse test specification, which follows the following syntax:
  996. *
  997. * BIDI_CLASS ( LWSP BIDI_CLASS )+ ';' LWSP NUMBER
  998. */
  999. private static int[] parseTestSpec(String line, int[] levels) {
  1000. CharacterIterator ci = new StringCharacterIterator(line);
  1001. List cl = new ArrayList();
  1002. // read bidi class identifier sequence
  1003. while (!atEnd(ci) && !maybeReadNext(ci, ';')) {
  1004. skipSpace(ci);
  1005. int bc;
  1006. if ((bc = maybeReadBidiClass(ci)) >= 0) {
  1007. cl.add(Integer.valueOf(bc));
  1008. } else {
  1009. break;
  1010. }
  1011. }
  1012. // read bit set
  1013. skipSpace(ci);
  1014. String s;
  1015. int bs = 0;
  1016. if ((s = maybeReadHexDigits(ci, -1)) != null) {
  1017. bs = Integer.parseInt(s, 16);
  1018. } else {
  1019. badTestSpec("missing bit set", ci);
  1020. }
  1021. // read to end of line
  1022. skipSpace(ci);
  1023. if (!atEnd(ci)) {
  1024. badTestSpec("extraneous content prior to end of line", ci);
  1025. }
  1026. return createTestArray(cl, bs, levels);
  1027. }
  1028. private static String maybeReadIdentifier(CharacterIterator ci) {
  1029. // read keyword chars ([A-Z])
  1030. StringBuffer sb = new StringBuffer();
  1031. while (true) {
  1032. char c = ci.current();
  1033. if (c == CharacterIterator.DONE) {
  1034. break;
  1035. } else if (sb.length() == 0) {
  1036. if (Character.isUnicodeIdentifierStart(c)) {
  1037. ci.next();
  1038. sb.append(c);
  1039. } else {
  1040. break;
  1041. }
  1042. } else {
  1043. if (Character.isUnicodeIdentifierPart(c)) {
  1044. ci.next();
  1045. sb.append(c);
  1046. } else {
  1047. break;
  1048. }
  1049. }
  1050. }
  1051. if (sb.length() == 0) {
  1052. return null;
  1053. } else {
  1054. return sb.toString();
  1055. }
  1056. }
  1057. private static int maybeReadBidiClass(CharacterIterator ci) {
  1058. int bc = -1;
  1059. int i = ci.getIndex();
  1060. String s;
  1061. if ((s = maybeReadIdentifier(ci)) != null) {
  1062. try {
  1063. bc = parseBidiClass(s);
  1064. } catch (IllegalArgumentException e) {
  1065. throw e;
  1066. }
  1067. }
  1068. if (bc < 0) {
  1069. ci.setIndex(i);
  1070. }
  1071. return bc;
  1072. }
  1073. private static void badTestSpec(String reason, CharacterIterator ci) throws IllegalArgumentException {
  1074. if (verbose) {
  1075. System.out.println();
  1076. }
  1077. throw new IllegalArgumentException("bad test specification: " + reason + ": starting at \"" + remainder(ci) + "\"");
  1078. }
  1079. private static int[] createTestArray(List classes, int bitset, int[] levels) {
  1080. int nc = classes.size();
  1081. if (nc <= levels.length) {
  1082. int[] ta = new int [ 1 + nc ];
  1083. int k = 0;
  1084. ta [ k++ ] = bitset;
  1085. for (Iterator it = classes.iterator(); it.hasNext(); ) {
  1086. ta [ k++ ] = ((Integer) it.next()).intValue();
  1087. }
  1088. return ta;
  1089. } else {
  1090. throw new IllegalArgumentException("excessive number of test array entries, expected no more than " + levels.length + ", but got " + nc + " entries");
  1091. }
  1092. }
  1093. /**
  1094. * Dump data arrays to output and resource files.
  1095. * @param out - bidi test data java class file print writer
  1096. * @param outFileName - (full path) name of bidi test data java class file
  1097. */
  1098. private static void dumpData(PrintWriter out, String outFileName) throws IOException {
  1099. File f = new File(outFileName);
  1100. File p = f.getParentFile();
  1101. if (td != null) {
  1102. String pfxTD = "TD";
  1103. dumpResourcesDescriptor(out, pfxTD, td.length);
  1104. dumpResourcesData(p, f.getName(), pfxTD, td);
  1105. }
  1106. if (ld != null) {
  1107. String pfxTD = "LD";
  1108. dumpResourcesDescriptor(out, pfxTD, ld.length);
  1109. dumpResourcesData(p, f.getName(), pfxTD, ld);
  1110. }
  1111. }
  1112. private static void dumpResourcesDescriptor(PrintWriter out, String prefix, int numResources) {
  1113. out.println(" public static final String " + prefix + "_PFX = \"" + prefix + "\";");
  1114. out.println(" public static final int " + prefix + "_CNT = " + numResources + ";");
  1115. out.println("");
  1116. }
  1117. private static void dumpResourcesData(File btcDir, String btcName, String prefix, int[][] data) throws IOException {
  1118. String btdName = extractDataFileName(btcName);
  1119. for (int i = 0, n = data.length; i < n; i++) {
  1120. File f = new File(btcDir, btdName + "$" + prefix + i + ".ser");
  1121. ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(f));
  1122. os.writeObject(data[i]);
  1123. os.close();
  1124. }
  1125. }
  1126. private static final String JAVA_EXT = ".java";
  1127. private static String extractDataFileName(String btcName) {
  1128. if (btcName.endsWith(JAVA_EXT)) {
  1129. return btcName.substring(0, btcName.length() - JAVA_EXT.length());
  1130. } else {
  1131. return btcName;
  1132. }
  1133. }
  1134. /**
  1135. * Main entry point for generator.
  1136. * @param args array of command line arguments
  1137. */
  1138. public static void main(String[] args) {
  1139. String bidiFileName = "http://www.unicode.org/Public/UNIDATA/BidiTest.txt";
  1140. String ucdFileName = "http://www.unicode.org/Public/UNIDATA/BidiTest.txt";
  1141. String outFileName = "BidiTestData.java";
  1142. boolean ok = true;
  1143. for (int i = 0; ok && (i < args.length); i++) {
  1144. String opt = args[i];
  1145. if ("-b".equals(opt)) {
  1146. if ((i + 1) <= args.length) {
  1147. bidiFileName = args[++i];
  1148. } else {
  1149. ok = false;
  1150. }
  1151. } else if ("-d".equals(opt)) {
  1152. if ((i + 1) <= args.length) {
  1153. ucdFileName = args[++i];
  1154. } else {
  1155. ok = false;
  1156. }
  1157. } else if ("-i".equals(opt)) {
  1158. ignoreDeprecatedTypeData = true;
  1159. } else if ("-o".equals(opt)) {
  1160. if ((i + 1) <= args.length) {
  1161. outFileName = args[++i];
  1162. } else {
  1163. ok = false;
  1164. }
  1165. } else if ("-v".equals(opt)) {
  1166. verbose = true;
  1167. } else {
  1168. ok = false;
  1169. }
  1170. }
  1171. if (!ok) {
  1172. System.out.println("Usage: GenerateBidiTestData [-v] [-i] [-d <ucdFile>] [-b <bidiFile>] [-o <outputFile>]");
  1173. System.out.println(" defaults:");
  1174. if (ignoreDeprecatedTypeData) {
  1175. System.out.println(" <ucdFile> : " + ucdFileName);
  1176. }
  1177. System.out.println(" <bidiFile> : " + bidiFileName);
  1178. System.out.println(" <outputFile> : " + outFileName);
  1179. } else {
  1180. try {
  1181. convertBidiTestData(ucdFileName, bidiFileName, outFileName);
  1182. System.out.println("Generated " + outFileName + " from");
  1183. if (ignoreDeprecatedTypeData) {
  1184. System.out.println(" <ucdFile> : " + ucdFileName);
  1185. }
  1186. System.out.println(" <bidiFile> : " + bidiFileName);
  1187. } catch (Exception e) {
  1188. System.out.println("An unexpected error occured at line: " + lineNumber);
  1189. e.printStackTrace();
  1190. }
  1191. }
  1192. }
  1193. }