| /* |
| ****************************************************************************** |
| * Copyright (C) 2004, International Business Machines Corporation and * |
| * others. All Rights Reserved. * |
| ****************************************************************************** |
| */ |
| /** |
| * @author Ram Viswanadha |
| * @author Brian Rower - June 2008 - added writeBinary methods |
| */ |
| package org.unicode.cldr.icu; |
| |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.UnsupportedEncodingException; |
| |
| import com.ibm.icu.text.UTF16; |
| |
| public class ICUResourceWriter { |
| private static final String CHARSET = "UTF-8"; |
| private static final String OPENBRACE = "{"; |
| private static final String CLOSEBRACE = "}"; |
| private static final String OPENPAREN = "("; |
| private static final String CLOSEPAREN = ")"; |
| private static final String COLON = ":"; |
| private static final String COMMA = ","; |
| private static final String QUOTE = "\""; |
| private static final String COMMENTSTART = "/**"; |
| private static final String COMMENTEND = " */"; |
| private static final String COMMENTMIDDLE = " * "; |
| private static final String INDENT = " "; |
| private static final String EMPTY = ""; |
| private static final String BIN = "bin"; |
| private static final String INTS = "int"; |
| private static final String TABLE = "table"; |
| private static final String IMPORT = "import"; |
| private static final String INCLUDE = "include"; |
| private static final String PROCESS = "process"; |
| private static final String ALIAS = "alias"; |
| private static final String INTVECTOR = "intvector"; |
| // private static final String ARRAYS = "array"; |
| private static final String LINESEP = System.getProperty("line.separator"); |
| private static final String STRTERM = "\0"; |
| |
| public static final int SIZE_OF_INT = 4; |
| public static final int SIZE_OF_CHAR = 2; |
| |
| public static final String UCA_RULES = "uca_rules"; |
| public static final String TRANSLITERATOR = "transliaterator"; |
| public static final String COLLATION = "collation"; |
| public static final String DEPENDENCY = "dependency"; |
| |
| public static final int BIN_ALIGNMENT = 16; |
| /** |
| * This integer is a count of ALL the resources in this tree (not including the root object) |
| */ |
| public static int maxTableLength; |
| |
| public static class Resource { |
| public class MalformedResourceError extends Error { |
| private static final long serialVersionUID = -5943014701317383613L; |
| public Resource offendingResource; |
| |
| public MalformedResourceError(String str, Resource res) { |
| super(str); |
| offendingResource = res; |
| } |
| } |
| |
| String[] note = new String[20]; |
| int noteLen = 0; |
| String translate; |
| /** |
| * This is a comment which will appear on the item. |
| */ |
| String comment; |
| /** |
| * This is the resource's name, or, 'key' |
| */ |
| public String name; |
| /** |
| * This links to the next sibling of this item in the list |
| */ |
| public Resource next; |
| boolean noSort = false; |
| /** |
| * If this item contains other items, this points to the first item in its list |
| */ |
| public Resource first = null; |
| |
| /** |
| * A counter for how many children there are below this object. |
| */ |
| public int numChildren; |
| |
| /** |
| * Stores how many bytes are used by the children of this object. |
| */ |
| public int sizeOfChildren; |
| |
| /** |
| * Stores how many bytes are used by this resource. |
| */ |
| public int size; |
| |
| /** |
| * This integer stores the number of bytes from the beginning of the "key string" |
| * this resources key starts. For more information see the comment in LDML2ICUBinaryWriter above |
| * the writeKeyString method. |
| */ |
| public int keyStringOffset; |
| |
| public boolean hasKey = true; |
| |
| public boolean isTop = false; |
| |
| /** |
| * This method will set the size of the resource. Overwritten for each child object |
| */ |
| public void setSize() |
| { |
| size = 0; |
| } |
| |
| public Resource() |
| { |
| isTop = false; |
| } |
| |
| /** |
| * @return the end of this chain, by repeatedly calling next |
| * @see next |
| */ |
| public Resource end() { |
| ICUResourceWriter.Resource current = this; |
| while (current != null) { |
| if (current.next == null) { |
| return current; |
| } |
| current = current.next; |
| } |
| return current; |
| } |
| |
| /** |
| * Append to a basic list. |
| * Usage: |
| * Resource list = null; list = Resource.addAfter(list, res1); list = Resource.addAfter(list,res2); ... |
| * |
| * @param list |
| * the list to append to (could be null) |
| * @param res |
| * the item to add |
| * @return the beginning of the list |
| */ |
| static final Resource addAfter(Resource list, Resource res) { |
| if (list == null) { |
| list = res; |
| } else { |
| // go to end of the list |
| Resource last = list.end(); |
| last.next = res; |
| } |
| // return the beginning |
| return list; |
| } |
| |
| /** |
| * Appends 'res' to the end of 'this' (next sibling chain) |
| * |
| * @param res |
| * the item (or items) to be added |
| * @return the new beginning of the chain (this) |
| */ |
| public Resource addAfter(Resource res) { |
| return addAfter(this, res); |
| } |
| |
| /** |
| * Replace the contents (first) of this object with the parameter |
| * |
| * @param res |
| * The object to be replaced |
| * @return the old contents |
| */ |
| public Resource replaceContents(Resource res) { |
| Resource old = first; |
| first = res; |
| return old; |
| } |
| |
| /** |
| * Append the contents (first) of this object with the parameter |
| * |
| * @param res |
| * the object to be added to the contents |
| * @return the end of the contents chain |
| */ |
| public Resource appendContents(Resource res) { |
| if (first == null) { |
| first = res; |
| } else { |
| first.end().next = res; |
| } |
| return res.end(); |
| } |
| |
| /** |
| * Check whether this item has contents |
| * |
| * @return true if this item is empty (first==null) |
| */ |
| public boolean isEmpty() { |
| return (first == null); |
| } |
| |
| /** |
| * @param val |
| * @return |
| */ |
| |
| public StringBuffer escapeSyntaxChars(String val) { |
| // escape the embedded quotes |
| if (val == null) { |
| System.err.println("Resource.escapeSyntaxChars: error, resource '" + name |
| + "': string value is NULL - assuming 'empty'"); |
| throw new MalformedResourceError("Resource.escapeSyntaxChars: error, resource '" + name |
| + "': string value is NULL - assuming 'empty'", this); |
| // return new StringBuffer(""); |
| } |
| char[] str = val.toCharArray(); |
| StringBuffer result = new StringBuffer(); |
| for (int i = 0; i < str.length; i++) { |
| switch (str[i]) { |
| case '\u0022': |
| result.append('\\'); // append backslash |
| default: |
| result.append(str[i]); |
| } |
| } |
| return result; |
| } |
| |
| public void write(OutputStream writer, int numIndent, boolean bare) { |
| while (next != null) { |
| next.write(writer, numIndent + 1, false); |
| } |
| } |
| |
| public void writeIndent(OutputStream writer, int numIndent) { |
| for (int i = 0; i < numIndent; i++) { |
| write(writer, INDENT); |
| } |
| } |
| |
| public int writeBinary(FileOutputStream out, int usedOffset) |
| { |
| // should never get called |
| System.err.println("Unexpected type: " + this.getClass().toString()); |
| System.err.println("Resource Name: " + this.name); |
| return usedOffset; |
| } |
| |
| public void write(OutputStream writer, String value) { |
| try { |
| byte[] bytes = value.getBytes(CHARSET); |
| writer.write(bytes, 0, bytes.length); |
| |
| } catch (Exception e) { |
| System.err.println(e); |
| System.exit(1); |
| } |
| } |
| |
| public void writeComments(OutputStream writer, int numIndent) { |
| if (comment != null || translate != null || noteLen > 0) { |
| // print the start of the comment |
| writeIndent(writer, numIndent); |
| write(writer, COMMENTSTART + LINESEP); |
| |
| // print comment if any |
| if (comment != null) { |
| int index = comment.indexOf('\n'); |
| if (index > -1) { |
| StringBuffer indent = new StringBuffer("\n"); |
| for (int i = 0; i < numIndent; i++) { |
| indent.append(INDENT); |
| } |
| indent.append(COMMENTMIDDLE); |
| comment = comment.replaceAll("\n", indent.toString()); |
| } |
| writeIndent(writer, numIndent); |
| write(writer, COMMENTMIDDLE); |
| write(writer, comment); |
| write(writer, LINESEP); |
| |
| } |
| |
| // terminate the comment |
| writeIndent(writer, numIndent); |
| write(writer, COMMENTEND + LINESEP); |
| } |
| } |
| |
| public void sort() { |
| // System.out.println("In sort"); |
| return; |
| } |
| |
| public void swap() { |
| return; |
| } |
| |
| boolean findResourcePath(StringBuffer str, Resource res) { |
| if (name != null) { |
| str.append(name); |
| } |
| if (res == this) { |
| return true; |
| } |
| str.append('/'); |
| // process the siblings of the children... |
| int n = 0; |
| int oldLen = str.length(); |
| for (Resource child = first; child != null; child = child.next) { |
| if (child.name == null) { |
| str.append("#" + n); |
| } |
| if (child.findResourcePath(str, res)) { |
| return true; |
| } |
| n++; |
| str.setLength(oldLen); // reset path length |
| } |
| return false; |
| } |
| |
| String findResourcePath(Resource res) { |
| if (next != null) { |
| throw new IllegalArgumentException( |
| "Don't call findResourcePath(Resource res) on resources which have siblings"); |
| } |
| StringBuffer str = new StringBuffer(); |
| if (findResourcePath(str, res)) { |
| return str.toString(); |
| } else { |
| return null; |
| } |
| } |
| } |
| |
| /* ***************************END Resource *********** */ |
| |
| /* All the children resource types below************** */ |
| |
| public static class ResourceAlias extends Resource { |
| String val; |
| |
| public void write(OutputStream writer, int numIndent, boolean bare) { |
| writeComments(writer, numIndent); |
| writeIndent(writer, numIndent); |
| String line = ((name == null) ? EMPTY : name) + COLON + ALIAS + OPENBRACE + QUOTE + escapeSyntaxChars(val) |
| + QUOTE + CLOSEBRACE; |
| if (bare == true) { |
| if (name != null) { |
| throw new RuntimeException("Bare option is set to true but the resource has a name! " + name); |
| } |
| write(writer, line); |
| } else { |
| write(writer, line + LINESEP); |
| } |
| } |
| |
| /** |
| * Writes this object to the provided output stream in binary format. Copies formating from Genrb (in ICU4C |
| * tools. |
| * |
| * @param out |
| * A File output stream which has already been set up to write to. |
| */ |
| public int writeBinary(FileOutputStream out, int usedOffset) |
| { |
| byte[] valLenBytes; |
| byte[] valBytes; |
| byte[] padding; |
| |
| valLenBytes = intToBytes(val.length()); |
| |
| try |
| { |
| valBytes = (val + STRTERM).getBytes(LDML2ICUBinaryWriter.CHARSET16); |
| padding = create32Padding(valBytes.length); |
| out.write(valLenBytes); |
| LDML2ICUBinaryWriter.written += valLenBytes.length; |
| |
| out.write(valBytes); |
| LDML2ICUBinaryWriter.written += valBytes.length; |
| |
| if (padding != null) |
| { |
| out.write(padding); |
| LDML2ICUBinaryWriter.written += padding.length; |
| } |
| |
| } catch (UnsupportedEncodingException e) |
| { |
| errUnsupportedEncoding(); |
| } catch (IOException e) |
| { |
| errIO(); |
| } |
| return usedOffset; |
| } |
| |
| public void setSize() |
| { |
| // a pointer + the string |
| size = SIZE_OF_INT + ((val.length() + 1) * SIZE_OF_CHAR); |
| } |
| } |
| |
| public static class ResourceArray extends Resource { |
| public void write(OutputStream writer, int numIndent, boolean bare) { |
| writeComments(writer, numIndent); |
| writeIndent(writer, numIndent); |
| if (name != null) { |
| write(writer, name + OPENBRACE + LINESEP); |
| } else { |
| write(writer, OPENBRACE + LINESEP); |
| } |
| numIndent++; |
| Resource current = first; |
| while (current != null) { |
| current.write(writer, numIndent, true); |
| if (current instanceof ResourceTable || |
| current instanceof ResourceArray) { |
| |
| } else { |
| write(writer, COMMA + LINESEP); |
| } |
| current = current.next; |
| } |
| numIndent--; |
| writeIndent(writer, numIndent); |
| write(writer, CLOSEBRACE + LINESEP); |
| } |
| |
| public void sort() { |
| if (noSort == true) { |
| return; |
| } |
| Resource current = first; |
| while (current != null) { |
| current.sort(); |
| current = current.next; |
| } |
| } |
| |
| public int writeBinary(FileOutputStream out, int usedOffset) |
| { |
| int count = 0; |
| int[] resources = new int[numChildren]; |
| byte[] resourceBytes; |
| Resource current = this.first; |
| |
| // if there are items in the array |
| if (current != null) |
| { |
| // start at the first one and loop |
| while (current != null) |
| { |
| // if it's an int: resources[i] = (current->fType << 28) | (current->u.fIntValue.fValue & |
| // 0xFFFFFFF); |
| if (current instanceof ResourceInt) |
| { |
| int value = 0; |
| |
| try |
| { |
| value = Integer.parseInt(((ResourceInt) current).val); |
| } catch (NumberFormatException e) |
| { |
| System.err.println("Error converting string to int: " + e.getMessage()); |
| System.exit(1); |
| } |
| resources[count] = LDML2ICUBinaryWriter.URES_INT << 28 | (value & 0xFFFFFFF); |
| } |
| else |
| { |
| // write the current object |
| usedOffset = current.writeBinary(out, usedOffset); |
| |
| // write 32 bits for identification? |
| if (current instanceof ResourceString) |
| { |
| resources[count] = LDML2ICUBinaryWriter.URES_STRING << 28 | usedOffset >>> 2; |
| } |
| else if (current instanceof ResourceTable) |
| { |
| if (((ResourceTable) current).is32Bit()) |
| { |
| resources[count] = LDML2ICUBinaryWriter.URES_TABLE32 << 28 | usedOffset >>> 2; |
| } |
| else |
| { |
| resources[count] = LDML2ICUBinaryWriter.URES_TABLE << 28 | usedOffset >>> 2; |
| } |
| |
| } |
| else if (current instanceof ResourceAlias) |
| { |
| resources[count] = LDML2ICUBinaryWriter.URES_ALIAS << 28 | usedOffset >>> 2; |
| } |
| else if (current instanceof ResourceArray) |
| { |
| resources[count] = LDML2ICUBinaryWriter.URES_ARRAY << 28 | usedOffset >>> 2; |
| } |
| else if (current instanceof ResourceIntVector) |
| { |
| resources[count] = LDML2ICUBinaryWriter.URES_INT_VECTOR << 28 | usedOffset >>> 2; |
| } |
| |
| usedOffset += current.size + pad32(current.size); |
| } |
| count++; |
| current = current.next; |
| } |
| |
| // convert the resource array into the resourceBytes |
| resourceBytes = intArrayToBytes(resources); |
| |
| try |
| { |
| // write the array count (int32) |
| out.write(intToBytes(count)); |
| LDML2ICUBinaryWriter.written += intToBytes(count).length; |
| |
| // write the resources array...should be size of int32 * array count |
| out.write(resourceBytes); |
| LDML2ICUBinaryWriter.written += resourceBytes.length; |
| } catch (IOException e) |
| { |
| errIO(); |
| } |
| |
| } |
| else // Empty array |
| { |
| try |
| { |
| out.write(intToBytes(0)); |
| LDML2ICUBinaryWriter.written += intToBytes(0).length; |
| } catch (IOException e) |
| { |
| errIO(); |
| } |
| } |
| return usedOffset; |
| |
| } |
| |
| /** |
| * This method will set the size of the resource. |
| */ |
| public void setSize() |
| { |
| // Arrays have children. |
| int x = 0; |
| Resource current = this.first; |
| |
| this.sizeOfChildren = 0; |
| |
| while (current != null) |
| { |
| x++; |
| |
| this.sizeOfChildren += current.size + pad32(current.size); |
| |
| if (current instanceof ResourceTable || current instanceof ResourceArray) |
| { |
| this.sizeOfChildren += current.sizeOfChildren; |
| } |
| |
| current = current.next; |
| } |
| |
| // pointer to the key + pointer to each member |
| size = SIZE_OF_INT + (x * SIZE_OF_INT); |
| |
| } |
| } |
| |
| public static class ResourceInt extends Resource { |
| String val; |
| |
| public void write(OutputStream writer, int numIndent, boolean bare) { |
| writeComments(writer, numIndent); |
| writeIndent(writer, numIndent); |
| String line = ((name == null) ? EMPTY : name) + COLON + INTS + OPENBRACE + val + CLOSEBRACE; |
| if (bare == true) { |
| if (name != null) { |
| throw new RuntimeException("Bare option is set to true but the resource has a name: " + name); |
| } |
| write(writer, line); |
| } else { |
| write(writer, line + LINESEP); |
| } |
| } |
| |
| public int writeBinary(FileOutputStream out, int usedOffset) |
| { |
| return usedOffset; |
| } |
| |
| /** |
| * This method will set the size of the resource. Overwritten for each child object |
| */ |
| public void setSize() |
| { |
| size = 0; |
| |
| } |
| } |
| |
| public static class ResourceIntVector extends Resource { |
| public String smallComment = null; |
| |
| public void write(OutputStream writer, int numIndent, boolean bare) { |
| writeComments(writer, numIndent); |
| writeIndent(writer, numIndent); |
| write(writer, name + COLON + INTVECTOR + OPENBRACE); |
| if (smallComment != null) { |
| write(writer, " " + COMMENTSTART + " " + smallComment + " " + COMMENTEND); |
| } |
| write(writer, LINESEP); |
| numIndent++; |
| ResourceInt current = (ResourceInt) first; |
| while (current != null) { |
| // current.write(writer, numIndent, true); |
| writeIndent(writer, numIndent); |
| write(writer, current.val); |
| write(writer, COMMA + LINESEP); |
| current = (ResourceInt) current.next; |
| } |
| numIndent--; |
| writeIndent(writer, numIndent); |
| write(writer, CLOSEBRACE + LINESEP); |
| } |
| |
| public int writeBinary(FileOutputStream out, int usedOffset) |
| { |
| int count = 0; |
| int[] numbers = new int[numChildren]; |
| byte[] numBytes; |
| Resource current = this.first; |
| |
| while (current != null) |
| { |
| numbers[count] = Integer.parseInt(((ResourceInt) current).val); |
| count++; |
| current = current.next; |
| } |
| |
| numBytes = intArrayToBytes(numbers); |
| |
| try |
| { |
| out.write(intToBytes(count)); |
| LDML2ICUBinaryWriter.written += intToBytes(count).length; |
| |
| out.write(numBytes); |
| LDML2ICUBinaryWriter.written += numBytes.length; |
| } catch (IOException e) |
| { |
| errIO(); |
| } |
| return usedOffset; |
| } |
| |
| /** |
| * This method will set the size of the resource. Overwritten for each child object |
| */ |
| public void setSize() |
| { |
| // has children |
| int x = 0; |
| Resource current = this.first; |
| |
| while (current != null) |
| { |
| x++; |
| current = current.next; |
| } |
| |
| // this resources key offset + each int |
| size = SIZE_OF_INT + (x * SIZE_OF_INT); |
| } |
| } |
| |
| public static class ResourceString extends Resource { |
| public ResourceString() { |
| } |
| |
| public ResourceString(String name, String val) { |
| this.name = name; |
| this.val = val; |
| } |
| |
| public String val; |
| /** |
| * one-line comment following the value. ignored unless in bare mode. |
| */ |
| public String smallComment = null; |
| |
| public void write(OutputStream writer, int numIndent, boolean bare) { |
| writeComments(writer, numIndent); |
| writeIndent(writer, numIndent); |
| if (bare == true) { |
| if (name != null) { |
| throw new RuntimeException("Bare option is set to true but the resource has a name! " + name); |
| } |
| |
| write(writer, QUOTE + escapeSyntaxChars(val) + QUOTE); |
| if (smallComment != null) { |
| write(writer, " " + COMMENTSTART + " " + smallComment + " " + COMMENTEND); |
| } |
| } else { |
| StringBuffer str = escapeSyntaxChars(val); |
| |
| int colLen = 80 - (numIndent * 4); |
| int strLen = str.length(); |
| if (strLen > colLen) { |
| int startIndex = 0; |
| int endIndex = 0; |
| write(writer, name + OPENBRACE + LINESEP); |
| numIndent++; |
| boolean isRules = name.equals("Sequence"); |
| // Find a safe point where we can insert a line break! |
| while (endIndex < strLen) { |
| startIndex = endIndex; |
| endIndex = startIndex + colLen; |
| if (endIndex > strLen) { |
| endIndex = strLen; |
| } |
| if (isRules) { |
| // look for the reset tag only if we are writing |
| // collation rules! |
| int firstIndex = str.indexOf("&", startIndex); |
| |
| if (firstIndex > -1) { |
| if (startIndex != (firstIndex - 1) && startIndex != firstIndex && firstIndex < endIndex) { |
| if (str.charAt(firstIndex - 1) != 0x27) { |
| endIndex = firstIndex; |
| } |
| } |
| int nextIndex = 0; |
| while ((nextIndex = str.indexOf("&", firstIndex + 1)) != -1 && nextIndex < endIndex) { |
| |
| if (nextIndex > -1 && firstIndex != nextIndex) { |
| if (str.charAt(nextIndex - 1) != 0x27) { |
| endIndex = nextIndex; |
| break; |
| } else { |
| firstIndex = nextIndex; |
| } |
| } |
| } |
| } |
| } |
| int indexOfEsc = 0; |
| if ((indexOfEsc = str.lastIndexOf("\\u", endIndex)) > -1 && (endIndex - indexOfEsc) < 6 || |
| (indexOfEsc = str.lastIndexOf("\\U", endIndex)) > -1 && (endIndex - indexOfEsc) < 10 || |
| (indexOfEsc = str.lastIndexOf("'\'", endIndex)) > -1 && (endIndex - indexOfEsc) < 3) { |
| |
| endIndex = indexOfEsc; |
| } |
| if (indexOfEsc > -1 && str.charAt(indexOfEsc - 1) == 0x0027) { |
| endIndex = indexOfEsc - 1; |
| } |
| if (endIndex < strLen && UTF16.isLeadSurrogate(str.charAt(endIndex - 1))) { |
| endIndex--; |
| } |
| |
| writeIndent(writer, numIndent); |
| write(writer, QUOTE); |
| write(writer, str.substring(startIndex, endIndex)); |
| write(writer, QUOTE + LINESEP); |
| } |
| numIndent--; |
| writeIndent(writer, numIndent); |
| write(writer, CLOSEBRACE + LINESEP); |
| |
| } else { |
| write(writer, name + OPENBRACE + QUOTE + str.toString() + QUOTE + CLOSEBRACE + LINESEP); |
| } |
| |
| } |
| } |
| |
| public int writeBinary(FileOutputStream out, int usedOffset) |
| { |
| |
| // clean up quotes if any |
| if (this.val.indexOf("\"") >= 0) |
| { |
| this.val = LDML2ICUBinaryWriter.removeQuotes(this.val); |
| } |
| |
| String valPlusTerm = val + STRTERM; |
| byte[] valBytes; |
| byte[] valLenBytes; |
| byte[] padding; |
| |
| valLenBytes = intToBytes(val.length()); |
| |
| try |
| { |
| valBytes = valPlusTerm.getBytes(LDML2ICUBinaryWriter.CHARSET16); |
| padding = create32Padding(valBytes.length); |
| out.write(valLenBytes); // 32 bit int |
| LDML2ICUBinaryWriter.written += valLenBytes.length; |
| |
| out.write(valBytes); // The string plus a null terminator |
| LDML2ICUBinaryWriter.written += valBytes.length; |
| |
| if (padding != null) |
| { |
| out.write(padding); |
| LDML2ICUBinaryWriter.written += padding.length; |
| } |
| } catch (UnsupportedEncodingException e) |
| { |
| System.err.print("Problems converting string resource to " + LDML2ICUBinaryWriter.CHARSET16); |
| System.exit(1); |
| } catch (IOException e) |
| { |
| System.err.print("Problems writing the string resource to file."); |
| System.exit(1); |
| } |
| return usedOffset; |
| } |
| |
| /** |
| * This method will set the size of the resource. Overwritten for each child object |
| */ |
| public void setSize() |
| { |
| // a pointer to the key + a string |
| size = SIZE_OF_INT + (SIZE_OF_CHAR * (val.length() + 1)); |
| } |
| } |
| |
| public static class ResourceTable extends Resource { |
| public String annotation; |
| public static final String NO_FALLBACK = "nofallback"; |
| |
| public void write(OutputStream writer, int numIndent, boolean bare) { |
| writeComments(writer, numIndent); |
| writeIndent(writer, numIndent); |
| if (annotation == null) { |
| write(writer, name + OPENBRACE + LINESEP); |
| } else { |
| write(writer, name + COLON + TABLE + OPENPAREN + annotation + CLOSEPAREN + OPENBRACE + LINESEP); |
| } |
| numIndent++; |
| Resource current = first; |
| while (current != null) { |
| current.write(writer, numIndent, false); |
| current = current.next; |
| } |
| numIndent--; |
| writeIndent(writer, numIndent); |
| write(writer, CLOSEBRACE + LINESEP); |
| } |
| |
| // insertion sort of the linked list |
| // from Algorithms in C++ Sedgewick |
| public void sort() { |
| if (noSort == true) { |
| return; |
| } |
| // System.out.println("Entering sort of table: "+name); |
| Resource b = new Resource(); |
| Resource a = first; |
| Resource t, u, x; |
| for (t = a; t != null; t = u) { |
| u = t.next; |
| for (x = b; x.next != null; x = x.next) { |
| // if(x.next == null) { |
| // throw new InternalError("Null NEXT node from " + x.name+","+x.toString()); |
| // } else if(x.next.name == null) { |
| // throw new InternalError("Null NEXT name from " + x.name+","+x.toString()+" -> " + |
| // x.next.toString()); |
| // } |
| if (x.next.name.compareTo(t.name) > 0) { |
| break; |
| } |
| } |
| t.next = x.next; |
| x.next = t; |
| } |
| // System.out.println("Exiting sort of table"); |
| if (b.next != null) { |
| first = b.next; |
| } |
| |
| Resource current = first; |
| // if(current == this) { |
| // throw new InternalError("I'm my own child.. name="+name); |
| // } |
| while (current != null) { |
| current.sort(); |
| // if(current.next == current) { |
| // throw new InternalError("Sibling links to self: " + current.name); |
| // } |
| current = current.next; |
| } |
| |
| } // end sort() |
| |
| public boolean is32Bit() |
| { |
| Resource current = this.first; |
| boolean mustBe32 = false; |
| |
| while (current != null) |
| { |
| if (current.keyStringOffset > 0xFFFF) |
| { |
| mustBe32 = true; |
| } |
| current = current.next; |
| } |
| return mustBe32; |
| } |
| |
| public int writeBinary(FileOutputStream out, int usedOffset) |
| { |
| int count = 0; |
| int pad; |
| Resource current = this.first; |
| int[] resources = new int[numChildren]; |
| short[] keys16 = null; |
| int[] keys32 = null; |
| boolean is32Bit = this.is32Bit(); |
| byte[] padding; |
| |
| if (is32Bit) |
| { |
| keys32 = new int[numChildren]; |
| } |
| else |
| { |
| keys16 = new short[numChildren]; |
| } |
| |
| // if the table has objects in it |
| if (current != null) |
| { |
| |
| // loop through them all |
| while (current != null) |
| { |
| // get the key ptr for current (size depends on table size, store in array |
| if (is32Bit) |
| { |
| keys32[count] = current.keyStringOffset; |
| } |
| else |
| { |
| keys16[count] = (short) current.keyStringOffset; |
| } |
| |
| // if INT |
| if (current instanceof ResourceInt) |
| { |
| // resources[i] = (current->fType << 28) | (current->u.fIntValue.fValue & 0xFFFFFFF); |
| int value = 0; |
| |
| try |
| { |
| value = Integer.parseInt(((ResourceInt) current).val); |
| } catch (NumberFormatException e) |
| { |
| System.err.println("Error converting string to int: " + e.getMessage()); |
| System.exit(1); |
| } |
| resources[count] = LDML2ICUBinaryWriter.URES_INT << 28 | (value & 0xFFFFFFF); |
| |
| } |
| else |
| { |
| // write the current object |
| usedOffset = current.writeBinary(out, usedOffset); |
| |
| // write 32 bits for identification? |
| if (current instanceof ResourceString) |
| { |
| resources[count] = LDML2ICUBinaryWriter.URES_STRING << 28 | usedOffset >>> 2; |
| } |
| else if (current instanceof ResourceTable) |
| { |
| resources[count] = LDML2ICUBinaryWriter.URES_TABLE << 28 | usedOffset >>> 2; |
| } |
| else if (current instanceof ResourceAlias) |
| { |
| resources[count] = LDML2ICUBinaryWriter.URES_ALIAS << 28 | usedOffset >>> 2; |
| } |
| else if (current instanceof ResourceArray) |
| { |
| resources[count] = LDML2ICUBinaryWriter.URES_ARRAY << 28 | usedOffset >>> 2; |
| } |
| else if (current instanceof ResourceIntVector) |
| { |
| resources[count] = LDML2ICUBinaryWriter.URES_INT_VECTOR << 28 | usedOffset >>> 2; |
| } |
| |
| usedOffset += current.size + pad32(current.size); |
| } |
| count++; |
| current = current.next; |
| } |
| |
| // write the member count and the key offsets |
| if (is32Bit) |
| { |
| try |
| { |
| // write a 32 bit block with the number of items in this table |
| out.write(intToBytes(count)); |
| LDML2ICUBinaryWriter.written += intToBytes(count).length; |
| |
| // write all the 32 bit keys which were added to the array. |
| out.write(intArrayToBytes(keys32)); |
| LDML2ICUBinaryWriter.written += intArrayToBytes(keys32).length; |
| |
| out.write(intArrayToBytes(resources)); |
| LDML2ICUBinaryWriter.written += intArrayToBytes(resources).length; |
| } catch (IOException e) |
| { |
| errIO(); |
| } |
| |
| } |
| else |
| { |
| try |
| { |
| // write 2 byte block with the number of items in this table |
| out.write(shortToBytes((short) count)); |
| LDML2ICUBinaryWriter.written += shortToBytes((short) count).length; |
| |
| // write all the 2 byte keys which were added to an array |
| out.write(shortArrayToBytes(keys16)); |
| LDML2ICUBinaryWriter.written += shortArrayToBytes(keys16).length; |
| |
| pad = pad32(this.size); |
| padding = createPadding(pad); |
| if (padding != null) |
| { |
| out.write(padding); |
| LDML2ICUBinaryWriter.written += padding.length; |
| } |
| |
| out.write(intArrayToBytes(resources)); |
| LDML2ICUBinaryWriter.written += intArrayToBytes(resources).length; |
| |
| } catch (IOException e) |
| { |
| errIO(); |
| } |
| |
| } |
| } |
| else // else (the table is empty) |
| { |
| short zero = 0; |
| |
| // We'll write it as a 16 bit table, because it's empty... |
| try |
| { |
| // write a 16 bit zero. |
| out.write(shortToBytes(zero)); |
| LDML2ICUBinaryWriter.written += shortToBytes(zero).length; |
| |
| // pad it |
| padding = createPadding(pad16Bytes(2)); |
| if (padding != null) |
| { |
| out.write(padding); |
| LDML2ICUBinaryWriter.written += padding.length; |
| } |
| |
| } catch (IOException e) |
| { |
| errIO(); |
| } |
| } |
| return usedOffset; |
| } |
| |
| /** |
| * This method will set the size of the resource. Overwritten for each child object |
| */ |
| public void setSize() |
| { |
| // Tables have children. |
| int x = 0; |
| Resource current = this.first; |
| this.sizeOfChildren = 0; |
| while (current != null) |
| { |
| x++; |
| this.sizeOfChildren += current.size + pad32(current.size); |
| |
| if (current instanceof ResourceTable || current instanceof ResourceArray) |
| { |
| this.sizeOfChildren += current.sizeOfChildren; |
| } |
| |
| current = current.next; |
| } |
| |
| if (x > maxTableLength) |
| { |
| maxTableLength = x; |
| } |
| |
| if (this.is32Bit()) |
| { |
| // this resources key offset + a key offset for each child + a pointer to their resource object. |
| size = SIZE_OF_INT + (x * 2 * SIZE_OF_INT); |
| } |
| else |
| { |
| // this resources key offset + a pointer to each childs resource + a 16 bit pointer to each childs key |
| size = SIZE_OF_INT / 2 + (x * (SIZE_OF_INT + (SIZE_OF_INT / 2))); |
| } |
| } |
| } |
| |
| /* Currently there is nothing in LDML which converts to a Binary resource. So this type is currently unused. */ |
| public static class ResourceBinary extends Resource |
| { |
| String internal; |
| String external; |
| byte[] data; |
| |
| public void write(OutputStream writer, int numIndent, boolean bare) |
| { |
| writeComments(writer, numIndent); |
| writeIndent(writer, numIndent); |
| if (internal == null) |
| { |
| String line = ((name == null) ? EMPTY : name) + COLON + IMPORT + OPENBRACE + QUOTE + external + QUOTE |
| + CLOSEBRACE + ((bare == true) ? EMPTY : LINESEP); |
| write(writer, line); |
| } |
| else |
| { |
| String line = ((name == null) ? EMPTY : name) + COLON + BIN + OPENBRACE + internal + CLOSEBRACE |
| + ((bare == true) ? EMPTY : LINESEP); |
| write(writer, line); |
| } |
| } |
| |
| public void setSize() |
| { |
| // sizeof(int32_t) + sizeof(uint8_t) * length + BIN_ALIGNMENT; |
| size = SIZE_OF_INT + data.length + BIN_ALIGNMENT; |
| } |
| |
| public int writeBinary(FileOutputStream out, int usedOffset) |
| { |
| int pad = 0; |
| int extrapad = pad32(this.size); |
| int dataStart = usedOffset + SIZE_OF_INT; |
| |
| try |
| { |
| |
| // write some padding |
| if (dataStart % BIN_ALIGNMENT != 0) |
| { |
| pad = (BIN_ALIGNMENT - (dataStart % BIN_ALIGNMENT)); |
| out.write(createPadding(pad)); |
| usedOffset += pad; |
| } |
| |
| // write the length of the data |
| out.write(intToBytes(data.length)); |
| |
| // if there is data, write it |
| if (data.length > 0) |
| { |
| out.write(data); |
| } |
| |
| // write some more padding |
| out.write(createPadding(BIN_ALIGNMENT - pad + extrapad)); |
| } catch (Exception e) |
| { |
| System.err.println("Had problems writing Binary Resource"); |
| } |
| return usedOffset; |
| } |
| } |
| |
| public static class ResourceProcess extends Resource { |
| String val; |
| String ext; |
| |
| public void write(OutputStream writer, int numIndent, boolean bare) |
| { |
| writeComments(writer, numIndent); |
| writeIndent(writer, numIndent); |
| String line = ((name == null) ? EMPTY : name) + COLON + PROCESS + |
| OPENPAREN + ext + CLOSEPAREN + OPENBRACE + QUOTE + escapeSyntaxChars(val) + QUOTE + CLOSEBRACE; |
| if (bare == true) { |
| if (name != null) { |
| throw new RuntimeException("Bare option is set to true but the resource has a name! " + name); |
| } |
| write(writer, line); |
| } else { |
| write(writer, line + LINESEP); |
| } |
| } |
| |
| public int writeBinary(FileOutputStream out, int usedOffset) |
| { |
| if (this.name.equals("depends")) |
| { |
| |
| } |
| else |
| { |
| |
| // should never get called |
| System.err.println("Unexpected type: " + this.getClass().toString()); |
| System.err.println("Resource Name: " + this.name); |
| return usedOffset; |
| } |
| return usedOffset; |
| } |
| } |
| |
| public static class ResourceImport extends Resource { |
| String val; |
| |
| public void write(OutputStream writer, int numIndent, boolean bare) { |
| writeComments(writer, numIndent); |
| writeIndent(writer, numIndent); |
| String line = ((name == null) ? EMPTY : name) + COLON + IMPORT + OPENBRACE + QUOTE + escapeSyntaxChars(val) |
| + QUOTE + CLOSEBRACE; |
| if (bare == true) { |
| if (name != null) { |
| throw new RuntimeException("Bare option is set to true but the resource has a name! " + name); |
| } |
| write(writer, line); |
| } else { |
| write(writer, line + LINESEP); |
| } |
| } |
| } |
| |
| /* Seems to be unused. Never parsed in */ |
| public static class ResourceInclude extends Resource { |
| String val; |
| |
| public void write(OutputStream writer, int numIndent, boolean bare) { |
| writeComments(writer, numIndent); |
| writeIndent(writer, numIndent); |
| String line = ((name == null) ? EMPTY : name) + COLON + INCLUDE + OPENBRACE + QUOTE |
| + escapeSyntaxChars(val) + QUOTE + CLOSEBRACE; |
| if (bare == true) { |
| if (name != null) { |
| throw new RuntimeException("Bare option is set to true but the resource has a name! " + name); |
| } |
| write(writer, line); |
| } else { |
| write(writer, line + LINESEP); |
| } |
| } |
| } |
| |
| /* END Resources ***************************************************************************** */ |
| |
| /* Helper methods. *************************************************************************** */ |
| /** |
| * Convenience function |
| * |
| * @param name |
| * @param val |
| * @return new ResourceString |
| */ |
| public static Resource createString(String name, String val) { |
| return new ResourceString(name, val); |
| } |
| |
| private static int pad32(int x) |
| { |
| return ((x % SIZE_OF_INT) == 0) ? 0 : (SIZE_OF_INT - (x % SIZE_OF_INT)); |
| } |
| |
| private static byte[] create32Padding(int x) |
| { |
| byte[] b = new byte[pad32(x)]; |
| if (pad32(x) == 0) |
| { |
| return null; |
| } |
| |
| for (int z = 0; z < b.length; z++) |
| { |
| b[z] = 0; |
| } |
| return b; |
| } |
| |
| private static int pad16Bytes(int x) |
| { |
| return ((x % 16) == 0) ? 0 : (16 - (x % 16)); |
| } |
| |
| /** |
| * Takes a 32 bit integer and returns an array of 4 bytes. |
| * |
| */ |
| private static byte[] intToBytes(int x) |
| { |
| byte[] b = new byte[4]; |
| b[3] = (byte) (x); // just the last byte |
| |
| x = x >>> 8; // shift each byte over one spot. |
| b[2] = (byte) (x); // just the last byte |
| |
| x = x >>> 8; // shift each byte over one spot. |
| b[1] = (byte) (x); // just the last byte |
| |
| x = x >>> 8; // shift each byte over one spot. |
| b[0] = (byte) (x); // just the last byte |
| |
| return b; |
| } |
| |
| /** |
| * Takes an array of integers and returns a byte array of the memory representation. |
| * |
| * @param x |
| * @return |
| */ |
| private static byte[] intArrayToBytes(int[] x) |
| { |
| byte[] b = new byte[x.length * 4]; |
| byte[] temp; |
| int i, z; |
| |
| for (i = 0; i < x.length; i++) |
| { |
| temp = intToBytes(x[i]); |
| for (z = 0; z < temp.length; z++) |
| { |
| b[i * temp.length + z] = temp[z]; |
| } |
| } |
| return b; |
| } |
| |
| private static byte[] shortArrayToBytes(short[] x) |
| { |
| byte[] b = new byte[x.length * 2]; |
| byte[] temp; |
| int i, z; |
| |
| for (i = 0; i < x.length; i++) |
| { |
| temp = shortToBytes(x[i]); |
| for (z = 0; z < temp.length; z++) |
| { |
| b[i * temp.length + z] = temp[z]; |
| } |
| } |
| return b; |
| } |
| |
| private static byte[] shortToBytes(short x) |
| { |
| byte[] b = new byte[2]; |
| b[1] = (byte) (x); // bitwise AND with the lower byte |
| b[0] = (byte) (x >>> 8); // shift four bits to the right and fill with zeros, and then bitwise and with the |
| // lower byte |
| return b; |
| } |
| |
| private static void errUnsupportedEncoding() |
| { |
| System.err.print("Unsupported Encoding"); |
| System.exit(1); |
| } |
| |
| private static void errIO() |
| { |
| System.err.print("An error occured while writing to file."); |
| System.exit(1); |
| } |
| |
| private static byte[] createPadding(int length) |
| { |
| byte x = (byte) 0x00; |
| byte[] b = new byte[length]; |
| if (length == 0) |
| { |
| return null; |
| } |
| for (int z = 0; z < b.length; z++) |
| { |
| b[z] = x; |
| } |
| |
| return b; |
| } |
| |
| } |