• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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