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