| /* |
| ******************************************************************************* |
| * Copyright (C) 2004-2011, International Business Machines Corporation and * |
| * others. All Rights Reserved. * |
| ******************************************************************************* |
| */ |
| package com.ibm.icu.impl; |
| |
| import java.io.BufferedInputStream; |
| import java.io.DataInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.ByteBuffer; |
| |
| import com.ibm.icu.util.ULocale; |
| import com.ibm.icu.util.UResourceBundle; |
| import com.ibm.icu.util.VersionInfo; |
| |
| /** |
| * This class reads the *.res resource bundle format |
| * |
| * (For the latest version of the file format documentation see |
| * ICU4C's source/common/uresdata.h file.) |
| * |
| * File format for .res resource bundle files (formatVersion=2, ICU 4.4) |
| * |
| * New in formatVersion 2 compared with 1.3: ------------- |
| * |
| * Three new resource types -- String-v2, Table16 and Array16 -- have their |
| * values stored in a new array of 16-bit units between the table key strings |
| * and the start of the other resources. |
| * |
| * genrb eliminates duplicates among Unicode string-v2 values. |
| * Multiple Unicode strings may use the same offset and string data, |
| * or a short string may point to the suffix of a longer string. ("Suffix sharing") |
| * For example, one string "abc" may be reused for another string "bc" by pointing |
| * to the second character. (Short strings-v2 are NUL-terminated |
| * and not preceded by an explicit length value.) |
| * |
| * It is allowed for all resource types to share values. |
| * The swapper code (ures_swap()) has been modified so that it swaps each item |
| * exactly once. |
| * |
| * A resource bundle may use a special pool bundle. Some or all of the table key strings |
| * of the using-bundle are omitted, and the key string offsets for such key strings refer |
| * to offsets in the pool bundle. |
| * The using-bundle's and the pool-bundle's indexes[URES_INDEX_POOL_CHECKSUM] values |
| * must match. |
| * Two bits in indexes[URES_INDEX_ATTRIBUTES] indicate whether a resource bundle |
| * is or uses a pool bundle. |
| * |
| * Table key strings must be compared in ASCII order, even if they are not |
| * stored in ASCII. |
| * |
| * New in formatVersion 1.3 compared with 1.2: ------------- |
| * |
| * genrb eliminates duplicates among key strings. |
| * Multiple table items may share one key string, or one item may point |
| * to the suffix of another's key string. ("Suffix sharing") |
| * For example, one key "abc" may be reused for another key "bc" by pointing |
| * to the second character. (Key strings are NUL-terminated.) |
| * |
| * ------------- |
| * |
| * An ICU4C resource bundle file (.res) is a binary, memory-mappable file |
| * with nested, hierarchical data structures. |
| * It physically contains the following: |
| * |
| * Resource root; -- 32-bit Resource item, root item for this bundle's tree; |
| * currently, the root item must be a table or table32 resource item |
| * int32_t indexes[indexes[0]]; -- array of indexes for friendly |
| * reading and swapping; see URES_INDEX_* above |
| * new in formatVersion 1.1 (ICU 2.8) |
| * char keys[]; -- characters for key strings |
| * (formatVersion 1.0: up to 65k of characters; 1.1: <2G) |
| * (minus the space for root and indexes[]), |
| * which consist of invariant characters (ASCII/EBCDIC) and are NUL-terminated; |
| * padded to multiple of 4 bytes for 4-alignment of the following data |
| * uint16_t 16BitUnits[]; -- resources that are stored entirely as sequences of 16-bit units |
| * (new in formatVersion 2/ICU 4.4) |
| * data is indexed by the offset values in 16-bit resource types, |
| * with offset 0 pointing to the beginning of this array; |
| * there is a 0 at offset 0, for empty resources; |
| * padded to multiple of 4 bytes for 4-alignment of the following data |
| * data; -- data directly and indirectly indexed by the root item; |
| * the structure is determined by walking the tree |
| * |
| * Each resource bundle item has a 32-bit Resource handle (see typedef above) |
| * which contains the item type number in its upper 4 bits (31..28) and either |
| * an offset or a direct value in its lower 28 bits (27..0). |
| * The order of items is undefined and only determined by walking the tree. |
| * Leaves of the tree may be stored first or last or anywhere in between, |
| * and it is in theory possible to have unreferenced holes in the file. |
| * |
| * 16-bit-unit values: |
| * Starting with formatVersion 2/ICU 4.4, some resources are stored in a special |
| * array of 16-bit units. Each resource value is a sequence of 16-bit units, |
| * with no per-resource padding to a 4-byte boundary. |
| * 16-bit container types (Table16 and Array16) contain Resource16 values |
| * which are offsets to String-v2 resources in the same 16-bit-units array. |
| * |
| * Direct values: |
| * - Empty Unicode strings have an offset value of 0 in the Resource handle itself. |
| * - Starting with formatVersion 2/ICU 4.4, an offset value of 0 for |
| * _any_ resource type indicates an empty value. |
| * - Integer values are 28-bit values stored in the Resource handle itself; |
| * the interpretation of unsigned vs. signed integers is up to the application. |
| * |
| * All other types and values use 28-bit offsets to point to the item's data. |
| * The offset is an index to the first 32-bit word of the value, relative to the |
| * start of the resource data (i.e., the root item handle is at offset 0). |
| * To get byte offsets, the offset is multiplied by 4 (or shifted left by 2 bits). |
| * All resource item values are 4-aligned. |
| * |
| * New in formatVersion 2/ICU 4.4: Some types use offsets into the 16-bit-units array, |
| * indexing 16-bit units in that array. |
| * |
| * The structures (memory layouts) for the values for each item type are listed |
| * in the table below. |
| * |
| * Nested, hierarchical structures: ------------- |
| * |
| * Table items contain key-value pairs where the keys are offsets to char * key strings. |
| * The values of these pairs are either Resource handles or |
| * offsets into the 16-bit-units array, depending on the table type. |
| * |
| * Array items are simple vectors of Resource handles, |
| * or of offsets into the 16-bit-units array, depending on the array type. |
| * |
| * Table key string offsets: ------- |
| * |
| * Key string offsets are relative to the start of the resource data (of the root handle), |
| * i.e., the first string has an offset of 4+sizeof(indexes). |
| * (After the 4-byte root handle and after the indexes array.) |
| * |
| * If the resource bundle uses a pool bundle, then some key strings are stored |
| * in the pool bundle rather than in the local bundle itself. |
| * - In a Table or Table16, the 16-bit key string offset is local if it is |
| * less than indexes[URES_INDEX_KEYS_TOP]<<2. |
| * Otherwise, subtract indexes[URES_INDEX_KEYS_TOP]<<2 to get the offset into |
| * the pool bundle key strings. |
| * - In a Table32, the 32-bit key string offset is local if it is non-negative. |
| * Otherwise, reset bit 31 to get the pool key string offset. |
| * |
| * Unlike the local offset, the pool key offset is relative to |
| * the start of the key strings, not to the start of the bundle. |
| * |
| * An alias item is special (and new in ICU 2.4): -------------- |
| * |
| * Its memory layout is just like for a UnicodeString, but at runtime it resolves to |
| * another resource bundle's item according to the path in the string. |
| * This is used to share items across bundles that are in different lookup/fallback |
| * chains (e.g., large collation data among zh_TW and zh_HK). |
| * This saves space (for large items) and maintenance effort (less duplication of data). |
| * |
| * -------------------------------------------------------------------------- |
| * |
| * Resource types: |
| * |
| * Most resources have their values stored at four-byte offsets from the start |
| * of the resource data. These values are at least 4-aligned. |
| * Some resource values are stored directly in the offset field of the Resource itself. |
| * See UResType in unicode/ures.h for enumeration constants for Resource types. |
| * |
| * Some resources have their values stored as sequences of 16-bit units, |
| * at 2-byte offsets from the start of a contiguous 16-bit-unit array between |
| * the table key strings and the other resources. (new in formatVersion 2/ICU 4.4) |
| * At offset 0 of that array is a 16-bit zero value for empty 16-bit resources. |
| * Resource16 values in Table16 and Array16 are 16-bit offsets to String-v2 |
| * resources, with the offsets relative to the start of the 16-bit-units array. |
| * |
| * Type Name Memory layout of values |
| * (in parentheses: scalar, non-offset values) |
| * |
| * 0 Unicode String: int32_t length, UChar[length], (UChar)0, (padding) |
| * or (empty string ("") if offset==0) |
| * 1 Binary: int32_t length, uint8_t[length], (padding) |
| * - the start of the bytes is 16-aligned - |
| * 2 Table: uint16_t count, uint16_t keyStringOffsets[count], (uint16_t padding), Resource[count] |
| * 3 Alias: (physically same value layout as string, new in ICU 2.4) |
| * 4 Table32: int32_t count, int32_t keyStringOffsets[count], Resource[count] |
| * (new in formatVersion 1.1/ICU 2.8) |
| * 5 Table16: uint16_t count, uint16_t keyStringOffsets[count], Resource16[count] |
| * (stored in the 16-bit-units array; new in formatVersion 2/ICU 4.4) |
| * 6 Unicode String-v2:UChar[length], (UChar)0; length determined by the first UChar: |
| * - if first is not a trail surrogate, then the length is implicit |
| * and u_strlen() needs to be called |
| * - if first<0xdfef then length=first&0x3ff (and skip first) |
| * - if first<0xdfff then length=((first-0xdfef)<<16) | second UChar |
| * - if first==0xdfff then length=((second UChar)<<16) | third UChar |
| * (stored in the 16-bit-units array; new in formatVersion 2/ICU 4.4) |
| * 7 Integer: (28-bit offset is integer value) |
| * 8 Array: int32_t count, Resource[count] |
| * 9 Array16: uint16_t count, Resource16[count] |
| * (stored in the 16-bit-units array; new in formatVersion 2/ICU 4.4) |
| * 14 Integer Vector: int32_t length, int32_t[length] |
| * 15 Reserved: This value denotes special purpose resources and is for internal use. |
| * |
| * Note that there are 3 types with data vector values: |
| * - Vectors of 8-bit bytes stored as type Binary. |
| * - Vectors of 16-bit words stored as type Unicode String or Unicode String-v2 |
| * (no value restrictions, all values 0..ffff allowed!). |
| * - Vectors of 32-bit words stored as type Integer Vector. |
| */ |
| public final class ICUResourceBundleReader implements ICUBinary.Authenticate { |
| /** |
| * File format version that this class understands. |
| * "ResB" |
| */ |
| private static final byte DATA_FORMAT_ID[] = {(byte)0x52, (byte)0x65, |
| (byte)0x73, (byte)0x42}; |
| |
| /* indexes[] value names; indexes are generally 32-bit (Resource) indexes */ |
| private static final int URES_INDEX_LENGTH = 0; /* contains URES_INDEX_TOP==the length of indexes[]; |
| * formatVersion==1: all bits contain the length of indexes[] |
| * but the length is much less than 0xff; |
| * formatVersion>1: |
| * only bits 7..0 contain the length of indexes[], |
| * bits 31..8 are reserved and set to 0 */ |
| private static final int URES_INDEX_KEYS_TOP = 1; /* contains the top of the key strings, */ |
| /* same as the bottom of resources or UTF-16 strings, rounded up */ |
| //ivate static final int URES_INDEX_RESOURCES_TOP = 2; /* contains the top of all resources */ |
| private static final int URES_INDEX_BUNDLE_TOP = 3; /* contains the top of the bundle, */ |
| /* in case it were ever different from [2] */ |
| //ivate static final int URES_INDEX_MAX_TABLE_LENGTH = 4; /* max. length of any table */ |
| private static final int URES_INDEX_ATTRIBUTES = 5; /* attributes bit set, see URES_ATT_* (new in formatVersion 1.2) */ |
| private static final int URES_INDEX_16BIT_TOP = 6; /* top of the 16-bit units (UTF-16 string v2 UChars, URES_TABLE16, URES_ARRAY16), |
| * rounded up (new in formatVersion 2.0, ICU 4.4) */ |
| private static final int URES_INDEX_POOL_CHECKSUM = 7; /* checksum of the pool bundle (new in formatVersion 2.0, ICU 4.4) */ |
| //ivate static final int URES_INDEX_TOP = 8; |
| |
| /* |
| * Nofallback attribute, attribute bit 0 in indexes[URES_INDEX_ATTRIBUTES]. |
| * New in formatVersion 1.2 (ICU 3.6). |
| * |
| * If set, then this resource bundle is a standalone bundle. |
| * If not set, then the bundle participates in locale fallback, eventually |
| * all the way to the root bundle. |
| * If indexes[] is missing or too short, then the attribute cannot be determined |
| * reliably. Dependency checking should ignore such bundles, and loading should |
| * use fallbacks. |
| */ |
| private static final int URES_ATT_NO_FALLBACK = 1; |
| |
| /* |
| * Attributes for bundles that are, or use, a pool bundle. |
| * A pool bundle provides key strings that are shared among several other bundles |
| * to reduce their total size. |
| * New in formatVersion 2 (ICU 4.4). |
| */ |
| private static final int URES_ATT_IS_POOL_BUNDLE = 2; |
| private static final int URES_ATT_USES_POOL_BUNDLE = 4; |
| |
| private static final boolean DEBUG = false; |
| |
| private byte[] /* formatVersion, */ dataVersion; |
| |
| // See the ResourceData struct in ICU4C/source/common/uresdata.h. |
| private String s16BitUnits; |
| private byte[] poolBundleKeys; |
| private String poolBundleKeysAsString; |
| private int rootRes; |
| private int localKeyLimit; |
| private boolean noFallback; /* see URES_ATT_NO_FALLBACK */ |
| private boolean isPoolBundle; |
| private boolean usesPoolBundle; |
| |
| // Fields specific to the Java port. |
| private int[] indexes; |
| private byte[] keyStrings; |
| private String keyStringsAsString; // null except if isPoolBundle |
| private byte[] resourceBytes; |
| private int resourceBottom; // File offset where the mixed-type resources start. |
| |
| private static ReaderCache CACHE = new ReaderCache(); |
| private static final ICUResourceBundleReader NULL_READER = new ICUResourceBundleReader(); |
| |
| private static class ReaderInfo { |
| final String baseName; |
| final String localeID; |
| final ClassLoader loader; |
| |
| ReaderInfo(String baseName, String localeID, ClassLoader loader) { |
| this.baseName = (baseName == null) ? "" : baseName; |
| this.localeID = (localeID == null) ? "" : localeID; |
| this.loader = loader; |
| } |
| |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (!(obj instanceof ReaderInfo)) { |
| return false; |
| } |
| ReaderInfo info = (ReaderInfo)obj; |
| return this.baseName.equals(info.baseName) |
| && this.localeID.equals(info.localeID) |
| && this.loader.equals(info.loader); |
| } |
| |
| public int hashCode() { |
| return baseName.hashCode() ^ localeID.hashCode() ^ loader.hashCode(); |
| } |
| } |
| |
| private static class ReaderCache extends SoftCache<ReaderInfo, ICUResourceBundleReader, ReaderInfo> { |
| /* (non-Javadoc) |
| * @see com.ibm.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object) |
| */ |
| @Override |
| protected ICUResourceBundleReader createInstance(ReaderInfo key, ReaderInfo data) { |
| String fullName = ICUResourceBundleReader.getFullName(data.baseName, data.localeID); |
| InputStream stream = ICUData.getStream(data.loader, fullName); |
| if (stream == null) { |
| return NULL_READER; |
| } |
| return new ICUResourceBundleReader(stream, data.baseName, data.localeID, data.loader); |
| } |
| } |
| |
| /* |
| * Sole constructor, just used for NULL_READER |
| */ |
| private ICUResourceBundleReader() { |
| } |
| |
| private ICUResourceBundleReader(InputStream stream, String baseName, String localeID, ClassLoader loader) { |
| BufferedInputStream bs = new BufferedInputStream(stream); |
| try { |
| if (DEBUG) { |
| System.out.println("The InputStream class is: " + stream.getClass().getName()); |
| System.out.println("The BufferedInputStream class is: " + bs.getClass().getName()); |
| System.out.println("The bytes avialable in stream before reading the header: " + bs.available()); |
| } |
| |
| dataVersion = ICUBinary.readHeader(bs, DATA_FORMAT_ID, this); |
| |
| if (DEBUG) System.out.println("The bytes available in stream after reading the header: " + bs.available()); |
| |
| readData(bs); |
| stream.close(); |
| |
| } catch (IOException ex) { |
| String fullName = ICUResourceBundleReader.getFullName(baseName, localeID); |
| throw new RuntimeException("Data file " + fullName + " is corrupt - " + ex.getMessage()); |
| } |
| |
| // set pool bundle keys if necessary |
| if (usesPoolBundle) { |
| ICUResourceBundleReader poolBundleReader = getReader(baseName, "pool", loader); |
| if (!poolBundleReader.isPoolBundle) { |
| throw new IllegalStateException("pool.res is not a pool bundle"); |
| } |
| if (poolBundleReader.indexes[URES_INDEX_POOL_CHECKSUM] != indexes[URES_INDEX_POOL_CHECKSUM]) { |
| throw new IllegalStateException("pool.res has a different checksum than this bundle"); |
| } |
| poolBundleKeys = poolBundleReader.keyStrings; |
| poolBundleKeysAsString = poolBundleReader.keyStringsAsString; |
| } |
| } |
| |
| static ICUResourceBundleReader getReader(String baseName, String localeID, ClassLoader root) { |
| ReaderInfo info = new ReaderInfo(baseName, localeID, root); |
| ICUResourceBundleReader reader = CACHE.getInstance(info, info); |
| if (reader == NULL_READER) { |
| return null; |
| } |
| return reader; |
| } |
| |
| // See res_init() in ICU4C/source/common/uresdata.c. |
| private void readData(InputStream stream) throws IOException { |
| DataInputStream ds = new DataInputStream(stream); |
| |
| if(DEBUG) System.out.println("The DataInputStream class is: " + ds.getClass().getName()); |
| if(DEBUG) System.out.println("The available bytes in the stream before reading the data: "+ds.available()); |
| |
| rootRes = ds.readInt(); |
| |
| // read the variable-length indexes[] array |
| int indexes0 = ds.readInt(); |
| int indexLength = indexes0 & 0xff; |
| indexes = new int[indexLength]; |
| indexes[URES_INDEX_LENGTH] = indexes0; |
| for(int i=1; i<indexLength; i++){ |
| indexes[i] = ds.readInt(); |
| } |
| resourceBottom = (1 + indexLength) << 2; |
| |
| if(indexLength > URES_INDEX_ATTRIBUTES) { |
| // determine if this resource bundle falls back to a parent bundle |
| // along normal locale ID fallback |
| int att = indexes[URES_INDEX_ATTRIBUTES]; |
| noFallback = (att & URES_ATT_NO_FALLBACK) != 0; |
| isPoolBundle = (att & URES_ATT_IS_POOL_BUNDLE) != 0; |
| usesPoolBundle = (att & URES_ATT_USES_POOL_BUNDLE) != 0; |
| } |
| |
| int length = indexes[URES_INDEX_BUNDLE_TOP]*4; |
| if(DEBUG) System.out.println("The number of bytes in the bundle: "+length); |
| |
| // Read the local key strings. |
| // The keyStrings include NUL characters corresponding to the bytes |
| // up to the end of the indexes. |
| if(indexes[URES_INDEX_KEYS_TOP] > (1 + indexLength)) { |
| int keysBottom = (1 + indexLength) << 2; |
| int keysTop = indexes[URES_INDEX_KEYS_TOP] << 2; |
| resourceBottom = keysTop; |
| if(isPoolBundle) { |
| // Shift the key strings down: |
| // Pool bundle key strings are used with a 0-based index, |
| // unlike regular bundles' key strings for which indexes |
| // are based on the start of the bundle data. |
| keysTop -= keysBottom; |
| keysBottom = 0; |
| } else { |
| localKeyLimit = keysTop; |
| } |
| keyStrings = new byte[keysTop]; |
| ds.readFully(keyStrings, keysBottom, keysTop - keysBottom); |
| if(isPoolBundle) { |
| // Overwrite trailing padding bytes so that the conversion works. |
| while(keysBottom < keysTop && keyStrings[keysTop - 1] == (byte)0xaa) { |
| keyStrings[--keysTop] = 0; |
| } |
| keyStringsAsString = new String(keyStrings, "US-ASCII"); |
| } |
| } |
| |
| // Read the array of 16-bit units. |
| // We are not using |
| // new String(keys, "UTF-16BE") |
| // because the 16-bit units may not be well-formed Unicode. |
| if( indexLength > URES_INDEX_16BIT_TOP && |
| indexes[URES_INDEX_16BIT_TOP] > indexes[URES_INDEX_KEYS_TOP] |
| ) { |
| int num16BitUnits = (indexes[URES_INDEX_16BIT_TOP] - |
| indexes[URES_INDEX_KEYS_TOP]) * 2; |
| char[] c16BitUnits = new char[num16BitUnits]; |
| // Note: Calling readFully() to read data into byte[] and copy |
| // the data to char[] is faster than calling readChar() one by one |
| // for large data |
| byte[] c16BitUnitsBytes = new byte[num16BitUnits * 2]; |
| ds.readFully(c16BitUnitsBytes); |
| for (int i = 0; i < num16BitUnits; i++) { |
| c16BitUnits[i] = (char)((c16BitUnitsBytes[i*2] << 8) | (c16BitUnitsBytes[i*2 + 1] & 0xFF)); |
| } |
| s16BitUnits = new String(c16BitUnits); |
| resourceBottom = indexes[URES_INDEX_16BIT_TOP] << 2; |
| } else { |
| s16BitUnits = "\0"; |
| } |
| |
| // Read the block of bytes for the mixed-type resources. |
| resourceBytes = new byte[length - resourceBottom]; |
| ds.readFully(resourceBytes); |
| } |
| |
| VersionInfo getVersion(){ |
| return VersionInfo.getInstance(dataVersion[0],dataVersion[1],dataVersion[2],dataVersion[3]); |
| } |
| public boolean isDataVersionAcceptable(byte version[]){ |
| // while ICU4C can read formatVersion 1.0 and up, |
| // ICU4J requires 1.1 as a minimum |
| // formatVersion = version; |
| return ((version[0] == 1 && version[1] >= 1) || version[0] == 2); |
| } |
| |
| int getRootResource() { |
| return rootRes; |
| } |
| boolean getNoFallback() { |
| return noFallback; |
| } |
| boolean getUsesPoolBundle() { |
| return usesPoolBundle; |
| } |
| |
| static int RES_GET_TYPE(int res) { |
| return res >>> 28; |
| } |
| private static int RES_GET_OFFSET(int res) { |
| return res & 0x0fffffff; |
| } |
| private int getResourceByteOffset(int offset) { |
| return (offset << 2) - resourceBottom; |
| } |
| /* get signed and unsigned integer values directly from the Resource handle */ |
| static int RES_GET_INT(int res) { |
| return (res << 4) >> 4; |
| } |
| static int RES_GET_UINT(int res) { |
| return res & 0x0fffffff; |
| } |
| static boolean URES_IS_TABLE(int type) { |
| return type==UResourceBundle.TABLE || type==ICUResourceBundle.TABLE16 || type==ICUResourceBundle.TABLE32; |
| } |
| |
| private static byte[] emptyBytes = new byte[0]; |
| private static ByteBuffer emptyByteBuffer = ByteBuffer.allocate(0).asReadOnlyBuffer(); |
| private static char[] emptyChars = new char[0]; |
| private static int[] emptyInts = new int[0]; |
| private static String emptyString = ""; |
| |
| private char getChar(int offset) { |
| return (char)((resourceBytes[offset] << 8) | (resourceBytes[offset + 1] & 0xff)); |
| } |
| private char[] getChars(int offset, int count) { |
| char[] chars = new char[count]; |
| for(int i = 0; i < count; offset += 2, ++i) { |
| chars[i] = (char)(((int)resourceBytes[offset] << 8) | (resourceBytes[offset + 1] & 0xff)); |
| } |
| return chars; |
| } |
| private int getInt(int offset) { |
| return (resourceBytes[offset] << 24) | |
| ((resourceBytes[offset+1] & 0xff) << 16) | |
| ((resourceBytes[offset+2] & 0xff) << 8) | |
| ((resourceBytes[offset+3] & 0xff)); |
| } |
| private int[] getInts(int offset, int count) { |
| int[] ints = new int[count]; |
| for(int i = 0; i < count; offset += 4, ++i) { |
| ints[i] = (resourceBytes[offset] << 24) | |
| ((resourceBytes[offset+1] & 0xff) << 16) | |
| ((resourceBytes[offset+2] & 0xff) << 8) | |
| ((resourceBytes[offset+3] & 0xff)); |
| } |
| return ints; |
| } |
| private char[] getTable16KeyOffsets(int offset) { |
| int length = s16BitUnits.charAt(offset++); |
| if(length > 0) { |
| return s16BitUnits.substring(offset, offset + length).toCharArray(); |
| } else { |
| return emptyChars; |
| } |
| } |
| private char[] getTableKeyOffsets(int offset) { |
| int length = getChar(offset); |
| if(length > 0) { |
| return getChars(offset + 2, length); |
| } else { |
| return emptyChars; |
| } |
| } |
| private int[] getTable32KeyOffsets(int offset) { |
| int length = getInt(offset); |
| if(length > 0) { |
| return getInts(offset + 4, length); |
| } else { |
| return emptyInts; |
| } |
| } |
| |
| /** Refers to ASCII key string bytes, for key string matching. */ |
| private static final class ByteSequence { |
| private byte[] bytes; |
| private int offset; |
| public ByteSequence(byte[] bytes, int offset) { |
| this.bytes = bytes; |
| this.offset = offset; |
| } |
| public byte charAt(int index) { |
| return bytes[offset + index]; |
| } |
| } |
| private String makeKeyStringFromBytes(int keyOffset) { |
| StringBuilder sb = new StringBuilder(); |
| byte b; |
| while((b = keyStrings[keyOffset++]) != 0) { |
| sb.append((char)b); |
| } |
| return sb.toString(); |
| } |
| private String makeKeyStringFromString(int keyOffset) { |
| int endOffset = keyOffset; |
| while(poolBundleKeysAsString.charAt(endOffset) != 0) { |
| ++endOffset; |
| } |
| return poolBundleKeysAsString.substring(keyOffset, endOffset); |
| } |
| private ByteSequence RES_GET_KEY16(char keyOffset) { |
| if(keyOffset < localKeyLimit) { |
| return new ByteSequence(keyStrings, keyOffset); |
| } else { |
| return new ByteSequence(poolBundleKeys, keyOffset - localKeyLimit); |
| } |
| } |
| private String getKey16String(int keyOffset) { |
| if(keyOffset < localKeyLimit) { |
| return makeKeyStringFromBytes(keyOffset); |
| } else { |
| return makeKeyStringFromString(keyOffset - localKeyLimit); |
| } |
| } |
| private ByteSequence RES_GET_KEY32(int keyOffset) { |
| if(keyOffset >= 0) { |
| return new ByteSequence(keyStrings, keyOffset); |
| } else { |
| return new ByteSequence(poolBundleKeys, keyOffset & 0x7fffffff); |
| } |
| } |
| private String getKey32String(int keyOffset) { |
| if(keyOffset >= 0) { |
| return makeKeyStringFromBytes(keyOffset); |
| } else { |
| return makeKeyStringFromString(keyOffset & 0x7fffffff); |
| } |
| } |
| // Compare the length-specified input key with the |
| // NUL-terminated tableKey. |
| private static int compareKeys(CharSequence key, ByteSequence tableKey) { |
| int i; |
| for(i = 0; i < key.length(); ++i) { |
| int c2 = tableKey.charAt(i); |
| if(c2 == 0) { |
| return 1; // key > tableKey because key is longer. |
| } |
| int diff = (int)key.charAt(i) - c2; |
| if(diff != 0) { |
| return diff; |
| } |
| } |
| return -(int)tableKey.charAt(i); |
| } |
| private int compareKeys(CharSequence key, char keyOffset) { |
| return compareKeys(key, RES_GET_KEY16(keyOffset)); |
| } |
| private int compareKeys32(CharSequence key, int keyOffset) { |
| return compareKeys(key, RES_GET_KEY32(keyOffset)); |
| } |
| |
| String getString(int res) { |
| int offset=RES_GET_OFFSET(res); |
| int length; |
| if(RES_GET_TYPE(res)==ICUResourceBundle.STRING_V2) { |
| int first = s16BitUnits.charAt(offset); |
| if((first&0xfffffc00)!=0xdc00) { // C: if(!U16_IS_TRAIL(first)) { |
| if(first==0) { |
| return emptyString; |
| } |
| int endOffset; |
| for(endOffset=offset+1; s16BitUnits.charAt(endOffset)!=0; ++endOffset) {} |
| return s16BitUnits.substring(offset, endOffset); |
| } else if(first<0xdfef) { |
| length=first&0x3ff; |
| ++offset; |
| } else if(first<0xdfff) { |
| length=((first-0xdfef)<<16)|s16BitUnits.charAt(offset+1); |
| offset+=2; |
| } else { |
| length=((int)s16BitUnits.charAt(offset+1)<<16)|s16BitUnits.charAt(offset+2); |
| offset+=3; |
| } |
| return s16BitUnits.substring(offset, offset+length); |
| } else if(res==offset) /* RES_GET_TYPE(res)==URES_STRING */ { |
| if(res==0) { |
| return emptyString; |
| } else { |
| offset=getResourceByteOffset(offset); |
| length=getInt(offset); |
| return new String(getChars(offset+4, length)); |
| } |
| } else { |
| return null; |
| } |
| } |
| |
| String getAlias(int res) { |
| int offset=RES_GET_OFFSET(res); |
| int length; |
| if(RES_GET_TYPE(res)==ICUResourceBundle.ALIAS) { |
| if(offset==0) { |
| return emptyString; |
| } else { |
| offset=getResourceByteOffset(offset); |
| length=getInt(offset); |
| return new String(getChars(offset+4, length)); |
| } |
| } else { |
| return null; |
| } |
| } |
| |
| byte[] getBinary(int res, byte[] ba) { |
| int offset=RES_GET_OFFSET(res); |
| int length; |
| if(RES_GET_TYPE(res)==UResourceBundle.BINARY) { |
| if(offset==0) { |
| return emptyBytes; |
| } else { |
| offset=getResourceByteOffset(offset); |
| length=getInt(offset); |
| if(ba==null || ba.length!=length) { |
| ba=new byte[length]; |
| } |
| System.arraycopy(resourceBytes, offset+4, ba, 0, length); |
| return ba; |
| } |
| } else { |
| return null; |
| } |
| } |
| |
| ByteBuffer getBinary(int res) { |
| int offset=RES_GET_OFFSET(res); |
| int length; |
| if(RES_GET_TYPE(res)==UResourceBundle.BINARY) { |
| if(offset==0) { |
| // Don't just |
| // return emptyByteBuffer; |
| // in case it matters whether the buffer's mark is defined or undefined. |
| return emptyByteBuffer.duplicate(); |
| } else { |
| offset=getResourceByteOffset(offset); |
| length=getInt(offset); |
| return ByteBuffer.wrap(resourceBytes, offset+4, length).slice().asReadOnlyBuffer(); |
| } |
| } else { |
| return null; |
| } |
| } |
| |
| int[] getIntVector(int res) { |
| int offset=RES_GET_OFFSET(res); |
| int length; |
| if(RES_GET_TYPE(res)==UResourceBundle.INT_VECTOR) { |
| if(offset==0) { |
| return emptyInts; |
| } else { |
| offset=getResourceByteOffset(offset); |
| length=getInt(offset); |
| return getInts(offset+4, length); |
| } |
| } else { |
| return null; |
| } |
| } |
| |
| Container getArray(int res) { |
| int type=RES_GET_TYPE(res); |
| int offset=RES_GET_OFFSET(res); |
| switch(type) { |
| case UResourceBundle.ARRAY: |
| case ICUResourceBundle.ARRAY16: |
| if(offset==0) { |
| return new Container(this); |
| } |
| break; |
| default: |
| return null; |
| } |
| switch(type) { |
| case UResourceBundle.ARRAY: |
| return new Array(this, offset); |
| case ICUResourceBundle.ARRAY16: |
| return new Array16(this, offset); |
| default: |
| return null; |
| } |
| } |
| |
| Table getTable(int res) { |
| int type=RES_GET_TYPE(res); |
| int offset=RES_GET_OFFSET(res); |
| switch(type) { |
| case UResourceBundle.TABLE: |
| case ICUResourceBundle.TABLE16: |
| case ICUResourceBundle.TABLE32: |
| if(offset==0) { |
| return new Table(this); |
| } |
| break; |
| default: |
| return null; |
| } |
| switch(type) { |
| case UResourceBundle.TABLE: |
| return new Table1632(this, offset); |
| case ICUResourceBundle.TABLE16: |
| return new Table16(this, offset); |
| case ICUResourceBundle.TABLE32: |
| return new Table32(this, offset); |
| default: |
| return null; |
| } |
| } |
| |
| // Container value classes --------------------------------------------- *** |
| |
| static class Container { |
| protected ICUResourceBundleReader reader; |
| protected int size; |
| protected int itemsOffset; |
| |
| int getSize() { |
| return size; |
| } |
| int getContainerResource(int index) { |
| return ICUResourceBundle.RES_BOGUS; |
| } |
| protected int getContainer16Resource(int index) { |
| if (index < 0 || size <= index) { |
| return ICUResourceBundle.RES_BOGUS; |
| } |
| return (ICUResourceBundle.STRING_V2 << 28) | |
| reader.s16BitUnits.charAt(itemsOffset + index); |
| } |
| protected int getContainer32Resource(int index) { |
| if (index < 0 || size <= index) { |
| return ICUResourceBundle.RES_BOGUS; |
| } |
| return reader.getInt(itemsOffset + 4 * index); |
| } |
| Container(ICUResourceBundleReader reader) { |
| this.reader = reader; |
| } |
| } |
| private static final class Array extends Container { |
| int getContainerResource(int index) { |
| return getContainer32Resource(index); |
| } |
| Array(ICUResourceBundleReader reader, int offset) { |
| super(reader); |
| offset = reader.getResourceByteOffset(offset); |
| size = reader.getInt(offset); |
| itemsOffset = offset + 4; |
| } |
| } |
| private static final class Array16 extends Container { |
| int getContainerResource(int index) { |
| return getContainer16Resource(index); |
| } |
| Array16(ICUResourceBundleReader reader, int offset) { |
| super(reader); |
| size = reader.s16BitUnits.charAt(offset); |
| itemsOffset = offset + 1; |
| } |
| } |
| static class Table extends Container { |
| protected char[] keyOffsets; |
| protected int[] key32Offsets; |
| |
| String getKey(int index) { |
| if (index < 0 || size <= index) { |
| return null; |
| } |
| return keyOffsets != null ? |
| reader.getKey16String(keyOffsets[index]) : |
| reader.getKey32String(key32Offsets[index]); |
| } |
| private static final int URESDATA_ITEM_NOT_FOUND = -1; |
| int findTableItem(CharSequence key) { |
| int mid, start, limit; |
| int result; |
| |
| /* do a binary search for the key */ |
| start=0; |
| limit=size; |
| while(start<limit) { |
| mid = (start + limit) >>> 1; |
| if (keyOffsets != null) { |
| result = reader.compareKeys(key, keyOffsets[mid]); |
| } else { |
| result = reader.compareKeys32(key, key32Offsets[mid]); |
| } |
| if (result < 0) { |
| limit = mid; |
| } else if (result > 0) { |
| start = mid + 1; |
| } else { |
| /* We found it! */ |
| return mid; |
| } |
| } |
| return URESDATA_ITEM_NOT_FOUND; /* not found or table is empty. */ |
| } |
| int getTableResource(String resKey) { |
| return getContainerResource(findTableItem(resKey)); |
| } |
| Table(ICUResourceBundleReader reader) { |
| super(reader); |
| } |
| } |
| private static final class Table1632 extends Table { |
| int getContainerResource(int index) { |
| return getContainer32Resource(index); |
| } |
| Table1632(ICUResourceBundleReader reader, int offset) { |
| super(reader); |
| offset = reader.getResourceByteOffset(offset); |
| keyOffsets = reader.getTableKeyOffsets(offset); |
| size = keyOffsets.length; |
| itemsOffset = offset + 2 * ((size + 2) & ~1); // Skip padding for 4-alignment. |
| } |
| } |
| private static final class Table16 extends Table { |
| int getContainerResource(int index) { |
| return getContainer16Resource(index); |
| } |
| Table16(ICUResourceBundleReader reader, int offset) { |
| super(reader); |
| keyOffsets = reader.getTable16KeyOffsets(offset); |
| size = keyOffsets.length; |
| itemsOffset = offset + 1 + size; |
| } |
| } |
| private static final class Table32 extends Table { |
| int getContainerResource(int index) { |
| return getContainer32Resource(index); |
| } |
| Table32(ICUResourceBundleReader reader, int offset) { |
| super(reader); |
| offset = reader.getResourceByteOffset(offset); |
| key32Offsets = reader.getTable32KeyOffsets(offset); |
| size = key32Offsets.length; |
| itemsOffset = offset + 4 * (1 + size); |
| } |
| } |
| |
| private static final String ICU_RESOURCE_SUFFIX = ".res"; |
| |
| /** |
| * Gets the full name of the resource with suffix. |
| */ |
| public static String getFullName(String baseName, String localeName) { |
| if (baseName == null || baseName.length() == 0) { |
| if (localeName.length() == 0) { |
| return localeName = ULocale.getDefault().toString(); |
| } |
| return localeName + ICU_RESOURCE_SUFFIX; |
| } else { |
| if (baseName.indexOf('.') == -1) { |
| if (baseName.charAt(baseName.length() - 1) != '/') { |
| return baseName + "/" + localeName + ICU_RESOURCE_SUFFIX; |
| } else { |
| return baseName + localeName + ICU_RESOURCE_SUFFIX; |
| } |
| } else { |
| baseName = baseName.replace('.', '/'); |
| if (localeName.length() == 0) { |
| return baseName + ICU_RESOURCE_SUFFIX; |
| } else { |
| return baseName + "_" + localeName + ICU_RESOURCE_SUFFIX; |
| } |
| } |
| } |
| } |
| } |