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