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 write(OutputStream writer, int numIndent, boolean bare)357 public void write(OutputStream writer, int numIndent, boolean bare) { 358 writeComments(writer, numIndent); 359 writeIndent(writer, numIndent); 360 String line = ((name == null) ? EMPTY : name) + COLON + ALIAS + OPENBRACE + QUOTE + escapeSyntaxChars(val) 361 + QUOTE + CLOSEBRACE; 362 if (bare == true) { 363 if (name != null) { 364 throw new RuntimeException("Bare option is set to true but the resource has a name! " + name); 365 } 366 write(writer, line); 367 } else { 368 write(writer, line + LINESEP); 369 } 370 } 371 372 /** 373 * Writes this object to the provided output stream in binary format. Copies formating from Genrb (in ICU4C 374 * tools. 375 * 376 * @param out 377 * A File output stream which has already been set up to write to. 378 */ writeBinary(FileOutputStream out, int usedOffset)379 public int writeBinary(FileOutputStream out, int usedOffset) { 380 byte[] valLenBytes; 381 byte[] valBytes; 382 byte[] padding; 383 384 valLenBytes = intToBytes(val.length()); 385 386 try { 387 valBytes = (val + STRTERM).getBytes(LDML2ICUBinaryWriter.CHARSET16); 388 padding = create32Padding(valBytes.length); 389 out.write(valLenBytes); 390 LDML2ICUBinaryWriter.written += valLenBytes.length; 391 392 out.write(valBytes); 393 LDML2ICUBinaryWriter.written += valBytes.length; 394 395 if (padding != null) { 396 out.write(padding); 397 LDML2ICUBinaryWriter.written += padding.length; 398 } 399 400 } catch (UnsupportedEncodingException e) { 401 errUnsupportedEncoding(); 402 } catch (IOException e) { 403 errIO(); 404 } 405 return usedOffset; 406 } 407 setSize()408 public void setSize() { 409 // a pointer + the string 410 size = SIZE_OF_INT + ((val.length() + 1) * SIZE_OF_CHAR); 411 } 412 } 413 414 public static class ResourceArray extends Resource { write(OutputStream writer, int numIndent, boolean bare)415 public void write(OutputStream writer, int numIndent, boolean bare) { 416 writeComments(writer, numIndent); 417 writeIndent(writer, numIndent); 418 if (name != null) { 419 write(writer, name + OPENBRACE + LINESEP); 420 } else { 421 write(writer, OPENBRACE + LINESEP); 422 } 423 numIndent++; 424 Resource current = first; 425 while (current != null) { 426 current.write(writer, numIndent, true); 427 if (current instanceof ResourceTable || 428 current instanceof ResourceArray) { 429 430 } else { 431 write(writer, COMMA + LINESEP); 432 } 433 current = current.next; 434 } 435 numIndent--; 436 writeIndent(writer, numIndent); 437 write(writer, CLOSEBRACE + LINESEP); 438 } 439 sort()440 public void sort() { 441 if (noSort == true) { 442 return; 443 } 444 Resource current = first; 445 while (current != null) { 446 current.sort(); 447 current = current.next; 448 } 449 } 450 writeBinary(FileOutputStream out, int usedOffset)451 public int writeBinary(FileOutputStream out, int usedOffset) { 452 int count = 0; 453 int[] resources = new int[numChildren]; 454 byte[] resourceBytes; 455 Resource current = this.first; 456 457 // if there are items in the array 458 if (current != null) { 459 // start at the first one and loop 460 while (current != null) { 461 // if it's an int: resources[i] = (current->fType << 28) | (current->u.fIntValue.fValue & 462 // 0xFFFFFFF); 463 if (current instanceof ResourceInt) { 464 int value = 0; 465 466 try { 467 value = Integer.parseInt(((ResourceInt) current).val); 468 } catch (NumberFormatException e) { 469 System.err.println("Error converting string to int: " + e.getMessage()); 470 System.exit(1); 471 } 472 resources[count] = LDML2ICUBinaryWriter.URES_INT << 28 | (value & 0xFFFFFFF); 473 } else { 474 // write the current object 475 usedOffset = current.writeBinary(out, usedOffset); 476 477 // write 32 bits for identification? 478 if (current instanceof ResourceString) { 479 resources[count] = LDML2ICUBinaryWriter.URES_STRING << 28 | usedOffset >>> 2; 480 } else if (current instanceof ResourceTable) { 481 if (((ResourceTable) current).is32Bit()) { 482 resources[count] = LDML2ICUBinaryWriter.URES_TABLE32 << 28 | usedOffset >>> 2; 483 } else { 484 resources[count] = LDML2ICUBinaryWriter.URES_TABLE << 28 | usedOffset >>> 2; 485 } 486 487 } else if (current instanceof ResourceAlias) { 488 resources[count] = LDML2ICUBinaryWriter.URES_ALIAS << 28 | usedOffset >>> 2; 489 } else if (current instanceof ResourceArray) { 490 resources[count] = LDML2ICUBinaryWriter.URES_ARRAY << 28 | usedOffset >>> 2; 491 } else if (current instanceof ResourceIntVector) { 492 resources[count] = LDML2ICUBinaryWriter.URES_INT_VECTOR << 28 | usedOffset >>> 2; 493 } 494 495 usedOffset += current.size + pad32(current.size); 496 } 497 count++; 498 current = current.next; 499 } 500 501 // convert the resource array into the resourceBytes 502 resourceBytes = intArrayToBytes(resources); 503 504 try { 505 // write the array count (int32) 506 out.write(intToBytes(count)); 507 LDML2ICUBinaryWriter.written += intToBytes(count).length; 508 509 // write the resources array...should be size of int32 * array count 510 out.write(resourceBytes); 511 LDML2ICUBinaryWriter.written += resourceBytes.length; 512 } catch (IOException e) { 513 errIO(); 514 } 515 516 } else // Empty array 517 { 518 try { 519 out.write(intToBytes(0)); 520 LDML2ICUBinaryWriter.written += intToBytes(0).length; 521 } catch (IOException e) { 522 errIO(); 523 } 524 } 525 return usedOffset; 526 527 } 528 529 /** 530 * This method will set the size of the resource. 531 */ setSize()532 public void setSize() { 533 // Arrays have children. 534 int x = 0; 535 Resource current = this.first; 536 537 this.sizeOfChildren = 0; 538 539 while (current != null) { 540 x++; 541 542 this.sizeOfChildren += current.size + pad32(current.size); 543 544 if (current instanceof ResourceTable || current instanceof ResourceArray) { 545 this.sizeOfChildren += current.sizeOfChildren; 546 } 547 548 current = current.next; 549 } 550 551 // pointer to the key + pointer to each member 552 size = SIZE_OF_INT + (x * SIZE_OF_INT); 553 554 } 555 } 556 557 public static class ResourceInt extends Resource { 558 String val; 559 write(OutputStream writer, int numIndent, boolean bare)560 public void write(OutputStream writer, int numIndent, boolean bare) { 561 writeComments(writer, numIndent); 562 writeIndent(writer, numIndent); 563 String line = ((name == null) ? EMPTY : name) + COLON + INTS + OPENBRACE + val + CLOSEBRACE; 564 if (bare == true) { 565 if (name != null) { 566 throw new RuntimeException("Bare option is set to true but the resource has a name: " + name); 567 } 568 write(writer, line); 569 } else { 570 write(writer, line + LINESEP); 571 } 572 } 573 writeBinary(FileOutputStream out, int usedOffset)574 public int writeBinary(FileOutputStream out, int usedOffset) { 575 return usedOffset; 576 } 577 578 /** 579 * This method will set the size of the resource. Overwritten for each child object 580 */ setSize()581 public void setSize() { 582 size = 0; 583 584 } 585 } 586 587 public static class ResourceIntVector extends Resource { 588 public String smallComment = null; 589 write(OutputStream writer, int numIndent, boolean bare)590 public void write(OutputStream writer, int numIndent, boolean bare) { 591 writeComments(writer, numIndent); 592 writeIndent(writer, numIndent); 593 write(writer, name + COLON + INTVECTOR + OPENBRACE); 594 if (smallComment != null) { 595 write(writer, " " + COMMENTSTART + " " + smallComment + " " + COMMENTEND); 596 } 597 write(writer, LINESEP); 598 numIndent++; 599 ResourceInt current = (ResourceInt) first; 600 while (current != null) { 601 // current.write(writer, numIndent, true); 602 writeIndent(writer, numIndent); 603 write(writer, current.val); 604 write(writer, COMMA + LINESEP); 605 current = (ResourceInt) current.next; 606 } 607 numIndent--; 608 writeIndent(writer, numIndent); 609 write(writer, CLOSEBRACE + LINESEP); 610 } 611 writeBinary(FileOutputStream out, int usedOffset)612 public int writeBinary(FileOutputStream out, int usedOffset) { 613 int count = 0; 614 int[] numbers = new int[numChildren]; 615 byte[] numBytes; 616 Resource current = this.first; 617 618 while (current != null) { 619 numbers[count] = Integer.parseInt(((ResourceInt) current).val); 620 count++; 621 current = current.next; 622 } 623 624 numBytes = intArrayToBytes(numbers); 625 626 try { 627 out.write(intToBytes(count)); 628 LDML2ICUBinaryWriter.written += intToBytes(count).length; 629 630 out.write(numBytes); 631 LDML2ICUBinaryWriter.written += numBytes.length; 632 } catch (IOException e) { 633 errIO(); 634 } 635 return usedOffset; 636 } 637 638 /** 639 * This method will set the size of the resource. Overwritten for each child object 640 */ setSize()641 public void setSize() { 642 // has children 643 int x = 0; 644 Resource current = this.first; 645 646 while (current != null) { 647 x++; 648 current = current.next; 649 } 650 651 // this resources key offset + each int 652 size = SIZE_OF_INT + (x * SIZE_OF_INT); 653 } 654 } 655 656 public static class ResourceString extends Resource { ResourceString()657 public ResourceString() { 658 } 659 ResourceString(String name, String val)660 public ResourceString(String name, String val) { 661 this.name = name; 662 this.val = val; 663 } 664 665 public String val; 666 /** 667 * one-line comment following the value. ignored unless in bare mode. 668 */ 669 public String smallComment = null; 670 write(OutputStream writer, int numIndent, boolean bare)671 public void write(OutputStream writer, int numIndent, boolean bare) { 672 writeComments(writer, numIndent); 673 writeIndent(writer, numIndent); 674 if (bare == true) { 675 if (name != null) { 676 throw new RuntimeException("Bare option is set to true but the resource has a name! " + name); 677 } 678 679 write(writer, QUOTE + escapeSyntaxChars(val) + QUOTE); 680 if (smallComment != null) { 681 write(writer, " " + COMMENTSTART + " " + smallComment + " " + COMMENTEND); 682 } 683 } else { 684 StringBuffer str = escapeSyntaxChars(val); 685 686 int colLen = 80 - (numIndent * 4); 687 int strLen = str.length(); 688 if (strLen > colLen) { 689 int startIndex = 0; 690 int endIndex = 0; 691 write(writer, name + OPENBRACE + LINESEP); 692 numIndent++; 693 boolean isRules = name.equals("Sequence"); 694 // Find a safe point where we can insert a line break! 695 while (endIndex < strLen) { 696 startIndex = endIndex; 697 endIndex = startIndex + colLen; 698 if (endIndex > strLen) { 699 endIndex = strLen; 700 } 701 if (isRules) { 702 // look for the reset tag only if we are writing 703 // collation rules! 704 int firstIndex = str.indexOf("&", startIndex); 705 706 if (firstIndex > -1) { 707 if (startIndex != (firstIndex - 1) && startIndex != firstIndex && firstIndex < endIndex) { 708 if (str.charAt(firstIndex - 1) != 0x27) { 709 endIndex = firstIndex; 710 } 711 } 712 int nextIndex = 0; 713 while ((nextIndex = str.indexOf("&", firstIndex + 1)) != -1 && nextIndex < endIndex) { 714 715 if (nextIndex > -1 && firstIndex != nextIndex) { 716 if (str.charAt(nextIndex - 1) != 0x27) { 717 endIndex = nextIndex; 718 break; 719 } else { 720 firstIndex = nextIndex; 721 } 722 } 723 } 724 } 725 } 726 int indexOfEsc = 0; 727 if ((indexOfEsc = str.lastIndexOf("\\u", endIndex)) > -1 && (endIndex - indexOfEsc) < 6 || 728 (indexOfEsc = str.lastIndexOf("\\U", endIndex)) > -1 && (endIndex - indexOfEsc) < 10 || 729 (indexOfEsc = str.lastIndexOf("'\'", endIndex)) > -1 && (endIndex - indexOfEsc) < 3) { 730 731 endIndex = indexOfEsc; 732 } 733 if (indexOfEsc > -1 && str.charAt(indexOfEsc - 1) == 0x0027) { 734 endIndex = indexOfEsc - 1; 735 } 736 if (endIndex < strLen && UTF16.isLeadSurrogate(str.charAt(endIndex - 1))) { 737 endIndex--; 738 } 739 740 writeIndent(writer, numIndent); 741 write(writer, QUOTE); 742 write(writer, str.substring(startIndex, endIndex)); 743 write(writer, QUOTE + LINESEP); 744 } 745 numIndent--; 746 writeIndent(writer, numIndent); 747 write(writer, CLOSEBRACE + LINESEP); 748 749 } else { 750 write(writer, name + OPENBRACE + QUOTE + str.toString() + QUOTE + CLOSEBRACE + LINESEP); 751 } 752 753 } 754 } 755 writeBinary(FileOutputStream out, int usedOffset)756 public int writeBinary(FileOutputStream out, int usedOffset) { 757 758 // clean up quotes if any 759 if (this.val.indexOf("\"") >= 0) { 760 this.val = LDML2ICUBinaryWriter.removeQuotes(this.val); 761 } 762 763 String valPlusTerm = val + STRTERM; 764 byte[] valBytes; 765 byte[] valLenBytes; 766 byte[] padding; 767 768 valLenBytes = intToBytes(val.length()); 769 770 try { 771 valBytes = valPlusTerm.getBytes(LDML2ICUBinaryWriter.CHARSET16); 772 padding = create32Padding(valBytes.length); 773 out.write(valLenBytes); // 32 bit int 774 LDML2ICUBinaryWriter.written += valLenBytes.length; 775 776 out.write(valBytes); // The string plus a null terminator 777 LDML2ICUBinaryWriter.written += valBytes.length; 778 779 if (padding != null) { 780 out.write(padding); 781 LDML2ICUBinaryWriter.written += padding.length; 782 } 783 } catch (UnsupportedEncodingException e) { 784 System.err.print("Problems converting string resource to " + LDML2ICUBinaryWriter.CHARSET16); 785 System.exit(1); 786 } catch (IOException e) { 787 System.err.print("Problems writing the string resource to file."); 788 System.exit(1); 789 } 790 return usedOffset; 791 } 792 793 /** 794 * This method will set the size of the resource. Overwritten for each child object 795 */ setSize()796 public void setSize() { 797 // a pointer to the key + a string 798 size = SIZE_OF_INT + (SIZE_OF_CHAR * (val.length() + 1)); 799 } 800 } 801 802 public static class ResourceTable extends Resource { 803 public String annotation; 804 public static final String NO_FALLBACK = "nofallback"; 805 write(OutputStream writer, int numIndent, boolean bare)806 public void write(OutputStream writer, int numIndent, boolean bare) { 807 writeComments(writer, numIndent); 808 writeIndent(writer, numIndent); 809 if (annotation == null) { 810 write(writer, name + OPENBRACE + LINESEP); 811 } else { 812 write(writer, name + COLON + TABLE + OPENPAREN + annotation + CLOSEPAREN + OPENBRACE + LINESEP); 813 } 814 numIndent++; 815 Resource current = first; 816 while (current != null) { 817 current.write(writer, numIndent, false); 818 current = current.next; 819 } 820 numIndent--; 821 writeIndent(writer, numIndent); 822 write(writer, CLOSEBRACE + LINESEP); 823 } 824 825 // insertion sort of the linked list 826 // from Algorithms in C++ Sedgewick sort()827 public void sort() { 828 if (noSort == true) { 829 return; 830 } 831 // System.out.println("Entering sort of table: "+name); 832 Resource b = new Resource(); 833 Resource a = first; 834 Resource t, u, x; 835 for (t = a; t != null; t = u) { 836 u = t.next; 837 for (x = b; x.next != null; x = x.next) { 838 // if(x.next == null) { 839 // throw new InternalError("Null NEXT node from " + x.name+","+x.toString()); 840 // } else if(x.next.name == null) { 841 // throw new InternalError("Null NEXT name from " + x.name+","+x.toString()+" -> " + 842 // x.next.toString()); 843 // } 844 if (x.next.name.compareTo(t.name) > 0) { 845 break; 846 } 847 } 848 t.next = x.next; 849 x.next = t; 850 } 851 // System.out.println("Exiting sort of table"); 852 if (b.next != null) { 853 first = b.next; 854 } 855 856 Resource current = first; 857 // if(current == this) { 858 // throw new InternalError("I'm my own child.. name="+name); 859 // } 860 while (current != null) { 861 current.sort(); 862 // if(current.next == current) { 863 // throw new InternalError("Sibling links to self: " + current.name); 864 // } 865 current = current.next; 866 } 867 868 } // end sort() 869 is32Bit()870 public boolean is32Bit() { 871 Resource current = this.first; 872 boolean mustBe32 = false; 873 874 while (current != null) { 875 if (current.keyStringOffset > 0xFFFF) { 876 mustBe32 = true; 877 } 878 current = current.next; 879 } 880 return mustBe32; 881 } 882 writeBinary(FileOutputStream out, int usedOffset)883 public int writeBinary(FileOutputStream out, int usedOffset) { 884 int count = 0; 885 int pad; 886 Resource current = this.first; 887 int[] resources = new int[numChildren]; 888 short[] keys16 = null; 889 int[] keys32 = null; 890 boolean is32Bit = this.is32Bit(); 891 byte[] padding; 892 893 if (is32Bit) { 894 keys32 = new int[numChildren]; 895 } else { 896 keys16 = new short[numChildren]; 897 } 898 899 // if the table has objects in it 900 if (current != null) { 901 902 // loop through them all 903 while (current != null) { 904 // get the key ptr for current (size depends on table size, store in array 905 if (is32Bit) { 906 keys32[count] = current.keyStringOffset; 907 } else { 908 keys16[count] = (short) current.keyStringOffset; 909 } 910 911 // if INT 912 if (current instanceof ResourceInt) { 913 // resources[i] = (current->fType << 28) | (current->u.fIntValue.fValue & 0xFFFFFFF); 914 int value = 0; 915 916 try { 917 value = Integer.parseInt(((ResourceInt) current).val); 918 } catch (NumberFormatException e) { 919 System.err.println("Error converting string to int: " + e.getMessage()); 920 System.exit(1); 921 } 922 resources[count] = LDML2ICUBinaryWriter.URES_INT << 28 | (value & 0xFFFFFFF); 923 924 } else { 925 // write the current object 926 usedOffset = current.writeBinary(out, usedOffset); 927 928 // write 32 bits for identification? 929 if (current instanceof ResourceString) { 930 resources[count] = LDML2ICUBinaryWriter.URES_STRING << 28 | usedOffset >>> 2; 931 } else if (current instanceof ResourceTable) { 932 resources[count] = LDML2ICUBinaryWriter.URES_TABLE << 28 | usedOffset >>> 2; 933 } else if (current instanceof ResourceAlias) { 934 resources[count] = LDML2ICUBinaryWriter.URES_ALIAS << 28 | usedOffset >>> 2; 935 } else if (current instanceof ResourceArray) { 936 resources[count] = LDML2ICUBinaryWriter.URES_ARRAY << 28 | usedOffset >>> 2; 937 } else if (current instanceof ResourceIntVector) { 938 resources[count] = LDML2ICUBinaryWriter.URES_INT_VECTOR << 28 | usedOffset >>> 2; 939 } 940 941 usedOffset += current.size + pad32(current.size); 942 } 943 count++; 944 current = current.next; 945 } 946 947 // write the member count and the key offsets 948 if (is32Bit) { 949 try { 950 // write a 32 bit block with the number of items in this table 951 out.write(intToBytes(count)); 952 LDML2ICUBinaryWriter.written += intToBytes(count).length; 953 954 // write all the 32 bit keys which were added to the array. 955 out.write(intArrayToBytes(keys32)); 956 LDML2ICUBinaryWriter.written += intArrayToBytes(keys32).length; 957 958 out.write(intArrayToBytes(resources)); 959 LDML2ICUBinaryWriter.written += intArrayToBytes(resources).length; 960 } catch (IOException e) { 961 errIO(); 962 } 963 964 } else { 965 try { 966 // write 2 byte block with the number of items in this table 967 out.write(shortToBytes((short) count)); 968 LDML2ICUBinaryWriter.written += shortToBytes((short) count).length; 969 970 // write all the 2 byte keys which were added to an array 971 out.write(shortArrayToBytes(keys16)); 972 LDML2ICUBinaryWriter.written += shortArrayToBytes(keys16).length; 973 974 pad = pad32(this.size); 975 padding = createPadding(pad); 976 if (padding != null) { 977 out.write(padding); 978 LDML2ICUBinaryWriter.written += padding.length; 979 } 980 981 out.write(intArrayToBytes(resources)); 982 LDML2ICUBinaryWriter.written += intArrayToBytes(resources).length; 983 984 } catch (IOException e) { 985 errIO(); 986 } 987 988 } 989 } else // else (the table is empty) 990 { 991 short zero = 0; 992 993 // We'll write it as a 16 bit table, because it's empty... 994 try { 995 // write a 16 bit zero. 996 out.write(shortToBytes(zero)); 997 LDML2ICUBinaryWriter.written += shortToBytes(zero).length; 998 999 // pad it 1000 padding = createPadding(pad16Bytes(2)); 1001 if (padding != null) { 1002 out.write(padding); 1003 LDML2ICUBinaryWriter.written += padding.length; 1004 } 1005 1006 } catch (IOException e) { 1007 errIO(); 1008 } 1009 } 1010 return usedOffset; 1011 } 1012 1013 /** 1014 * This method will set the size of the resource. Overwritten for each child object 1015 */ setSize()1016 public void setSize() { 1017 // Tables have children. 1018 int x = 0; 1019 Resource current = this.first; 1020 this.sizeOfChildren = 0; 1021 while (current != null) { 1022 x++; 1023 this.sizeOfChildren += current.size + pad32(current.size); 1024 1025 if (current instanceof ResourceTable || current instanceof ResourceArray) { 1026 this.sizeOfChildren += current.sizeOfChildren; 1027 } 1028 1029 current = current.next; 1030 } 1031 1032 if (x > maxTableLength) { 1033 maxTableLength = x; 1034 } 1035 1036 if (this.is32Bit()) { 1037 // this resources key offset + a key offset for each child + a pointer to their resource object. 1038 size = SIZE_OF_INT + (x * 2 * SIZE_OF_INT); 1039 } else { 1040 // this resources key offset + a pointer to each childs resource + a 16 bit pointer to each childs key 1041 size = SIZE_OF_INT / 2 + (x * (SIZE_OF_INT + (SIZE_OF_INT / 2))); 1042 } 1043 } 1044 } 1045 1046 /* Currently there is nothing in LDML which converts to a Binary resource. So this type is currently unused. */ 1047 public static class ResourceBinary extends Resource { 1048 String internal; 1049 String external; 1050 byte[] data; 1051 write(OutputStream writer, int numIndent, boolean bare)1052 public void write(OutputStream writer, int numIndent, boolean bare) { 1053 writeComments(writer, numIndent); 1054 writeIndent(writer, numIndent); 1055 if (internal == null) { 1056 String line = ((name == null) ? EMPTY : name) + COLON + IMPORT + OPENBRACE + QUOTE + external + QUOTE 1057 + CLOSEBRACE + ((bare == true) ? EMPTY : LINESEP); 1058 write(writer, line); 1059 } else { 1060 String line = ((name == null) ? EMPTY : name) + COLON + BIN + OPENBRACE + internal + CLOSEBRACE 1061 + ((bare == true) ? EMPTY : LINESEP); 1062 write(writer, line); 1063 } 1064 } 1065 setSize()1066 public void setSize() { 1067 // sizeof(int32_t) + sizeof(uint8_t) * length + BIN_ALIGNMENT; 1068 size = SIZE_OF_INT + data.length + BIN_ALIGNMENT; 1069 } 1070 writeBinary(FileOutputStream out, int usedOffset)1071 public int writeBinary(FileOutputStream out, int usedOffset) { 1072 int pad = 0; 1073 int extrapad = pad32(this.size); 1074 int dataStart = usedOffset + SIZE_OF_INT; 1075 1076 try { 1077 1078 // write some padding 1079 if (dataStart % BIN_ALIGNMENT != 0) { 1080 pad = (BIN_ALIGNMENT - (dataStart % BIN_ALIGNMENT)); 1081 out.write(createPadding(pad)); 1082 usedOffset += pad; 1083 } 1084 1085 // write the length of the data 1086 out.write(intToBytes(data.length)); 1087 1088 // if there is data, write it 1089 if (data.length > 0) { 1090 out.write(data); 1091 } 1092 1093 // write some more padding 1094 out.write(createPadding(BIN_ALIGNMENT - pad + extrapad)); 1095 } catch (Exception e) { 1096 System.err.println("Had problems writing Binary Resource"); 1097 } 1098 return usedOffset; 1099 } 1100 } 1101 1102 public static class ResourceProcess extends Resource { 1103 String val; 1104 String ext; 1105 write(OutputStream writer, int numIndent, boolean bare)1106 public void write(OutputStream writer, int numIndent, boolean bare) { 1107 writeComments(writer, numIndent); 1108 writeIndent(writer, numIndent); 1109 String line = ((name == null) ? EMPTY : name) + COLON + PROCESS + 1110 OPENPAREN + ext + CLOSEPAREN + OPENBRACE + QUOTE + escapeSyntaxChars(val) + QUOTE + CLOSEBRACE; 1111 if (bare == true) { 1112 if (name != null) { 1113 throw new RuntimeException("Bare option is set to true but the resource has a name! " + name); 1114 } 1115 write(writer, line); 1116 } else { 1117 write(writer, line + LINESEP); 1118 } 1119 } 1120 writeBinary(FileOutputStream out, int usedOffset)1121 public int writeBinary(FileOutputStream out, int usedOffset) { 1122 if (this.name.equals("depends")) { 1123 1124 } else { 1125 1126 // should never get called 1127 System.err.println("Unexpected type: " + this.getClass().toString()); 1128 System.err.println("Resource Name: " + this.name); 1129 return usedOffset; 1130 } 1131 return usedOffset; 1132 } 1133 } 1134 1135 public static class ResourceImport extends Resource { 1136 String val; 1137 write(OutputStream writer, int numIndent, boolean bare)1138 public void write(OutputStream writer, int numIndent, boolean bare) { 1139 writeComments(writer, numIndent); 1140 writeIndent(writer, numIndent); 1141 String line = ((name == null) ? EMPTY : name) + COLON + IMPORT + OPENBRACE + QUOTE + escapeSyntaxChars(val) 1142 + QUOTE + CLOSEBRACE; 1143 if (bare == true) { 1144 if (name != null) { 1145 throw new RuntimeException("Bare option is set to true but the resource has a name! " + name); 1146 } 1147 write(writer, line); 1148 } else { 1149 write(writer, line + LINESEP); 1150 } 1151 } 1152 } 1153 1154 /* Seems to be unused. Never parsed in */ 1155 public static class ResourceInclude extends Resource { 1156 String val; 1157 write(OutputStream writer, int numIndent, boolean bare)1158 public void write(OutputStream writer, int numIndent, boolean bare) { 1159 writeComments(writer, numIndent); 1160 writeIndent(writer, numIndent); 1161 String line = ((name == null) ? EMPTY : name) + COLON + INCLUDE + OPENBRACE + QUOTE 1162 + escapeSyntaxChars(val) + QUOTE + CLOSEBRACE; 1163 if (bare == true) { 1164 if (name != null) { 1165 throw new RuntimeException("Bare option is set to true but the resource has a name! " + name); 1166 } 1167 write(writer, line); 1168 } else { 1169 write(writer, line + LINESEP); 1170 } 1171 } 1172 } 1173 1174 /* END Resources ***************************************************************************** */ 1175 1176 /* Helper methods. *************************************************************************** */ 1177 /** 1178 * Convenience function 1179 * 1180 * @param name 1181 * @param val 1182 * @return new ResourceString 1183 */ createString(String name, String val)1184 public static Resource createString(String name, String val) { 1185 return new ResourceString(name, val); 1186 } 1187 pad32(int x)1188 private static int pad32(int x) { 1189 return ((x % SIZE_OF_INT) == 0) ? 0 : (SIZE_OF_INT - (x % SIZE_OF_INT)); 1190 } 1191 create32Padding(int x)1192 private static byte[] create32Padding(int x) { 1193 byte[] b = new byte[pad32(x)]; 1194 if (pad32(x) == 0) { 1195 return null; 1196 } 1197 1198 for (int z = 0; z < b.length; z++) { 1199 b[z] = 0; 1200 } 1201 return b; 1202 } 1203 pad16Bytes(int x)1204 private static int pad16Bytes(int x) { 1205 return ((x % 16) == 0) ? 0 : (16 - (x % 16)); 1206 } 1207 1208 /** 1209 * Takes a 32 bit integer and returns an array of 4 bytes. 1210 * 1211 */ intToBytes(int x)1212 private static byte[] intToBytes(int x) { 1213 byte[] b = new byte[4]; 1214 b[3] = (byte) (x); // just the last byte 1215 1216 x = x >>> 8; // shift each byte over one spot. 1217 b[2] = (byte) (x); // just the last byte 1218 1219 x = x >>> 8; // shift each byte over one spot. 1220 b[1] = (byte) (x); // just the last byte 1221 1222 x = x >>> 8; // shift each byte over one spot. 1223 b[0] = (byte) (x); // just the last byte 1224 1225 return b; 1226 } 1227 1228 /** 1229 * Takes an array of integers and returns a byte array of the memory representation. 1230 * 1231 * @param x 1232 * @return 1233 */ intArrayToBytes(int[] x)1234 private static byte[] intArrayToBytes(int[] x) { 1235 byte[] b = new byte[x.length * 4]; 1236 byte[] temp; 1237 int i, z; 1238 1239 for (i = 0; i < x.length; i++) { 1240 temp = intToBytes(x[i]); 1241 for (z = 0; z < temp.length; z++) { 1242 b[i * temp.length + z] = temp[z]; 1243 } 1244 } 1245 return b; 1246 } 1247 shortArrayToBytes(short[] x)1248 private static byte[] shortArrayToBytes(short[] x) { 1249 byte[] b = new byte[x.length * 2]; 1250 byte[] temp; 1251 int i, z; 1252 1253 for (i = 0; i < x.length; i++) { 1254 temp = shortToBytes(x[i]); 1255 for (z = 0; z < temp.length; z++) { 1256 b[i * temp.length + z] = temp[z]; 1257 } 1258 } 1259 return b; 1260 } 1261 shortToBytes(short x)1262 private static byte[] shortToBytes(short x) { 1263 byte[] b = new byte[2]; 1264 b[1] = (byte) (x); // bitwise AND with the lower byte 1265 b[0] = (byte) (x >>> 8); // shift four bits to the right and fill with zeros, and then bitwise and with the 1266 // lower byte 1267 return b; 1268 } 1269 errUnsupportedEncoding()1270 private static void errUnsupportedEncoding() { 1271 System.err.print("Unsupported Encoding"); 1272 System.exit(1); 1273 } 1274 errIO()1275 private static void errIO() { 1276 System.err.print("An error occured while writing to file."); 1277 System.exit(1); 1278 } 1279 createPadding(int length)1280 private static byte[] createPadding(int length) { 1281 byte x = (byte) 0x00; 1282 byte[] b = new byte[length]; 1283 if (length == 0) { 1284 return null; 1285 } 1286 for (int z = 0; z < b.length; z++) { 1287 b[z] = x; 1288 } 1289 1290 return b; 1291 } 1292 1293 } 1294