Home | History | Annotate | Download | only in impl
      1 //  2016 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html#License
      3 /*
      4  *******************************************************************************
      5  * Copyright (C) 2004-2016, International Business Machines Corporation and
      6  * others. All Rights Reserved.
      7  *******************************************************************************
      8  */
      9 package com.ibm.icu.impl;
     10 
     11 import java.io.IOException;
     12 import java.io.InputStream;
     13 import java.lang.ref.SoftReference;
     14 import java.nio.ByteBuffer;
     15 import java.nio.CharBuffer;
     16 import java.nio.IntBuffer;
     17 
     18 import com.ibm.icu.util.ICUException;
     19 import com.ibm.icu.util.ICUUncheckedIOException;
     20 import com.ibm.icu.util.ULocale;
     21 import com.ibm.icu.util.UResourceBundle;
     22 import com.ibm.icu.util.UResourceTypeMismatchException;
     23 import com.ibm.icu.util.VersionInfo;
     24 
     25 /**
     26  * This class reads the *.res resource bundle format.
     27  *
     28  * For the file format documentation see ICU4C's source/common/uresdata.h file.
     29  */
     30 public final class ICUResourceBundleReader {
     31     /**
     32      * File format version that this class understands.
     33      * "ResB"
     34      */
     35     private static final int DATA_FORMAT = 0x52657342;
     36     private static final class IsAcceptable implements ICUBinary.Authenticate {
     37         @Override
     38         public boolean isDataVersionAcceptable(byte formatVersion[]) {
     39             return
     40                     (formatVersion[0] == 1 && (formatVersion[1] & 0xff) >= 1) ||
     41                     (2 <= formatVersion[0] && formatVersion[0] <= 3);
     42         }
     43     }
     44     private static final IsAcceptable IS_ACCEPTABLE = new IsAcceptable();
     45 
     46     /* indexes[] value names; indexes are generally 32-bit (Resource) indexes */
     47     /**
     48      * [0] contains the length of indexes[]
     49      * which is at most URES_INDEX_TOP of the latest format version
     50      *
     51      * formatVersion==1: all bits contain the length of indexes[]
     52      *   but the length is much less than 0xff;
     53      * formatVersion>1:
     54      *   only bits  7..0 contain the length of indexes[],
     55      *        bits 31..8 are reserved and set to 0
     56      * formatVersion>=3:
     57      *        bits 31..8 poolStringIndexLimit bits 23..0
     58      */
     59     private static final int URES_INDEX_LENGTH           = 0;
     60     /**
     61      * [1] contains the top of the key strings,
     62      *     same as the bottom of resources or UTF-16 strings, rounded up
     63      */
     64     private static final int URES_INDEX_KEYS_TOP         = 1;
     65     /** [2] contains the top of all resources */
     66     //ivate static final int URES_INDEX_RESOURCES_TOP    = 2;
     67     /**
     68      * [3] contains the top of the bundle,
     69      *     in case it were ever different from [2]
     70      */
     71     private static final int URES_INDEX_BUNDLE_TOP       = 3;
     72     /** [4] max. length of any table */
     73     private static final int URES_INDEX_MAX_TABLE_LENGTH = 4;
     74     /**
     75      * [5] attributes bit set, see URES_ATT_* (new in formatVersion 1.2)
     76      *
     77      * formatVersion>=3:
     78      *   bits 31..16 poolStringIndex16Limit
     79      *   bits 15..12 poolStringIndexLimit bits 27..24
     80      */
     81     private static final int URES_INDEX_ATTRIBUTES       = 5;
     82     /**
     83      * [6] top of the 16-bit units (UTF-16 string v2 UChars, URES_TABLE16, URES_ARRAY16),
     84      *     rounded up (new in formatVersion 2.0, ICU 4.4)
     85      */
     86     private static final int URES_INDEX_16BIT_TOP        = 6;
     87     /** [7] checksum of the pool bundle (new in formatVersion 2.0, ICU 4.4) */
     88     private static final int URES_INDEX_POOL_CHECKSUM    = 7;
     89     //ivate static final int URES_INDEX_TOP              = 8;
     90 
     91     /*
     92      * Nofallback attribute, attribute bit 0 in indexes[URES_INDEX_ATTRIBUTES].
     93      * New in formatVersion 1.2 (ICU 3.6).
     94      *
     95      * If set, then this resource bundle is a standalone bundle.
     96      * If not set, then the bundle participates in locale fallback, eventually
     97      * all the way to the root bundle.
     98      * If indexes[] is missing or too short, then the attribute cannot be determined
     99      * reliably. Dependency checking should ignore such bundles, and loading should
    100      * use fallbacks.
    101      */
    102     private static final int URES_ATT_NO_FALLBACK = 1;
    103 
    104     /*
    105      * Attributes for bundles that are, or use, a pool bundle.
    106      * A pool bundle provides key strings that are shared among several other bundles
    107      * to reduce their total size.
    108      * New in formatVersion 2 (ICU 4.4).
    109      */
    110     private static final int URES_ATT_IS_POOL_BUNDLE = 2;
    111     private static final int URES_ATT_USES_POOL_BUNDLE = 4;
    112 
    113     private static final CharBuffer EMPTY_16_BIT_UNITS = CharBuffer.wrap("\0");  // read-only
    114 
    115     /**
    116      * Objects with more value bytes are stored in SoftReferences.
    117      * Smaller objects (which are not much larger than a SoftReference)
    118      * are stored directly, avoiding the overhead of the reference.
    119      */
    120     static final int LARGE_SIZE = 24;
    121 
    122     private static final boolean DEBUG = false;
    123 
    124     private int /* formatVersion, */ dataVersion;
    125 
    126     // See the ResourceData struct in ICU4C/source/common/uresdata.h.
    127     /**
    128      * Buffer of all of the resource bundle bytes after the header.
    129      * (equivalent of C++ pRoot)
    130      */
    131     private ByteBuffer bytes;
    132     private byte[] keyBytes;
    133     private CharBuffer b16BitUnits;
    134     private ICUResourceBundleReader poolBundleReader;
    135     private int rootRes;
    136     private int localKeyLimit;
    137     private int poolStringIndexLimit;
    138     private int poolStringIndex16Limit;
    139     private boolean noFallback; /* see URES_ATT_NO_FALLBACK */
    140     private boolean isPoolBundle;
    141     private boolean usesPoolBundle;
    142     private int poolCheckSum;
    143 
    144     private ResourceCache resourceCache;
    145 
    146     private static ReaderCache CACHE = new ReaderCache();
    147     private static final ICUResourceBundleReader NULL_READER = new ICUResourceBundleReader();
    148 
    149     private static class ReaderCacheKey {
    150         final String baseName;
    151         final String localeID;
    152 
    153         ReaderCacheKey(String baseName, String localeID) {
    154             this.baseName = (baseName == null) ? "" : baseName;
    155             this.localeID = (localeID == null) ? "" : localeID;
    156         }
    157 
    158         @Override
    159         public boolean equals(Object obj) {
    160             if (this == obj) {
    161                 return true;
    162             }
    163             if (!(obj instanceof ReaderCacheKey)) {
    164                 return false;
    165             }
    166             ReaderCacheKey info = (ReaderCacheKey)obj;
    167             return this.baseName.equals(info.baseName)
    168                     && this.localeID.equals(info.localeID);
    169         }
    170 
    171         @Override
    172         public int hashCode() {
    173             return baseName.hashCode() ^ localeID.hashCode();
    174         }
    175     }
    176 
    177     private static class ReaderCache extends SoftCache<ReaderCacheKey, ICUResourceBundleReader, ClassLoader> {
    178         /* (non-Javadoc)
    179          * @see com.ibm.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object)
    180          */
    181         @Override
    182         protected ICUResourceBundleReader createInstance(ReaderCacheKey key, ClassLoader loader) {
    183             String fullName = ICUResourceBundleReader.getFullName(key.baseName, key.localeID);
    184             try {
    185                 ByteBuffer inBytes;
    186                 if (key.baseName != null && key.baseName.startsWith(ICUData.ICU_BASE_NAME)) {
    187                     String itemPath = fullName.substring(ICUData.ICU_BASE_NAME.length() + 1);
    188                     inBytes = ICUBinary.getData(loader, fullName, itemPath);
    189                     if (inBytes == null) {
    190                         return NULL_READER;
    191                     }
    192                 } else {
    193                     @SuppressWarnings("resource")  // Closed by getByteBufferFromInputStreamAndCloseStream().
    194                     InputStream stream = ICUData.getStream(loader, fullName);
    195                     if (stream == null) {
    196                         return NULL_READER;
    197                     }
    198                     inBytes = ICUBinary.getByteBufferFromInputStreamAndCloseStream(stream);
    199                 }
    200                 return new ICUResourceBundleReader(inBytes, key.baseName, key.localeID, loader);
    201             } catch (IOException ex) {
    202                 throw new ICUUncheckedIOException("Data file " + fullName + " is corrupt - " + ex.getMessage(), ex);
    203             }
    204         }
    205     }
    206 
    207     /*
    208      * Default constructor, just used for NULL_READER.
    209      */
    210     private ICUResourceBundleReader() {
    211     }
    212 
    213     private ICUResourceBundleReader(ByteBuffer inBytes,
    214             String baseName, String localeID,
    215             ClassLoader loader) throws IOException {
    216         init(inBytes);
    217 
    218         // set pool bundle if necessary
    219         if (usesPoolBundle) {
    220             poolBundleReader = getReader(baseName, "pool", loader);
    221             if (poolBundleReader == null || !poolBundleReader.isPoolBundle) {
    222                 throw new IllegalStateException("pool.res is not a pool bundle");
    223             }
    224             if (poolBundleReader.poolCheckSum != poolCheckSum) {
    225                 throw new IllegalStateException("pool.res has a different checksum than this bundle");
    226             }
    227         }
    228     }
    229 
    230     static ICUResourceBundleReader getReader(String baseName, String localeID, ClassLoader root) {
    231         ReaderCacheKey info = new ReaderCacheKey(baseName, localeID);
    232         ICUResourceBundleReader reader = CACHE.getInstance(info, root);
    233         if (reader == NULL_READER) {
    234             return null;
    235         }
    236         return reader;
    237     }
    238 
    239     // See res_init() in ICU4C/source/common/uresdata.c.
    240     private void init(ByteBuffer inBytes) throws IOException {
    241         dataVersion = ICUBinary.readHeader(inBytes, DATA_FORMAT, IS_ACCEPTABLE);
    242         int majorFormatVersion = inBytes.get(16);
    243         bytes = ICUBinary.sliceWithOrder(inBytes);
    244         int dataLength = bytes.remaining();
    245 
    246         if(DEBUG) System.out.println("The ByteBuffer is direct (memory-mapped): " + bytes.isDirect());
    247         if(DEBUG) System.out.println("The available bytes in the buffer before reading the data: " + dataLength);
    248 
    249         rootRes = bytes.getInt(0);
    250 
    251         // Bundles with formatVersion 1.1 and later contain an indexes[] array.
    252         // We need it so that we can read the key string bytes up front, for lookup performance.
    253 
    254         // read the variable-length indexes[] array
    255         int indexes0 = getIndexesInt(URES_INDEX_LENGTH);
    256         int indexLength = indexes0 & 0xff;
    257         if(indexLength <= URES_INDEX_MAX_TABLE_LENGTH) {
    258             throw new ICUException("not enough indexes");
    259         }
    260         int bundleTop;
    261         if(dataLength < ((1 + indexLength) << 2) ||
    262                 dataLength < ((bundleTop = getIndexesInt(URES_INDEX_BUNDLE_TOP)) << 2)) {
    263             throw new ICUException("not enough bytes");
    264         }
    265         int maxOffset = bundleTop - 1;
    266 
    267         if (majorFormatVersion >= 3) {
    268             // In formatVersion 1, the indexLength took up this whole int.
    269             // In version 2, bits 31..8 were reserved and always 0.
    270             // In version 3, they contain bits 23..0 of the poolStringIndexLimit.
    271             // Bits 27..24 are in indexes[URES_INDEX_ATTRIBUTES] bits 15..12.
    272             poolStringIndexLimit = indexes0 >>> 8;
    273         }
    274         if(indexLength > URES_INDEX_ATTRIBUTES) {
    275             // determine if this resource bundle falls back to a parent bundle
    276             // along normal locale ID fallback
    277             int att = getIndexesInt(URES_INDEX_ATTRIBUTES);
    278             noFallback = (att & URES_ATT_NO_FALLBACK) != 0;
    279             isPoolBundle = (att & URES_ATT_IS_POOL_BUNDLE) != 0;
    280             usesPoolBundle = (att & URES_ATT_USES_POOL_BUNDLE) != 0;
    281             poolStringIndexLimit |= (att & 0xf000) << 12;  // bits 15..12 -> 27..24
    282             poolStringIndex16Limit = att >>> 16;
    283         }
    284 
    285         int keysBottom = 1 + indexLength;
    286         int keysTop = getIndexesInt(URES_INDEX_KEYS_TOP);
    287         if(keysTop > keysBottom) {
    288             // Deserialize the key strings up front.
    289             // Faster table item search at the cost of slower startup and some heap memory.
    290             if(isPoolBundle) {
    291                 // Shift the key strings down:
    292                 // Pool bundle key strings are used with a 0-based index,
    293                 // unlike regular bundles' key strings for which indexes
    294                 // are based on the start of the bundle data.
    295                 keyBytes = new byte[(keysTop - keysBottom) << 2];
    296                 bytes.position(keysBottom << 2);
    297             } else {
    298                 localKeyLimit = keysTop << 2;
    299                 keyBytes = new byte[localKeyLimit];
    300             }
    301             bytes.get(keyBytes);
    302         }
    303 
    304         // Read the array of 16-bit units.
    305         if(indexLength > URES_INDEX_16BIT_TOP) {
    306             int _16BitTop = getIndexesInt(URES_INDEX_16BIT_TOP);
    307             if(_16BitTop > keysTop) {
    308                 int num16BitUnits = (_16BitTop - keysTop) * 2;
    309                 bytes.position(keysTop << 2);
    310                 b16BitUnits = bytes.asCharBuffer();
    311                 b16BitUnits.limit(num16BitUnits);
    312                 maxOffset |= num16BitUnits - 1;
    313             } else {
    314                 b16BitUnits = EMPTY_16_BIT_UNITS;
    315             }
    316         } else {
    317             b16BitUnits = EMPTY_16_BIT_UNITS;
    318         }
    319 
    320         if(indexLength > URES_INDEX_POOL_CHECKSUM) {
    321             poolCheckSum = getIndexesInt(URES_INDEX_POOL_CHECKSUM);
    322         }
    323 
    324         if(!isPoolBundle || b16BitUnits.length() > 1) {
    325             resourceCache = new ResourceCache(maxOffset);
    326         }
    327 
    328         // Reset the position for future .asCharBuffer() etc.
    329         bytes.position(0);
    330     }
    331 
    332     private int getIndexesInt(int i) {
    333         return bytes.getInt((1 + i) << 2);
    334     }
    335 
    336     VersionInfo getVersion() {
    337         return ICUBinary.getVersionInfoFromCompactInt(dataVersion);
    338     }
    339 
    340     int getRootResource() {
    341         return rootRes;
    342     }
    343     boolean getNoFallback() {
    344         return noFallback;
    345     }
    346     boolean getUsesPoolBundle() {
    347         return usesPoolBundle;
    348     }
    349 
    350     static int RES_GET_TYPE(int res) {
    351         return res >>> 28;
    352     }
    353     private static int RES_GET_OFFSET(int res) {
    354         return res & 0x0fffffff;
    355     }
    356     private int getResourceByteOffset(int offset) {
    357         return offset << 2;
    358     }
    359     /* get signed and unsigned integer values directly from the Resource handle */
    360     static int RES_GET_INT(int res) {
    361         return (res << 4) >> 4;
    362     }
    363     static int RES_GET_UINT(int res) {
    364         return res & 0x0fffffff;
    365     }
    366     static boolean URES_IS_ARRAY(int type) {
    367         return type == UResourceBundle.ARRAY || type == ICUResourceBundle.ARRAY16;
    368     }
    369     static boolean URES_IS_TABLE(int type) {
    370         return type==UResourceBundle.TABLE || type==ICUResourceBundle.TABLE16 || type==ICUResourceBundle.TABLE32;
    371     }
    372 
    373     private static final byte[] emptyBytes = new byte[0];
    374     private static final ByteBuffer emptyByteBuffer = ByteBuffer.allocate(0).asReadOnlyBuffer();
    375     private static final char[] emptyChars = new char[0];
    376     private static final int[] emptyInts = new int[0];
    377     private static final String emptyString = "";
    378     private static final Array EMPTY_ARRAY = new Array();
    379     private static final Table EMPTY_TABLE = new Table();
    380 
    381     private char[] getChars(int offset, int count) {
    382         char[] chars = new char[count];
    383         if (count <= 16) {
    384             for(int i = 0; i < count; offset += 2, ++i) {
    385                 chars[i] = bytes.getChar(offset);
    386             }
    387         } else {
    388             CharBuffer temp = bytes.asCharBuffer();
    389             temp.position(offset / 2);
    390             temp.get(chars);
    391         }
    392         return chars;
    393     }
    394     private int getInt(int offset) {
    395         return bytes.getInt(offset);
    396     }
    397     private int[] getInts(int offset, int count) {
    398         int[] ints = new int[count];
    399         if (count <= 16) {
    400             for(int i = 0; i < count; offset += 4, ++i) {
    401                 ints[i] = bytes.getInt(offset);
    402             }
    403         } else {
    404             IntBuffer temp = bytes.asIntBuffer();
    405             temp.position(offset / 4);
    406             temp.get(ints);
    407         }
    408         return ints;
    409     }
    410     private char[] getTable16KeyOffsets(int offset) {
    411         int length = b16BitUnits.charAt(offset++);
    412         if(length > 0) {
    413             char[] result = new char[length];
    414             if(length <= 16) {
    415                 for(int i = 0; i < length; ++i) {
    416                     result[i] = b16BitUnits.charAt(offset++);
    417                 }
    418             } else {
    419                 CharBuffer temp = b16BitUnits.duplicate();
    420                 temp.position(offset);
    421                 temp.get(result);
    422             }
    423             return result;
    424         } else {
    425             return emptyChars;
    426         }
    427     }
    428     private char[] getTableKeyOffsets(int offset) {
    429         int length = bytes.getChar(offset);
    430         if(length > 0) {
    431             return getChars(offset + 2, length);
    432         } else {
    433             return emptyChars;
    434         }
    435     }
    436     private int[] getTable32KeyOffsets(int offset) {
    437         int length = getInt(offset);
    438         if(length > 0) {
    439             return getInts(offset + 4, length);
    440         } else {
    441             return emptyInts;
    442         }
    443     }
    444 
    445     private static String makeKeyStringFromBytes(byte[] keyBytes, int keyOffset) {
    446         StringBuilder sb = new StringBuilder();
    447         byte b;
    448         while((b = keyBytes[keyOffset]) != 0) {
    449             ++keyOffset;
    450             sb.append((char)b);
    451         }
    452         return sb.toString();
    453     }
    454     private String getKey16String(int keyOffset) {
    455         if(keyOffset < localKeyLimit) {
    456             return makeKeyStringFromBytes(keyBytes, keyOffset);
    457         } else {
    458             return makeKeyStringFromBytes(poolBundleReader.keyBytes, keyOffset - localKeyLimit);
    459         }
    460     }
    461     private String getKey32String(int keyOffset) {
    462         if(keyOffset >= 0) {
    463             return makeKeyStringFromBytes(keyBytes, keyOffset);
    464         } else {
    465             return makeKeyStringFromBytes(poolBundleReader.keyBytes, keyOffset & 0x7fffffff);
    466         }
    467     }
    468     private void setKeyFromKey16(int keyOffset, UResource.Key key) {
    469         if(keyOffset < localKeyLimit) {
    470             key.setBytes(keyBytes, keyOffset);
    471         } else {
    472             key.setBytes(poolBundleReader.keyBytes, keyOffset - localKeyLimit);
    473         }
    474     }
    475     private void setKeyFromKey32(int keyOffset, UResource.Key key) {
    476         if(keyOffset >= 0) {
    477             key.setBytes(keyBytes, keyOffset);
    478         } else {
    479             key.setBytes(poolBundleReader.keyBytes, keyOffset & 0x7fffffff);
    480         }
    481     }
    482     private int compareKeys(CharSequence key, char keyOffset) {
    483         if(keyOffset < localKeyLimit) {
    484             return ICUBinary.compareKeys(key, keyBytes, keyOffset);
    485         } else {
    486             return ICUBinary.compareKeys(key, poolBundleReader.keyBytes, keyOffset - localKeyLimit);
    487         }
    488     }
    489     private int compareKeys32(CharSequence key, int keyOffset) {
    490         if(keyOffset >= 0) {
    491             return ICUBinary.compareKeys(key, keyBytes, keyOffset);
    492         } else {
    493             return ICUBinary.compareKeys(key, poolBundleReader.keyBytes, keyOffset & 0x7fffffff);
    494         }
    495     }
    496 
    497     /**
    498      * @return a string from the local bundle's b16BitUnits at the local offset
    499      */
    500     String getStringV2(int res) {
    501         // Use the pool bundle's resource cache for pool bundle strings;
    502         // use the local bundle's cache for local strings.
    503         // The cache requires a resource word with the proper type,
    504         // and with an offset that is local to this bundle so that the offset fits
    505         // within the maximum number of bits for which the cache was constructed.
    506         assert RES_GET_TYPE(res) == ICUResourceBundle.STRING_V2;
    507         int offset = RES_GET_OFFSET(res);
    508         assert offset != 0;  // handled by the caller
    509         Object value = resourceCache.get(res);
    510         if(value != null) {
    511             return (String)value;
    512         }
    513         String s;
    514         int first = b16BitUnits.charAt(offset);
    515         if((first&0xfffffc00)!=0xdc00) {  // C: if(!U16_IS_TRAIL(first)) {
    516             if(first==0) {
    517                 return emptyString;  // Should not occur, but is not forbidden.
    518             }
    519             StringBuilder sb = new StringBuilder();
    520             sb.append((char)first);
    521             char c;
    522             while((c = b16BitUnits.charAt(++offset)) != 0) {
    523                 sb.append(c);
    524             }
    525             s = sb.toString();
    526         } else {
    527             int length;
    528             if(first<0xdfef) {
    529                 length=first&0x3ff;
    530                 ++offset;
    531             } else if(first<0xdfff) {
    532                 length=((first-0xdfef)<<16)|b16BitUnits.charAt(offset+1);
    533                 offset+=2;
    534             } else {
    535                 length=(b16BitUnits.charAt(offset+1)<<16)|b16BitUnits.charAt(offset+2);
    536                 offset+=3;
    537             }
    538             // Cast up to CharSequence to insulate against the CharBuffer.subSequence() return type change
    539             // which makes code compiled for a newer JDK (7 and up) not run on an older one (6 and below).
    540             s = ((CharSequence) b16BitUnits).subSequence(offset, offset + length).toString();
    541         }
    542         return (String)resourceCache.putIfAbsent(res, s, s.length() * 2);
    543     }
    544 
    545     private String makeStringFromBytes(int offset, int length) {
    546         if (length <= 16) {
    547             StringBuilder sb = new StringBuilder(length);
    548             for (int i = 0; i < length; offset += 2, ++i) {
    549                 sb.append(bytes.getChar(offset));
    550             }
    551             return sb.toString();
    552         } else {
    553             CharSequence cs = bytes.asCharBuffer();
    554             offset /= 2;
    555             return cs.subSequence(offset, offset + length).toString();
    556         }
    557     }
    558 
    559     String getString(int res) {
    560         int offset=RES_GET_OFFSET(res);
    561         if(res != offset /* RES_GET_TYPE(res) != URES_STRING */ &&
    562                 RES_GET_TYPE(res) != ICUResourceBundle.STRING_V2) {
    563             return null;
    564         }
    565         if(offset == 0) {
    566             return emptyString;
    567         }
    568         if (res != offset) {  // STRING_V2
    569             if (offset < poolStringIndexLimit) {
    570                 return poolBundleReader.getStringV2(res);
    571             } else {
    572                 return getStringV2(res - poolStringIndexLimit);
    573             }
    574         }
    575         Object value = resourceCache.get(res);
    576         if(value != null) {
    577             return (String)value;
    578         }
    579         offset=getResourceByteOffset(offset);
    580         int length = getInt(offset);
    581         String s = makeStringFromBytes(offset+4, length);
    582         return (String)resourceCache.putIfAbsent(res, s, s.length() * 2);
    583     }
    584 
    585     /**
    586      * CLDR string value ""=="\u2205\u2205\u2205" prevents fallback to the parent bundle.
    587      */
    588     private boolean isNoInheritanceMarker(int res) {
    589         int offset = RES_GET_OFFSET(res);
    590         if (offset == 0) {
    591             // empty string
    592         } else if (res == offset) {
    593             offset = getResourceByteOffset(offset);
    594             return getInt(offset) == 3 && bytes.getChar(offset + 4) == 0x2205 &&
    595                     bytes.getChar(offset + 6) == 0x2205 && bytes.getChar(offset + 8) == 0x2205;
    596         } else if (RES_GET_TYPE(res) == ICUResourceBundle.STRING_V2) {
    597             if (offset < poolStringIndexLimit) {
    598                 return poolBundleReader.isStringV2NoInheritanceMarker(offset);
    599             } else {
    600                 return isStringV2NoInheritanceMarker(offset - poolStringIndexLimit);
    601             }
    602         }
    603         return false;
    604     }
    605 
    606     private boolean isStringV2NoInheritanceMarker(int offset) {
    607         int first = b16BitUnits.charAt(offset);
    608         if (first == 0x2205) {  // implicit length
    609             return b16BitUnits.charAt(offset + 1) == 0x2205 &&
    610                     b16BitUnits.charAt(offset + 2) == 0x2205 &&
    611                     b16BitUnits.charAt(offset + 3) == 0;
    612         } else if (first == 0xdc03) {  // explicit length 3 (should not occur)
    613             return b16BitUnits.charAt(offset + 1) == 0x2205 &&
    614                     b16BitUnits.charAt(offset + 2) == 0x2205 &&
    615                     b16BitUnits.charAt(offset + 3) == 0x2205;
    616         } else {
    617             // Assume that the string has not been stored with more length units than necessary.
    618             return false;
    619         }
    620     }
    621 
    622     String getAlias(int res) {
    623         int offset=RES_GET_OFFSET(res);
    624         int length;
    625         if(RES_GET_TYPE(res)==ICUResourceBundle.ALIAS) {
    626             if(offset==0) {
    627                 return emptyString;
    628             } else {
    629                 Object value = resourceCache.get(res);
    630                 if(value != null) {
    631                     return (String)value;
    632                 }
    633                 offset=getResourceByteOffset(offset);
    634                 length=getInt(offset);
    635                 String s = makeStringFromBytes(offset + 4, length);
    636                 return (String)resourceCache.putIfAbsent(res, s, length * 2);
    637             }
    638         } else {
    639             return null;
    640         }
    641     }
    642 
    643     byte[] getBinary(int res, byte[] ba) {
    644         int offset=RES_GET_OFFSET(res);
    645         int length;
    646         if(RES_GET_TYPE(res)==UResourceBundle.BINARY) {
    647             if(offset==0) {
    648                 return emptyBytes;
    649             } else {
    650                 offset=getResourceByteOffset(offset);
    651                 length=getInt(offset);
    652                 if(length==0) {
    653                     return emptyBytes;
    654                 }
    655                 // Not cached: The array would have to be cloned anyway because
    656                 // the cache must not be writable via the returned reference.
    657                 if(ba==null || ba.length!=length) {
    658                     ba=new byte[length];
    659                 }
    660                 offset += 4;
    661                 if(length <= 16) {
    662                     for(int i = 0; i < length; ++i) {
    663                         ba[i] = bytes.get(offset++);
    664                     }
    665                 } else {
    666                     ByteBuffer temp = bytes.duplicate();
    667                     temp.position(offset);
    668                     temp.get(ba);
    669                 }
    670                 return ba;
    671             }
    672         } else {
    673             return null;
    674         }
    675     }
    676 
    677     ByteBuffer getBinary(int res) {
    678         int offset=RES_GET_OFFSET(res);
    679         int length;
    680         if(RES_GET_TYPE(res)==UResourceBundle.BINARY) {
    681             if(offset==0) {
    682                 // Don't just
    683                 //   return emptyByteBuffer;
    684                 // in case it matters whether the buffer's mark is defined or undefined.
    685                 return emptyByteBuffer.duplicate();
    686             } else {
    687                 // Not cached: The returned buffer is small (shares its bytes with the bundle)
    688                 // and usually quickly discarded after use.
    689                 // Also, even a cached buffer would have to be cloned because it is mutable
    690                 // (position & mark).
    691                 offset=getResourceByteOffset(offset);
    692                 length=getInt(offset);
    693                 if(length == 0) {
    694                     return emptyByteBuffer.duplicate();
    695                 }
    696                 offset += 4;
    697                 ByteBuffer result = bytes.duplicate();
    698                 result.position(offset).limit(offset + length);
    699                 result = ICUBinary.sliceWithOrder(result);
    700                 if(!result.isReadOnly()) {
    701                     result = result.asReadOnlyBuffer();
    702                 }
    703                 return result;
    704             }
    705         } else {
    706             return null;
    707         }
    708     }
    709 
    710     int[] getIntVector(int res) {
    711         int offset=RES_GET_OFFSET(res);
    712         int length;
    713         if(RES_GET_TYPE(res)==UResourceBundle.INT_VECTOR) {
    714             if(offset==0) {
    715                 return emptyInts;
    716             } else {
    717                 // Not cached: The array would have to be cloned anyway because
    718                 // the cache must not be writable via the returned reference.
    719                 offset=getResourceByteOffset(offset);
    720                 length=getInt(offset);
    721                 return getInts(offset+4, length);
    722             }
    723         } else {
    724             return null;
    725         }
    726     }
    727 
    728     Array getArray(int res) {
    729         int type=RES_GET_TYPE(res);
    730         if(!URES_IS_ARRAY(type)) {
    731             return null;
    732         }
    733         int offset=RES_GET_OFFSET(res);
    734         if(offset == 0) {
    735             return EMPTY_ARRAY;
    736         }
    737         Object value = resourceCache.get(res);
    738         if(value != null) {
    739             return (Array)value;
    740         }
    741         Array array = (type == UResourceBundle.ARRAY) ?
    742                 new Array32(this, offset) : new Array16(this, offset);
    743         return (Array)resourceCache.putIfAbsent(res, array, 0);
    744     }
    745 
    746     Table getTable(int res) {
    747         int type = RES_GET_TYPE(res);
    748         if(!URES_IS_TABLE(type)) {
    749             return null;
    750         }
    751         int offset = RES_GET_OFFSET(res);
    752         if(offset == 0) {
    753             return EMPTY_TABLE;
    754         }
    755         Object value = resourceCache.get(res);
    756         if(value != null) {
    757             return (Table)value;
    758         }
    759         Table table;
    760         int size;  // Use size = 0 to never use SoftReferences for Tables?
    761         if(type == UResourceBundle.TABLE) {
    762             table = new Table1632(this, offset);
    763             size = table.getSize() * 2;
    764         } else if(type == ICUResourceBundle.TABLE16) {
    765             table = new Table16(this, offset);
    766             size = table.getSize() * 2;
    767         } else /* type == ICUResourceBundle.TABLE32 */ {
    768             table = new Table32(this, offset);
    769             size = table.getSize() * 4;
    770         }
    771         return (Table)resourceCache.putIfAbsent(res, table, size);
    772     }
    773 
    774     // ICUResource.Value --------------------------------------------------- ***
    775 
    776     /**
    777      * From C++ uresdata.c gPublicTypes[URES_LIMIT].
    778      */
    779     private static int PUBLIC_TYPES[] = {
    780         UResourceBundle.STRING,
    781         UResourceBundle.BINARY,
    782         UResourceBundle.TABLE,
    783         ICUResourceBundle.ALIAS,
    784 
    785         UResourceBundle.TABLE,     /* URES_TABLE32 */
    786         UResourceBundle.TABLE,     /* URES_TABLE16 */
    787         UResourceBundle.STRING,    /* URES_STRING_V2 */
    788         UResourceBundle.INT,
    789 
    790         UResourceBundle.ARRAY,
    791         UResourceBundle.ARRAY,     /* URES_ARRAY16 */
    792         UResourceBundle.NONE,
    793         UResourceBundle.NONE,
    794 
    795         UResourceBundle.NONE,
    796         UResourceBundle.NONE,
    797         UResourceBundle.INT_VECTOR,
    798         UResourceBundle.NONE
    799     };
    800 
    801     static class ReaderValue extends UResource.Value {
    802         ICUResourceBundleReader reader;
    803         int res;
    804 
    805         @Override
    806         public int getType() {
    807             return PUBLIC_TYPES[RES_GET_TYPE(res)];
    808         }
    809 
    810         @Override
    811         public String getString() {
    812             String s = reader.getString(res);
    813             if (s == null) {
    814                 throw new UResourceTypeMismatchException("");
    815             }
    816             return s;
    817         }
    818 
    819         @Override
    820         public String getAliasString() {
    821             String s = reader.getAlias(res);
    822             if (s == null) {
    823                 throw new UResourceTypeMismatchException("");
    824             }
    825             return s;
    826         }
    827 
    828         @Override
    829         public int getInt() {
    830             if (RES_GET_TYPE(res) != UResourceBundle.INT) {
    831                 throw new UResourceTypeMismatchException("");
    832             }
    833             return RES_GET_INT(res);
    834         }
    835 
    836         @Override
    837         public int getUInt() {
    838             if (RES_GET_TYPE(res) != UResourceBundle.INT) {
    839                 throw new UResourceTypeMismatchException("");
    840             }
    841             return RES_GET_UINT(res);
    842         }
    843 
    844         @Override
    845         public int[] getIntVector() {
    846             int[] iv = reader.getIntVector(res);
    847             if (iv == null) {
    848                 throw new UResourceTypeMismatchException("");
    849             }
    850             return iv;
    851         }
    852 
    853         @Override
    854         public ByteBuffer getBinary() {
    855             ByteBuffer bb = reader.getBinary(res);
    856             if (bb == null) {
    857                 throw new UResourceTypeMismatchException("");
    858             }
    859             return bb;
    860         }
    861 
    862         @Override
    863         public com.ibm.icu.impl.UResource.Array getArray() {
    864             Array array = reader.getArray(res);
    865             if (array == null) {
    866                 throw new UResourceTypeMismatchException("");
    867             }
    868             return array;
    869         }
    870 
    871         @Override
    872         public com.ibm.icu.impl.UResource.Table getTable() {
    873             Table table = reader.getTable(res);
    874             if (table == null) {
    875                 throw new UResourceTypeMismatchException("");
    876             }
    877             return table;
    878         }
    879 
    880         @Override
    881         public boolean isNoInheritanceMarker() {
    882             return reader.isNoInheritanceMarker(res);
    883         }
    884 
    885         @Override
    886         public String[] getStringArray() {
    887             Array array = reader.getArray(res);
    888             if (array == null) {
    889                 throw new UResourceTypeMismatchException("");
    890             }
    891             return getStringArray(array);
    892         }
    893 
    894         @Override
    895         public String[] getStringArrayOrStringAsArray() {
    896             Array array = reader.getArray(res);
    897             if (array != null) {
    898                 return getStringArray(array);
    899             }
    900             String s = reader.getString(res);
    901             if (s != null) {
    902                 return new String[] { s };
    903             }
    904             throw new UResourceTypeMismatchException("");
    905         }
    906 
    907         @Override
    908         public String getStringOrFirstOfArray() {
    909             String s = reader.getString(res);
    910             if (s != null) {
    911                 return s;
    912             }
    913             Array array = reader.getArray(res);
    914             if (array != null && array.size > 0) {
    915                 int r = array.getContainerResource(reader, 0);
    916                 s = reader.getString(r);
    917                 if (s != null) {
    918                     return s;
    919                 }
    920             }
    921             throw new UResourceTypeMismatchException("");
    922         }
    923 
    924         private String[] getStringArray(Array array) {
    925             String[] result = new String[array.size];
    926             for (int i = 0; i < array.size; ++i) {
    927                 int r = array.getContainerResource(reader, i);
    928                 String s = reader.getString(r);
    929                 if (s == null) {
    930                     throw new UResourceTypeMismatchException("");
    931                 }
    932                 result[i] = s;
    933             }
    934             return result;
    935         }
    936     }
    937 
    938     // Container value classes --------------------------------------------- ***
    939 
    940     static class Container {
    941         protected int size;
    942         protected int itemsOffset;
    943 
    944         public final int getSize() {
    945             return size;
    946         }
    947         int getContainerResource(ICUResourceBundleReader reader, int index) {
    948             return ICUResourceBundle.RES_BOGUS;
    949         }
    950         protected int getContainer16Resource(ICUResourceBundleReader reader, int index) {
    951             if (index < 0 || size <= index) {
    952                 return ICUResourceBundle.RES_BOGUS;
    953             }
    954             int res16 = reader.b16BitUnits.charAt(itemsOffset + index);
    955             if (res16 < reader.poolStringIndex16Limit) {
    956                 // Pool string, nothing to do.
    957             } else {
    958                 // Local string, adjust the 16-bit offset to a regular one,
    959                 // with a larger pool string index limit.
    960                 res16 = res16 - reader.poolStringIndex16Limit + reader.poolStringIndexLimit;
    961             }
    962             return (ICUResourceBundle.STRING_V2 << 28) | res16;
    963         }
    964         protected int getContainer32Resource(ICUResourceBundleReader reader, int index) {
    965             if (index < 0 || size <= index) {
    966                 return ICUResourceBundle.RES_BOGUS;
    967             }
    968             return reader.getInt(itemsOffset + 4 * index);
    969         }
    970         int getResource(ICUResourceBundleReader reader, String resKey) {
    971             return getContainerResource(reader, Integer.parseInt(resKey));
    972         }
    973         Container() {
    974         }
    975     }
    976     static class Array extends Container implements UResource.Array {
    977         Array() {}
    978         @Override
    979         public boolean getValue(int i, UResource.Value value) {
    980             if (0 <= i && i < size) {
    981                 ReaderValue readerValue = (ReaderValue)value;
    982                 readerValue.res = getContainerResource(readerValue.reader, i);
    983                 return true;
    984             }
    985             return false;
    986         }
    987     }
    988     private static final class Array32 extends Array {
    989         @Override
    990         int getContainerResource(ICUResourceBundleReader reader, int index) {
    991             return getContainer32Resource(reader, index);
    992         }
    993         Array32(ICUResourceBundleReader reader, int offset) {
    994             offset = reader.getResourceByteOffset(offset);
    995             size = reader.getInt(offset);
    996             itemsOffset = offset + 4;
    997         }
    998     }
    999     private static final class Array16 extends Array {
   1000         @Override
   1001         int getContainerResource(ICUResourceBundleReader reader, int index) {
   1002             return getContainer16Resource(reader, index);
   1003         }
   1004         Array16(ICUResourceBundleReader reader, int offset) {
   1005             size = reader.b16BitUnits.charAt(offset);
   1006             itemsOffset = offset + 1;
   1007         }
   1008     }
   1009     static class Table extends Container implements UResource.Table {
   1010         protected char[] keyOffsets;
   1011         protected int[] key32Offsets;
   1012 
   1013         Table() {
   1014         }
   1015         String getKey(ICUResourceBundleReader reader, int index) {
   1016             if (index < 0 || size <= index) {
   1017                 return null;
   1018             }
   1019             return keyOffsets != null ?
   1020                         reader.getKey16String(keyOffsets[index]) :
   1021                         reader.getKey32String(key32Offsets[index]);
   1022         }
   1023         private static final int URESDATA_ITEM_NOT_FOUND = -1;
   1024         int findTableItem(ICUResourceBundleReader reader, CharSequence key) {
   1025             int mid, start, limit;
   1026             int result;
   1027 
   1028             /* do a binary search for the key */
   1029             start=0;
   1030             limit=size;
   1031             while(start<limit) {
   1032                 mid = (start + limit) >>> 1;
   1033                 if (keyOffsets != null) {
   1034                     result = reader.compareKeys(key, keyOffsets[mid]);
   1035                 } else {
   1036                     result = reader.compareKeys32(key, key32Offsets[mid]);
   1037                 }
   1038                 if (result < 0) {
   1039                     limit = mid;
   1040                 } else if (result > 0) {
   1041                     start = mid + 1;
   1042                 } else {
   1043                     /* We found it! */
   1044                     return mid;
   1045                 }
   1046             }
   1047             return URESDATA_ITEM_NOT_FOUND;  /* not found or table is empty. */
   1048         }
   1049         @Override
   1050         int getResource(ICUResourceBundleReader reader, String resKey) {
   1051             return getContainerResource(reader, findTableItem(reader, resKey));
   1052         }
   1053         @Override
   1054         public boolean getKeyAndValue(int i, UResource.Key key, UResource.Value value) {
   1055             if (0 <= i && i < size) {
   1056                 ReaderValue readerValue = (ReaderValue)value;
   1057                 if (keyOffsets != null) {
   1058                     readerValue.reader.setKeyFromKey16(keyOffsets[i], key);
   1059                 } else {
   1060                     readerValue.reader.setKeyFromKey32(key32Offsets[i], key);
   1061                 }
   1062                 readerValue.res = getContainerResource(readerValue.reader, i);
   1063                 return true;
   1064             }
   1065             return false;
   1066         }
   1067     }
   1068     private static final class Table1632 extends Table {
   1069         @Override
   1070         int getContainerResource(ICUResourceBundleReader reader, int index) {
   1071             return getContainer32Resource(reader, index);
   1072         }
   1073         Table1632(ICUResourceBundleReader reader, int offset) {
   1074             offset = reader.getResourceByteOffset(offset);
   1075             keyOffsets = reader.getTableKeyOffsets(offset);
   1076             size = keyOffsets.length;
   1077             itemsOffset = offset + 2 * ((size + 2) & ~1);  // Skip padding for 4-alignment.
   1078         }
   1079     }
   1080     private static final class Table16 extends Table {
   1081         @Override
   1082         int getContainerResource(ICUResourceBundleReader reader, int index) {
   1083             return getContainer16Resource(reader, index);
   1084         }
   1085         Table16(ICUResourceBundleReader reader, int offset) {
   1086             keyOffsets = reader.getTable16KeyOffsets(offset);
   1087             size = keyOffsets.length;
   1088             itemsOffset = offset + 1 + size;
   1089         }
   1090     }
   1091     private static final class Table32 extends Table {
   1092         @Override
   1093         int getContainerResource(ICUResourceBundleReader reader, int index) {
   1094             return getContainer32Resource(reader, index);
   1095         }
   1096         Table32(ICUResourceBundleReader reader, int offset) {
   1097             offset = reader.getResourceByteOffset(offset);
   1098             key32Offsets = reader.getTable32KeyOffsets(offset);
   1099             size = key32Offsets.length;
   1100             itemsOffset = offset + 4 * (1 + size);
   1101         }
   1102     }
   1103 
   1104     // Resource cache ------------------------------------------------------ ***
   1105 
   1106     /**
   1107      * Cache of some of one resource bundle's resources.
   1108      * Avoids creating multiple Java objects for the same resource items,
   1109      * including multiple copies of their contents.
   1110      *
   1111      * <p>Mutable objects must not be cached and then returned to the caller
   1112      * because the cache must not be writable via the returned reference.
   1113      *
   1114      * <p>Resources are mapped by their resource integers.
   1115      * Empty resources with offset 0 cannot be mapped.
   1116      * Integers need not and should not be cached.
   1117      * Multiple .res items may share resource offsets (genrb eliminates some duplicates).
   1118      *
   1119      * <p>This cache uses int[] and Object[] arrays to minimize object creation
   1120      * and avoid auto-boxing.
   1121      *
   1122      * <p>Large resource objects are usually stored in SoftReferences.
   1123      *
   1124      * <p>For few resources, a small table is used with binary search.
   1125      * When more resources are cached, then the data structure changes to be faster
   1126      * but also use more memory.
   1127      */
   1128     private static final class ResourceCache {
   1129         // Number of items to be stored in a simple array with binary search and insertion sort.
   1130         private static final int SIMPLE_LENGTH = 32;
   1131 
   1132         // When more than SIMPLE_LENGTH items are cached,
   1133         // then switch to a trie-like tree of levels with different array lengths.
   1134         private static final int ROOT_BITS = 7;
   1135         private static final int NEXT_BITS = 6;
   1136 
   1137         // Simple table, used when length >= 0.
   1138         private int[] keys = new int[SIMPLE_LENGTH];
   1139         private Object[] values = new Object[SIMPLE_LENGTH];
   1140         private int length;
   1141 
   1142         // Trie-like tree of levels, used when length < 0.
   1143         private int maxOffsetBits;
   1144         /**
   1145          * Number of bits in each level, each stored in a nibble.
   1146          */
   1147         private int levelBitsList;
   1148         private Level rootLevel;
   1149 
   1150         private static boolean storeDirectly(int size) {
   1151             return size < LARGE_SIZE || CacheValue.futureInstancesWillBeStrong();
   1152         }
   1153 
   1154         @SuppressWarnings("unchecked")
   1155         private static final Object putIfCleared(Object[] values, int index, Object item, int size) {
   1156             Object value = values[index];
   1157             if(!(value instanceof SoftReference)) {
   1158                 // The caller should be consistent for each resource,
   1159                 // that is, create equivalent objects of equal size every time,
   1160                 // but the CacheValue "strength" may change over time.
   1161                 // assert size < LARGE_SIZE;
   1162                 return value;
   1163             }
   1164             assert size >= LARGE_SIZE;
   1165             value = ((SoftReference<Object>)value).get();
   1166             if(value != null) {
   1167                 return value;
   1168             }
   1169             values[index] = CacheValue.futureInstancesWillBeStrong() ?
   1170                     item : new SoftReference<Object>(item);
   1171             return item;
   1172         }
   1173 
   1174         private static final class Level {
   1175             int levelBitsList;
   1176             int shift;
   1177             int mask;
   1178             int[] keys;
   1179             Object[] values;
   1180 
   1181             Level(int levelBitsList, int shift) {
   1182                 this.levelBitsList = levelBitsList;
   1183                 this.shift = shift;
   1184                 int bits = levelBitsList & 0xf;
   1185                 assert bits != 0;
   1186                 int length = 1 << bits;
   1187                 mask = length - 1;
   1188                 keys = new int[length];
   1189                 values = new Object[length];
   1190             }
   1191 
   1192             Object get(int key) {
   1193                 int index = (key >> shift) & mask;
   1194                 int k = keys[index];
   1195                 if(k == key) {
   1196                     return values[index];
   1197                 }
   1198                 if(k == 0) {
   1199                     Level level = (Level)values[index];
   1200                     if(level != null) {
   1201                         return level.get(key);
   1202                     }
   1203                 }
   1204                 return null;
   1205             }
   1206 
   1207             Object putIfAbsent(int key, Object item, int size) {
   1208                 int index = (key >> shift) & mask;
   1209                 int k = keys[index];
   1210                 if(k == key) {
   1211                     return putIfCleared(values, index, item, size);
   1212                 }
   1213                 if(k == 0) {
   1214                     Level level = (Level)values[index];
   1215                     if(level != null) {
   1216                         return level.putIfAbsent(key, item, size);
   1217                     }
   1218                     keys[index] = key;
   1219                     values[index] = storeDirectly(size) ? item : new SoftReference<Object>(item);
   1220                     return item;
   1221                 }
   1222                 // Collision: Add a child level, move the old item there,
   1223                 // and then insert the current item.
   1224                 Level level = new Level(levelBitsList >> 4, shift + (levelBitsList & 0xf));
   1225                 int i = (k >> level.shift) & level.mask;
   1226                 level.keys[i] = k;
   1227                 level.values[i] = values[index];
   1228                 keys[index] = 0;
   1229                 values[index] = level;
   1230                 return level.putIfAbsent(key, item, size);
   1231             }
   1232         }
   1233 
   1234         ResourceCache(int maxOffset) {
   1235             assert maxOffset != 0;
   1236             maxOffsetBits = 28;
   1237             while(maxOffset <= 0x7ffffff) {
   1238                 maxOffset <<= 1;
   1239                 --maxOffsetBits;
   1240             }
   1241             int keyBits = maxOffsetBits + 2;  // +2 for mini type: at most 30 bits used in a key
   1242             // Precompute for each level the number of bits it handles.
   1243             if(keyBits <= ROOT_BITS) {
   1244                 levelBitsList = keyBits;
   1245             } else if(keyBits < (ROOT_BITS + 3)) {
   1246                 levelBitsList = 0x30 | (keyBits - 3);
   1247             } else {
   1248                 levelBitsList = ROOT_BITS;
   1249                 keyBits -= ROOT_BITS;
   1250                 int shift = 4;
   1251                 for(;;) {
   1252                     if(keyBits <= NEXT_BITS) {
   1253                         levelBitsList |= keyBits << shift;
   1254                         break;
   1255                     } else if(keyBits < (NEXT_BITS + 3)) {
   1256                         levelBitsList |= (0x30 | (keyBits - 3)) << shift;
   1257                         break;
   1258                     } else {
   1259                         levelBitsList |= NEXT_BITS << shift;
   1260                         keyBits -= NEXT_BITS;
   1261                         shift += 4;
   1262                     }
   1263                 }
   1264             }
   1265         }
   1266 
   1267         /**
   1268          * Turns a resource integer (with unused bits in the middle)
   1269          * into a key with fewer bits (at most keyBits).
   1270          */
   1271         private int makeKey(int res) {
   1272             // It is possible for resources of different types in the 16-bit array
   1273             // to share a start offset; distinguish between those with a 2-bit value,
   1274             // as a tie-breaker in the bits just above the highest possible offset.
   1275             // It is not possible for "regular" resources of different types
   1276             // to share a start offset with each other,
   1277             // but offsets for 16-bit and "regular" resources overlap;
   1278             // use 2-bit value 0 for "regular" resources.
   1279             int type = RES_GET_TYPE(res);
   1280             int miniType =
   1281                     (type == ICUResourceBundle.STRING_V2) ? 1 :
   1282                         (type == ICUResourceBundle.TABLE16) ? 3 :
   1283                             (type == ICUResourceBundle.ARRAY16) ? 2 : 0;
   1284             return RES_GET_OFFSET(res) | (miniType << maxOffsetBits);
   1285         }
   1286 
   1287         private int findSimple(int key) {
   1288             // With Java 6, return Arrays.binarySearch(keys, 0, length, key).
   1289             int start = 0;
   1290             int limit = length;
   1291             while((limit - start) > 8) {
   1292                 int mid = (start + limit) / 2;
   1293                 if(key < keys[mid]) {
   1294                     limit = mid;
   1295                 } else {
   1296                     start = mid;
   1297                 }
   1298             }
   1299             // For a small number of items, linear search should be a little faster.
   1300             while(start < limit) {
   1301                 int k = keys[start];
   1302                 if(key < k) {
   1303                     return ~start;
   1304                 }
   1305                 if(key == k) {
   1306                     return start;
   1307                 }
   1308                 ++start;
   1309             }
   1310             return ~start;
   1311         }
   1312 
   1313         @SuppressWarnings("unchecked")
   1314         synchronized Object get(int res) {
   1315             // Integers and empty resources need not be cached.
   1316             // The cache itself uses res=0 for "no match".
   1317             assert RES_GET_OFFSET(res) != 0;
   1318             Object value;
   1319             if(length >= 0) {
   1320                 int index = findSimple(res);
   1321                 if(index >= 0) {
   1322                     value = values[index];
   1323                 } else {
   1324                     return null;
   1325                 }
   1326             } else {
   1327                 value = rootLevel.get(makeKey(res));
   1328                 if(value == null) {
   1329                     return null;
   1330                 }
   1331             }
   1332             if(value instanceof SoftReference) {
   1333                 value = ((SoftReference<Object>)value).get();
   1334             }
   1335             return value;  // null if the reference was cleared
   1336         }
   1337 
   1338         synchronized Object putIfAbsent(int res, Object item, int size) {
   1339             if(length >= 0) {
   1340                 int index = findSimple(res);
   1341                 if(index >= 0) {
   1342                     return putIfCleared(values, index, item, size);
   1343                 } else if(length < SIMPLE_LENGTH) {
   1344                     index = ~index;
   1345                     if(index < length) {
   1346                         System.arraycopy(keys, index, keys, index + 1, length - index);
   1347                         System.arraycopy(values, index, values, index + 1, length - index);
   1348                     }
   1349                     ++length;
   1350                     keys[index] = res;
   1351                     values[index] = storeDirectly(size) ? item : new SoftReference<Object>(item);
   1352                     return item;
   1353                 } else /* not found && length == SIMPLE_LENGTH */ {
   1354                     // Grow to become trie-like.
   1355                     rootLevel = new Level(levelBitsList, 0);
   1356                     for(int i = 0; i < SIMPLE_LENGTH; ++i) {
   1357                         rootLevel.putIfAbsent(makeKey(keys[i]), values[i], 0);
   1358                     }
   1359                     keys = null;
   1360                     values = null;
   1361                     length = -1;
   1362                 }
   1363             }
   1364             return rootLevel.putIfAbsent(makeKey(res), item, size);
   1365         }
   1366     }
   1367 
   1368     private static final String ICU_RESOURCE_SUFFIX = ".res";
   1369 
   1370     /**
   1371      * Gets the full name of the resource with suffix.
   1372      */
   1373     public static String getFullName(String baseName, String localeName) {
   1374         if (baseName == null || baseName.length() == 0) {
   1375             if (localeName.length() == 0) {
   1376                 return localeName = ULocale.getDefault().toString();
   1377             }
   1378             return localeName + ICU_RESOURCE_SUFFIX;
   1379         } else {
   1380             if (baseName.indexOf('.') == -1) {
   1381                 if (baseName.charAt(baseName.length() - 1) != '/') {
   1382                     return baseName + "/" + localeName + ICU_RESOURCE_SUFFIX;
   1383                 } else {
   1384                     return baseName + localeName + ICU_RESOURCE_SUFFIX;
   1385                 }
   1386             } else {
   1387                 baseName = baseName.replace('.', '/');
   1388                 if (localeName.length() == 0) {
   1389                     return baseName + ICU_RESOURCE_SUFFIX;
   1390                 } else {
   1391                     return baseName + "_" + localeName + ICU_RESOURCE_SUFFIX;
   1392                 }
   1393             }
   1394         }
   1395     }
   1396 }
   1397