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