1 /* 2 ****************************************************************************** 3 * Copyright (C) 2004, International Business Machines Corporation and * 4 * others. All Rights Reserved. * 5 ****************************************************************************** 6 */ 7 /** 8 * @author Ram Viswanadha 9 * @author Brian Rower - June 2008 - added writeBinary methods 10 */ 11 package org.unicode.cldr.icu; 12 13 import java.io.FileOutputStream; 14 import java.io.IOException; 15 import java.io.OutputStream; 16 import java.io.UnsupportedEncodingException; 17 18 import com.ibm.icu.text.UTF16; 19 20 public class ICUResourceWriter { 21 private static final String CHARSET = "UTF-8"; 22 private static final String OPENBRACE = "{"; 23 private static final String CLOSEBRACE = "}"; 24 private static final String OPENPAREN = "("; 25 private static final String CLOSEPAREN = ")"; 26 private static final String COLON = ":"; 27 private static final String COMMA = ","; 28 private static final String QUOTE = "\""; 29 private static final String COMMENTSTART = "/**"; 30 private static final String COMMENTEND = " */"; 31 private static final String COMMENTMIDDLE = " * "; 32 private static final String INDENT = " "; 33 private static final String EMPTY = ""; 34 private static final String BIN = "bin"; 35 private static final String INTS = "int"; 36 private static final String TABLE = "table"; 37 private static final String IMPORT = "import"; 38 private static final String INCLUDE = "include"; 39 private static final String PROCESS = "process"; 40 private static final String ALIAS = "alias"; 41 private static final String INTVECTOR = "intvector"; 42 // private static final String ARRAYS = "array"; 43 private static final String LINESEP = System.getProperty("line.separator"); 44 private static final String STRTERM = "\0"; 45 46 public static final int SIZE_OF_INT = 4; 47 public static final int SIZE_OF_CHAR = 2; 48 49 public static final String UCA_RULES = "uca_rules"; 50 public static final String TRANSLITERATOR = "transliaterator"; 51 public static final String COLLATION = "collation"; 52 public static final String DEPENDENCY = "dependency"; 53 54 public static final int BIN_ALIGNMENT = 16; 55 /** 56 * This integer is a count of ALL the resources in this tree (not including the root object) 57 */ 58 public static int maxTableLength; 59 60 public static class Resource { 61 public class MalformedResourceError extends Error { 62 private static final long serialVersionUID = -5943014701317383613L; 63 public Resource offendingResource; 64 MalformedResourceError(String str, Resource res)65 public MalformedResourceError(String str, Resource res) { 66 super(str); 67 offendingResource = res; 68 } 69 } 70 71 String[] note = new String[20]; 72 int noteLen = 0; 73 String translate; 74 /** 75 * This is a comment which will appear on the item. 76 */ 77 String comment; 78 /** 79 * This is the resource's name, or, 'key' 80 */ 81 public String name; 82 /** 83 * This links to the next sibling of this item in the list 84 */ 85 public Resource next; 86 boolean noSort = false; 87 /** 88 * If this item contains other items, this points to the first item in its list 89 */ 90 public Resource first = null; 91 92 /** 93 * A counter for how many children there are below this object. 94 */ 95 public int numChildren; 96 97 /** 98 * Stores how many bytes are used by the children of this object. 99 */ 100 public int sizeOfChildren; 101 102 /** 103 * Stores how many bytes are used by this resource. 104 */ 105 public int size; 106 107 /** 108 * This integer stores the number of bytes from the beginning of the "key string" 109 * this resources key starts. For more information see the comment in LDML2ICUBinaryWriter above 110 * the writeKeyString method. 111 */ 112 public int keyStringOffset; 113 114 public boolean hasKey = true; 115 116 public boolean isTop = false; 117 118 /** 119 * This method will set the size of the resource. Overwritten for each child object 120 */ setSize()121 public void setSize() { 122 size = 0; 123 } 124 Resource()125 public Resource() { 126 isTop = false; 127 } 128 129 /** 130 * @return the end of this chain, by repeatedly calling next 131 * @see next 132 */ end()133 public Resource end() { 134 ICUResourceWriter.Resource current = this; 135 while (current != null) { 136 if (current.next == null) { 137 return current; 138 } 139 current = current.next; 140 } 141 return current; 142 } 143 144 /** 145 * Append to a basic list. 146 * Usage: 147 * Resource list = null; list = Resource.addAfter(list, res1); list = Resource.addAfter(list,res2); ... 148 * 149 * @param list 150 * the list to append to (could be null) 151 * @param res 152 * the item to add 153 * @return the beginning of the list 154 */ addAfter(Resource list, Resource res)155 static final Resource addAfter(Resource list, Resource res) { 156 if (list == null) { 157 list = res; 158 } else { 159 // go to end of the list 160 Resource last = list.end(); 161 last.next = res; 162 } 163 // return the beginning 164 return list; 165 } 166 167 /** 168 * Appends 'res' to the end of 'this' (next sibling chain) 169 * 170 * @param res 171 * the item (or items) to be added 172 * @return the new beginning of the chain (this) 173 */ addAfter(Resource res)174 public Resource addAfter(Resource res) { 175 return addAfter(this, res); 176 } 177 178 /** 179 * Replace the contents (first) of this object with the parameter 180 * 181 * @param res 182 * The object to be replaced 183 * @return the old contents 184 */ replaceContents(Resource res)185 public Resource replaceContents(Resource res) { 186 Resource old = first; 187 first = res; 188 return old; 189 } 190 191 /** 192 * Append the contents (first) of this object with the parameter 193 * 194 * @param res 195 * the object to be added to the contents 196 * @return the end of the contents chain 197 */ appendContents(Resource res)198 public Resource appendContents(Resource res) { 199 if (first == null) { 200 first = res; 201 } else { 202 first.end().next = res; 203 } 204 return res.end(); 205 } 206 207 /** 208 * Check whether this item has contents 209 * 210 * @return true if this item is empty (first==null) 211 */ isEmpty()212 public boolean isEmpty() { 213 return (first == null); 214 } 215 216 /** 217 * @param val 218 * @return 219 */ 220 escapeSyntaxChars(String val)221 public StringBuffer escapeSyntaxChars(String val) { 222 // escape the embedded quotes 223 if (val == null) { 224 System.err.println("Resource.escapeSyntaxChars: error, resource '" + name 225 + "': string value is NULL - assuming 'empty'"); 226 throw new MalformedResourceError("Resource.escapeSyntaxChars: error, resource '" + name 227 + "': string value is NULL - assuming 'empty'", this); 228 // return new StringBuffer(""); 229 } 230 char[] str = val.toCharArray(); 231 StringBuffer result = new StringBuffer(); 232 for (int i = 0; i < str.length; i++) { 233 switch (str[i]) { 234 case '\u0022': 235 result.append('\\'); // append backslash 236 default: 237 result.append(str[i]); 238 } 239 } 240 return result; 241 } 242 write(OutputStream writer, int numIndent, boolean bare)243 public void write(OutputStream writer, int numIndent, boolean bare) { 244 while (next != null) { 245 next.write(writer, numIndent + 1, false); 246 } 247 } 248 writeIndent(OutputStream writer, int numIndent)249 public void writeIndent(OutputStream writer, int numIndent) { 250 for (int i = 0; i < numIndent; i++) { 251 write(writer, INDENT); 252 } 253 } 254 writeBinary(FileOutputStream out, int usedOffset)255 public int writeBinary(FileOutputStream out, int usedOffset) { 256 // should never get called 257 System.err.println("Unexpected type: " + this.getClass().toString()); 258 System.err.println("Resource Name: " + this.name); 259 return usedOffset; 260 } 261 write(OutputStream writer, String value)262 public void write(OutputStream writer, String value) { 263 try { 264 byte[] bytes = value.getBytes(CHARSET); 265 writer.write(bytes, 0, bytes.length); 266 267 } catch (Exception e) { 268 System.err.println(e); 269 System.exit(1); 270 } 271 } 272 writeComments(OutputStream writer, int numIndent)273 public void writeComments(OutputStream writer, int numIndent) { 274 if (comment != null || translate != null || noteLen > 0) { 275 // print the start of the comment 276 writeIndent(writer, numIndent); 277 write(writer, COMMENTSTART + LINESEP); 278 279 // print comment if any 280 if (comment != null) { 281 int index = comment.indexOf('\n'); 282 if (index > -1) { 283 StringBuffer indent = new StringBuffer("\n"); 284 for (int i = 0; i < numIndent; i++) { 285 indent.append(INDENT); 286 } 287 indent.append(COMMENTMIDDLE); 288 comment = comment.replaceAll("\n", indent.toString()); 289 } 290 writeIndent(writer, numIndent); 291 write(writer, COMMENTMIDDLE); 292 write(writer, comment); 293 write(writer, LINESEP); 294 295 } 296 297 // terminate the comment 298 writeIndent(writer, numIndent); 299 write(writer, COMMENTEND + LINESEP); 300 } 301 } 302 sort()303 public void sort() { 304 // System.out.println("In sort"); 305 return; 306 } 307 swap()308 public void swap() { 309 return; 310 } 311 findResourcePath(StringBuffer str, Resource res)312 boolean findResourcePath(StringBuffer str, Resource res) { 313 if (name != null) { 314 str.append(name); 315 } 316 if (res == this) { 317 return true; 318 } 319 str.append('/'); 320 // process the siblings of the children... 321 int n = 0; 322 int oldLen = str.length(); 323 for (Resource child = first; child != null; child = child.next) { 324 if (child.name == null) { 325 str.append("#" + n); 326 } 327 if (child.findResourcePath(str, res)) { 328 return true; 329 } 330 n++; 331 str.setLength(oldLen); // reset path length 332 } 333 return false; 334 } 335 findResourcePath(Resource res)336 String findResourcePath(Resource res) { 337 if (next != null) { 338 throw new IllegalArgumentException( 339 "Don't call findResourcePath(Resource res) on resources which have siblings"); 340 } 341 StringBuffer str = new StringBuffer(); 342 if (findResourcePath(str, res)) { 343 return str.toString(); 344 } else { 345 return null; 346 } 347 } 348 } 349 350 /* ***************************END Resource *********** */ 351 352 /* All the children resource types below************** */ 353 354 public static class ResourceAlias extends Resource { 355 String val; 356 357 @Override write(OutputStream writer, int numIndent, boolean bare)358 public void write(OutputStream writer, int numIndent, boolean bare) { 359 writeComments(writer, numIndent); 360 writeIndent(writer, numIndent); 361 String line = ((name == null) ? EMPTY : name) + COLON + ALIAS + OPENBRACE + QUOTE + escapeSyntaxChars(val) 362 + QUOTE + CLOSEBRACE; 363 if (bare == true) { 364 if (name != null) { 365 throw new RuntimeException("Bare option is set to true but the resource has a name! " + name); 366 } 367 write(writer, line); 368 } else { 369 write(writer, line + LINESEP); 370 } 371 } 372 373 /** 374 * Writes this object to the provided output stream in binary format. Copies formating from Genrb (in ICU4C 375 * tools. 376 * 377 * @param out 378 * A File output stream which has already been set up to write to. 379 */ 380 @Override writeBinary(FileOutputStream out, int usedOffset)381 public int writeBinary(FileOutputStream out, int usedOffset) { 382 byte[] valLenBytes; 383 byte[] valBytes; 384 byte[] padding; 385 386 valLenBytes = intToBytes(val.length()); 387 388 try { 389 valBytes = (val + STRTERM).getBytes(LDML2ICUBinaryWriter.CHARSET16); 390 padding = create32Padding(valBytes.length); 391 out.write(valLenBytes); 392 LDML2ICUBinaryWriter.written += valLenBytes.length; 393 394 out.write(valBytes); 395 LDML2ICUBinaryWriter.written += valBytes.length; 396 397 if (padding != null) { 398 out.write(padding); 399 LDML2ICUBinaryWriter.written += padding.length; 400 } 401 402 } catch (UnsupportedEncodingException e) { 403 errUnsupportedEncoding(); 404 } catch (IOException e) { 405 errIO(); 406 } 407 return usedOffset; 408 } 409 410 @Override setSize()411 public void setSize() { 412 // a pointer + the string 413 size = SIZE_OF_INT + ((val.length() + 1) * SIZE_OF_CHAR); 414 } 415 } 416 417 public static class ResourceArray extends Resource { 418 @Override write(OutputStream writer, int numIndent, boolean bare)419 public void write(OutputStream writer, int numIndent, boolean bare) { 420 writeComments(writer, numIndent); 421 writeIndent(writer, numIndent); 422 if (name != null) { 423 write(writer, name + OPENBRACE + LINESEP); 424 } else { 425 write(writer, OPENBRACE + LINESEP); 426 } 427 numIndent++; 428 Resource current = first; 429 while (current != null) { 430 current.write(writer, numIndent, true); 431 if (current instanceof ResourceTable || 432 current instanceof ResourceArray) { 433 434 } else { 435 write(writer, COMMA + LINESEP); 436 } 437 current = current.next; 438 } 439 numIndent--; 440 writeIndent(writer, numIndent); 441 write(writer, CLOSEBRACE + LINESEP); 442 } 443 444 @Override sort()445 public void sort() { 446 if (noSort == true) { 447 return; 448 } 449 Resource current = first; 450 while (current != null) { 451 current.sort(); 452 current = current.next; 453 } 454 } 455 456 @Override writeBinary(FileOutputStream out, int usedOffset)457 public int writeBinary(FileOutputStream out, int usedOffset) { 458 int count = 0; 459 int[] resources = new int[numChildren]; 460 byte[] resourceBytes; 461 Resource current = this.first; 462 463 // if there are items in the array 464 if (current != null) { 465 // start at the first one and loop 466 while (current != null) { 467 // if it's an int: resources[i] = (current->fType << 28) | (current->u.fIntValue.fValue & 468 // 0xFFFFFFF); 469 if (current instanceof ResourceInt) { 470 int value = 0; 471 472 try { 473 value = Integer.parseInt(((ResourceInt) current).val); 474 } catch (NumberFormatException e) { 475 System.err.println("Error converting string to int: " + e.getMessage()); 476 System.exit(1); 477 } 478 resources[count] = LDML2ICUBinaryWriter.URES_INT << 28 | (value & 0xFFFFFFF); 479 } else { 480 // write the current object 481 usedOffset = current.writeBinary(out, usedOffset); 482 483 // write 32 bits for identification? 484 if (current instanceof ResourceString) { 485 resources[count] = LDML2ICUBinaryWriter.URES_STRING << 28 | usedOffset >>> 2; 486 } else if (current instanceof ResourceTable) { 487 if (((ResourceTable) current).is32Bit()) { 488 resources[count] = LDML2ICUBinaryWriter.URES_TABLE32 << 28 | usedOffset >>> 2; 489 } else { 490 resources[count] = LDML2ICUBinaryWriter.URES_TABLE << 28 | usedOffset >>> 2; 491 } 492 493 } else if (current instanceof ResourceAlias) { 494 resources[count] = LDML2ICUBinaryWriter.URES_ALIAS << 28 | usedOffset >>> 2; 495 } else if (current instanceof ResourceArray) { 496 resources[count] = LDML2ICUBinaryWriter.URES_ARRAY << 28 | usedOffset >>> 2; 497 } else if (current instanceof ResourceIntVector) { 498 resources[count] = LDML2ICUBinaryWriter.URES_INT_VECTOR << 28 | usedOffset >>> 2; 499 } 500 501 usedOffset += current.size + pad32(current.size); 502 } 503 count++; 504 current = current.next; 505 } 506 507 // convert the resource array into the resourceBytes 508 resourceBytes = intArrayToBytes(resources); 509 510 try { 511 // write the array count (int32) 512 out.write(intToBytes(count)); 513 LDML2ICUBinaryWriter.written += intToBytes(count).length; 514 515 // write the resources array...should be size of int32 * array count 516 out.write(resourceBytes); 517 LDML2ICUBinaryWriter.written += resourceBytes.length; 518 } catch (IOException e) { 519 errIO(); 520 } 521 522 } else // Empty array 523 { 524 try { 525 out.write(intToBytes(0)); 526 LDML2ICUBinaryWriter.written += intToBytes(0).length; 527 } catch (IOException e) { 528 errIO(); 529 } 530 } 531 return usedOffset; 532 533 } 534 535 /** 536 * This method will set the size of the resource. 537 */ 538 @Override setSize()539 public void setSize() { 540 // Arrays have children. 541 int x = 0; 542 Resource current = this.first; 543 544 this.sizeOfChildren = 0; 545 546 while (current != null) { 547 x++; 548 549 this.sizeOfChildren += current.size + pad32(current.size); 550 551 if (current instanceof ResourceTable || current instanceof ResourceArray) { 552 this.sizeOfChildren += current.sizeOfChildren; 553 } 554 555 current = current.next; 556 } 557 558 // pointer to the key + pointer to each member 559 size = SIZE_OF_INT + (x * SIZE_OF_INT); 560 561 } 562 } 563 564 public static class ResourceInt extends Resource { 565 String val; 566 567 @Override write(OutputStream writer, int numIndent, boolean bare)568 public void write(OutputStream writer, int numIndent, boolean bare) { 569 writeComments(writer, numIndent); 570 writeIndent(writer, numIndent); 571 String line = ((name == null) ? EMPTY : name) + COLON + INTS + OPENBRACE + val + CLOSEBRACE; 572 if (bare == true) { 573 if (name != null) { 574 throw new RuntimeException("Bare option is set to true but the resource has a name: " + name); 575 } 576 write(writer, line); 577 } else { 578 write(writer, line + LINESEP); 579 } 580 } 581 582 @Override writeBinary(FileOutputStream out, int usedOffset)583 public int writeBinary(FileOutputStream out, int usedOffset) { 584 return usedOffset; 585 } 586 587 /** 588 * This method will set the size of the resource. Overwritten for each child object 589 */ 590 @Override setSize()591 public void setSize() { 592 size = 0; 593 594 } 595 } 596 597 public static class ResourceIntVector extends Resource { 598 public String smallComment = null; 599 600 @Override write(OutputStream writer, int numIndent, boolean bare)601 public void write(OutputStream writer, int numIndent, boolean bare) { 602 writeComments(writer, numIndent); 603 writeIndent(writer, numIndent); 604 write(writer, name + COLON + INTVECTOR + OPENBRACE); 605 if (smallComment != null) { 606 write(writer, " " + COMMENTSTART + " " + smallComment + " " + COMMENTEND); 607 } 608 write(writer, LINESEP); 609 numIndent++; 610 ResourceInt current = (ResourceInt) first; 611 while (current != null) { 612 // current.write(writer, numIndent, true); 613 writeIndent(writer, numIndent); 614 write(writer, current.val); 615 write(writer, COMMA + LINESEP); 616 current = (ResourceInt) current.next; 617 } 618 numIndent--; 619 writeIndent(writer, numIndent); 620 write(writer, CLOSEBRACE + LINESEP); 621 } 622 623 @Override writeBinary(FileOutputStream out, int usedOffset)624 public int writeBinary(FileOutputStream out, int usedOffset) { 625 int count = 0; 626 int[] numbers = new int[numChildren]; 627 byte[] numBytes; 628 Resource current = this.first; 629 630 while (current != null) { 631 numbers[count] = Integer.parseInt(((ResourceInt) current).val); 632 count++; 633 current = current.next; 634 } 635 636 numBytes = intArrayToBytes(numbers); 637 638 try { 639 out.write(intToBytes(count)); 640 LDML2ICUBinaryWriter.written += intToBytes(count).length; 641 642 out.write(numBytes); 643 LDML2ICUBinaryWriter.written += numBytes.length; 644 } catch (IOException e) { 645 errIO(); 646 } 647 return usedOffset; 648 } 649 650 /** 651 * This method will set the size of the resource. Overwritten for each child object 652 */ 653 @Override setSize()654 public void setSize() { 655 // has children 656 int x = 0; 657 Resource current = this.first; 658 659 while (current != null) { 660 x++; 661 current = current.next; 662 } 663 664 // this resources key offset + each int 665 size = SIZE_OF_INT + (x * SIZE_OF_INT); 666 } 667 } 668 669 public static class ResourceString extends Resource { ResourceString()670 public ResourceString() { 671 } 672 ResourceString(String name, String val)673 public ResourceString(String name, String val) { 674 this.name = name; 675 this.val = val; 676 } 677 678 public String val; 679 /** 680 * one-line comment following the value. ignored unless in bare mode. 681 */ 682 public String smallComment = null; 683 684 @Override write(OutputStream writer, int numIndent, boolean bare)685 public void write(OutputStream writer, int numIndent, boolean bare) { 686 writeComments(writer, numIndent); 687 writeIndent(writer, numIndent); 688 if (bare == true) { 689 if (name != null) { 690 throw new RuntimeException("Bare option is set to true but the resource has a name! " + name); 691 } 692 693 write(writer, QUOTE + escapeSyntaxChars(val) + QUOTE); 694 if (smallComment != null) { 695 write(writer, " " + COMMENTSTART + " " + smallComment + " " + COMMENTEND); 696 } 697 } else { 698 StringBuffer str = escapeSyntaxChars(val); 699 700 int colLen = 80 - (numIndent * 4); 701 int strLen = str.length(); 702 if (strLen > colLen) { 703 int startIndex = 0; 704 int endIndex = 0; 705 write(writer, name + OPENBRACE + LINESEP); 706 numIndent++; 707 boolean isRules = name.equals("Sequence"); 708 // Find a safe point where we can insert a line break! 709 while (endIndex < strLen) { 710 startIndex = endIndex; 711 endIndex = startIndex + colLen; 712 if (endIndex > strLen) { 713 endIndex = strLen; 714 } 715 if (isRules) { 716 // look for the reset tag only if we are writing 717 // collation rules! 718 int firstIndex = str.indexOf("&", startIndex); 719 720 if (firstIndex > -1) { 721 if (startIndex != (firstIndex - 1) && startIndex != firstIndex && firstIndex < endIndex) { 722 if (str.charAt(firstIndex - 1) != 0x27) { 723 endIndex = firstIndex; 724 } 725 } 726 int nextIndex = 0; 727 while ((nextIndex = str.indexOf("&", firstIndex + 1)) != -1 && nextIndex < endIndex) { 728 729 if (nextIndex > -1 && firstIndex != nextIndex) { 730 if (str.charAt(nextIndex - 1) != 0x27) { 731 endIndex = nextIndex; 732 break; 733 } else { 734 firstIndex = nextIndex; 735 } 736 } 737 } 738 } 739 } 740 int indexOfEsc = 0; 741 if ((indexOfEsc = str.lastIndexOf("\\u", endIndex)) > -1 && (endIndex - indexOfEsc) < 6 || 742 (indexOfEsc = str.lastIndexOf("\\U", endIndex)) > -1 && (endIndex - indexOfEsc) < 10 || 743 (indexOfEsc = str.lastIndexOf("'\'", endIndex)) > -1 && (endIndex - indexOfEsc) < 3) { 744 745 endIndex = indexOfEsc; 746 } 747 if (indexOfEsc > -1 && str.charAt(indexOfEsc - 1) == 0x0027) { 748 endIndex = indexOfEsc - 1; 749 } 750 if (endIndex < strLen && UTF16.isLeadSurrogate(str.charAt(endIndex - 1))) { 751 endIndex--; 752 } 753 754 writeIndent(writer, numIndent); 755 write(writer, QUOTE); 756 write(writer, str.substring(startIndex, endIndex)); 757 write(writer, QUOTE + LINESEP); 758 } 759 numIndent--; 760 writeIndent(writer, numIndent); 761 write(writer, CLOSEBRACE + LINESEP); 762 763 } else { 764 write(writer, name + OPENBRACE + QUOTE + str.toString() + QUOTE + CLOSEBRACE + LINESEP); 765 } 766 767 } 768 } 769 770 @Override writeBinary(FileOutputStream out, int usedOffset)771 public int writeBinary(FileOutputStream out, int usedOffset) { 772 773 // clean up quotes if any 774 if (this.val.indexOf("\"") >= 0) { 775 this.val = LDML2ICUBinaryWriter.removeQuotes(this.val); 776 } 777 778 String valPlusTerm = val + STRTERM; 779 byte[] valBytes; 780 byte[] valLenBytes; 781 byte[] padding; 782 783 valLenBytes = intToBytes(val.length()); 784 785 try { 786 valBytes = valPlusTerm.getBytes(LDML2ICUBinaryWriter.CHARSET16); 787 padding = create32Padding(valBytes.length); 788 out.write(valLenBytes); // 32 bit int 789 LDML2ICUBinaryWriter.written += valLenBytes.length; 790 791 out.write(valBytes); // The string plus a null terminator 792 LDML2ICUBinaryWriter.written += valBytes.length; 793 794 if (padding != null) { 795 out.write(padding); 796 LDML2ICUBinaryWriter.written += padding.length; 797 } 798 } catch (UnsupportedEncodingException e) { 799 System.err.print("Problems converting string resource to " + LDML2ICUBinaryWriter.CHARSET16); 800 System.exit(1); 801 } catch (IOException e) { 802 System.err.print("Problems writing the string resource to file."); 803 System.exit(1); 804 } 805 return usedOffset; 806 } 807 808 /** 809 * This method will set the size of the resource. Overwritten for each child object 810 */ 811 @Override setSize()812 public void setSize() { 813 // a pointer to the key + a string 814 size = SIZE_OF_INT + (SIZE_OF_CHAR * (val.length() + 1)); 815 } 816 } 817 818 public static class ResourceTable extends Resource { 819 public String annotation; 820 public static final String NO_FALLBACK = "nofallback"; 821 822 @Override write(OutputStream writer, int numIndent, boolean bare)823 public void write(OutputStream writer, int numIndent, boolean bare) { 824 writeComments(writer, numIndent); 825 writeIndent(writer, numIndent); 826 if (annotation == null) { 827 write(writer, name + OPENBRACE + LINESEP); 828 } else { 829 write(writer, name + COLON + TABLE + OPENPAREN + annotation + CLOSEPAREN + OPENBRACE + LINESEP); 830 } 831 numIndent++; 832 Resource current = first; 833 while (current != null) { 834 current.write(writer, numIndent, false); 835 current = current.next; 836 } 837 numIndent--; 838 writeIndent(writer, numIndent); 839 write(writer, CLOSEBRACE + LINESEP); 840 } 841 842 // insertion sort of the linked list 843 // from Algorithms in C++ Sedgewick 844 @Override sort()845 public void sort() { 846 if (noSort == true) { 847 return; 848 } 849 // System.out.println("Entering sort of table: "+name); 850 Resource b = new Resource(); 851 Resource a = first; 852 Resource t, u, x; 853 for (t = a; t != null; t = u) { 854 u = t.next; 855 for (x = b; x.next != null; x = x.next) { 856 // if(x.next == null) { 857 // throw new InternalError("Null NEXT node from " + x.name+","+x.toString()); 858 // } else if(x.next.name == null) { 859 // throw new InternalError("Null NEXT name from " + x.name+","+x.toString()+" -> " + 860 // x.next.toString()); 861 // } 862 if (x.next.name.compareTo(t.name) > 0) { 863 break; 864 } 865 } 866 t.next = x.next; 867 x.next = t; 868 } 869 // System.out.println("Exiting sort of table"); 870 if (b.next != null) { 871 first = b.next; 872 } 873 874 Resource current = first; 875 // if(current == this) { 876 // throw new InternalError("I'm my own child.. name="+name); 877 // } 878 while (current != null) { 879 current.sort(); 880 // if(current.next == current) { 881 // throw new InternalError("Sibling links to self: " + current.name); 882 // } 883 current = current.next; 884 } 885 886 } // end sort() 887 is32Bit()888 public boolean is32Bit() { 889 Resource current = this.first; 890 boolean mustBe32 = false; 891 892 while (current != null) { 893 if (current.keyStringOffset > 0xFFFF) { 894 mustBe32 = true; 895 } 896 current = current.next; 897 } 898 return mustBe32; 899 } 900 901 @Override writeBinary(FileOutputStream out, int usedOffset)902 public int writeBinary(FileOutputStream out, int usedOffset) { 903 int count = 0; 904 int pad; 905 Resource current = this.first; 906 int[] resources = new int[numChildren]; 907 short[] keys16 = null; 908 int[] keys32 = null; 909 boolean is32Bit = this.is32Bit(); 910 byte[] padding; 911 912 if (is32Bit) { 913 keys32 = new int[numChildren]; 914 } else { 915 keys16 = new short[numChildren]; 916 } 917 918 // if the table has objects in it 919 if (current != null) { 920 921 // loop through them all 922 while (current != null) { 923 // get the key ptr for current (size depends on table size, store in array 924 if (is32Bit) { 925 keys32[count] = current.keyStringOffset; 926 } else { 927 keys16[count] = (short) current.keyStringOffset; 928 } 929 930 // if INT 931 if (current instanceof ResourceInt) { 932 // resources[i] = (current->fType << 28) | (current->u.fIntValue.fValue & 0xFFFFFFF); 933 int value = 0; 934 935 try { 936 value = Integer.parseInt(((ResourceInt) current).val); 937 } catch (NumberFormatException e) { 938 System.err.println("Error converting string to int: " + e.getMessage()); 939 System.exit(1); 940 } 941 resources[count] = LDML2ICUBinaryWriter.URES_INT << 28 | (value & 0xFFFFFFF); 942 943 } else { 944 // write the current object 945 usedOffset = current.writeBinary(out, usedOffset); 946 947 // write 32 bits for identification? 948 if (current instanceof ResourceString) { 949 resources[count] = LDML2ICUBinaryWriter.URES_STRING << 28 | usedOffset >>> 2; 950 } else if (current instanceof ResourceTable) { 951 resources[count] = LDML2ICUBinaryWriter.URES_TABLE << 28 | usedOffset >>> 2; 952 } else if (current instanceof ResourceAlias) { 953 resources[count] = LDML2ICUBinaryWriter.URES_ALIAS << 28 | usedOffset >>> 2; 954 } else if (current instanceof ResourceArray) { 955 resources[count] = LDML2ICUBinaryWriter.URES_ARRAY << 28 | usedOffset >>> 2; 956 } else if (current instanceof ResourceIntVector) { 957 resources[count] = LDML2ICUBinaryWriter.URES_INT_VECTOR << 28 | usedOffset >>> 2; 958 } 959 960 usedOffset += current.size + pad32(current.size); 961 } 962 count++; 963 current = current.next; 964 } 965 966 // write the member count and the key offsets 967 if (is32Bit) { 968 try { 969 // write a 32 bit block with the number of items in this table 970 out.write(intToBytes(count)); 971 LDML2ICUBinaryWriter.written += intToBytes(count).length; 972 973 // write all the 32 bit keys which were added to the array. 974 out.write(intArrayToBytes(keys32)); 975 LDML2ICUBinaryWriter.written += intArrayToBytes(keys32).length; 976 977 out.write(intArrayToBytes(resources)); 978 LDML2ICUBinaryWriter.written += intArrayToBytes(resources).length; 979 } catch (IOException e) { 980 errIO(); 981 } 982 983 } else { 984 try { 985 // write 2 byte block with the number of items in this table 986 out.write(shortToBytes((short) count)); 987 LDML2ICUBinaryWriter.written += shortToBytes((short) count).length; 988 989 // write all the 2 byte keys which were added to an array 990 out.write(shortArrayToBytes(keys16)); 991 LDML2ICUBinaryWriter.written += shortArrayToBytes(keys16).length; 992 993 pad = pad32(this.size); 994 padding = createPadding(pad); 995 if (padding != null) { 996 out.write(padding); 997 LDML2ICUBinaryWriter.written += padding.length; 998 } 999 1000 out.write(intArrayToBytes(resources)); 1001 LDML2ICUBinaryWriter.written += intArrayToBytes(resources).length; 1002 1003 } catch (IOException e) { 1004 errIO(); 1005 } 1006 1007 } 1008 } else // else (the table is empty) 1009 { 1010 short zero = 0; 1011 1012 // We'll write it as a 16 bit table, because it's empty... 1013 try { 1014 // write a 16 bit zero. 1015 out.write(shortToBytes(zero)); 1016 LDML2ICUBinaryWriter.written += shortToBytes(zero).length; 1017 1018 // pad it 1019 padding = createPadding(pad16Bytes(2)); 1020 if (padding != null) { 1021 out.write(padding); 1022 LDML2ICUBinaryWriter.written += padding.length; 1023 } 1024 1025 } catch (IOException e) { 1026 errIO(); 1027 } 1028 } 1029 return usedOffset; 1030 } 1031 1032 /** 1033 * This method will set the size of the resource. Overwritten for each child object 1034 */ 1035 @Override setSize()1036 public void setSize() { 1037 // Tables have children. 1038 int x = 0; 1039 Resource current = this.first; 1040 this.sizeOfChildren = 0; 1041 while (current != null) { 1042 x++; 1043 this.sizeOfChildren += current.size + pad32(current.size); 1044 1045 if (current instanceof ResourceTable || current instanceof ResourceArray) { 1046 this.sizeOfChildren += current.sizeOfChildren; 1047 } 1048 1049 current = current.next; 1050 } 1051 1052 if (x > maxTableLength) { 1053 maxTableLength = x; 1054 } 1055 1056 if (this.is32Bit()) { 1057 // this resources key offset + a key offset for each child + a pointer to their resource object. 1058 size = SIZE_OF_INT + (x * 2 * SIZE_OF_INT); 1059 } else { 1060 // this resources key offset + a pointer to each childs resource + a 16 bit pointer to each childs key 1061 size = SIZE_OF_INT / 2 + (x * (SIZE_OF_INT + (SIZE_OF_INT / 2))); 1062 } 1063 } 1064 } 1065 1066 /* Currently there is nothing in LDML which converts to a Binary resource. So this type is currently unused. */ 1067 public static class ResourceBinary extends Resource { 1068 String internal; 1069 String external; 1070 byte[] data; 1071 1072 @Override write(OutputStream writer, int numIndent, boolean bare)1073 public void write(OutputStream writer, int numIndent, boolean bare) { 1074 writeComments(writer, numIndent); 1075 writeIndent(writer, numIndent); 1076 if (internal == null) { 1077 String line = ((name == null) ? EMPTY : name) + COLON + IMPORT + OPENBRACE + QUOTE + external + QUOTE 1078 + CLOSEBRACE + ((bare == true) ? EMPTY : LINESEP); 1079 write(writer, line); 1080 } else { 1081 String line = ((name == null) ? EMPTY : name) + COLON + BIN + OPENBRACE + internal + CLOSEBRACE 1082 + ((bare == true) ? EMPTY : LINESEP); 1083 write(writer, line); 1084 } 1085 } 1086 1087 @Override setSize()1088 public void setSize() { 1089 // sizeof(int32_t) + sizeof(uint8_t) * length + BIN_ALIGNMENT; 1090 size = SIZE_OF_INT + data.length + BIN_ALIGNMENT; 1091 } 1092 1093 @Override writeBinary(FileOutputStream out, int usedOffset)1094 public int writeBinary(FileOutputStream out, int usedOffset) { 1095 int pad = 0; 1096 int extrapad = pad32(this.size); 1097 int dataStart = usedOffset + SIZE_OF_INT; 1098 1099 try { 1100 1101 // write some padding 1102 if (dataStart % BIN_ALIGNMENT != 0) { 1103 pad = (BIN_ALIGNMENT - (dataStart % BIN_ALIGNMENT)); 1104 out.write(createPadding(pad)); 1105 usedOffset += pad; 1106 } 1107 1108 // write the length of the data 1109 out.write(intToBytes(data.length)); 1110 1111 // if there is data, write it 1112 if (data.length > 0) { 1113 out.write(data); 1114 } 1115 1116 // write some more padding 1117 out.write(createPadding(BIN_ALIGNMENT - pad + extrapad)); 1118 } catch (Exception e) { 1119 System.err.println("Had problems writing Binary Resource"); 1120 } 1121 return usedOffset; 1122 } 1123 } 1124 1125 public static class ResourceProcess extends Resource { 1126 String val; 1127 String ext; 1128 1129 @Override write(OutputStream writer, int numIndent, boolean bare)1130 public void write(OutputStream writer, int numIndent, boolean bare) { 1131 writeComments(writer, numIndent); 1132 writeIndent(writer, numIndent); 1133 String line = ((name == null) ? EMPTY : name) + COLON + PROCESS + 1134 OPENPAREN + ext + CLOSEPAREN + OPENBRACE + QUOTE + escapeSyntaxChars(val) + QUOTE + CLOSEBRACE; 1135 if (bare == true) { 1136 if (name != null) { 1137 throw new RuntimeException("Bare option is set to true but the resource has a name! " + name); 1138 } 1139 write(writer, line); 1140 } else { 1141 write(writer, line + LINESEP); 1142 } 1143 } 1144 1145 @Override writeBinary(FileOutputStream out, int usedOffset)1146 public int writeBinary(FileOutputStream out, int usedOffset) { 1147 if (this.name.equals("depends")) { 1148 1149 } else { 1150 1151 // should never get called 1152 System.err.println("Unexpected type: " + this.getClass().toString()); 1153 System.err.println("Resource Name: " + this.name); 1154 return usedOffset; 1155 } 1156 return usedOffset; 1157 } 1158 } 1159 1160 public static class ResourceImport extends Resource { 1161 String val; 1162 1163 @Override write(OutputStream writer, int numIndent, boolean bare)1164 public void write(OutputStream writer, int numIndent, boolean bare) { 1165 writeComments(writer, numIndent); 1166 writeIndent(writer, numIndent); 1167 String line = ((name == null) ? EMPTY : name) + COLON + IMPORT + OPENBRACE + QUOTE + escapeSyntaxChars(val) 1168 + QUOTE + CLOSEBRACE; 1169 if (bare == true) { 1170 if (name != null) { 1171 throw new RuntimeException("Bare option is set to true but the resource has a name! " + name); 1172 } 1173 write(writer, line); 1174 } else { 1175 write(writer, line + LINESEP); 1176 } 1177 } 1178 } 1179 1180 /* Seems to be unused. Never parsed in */ 1181 public static class ResourceInclude extends Resource { 1182 String val; 1183 1184 @Override write(OutputStream writer, int numIndent, boolean bare)1185 public void write(OutputStream writer, int numIndent, boolean bare) { 1186 writeComments(writer, numIndent); 1187 writeIndent(writer, numIndent); 1188 String line = ((name == null) ? EMPTY : name) + COLON + INCLUDE + OPENBRACE + QUOTE 1189 + escapeSyntaxChars(val) + QUOTE + CLOSEBRACE; 1190 if (bare == true) { 1191 if (name != null) { 1192 throw new RuntimeException("Bare option is set to true but the resource has a name! " + name); 1193 } 1194 write(writer, line); 1195 } else { 1196 write(writer, line + LINESEP); 1197 } 1198 } 1199 } 1200 1201 /* END Resources ***************************************************************************** */ 1202 1203 /* Helper methods. *************************************************************************** */ 1204 /** 1205 * Convenience function 1206 * 1207 * @param name 1208 * @param val 1209 * @return new ResourceString 1210 */ createString(String name, String val)1211 public static Resource createString(String name, String val) { 1212 return new ResourceString(name, val); 1213 } 1214 pad32(int x)1215 private static int pad32(int x) { 1216 return ((x % SIZE_OF_INT) == 0) ? 0 : (SIZE_OF_INT - (x % SIZE_OF_INT)); 1217 } 1218 create32Padding(int x)1219 private static byte[] create32Padding(int x) { 1220 byte[] b = new byte[pad32(x)]; 1221 if (pad32(x) == 0) { 1222 return null; 1223 } 1224 1225 for (int z = 0; z < b.length; z++) { 1226 b[z] = 0; 1227 } 1228 return b; 1229 } 1230 pad16Bytes(int x)1231 private static int pad16Bytes(int x) { 1232 return ((x % 16) == 0) ? 0 : (16 - (x % 16)); 1233 } 1234 1235 /** 1236 * Takes a 32 bit integer and returns an array of 4 bytes. 1237 * 1238 */ intToBytes(int x)1239 private static byte[] intToBytes(int x) { 1240 byte[] b = new byte[4]; 1241 b[3] = (byte) (x); // just the last byte 1242 1243 x = x >>> 8; // shift each byte over one spot. 1244 b[2] = (byte) (x); // just the last byte 1245 1246 x = x >>> 8; // shift each byte over one spot. 1247 b[1] = (byte) (x); // just the last byte 1248 1249 x = x >>> 8; // shift each byte over one spot. 1250 b[0] = (byte) (x); // just the last byte 1251 1252 return b; 1253 } 1254 1255 /** 1256 * Takes an array of integers and returns a byte array of the memory representation. 1257 * 1258 * @param x 1259 * @return 1260 */ intArrayToBytes(int[] x)1261 private static byte[] intArrayToBytes(int[] x) { 1262 byte[] b = new byte[x.length * 4]; 1263 byte[] temp; 1264 int i, z; 1265 1266 for (i = 0; i < x.length; i++) { 1267 temp = intToBytes(x[i]); 1268 for (z = 0; z < temp.length; z++) { 1269 b[i * temp.length + z] = temp[z]; 1270 } 1271 } 1272 return b; 1273 } 1274 shortArrayToBytes(short[] x)1275 private static byte[] shortArrayToBytes(short[] x) { 1276 byte[] b = new byte[x.length * 2]; 1277 byte[] temp; 1278 int i, z; 1279 1280 for (i = 0; i < x.length; i++) { 1281 temp = shortToBytes(x[i]); 1282 for (z = 0; z < temp.length; z++) { 1283 b[i * temp.length + z] = temp[z]; 1284 } 1285 } 1286 return b; 1287 } 1288 shortToBytes(short x)1289 private static byte[] shortToBytes(short x) { 1290 byte[] b = new byte[2]; 1291 b[1] = (byte) (x); // bitwise AND with the lower byte 1292 b[0] = (byte) (x >>> 8); // shift four bits to the right and fill with zeros, and then bitwise and with the 1293 // lower byte 1294 return b; 1295 } 1296 errUnsupportedEncoding()1297 private static void errUnsupportedEncoding() { 1298 System.err.print("Unsupported Encoding"); 1299 System.exit(1); 1300 } 1301 errIO()1302 private static void errIO() { 1303 System.err.print("An error occured while writing to file."); 1304 System.exit(1); 1305 } 1306 createPadding(int length)1307 private static byte[] createPadding(int length) { 1308 byte x = (byte) 0x00; 1309 byte[] b = new byte[length]; 1310 if (length == 0) { 1311 return null; 1312 } 1313 for (int z = 0; z < b.length; z++) { 1314 b[z] = x; 1315 } 1316 1317 return b; 1318 } 1319 1320 } 1321