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