blob: 2341f55dda6d6b0b4389245939ce96aa0f1358b9 [file] [log] [blame]
/*
*******************************************************************************
* 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;
}
}
}
}
}