1/* GENERATED SOURCE. DO NOT MODIFY. */
2/*
3 *******************************************************************************
4 * Copyright (C) 2004-2015, International Business Machines Corporation and
5 * others. All Rights Reserved.
6 *******************************************************************************
7 */
8package android.icu.impl;
9
10import java.io.IOException;
11import java.io.InputStream;
12import java.lang.ref.SoftReference;
13import java.nio.ByteBuffer;
14import java.nio.CharBuffer;
15import java.nio.IntBuffer;
16
17import android.icu.impl.UResource.ArraySink;
18import android.icu.impl.UResource.TableSink;
19import android.icu.util.ICUException;
20import android.icu.util.ICUUncheckedIOException;
21import android.icu.util.ULocale;
22import android.icu.util.UResourceBundle;
23import android.icu.util.UResourceTypeMismatchException;
24import android.icu.util.VersionInfo;
25
26/**
27 * This class reads the *.res resource bundle format.
28 *
29 * For the file format documentation see ICU4C's source/common/uresdata.h file.
30 * @hide Only a subset of ICU is exposed in Android
31 */
32public final class ICUResourceBundleReader {
33    /**
34     * File format version that this class understands.
35     * "ResB"
36     */
37    private static final int DATA_FORMAT = 0x52657342;
38    private static final class IsAcceptable implements ICUBinary.Authenticate {
39        // @Override when we switch to Java 6
40        public boolean isDataVersionAcceptable(byte formatVersion[]) {
41            return
42                    (formatVersion[0] == 1 && (formatVersion[1] & 0xff) >= 1) ||
43                    (2 <= formatVersion[0] && formatVersion[0] <= 3);
44        }
45    }
46    private static final IsAcceptable IS_ACCEPTABLE = new IsAcceptable();
47
48    /* indexes[] value names; indexes are generally 32-bit (Resource) indexes */
49    /**
50     * [0] contains the length of indexes[]
51     * which is at most URES_INDEX_TOP of the latest format version
52     *
53     * formatVersion==1: all bits contain the length of indexes[]
54     *   but the length is much less than 0xff;
55     * formatVersion>1:
56     *   only bits  7..0 contain the length of indexes[],
57     *        bits 31..8 are reserved and set to 0
58     * formatVersion>=3:
59     *        bits 31..8 poolStringIndexLimit bits 23..0
60     */
61    private static final int URES_INDEX_LENGTH           = 0;
62    /**
63     * [1] contains the top of the key strings,
64     *     same as the bottom of resources or UTF-16 strings, rounded up
65     */
66    private static final int URES_INDEX_KEYS_TOP         = 1;
67    /** [2] contains the top of all resources */
68    //ivate static final int URES_INDEX_RESOURCES_TOP    = 2;
69    /**
70     * [3] contains the top of the bundle,
71     *     in case it were ever different from [2]
72     */
73    private static final int URES_INDEX_BUNDLE_TOP       = 3;
74    /** [4] max. length of any table */
75    private static final int URES_INDEX_MAX_TABLE_LENGTH = 4;
76    /**
77     * [5] attributes bit set, see URES_ATT_* (new in formatVersion 1.2)
78     *
79     * formatVersion>=3:
80     *   bits 31..16 poolStringIndex16Limit
81     *   bits 15..12 poolStringIndexLimit bits 27..24
82     */
83    private static final int URES_INDEX_ATTRIBUTES       = 5;
84    /**
85     * [6] top of the 16-bit units (UTF-16 string v2 UChars, URES_TABLE16, URES_ARRAY16),
86     *     rounded up (new in formatVersion 2.0, ICU 4.4)
87     */
88    private static final int URES_INDEX_16BIT_TOP        = 6;
89    /** [7] checksum of the pool bundle (new in formatVersion 2.0, ICU 4.4) */
90    private static final int URES_INDEX_POOL_CHECKSUM    = 7;
91    //ivate static final int URES_INDEX_TOP              = 8;
92
93    /*
94     * Nofallback attribute, attribute bit 0 in indexes[URES_INDEX_ATTRIBUTES].
95     * New in formatVersion 1.2 (ICU 3.6).
96     *
97     * If set, then this resource bundle is a standalone bundle.
98     * If not set, then the bundle participates in locale fallback, eventually
99     * all the way to the root bundle.
100     * If indexes[] is missing or too short, then the attribute cannot be determined
101     * reliably. Dependency checking should ignore such bundles, and loading should
102     * use fallbacks.
103     */
104    private static final int URES_ATT_NO_FALLBACK = 1;
105
106    /*
107     * Attributes for bundles that are, or use, a pool bundle.
108     * A pool bundle provides key strings that are shared among several other bundles
109     * to reduce their total size.
110     * New in formatVersion 2 (ICU 4.4).
111     */
112    private static final int URES_ATT_IS_POOL_BUNDLE = 2;
113    private static final int URES_ATT_USES_POOL_BUNDLE = 4;
114
115    private static final CharBuffer EMPTY_16_BIT_UNITS = CharBuffer.wrap("\0");  // read-only
116
117    /**
118     * Objects with more value bytes are stored in SoftReferences.
119     * Smaller objects (which are not much larger than a SoftReference)
120     * are stored directly, avoiding the overhead of the reference.
121     */
122    static final int LARGE_SIZE = 24;
123
124    private static final boolean DEBUG = false;
125
126    private int /* formatVersion, */ dataVersion;
127
128    // See the ResourceData struct in ICU4C/source/common/uresdata.h.
129    /**
130     * Buffer of all of the resource bundle bytes after the header.
131     * (equivalent of C++ pRoot)
132     */
133    private ByteBuffer bytes;
134    private byte[] keyBytes;
135    private CharBuffer b16BitUnits;
136    private ICUResourceBundleReader poolBundleReader;
137    private int rootRes;
138    private int localKeyLimit;
139    private int poolStringIndexLimit;
140    private int poolStringIndex16Limit;
141    private boolean noFallback; /* see URES_ATT_NO_FALLBACK */
142    private boolean isPoolBundle;
143    private boolean usesPoolBundle;
144    private int poolCheckSum;
145
146    private ResourceCache resourceCache;
147
148    private static ReaderCache CACHE = new ReaderCache();
149    private static final ICUResourceBundleReader NULL_READER = new ICUResourceBundleReader();
150
151    private static class ReaderCacheKey {
152        final String baseName;
153        final String localeID;
154
155        ReaderCacheKey(String baseName, String localeID) {
156            this.baseName = (baseName == null) ? "" : baseName;
157            this.localeID = (localeID == null) ? "" : localeID;
158        }
159
160        public boolean equals(Object obj) {
161            if (this == obj) {
162                return true;
163            }
164            if (!(obj instanceof ReaderCacheKey)) {
165                return false;
166            }
167            ReaderCacheKey info = (ReaderCacheKey)obj;
168            return this.baseName.equals(info.baseName)
169                    && this.localeID.equals(info.localeID);
170        }
171
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 android.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.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=((int)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    private int getArrayLength(int res) {
729        int offset = RES_GET_OFFSET(res);
730        if(offset == 0) {
731            return 0;
732        }
733        int type = RES_GET_TYPE(res);
734        if(type == UResourceBundle.ARRAY) {
735            offset = getResourceByteOffset(offset);
736            return getInt(offset);
737        } else if(type == ICUResourceBundle.ARRAY16) {
738            return b16BitUnits.charAt(offset);
739        } else {
740            return 0;
741        }
742    }
743
744    Array getArray(int res) {
745        int type=RES_GET_TYPE(res);
746        if(!URES_IS_ARRAY(type)) {
747            return null;
748        }
749        int offset=RES_GET_OFFSET(res);
750        if(offset == 0) {
751            return EMPTY_ARRAY;
752        }
753        Object value = resourceCache.get(res);
754        if(value != null) {
755            return (Array)value;
756        }
757        Array array = (type == UResourceBundle.ARRAY) ?
758                new Array32(this, offset) : new Array16(this, offset);
759        return (Array)resourceCache.putIfAbsent(res, array, 0);
760    }
761
762    private int getTableLength(int res) {
763        int offset = RES_GET_OFFSET(res);
764        if(offset == 0) {
765            return 0;
766        }
767        int type = RES_GET_TYPE(res);
768        if(type == UResourceBundle.TABLE) {
769            offset = getResourceByteOffset(offset);
770            return bytes.getChar(offset);
771        } else if(type == ICUResourceBundle.TABLE16) {
772            return b16BitUnits.charAt(offset);
773        } else if(type == ICUResourceBundle.TABLE32) {
774            offset = getResourceByteOffset(offset);
775            return getInt(offset);
776        } else {
777            return 0;
778        }
779    }
780
781    Table getTable(int res) {
782        int type = RES_GET_TYPE(res);
783        if(!URES_IS_TABLE(type)) {
784            return null;
785        }
786        int offset = RES_GET_OFFSET(res);
787        if(offset == 0) {
788            return EMPTY_TABLE;
789        }
790        Object value = resourceCache.get(res);
791        if(value != null) {
792            return (Table)value;
793        }
794        Table table;
795        int size;  // Use size = 0 to never use SoftReferences for Tables?
796        if(type == UResourceBundle.TABLE) {
797            table = new Table1632(this, offset);
798            size = table.getSize() * 2;
799        } else if(type == ICUResourceBundle.TABLE16) {
800            table = new Table16(this, offset);
801            size = table.getSize() * 2;
802        } else /* type == ICUResourceBundle.TABLE32 */ {
803            table = new Table32(this, offset);
804            size = table.getSize() * 4;
805        }
806        return (Table)resourceCache.putIfAbsent(res, table, size);
807    }
808
809    // ICUResource.Value --------------------------------------------------- ***
810
811    /**
812     * From C++ uresdata.c gPublicTypes[URES_LIMIT].
813     */
814    private static int PUBLIC_TYPES[] = {
815        UResourceBundle.STRING,
816        UResourceBundle.BINARY,
817        UResourceBundle.TABLE,
818        ICUResourceBundle.ALIAS,
819
820        UResourceBundle.TABLE,     /* URES_TABLE32 */
821        UResourceBundle.TABLE,     /* URES_TABLE16 */
822        UResourceBundle.STRING,    /* URES_STRING_V2 */
823        UResourceBundle.INT,
824
825        UResourceBundle.ARRAY,
826        UResourceBundle.ARRAY,     /* URES_ARRAY16 */
827        UResourceBundle.NONE,
828        UResourceBundle.NONE,
829
830        UResourceBundle.NONE,
831        UResourceBundle.NONE,
832        UResourceBundle.INT_VECTOR,
833        UResourceBundle.NONE
834    };
835
836    static class ReaderValue extends UResource.Value {
837        ICUResourceBundleReader reader;
838        private int res;
839
840        @Override
841        public int getType() {
842            return PUBLIC_TYPES[RES_GET_TYPE(res)];
843        }
844
845        @Override
846        public String getString() {
847            String s = reader.getString(res);
848            if (s == null) {
849                throw new UResourceTypeMismatchException("");
850            }
851            return s;
852        }
853
854        @Override
855        public String getAliasString() {
856            String s = reader.getAlias(res);
857            if (s == null) {
858                throw new UResourceTypeMismatchException("");
859            }
860            return s;
861        }
862
863        @Override
864        public int getInt() {
865            if (RES_GET_TYPE(res) != UResourceBundle.INT) {
866                throw new UResourceTypeMismatchException("");
867            }
868            return RES_GET_INT(res);
869        }
870
871        @Override
872        public int getUInt() {
873            if (RES_GET_TYPE(res) != UResourceBundle.INT) {
874                throw new UResourceTypeMismatchException("");
875            }
876            return RES_GET_UINT(res);
877        }
878
879        @Override
880        public int[] getIntVector() {
881            int[] iv = reader.getIntVector(res);
882            if (iv == null) {
883                throw new UResourceTypeMismatchException("");
884            }
885            return iv;
886        }
887
888        @Override
889        public ByteBuffer getBinary() {
890            ByteBuffer bb = reader.getBinary(res);
891            if (bb == null) {
892                throw new UResourceTypeMismatchException("");
893            }
894            return bb;
895        }
896    }
897
898    // Container value classes --------------------------------------------- ***
899
900    static class Container {
901        protected int size;
902        protected int itemsOffset;
903
904        final int getSize() {
905            return size;
906        }
907        int getContainerResource(ICUResourceBundleReader reader, int index) {
908            return ICUResourceBundle.RES_BOGUS;
909        }
910        protected int getContainer16Resource(ICUResourceBundleReader reader, int index) {
911            if (index < 0 || size <= index) {
912                return ICUResourceBundle.RES_BOGUS;
913            }
914            int res16 = reader.b16BitUnits.charAt(itemsOffset + index);
915            if (res16 < reader.poolStringIndex16Limit) {
916                // Pool string, nothing to do.
917            } else {
918                // Local string, adjust the 16-bit offset to a regular one,
919                // with a larger pool string index limit.
920                res16 = res16 - reader.poolStringIndex16Limit + reader.poolStringIndexLimit;
921            }
922            return (ICUResourceBundle.STRING_V2 << 28) | res16;
923        }
924        protected int getContainer32Resource(ICUResourceBundleReader reader, int index) {
925            if (index < 0 || size <= index) {
926                return ICUResourceBundle.RES_BOGUS;
927            }
928            return reader.getInt(itemsOffset + 4 * index);
929        }
930        int getResource(ICUResourceBundleReader reader, String resKey) {
931            return getContainerResource(reader, Integer.parseInt(resKey));
932        }
933        Container() {
934        }
935    }
936    static class Array extends Container {
937        Array() {}
938        void getAllItems(ICUResourceBundleReader reader,
939                UResource.Key key, ReaderValue value, ArraySink sink) {
940            for (int i = 0; i < size; ++i) {
941                int res = getContainerResource(reader, i);
942                int type = RES_GET_TYPE(res);
943                if (URES_IS_ARRAY(type)) {
944                    int numItems = reader.getArrayLength(res);
945                    ArraySink subSink = sink.getOrCreateArraySink(i, numItems);
946                    if (subSink != null) {
947                        Array array = reader.getArray(res);
948                        assert(array.size == numItems);
949                        array.getAllItems(reader, key, value, subSink);
950                    }
951                } else if (URES_IS_TABLE(type)) {
952                    int numItems = reader.getTableLength(res);
953                    TableSink subSink = sink.getOrCreateTableSink(i, numItems);
954                    if (subSink != null) {
955                        Table table = reader.getTable(res);
956                        assert(table.size == numItems);
957                        table.getAllItems(reader, key, value, subSink);
958                    }
959                /* TODO: settle on how to deal with aliases, port to C++
960                } else if (type == ICUResourceBundle.ALIAS) {
961                    throw new UnsupportedOperationException(
962                            "aliases not handled in resource enumeration"); */
963                } else {
964                    value.res = res;
965                    sink.put(i, value);
966                }
967            }
968            sink.leave();
969        }
970    }
971    private static final class Array32 extends Array {
972        @Override
973        int getContainerResource(ICUResourceBundleReader reader, int index) {
974            return getContainer32Resource(reader, index);
975        }
976        Array32(ICUResourceBundleReader reader, int offset) {
977            offset = reader.getResourceByteOffset(offset);
978            size = reader.getInt(offset);
979            itemsOffset = offset + 4;
980        }
981    }
982    private static final class Array16 extends Array {
983        @Override
984        int getContainerResource(ICUResourceBundleReader reader, int index) {
985            return getContainer16Resource(reader, index);
986        }
987        Array16(ICUResourceBundleReader reader, int offset) {
988            size = reader.b16BitUnits.charAt(offset);
989            itemsOffset = offset + 1;
990        }
991    }
992    static class Table extends Container {
993        protected char[] keyOffsets;
994        protected int[] key32Offsets;
995
996        String getKey(ICUResourceBundleReader reader, int index) {
997            if (index < 0 || size <= index) {
998                return null;
999            }
1000            return keyOffsets != null ?
1001                        reader.getKey16String(keyOffsets[index]) :
1002                        reader.getKey32String(key32Offsets[index]);
1003        }
1004        private static final int URESDATA_ITEM_NOT_FOUND = -1;
1005        int findTableItem(ICUResourceBundleReader reader, CharSequence key) {
1006            int mid, start, limit;
1007            int result;
1008
1009            /* do a binary search for the key */
1010            start=0;
1011            limit=size;
1012            while(start<limit) {
1013                mid = (start + limit) >>> 1;
1014                if (keyOffsets != null) {
1015                    result = reader.compareKeys(key, keyOffsets[mid]);
1016                } else {
1017                    result = reader.compareKeys32(key, key32Offsets[mid]);
1018                }
1019                if (result < 0) {
1020                    limit = mid;
1021                } else if (result > 0) {
1022                    start = mid + 1;
1023                } else {
1024                    /* We found it! */
1025                    return mid;
1026                }
1027            }
1028            return URESDATA_ITEM_NOT_FOUND;  /* not found or table is empty. */
1029        }
1030        @Override
1031        int getResource(ICUResourceBundleReader reader, String resKey) {
1032            return getContainerResource(reader, findTableItem(reader, resKey));
1033        }
1034        void getAllItems(ICUResourceBundleReader reader,
1035                UResource.Key key, ReaderValue value, TableSink sink) {
1036            for (int i = 0; i < size; ++i) {
1037                if (keyOffsets != null) {
1038                    reader.setKeyFromKey16(keyOffsets[i], key);
1039                } else {
1040                    reader.setKeyFromKey32(key32Offsets[i], key);
1041                }
1042                int res = getContainerResource(reader, i);
1043                int type = RES_GET_TYPE(res);
1044                if (URES_IS_ARRAY(type)) {
1045                    int numItems = reader.getArrayLength(res);
1046                    ArraySink subSink = sink.getOrCreateArraySink(key, numItems);
1047                    if (subSink != null) {
1048                        Array array = reader.getArray(res);
1049                        assert(array.size == numItems);
1050                        array.getAllItems(reader, key, value, subSink);
1051                    }
1052                } else if (URES_IS_TABLE(type)) {
1053                    int numItems = reader.getTableLength(res);
1054                    TableSink subSink = sink.getOrCreateTableSink(key, numItems);
1055                    if (subSink != null) {
1056                        Table table = reader.getTable(res);
1057                        assert(table.size == numItems);
1058                        table.getAllItems(reader, key, value, subSink);
1059                    }
1060                /* TODO: settle on how to deal with aliases, port to C++
1061                } else if (type == ICUResourceBundle.ALIAS) {
1062                    throw new UnsupportedOperationException(
1063                            "aliases not handled in resource enumeration"); */
1064                } else if (reader.isNoInheritanceMarker(res)) {
1065                    sink.putNoFallback(key);
1066                } else {
1067                    value.res = res;
1068                    sink.put(key, value);
1069                }
1070            }
1071            sink.leave();
1072        }
1073        Table() {
1074        }
1075    }
1076    private static final class Table1632 extends Table {
1077        @Override
1078        int getContainerResource(ICUResourceBundleReader reader, int index) {
1079            return getContainer32Resource(reader, index);
1080        }
1081        Table1632(ICUResourceBundleReader reader, int offset) {
1082            offset = reader.getResourceByteOffset(offset);
1083            keyOffsets = reader.getTableKeyOffsets(offset);
1084            size = keyOffsets.length;
1085            itemsOffset = offset + 2 * ((size + 2) & ~1);  // Skip padding for 4-alignment.
1086        }
1087    }
1088    private static final class Table16 extends Table {
1089        @Override
1090        int getContainerResource(ICUResourceBundleReader reader, int index) {
1091            return getContainer16Resource(reader, index);
1092        }
1093        Table16(ICUResourceBundleReader reader, int offset) {
1094            keyOffsets = reader.getTable16KeyOffsets(offset);
1095            size = keyOffsets.length;
1096            itemsOffset = offset + 1 + size;
1097        }
1098    }
1099    private static final class Table32 extends Table {
1100        @Override
1101        int getContainerResource(ICUResourceBundleReader reader, int index) {
1102            return getContainer32Resource(reader, index);
1103        }
1104        Table32(ICUResourceBundleReader reader, int offset) {
1105            offset = reader.getResourceByteOffset(offset);
1106            key32Offsets = reader.getTable32KeyOffsets(offset);
1107            size = key32Offsets.length;
1108            itemsOffset = offset + 4 * (1 + size);
1109        }
1110    }
1111
1112    // Resource cache ------------------------------------------------------ ***
1113
1114    /**
1115     * Cache of some of one resource bundle's resources.
1116     * Avoids creating multiple Java objects for the same resource items,
1117     * including multiple copies of their contents.
1118     *
1119     * <p>Mutable objects must not be cached and then returned to the caller
1120     * because the cache must not be writable via the returned reference.
1121     *
1122     * <p>Resources are mapped by their resource integers.
1123     * Empty resources with offset 0 cannot be mapped.
1124     * Integers need not and should not be cached.
1125     * Multiple .res items may share resource offsets (genrb eliminates some duplicates).
1126     *
1127     * <p>This cache uses int[] and Object[] arrays to minimize object creation
1128     * and avoid auto-boxing.
1129     *
1130     * <p>Large resource objects are usually stored in SoftReferences.
1131     *
1132     * <p>For few resources, a small table is used with binary search.
1133     * When more resources are cached, then the data structure changes to be faster
1134     * but also use more memory.
1135     */
1136    private static final class ResourceCache {
1137        // Number of items to be stored in a simple array with binary search and insertion sort.
1138        private static final int SIMPLE_LENGTH = 32;
1139
1140        // When more than SIMPLE_LENGTH items are cached,
1141        // then switch to a trie-like tree of levels with different array lengths.
1142        private static final int ROOT_BITS = 7;
1143        private static final int NEXT_BITS = 6;
1144
1145        // Simple table, used when length >= 0.
1146        private int[] keys = new int[SIMPLE_LENGTH];
1147        private Object[] values = new Object[SIMPLE_LENGTH];
1148        private int length;
1149
1150        // Trie-like tree of levels, used when length < 0.
1151        private int maxOffsetBits;
1152        /**
1153         * Number of bits in each level, each stored in a nibble.
1154         */
1155        private int levelBitsList;
1156        private Level rootLevel;
1157
1158        private static boolean storeDirectly(int size) {
1159            return size < LARGE_SIZE || CacheValue.futureInstancesWillBeStrong();
1160        }
1161
1162        @SuppressWarnings("unchecked")
1163        private static final Object putIfCleared(Object[] values, int index, Object item, int size) {
1164            Object value = values[index];
1165            if(!(value instanceof SoftReference)) {
1166                // The caller should be consistent for each resource,
1167                // that is, create equivalent objects of equal size every time,
1168                // but the CacheValue "strength" may change over time.
1169                // assert size < LARGE_SIZE;
1170                return value;
1171            }
1172            assert size >= LARGE_SIZE;
1173            value = ((SoftReference<Object>)value).get();
1174            if(value != null) {
1175                return value;
1176            }
1177            values[index] = CacheValue.futureInstancesWillBeStrong() ?
1178                    item : new SoftReference<Object>(item);
1179            return item;
1180        }
1181
1182        private static final class Level {
1183            int levelBitsList;
1184            int shift;
1185            int mask;
1186            int[] keys;
1187            Object[] values;
1188
1189            Level(int levelBitsList, int shift) {
1190                this.levelBitsList = levelBitsList;
1191                this.shift = shift;
1192                int bits = levelBitsList & 0xf;
1193                assert bits != 0;
1194                int length = 1 << bits;
1195                mask = length - 1;
1196                keys = new int[length];
1197                values = new Object[length];
1198            }
1199
1200            Object get(int key) {
1201                int index = (key >> shift) & mask;
1202                int k = keys[index];
1203                if(k == key) {
1204                    return values[index];
1205                }
1206                if(k == 0) {
1207                    Level level = (Level)values[index];
1208                    if(level != null) {
1209                        return level.get(key);
1210                    }
1211                }
1212                return null;
1213            }
1214
1215            Object putIfAbsent(int key, Object item, int size) {
1216                int index = (key >> shift) & mask;
1217                int k = keys[index];
1218                if(k == key) {
1219                    return putIfCleared(values, index, item, size);
1220                }
1221                if(k == 0) {
1222                    Level level = (Level)values[index];
1223                    if(level != null) {
1224                        return level.putIfAbsent(key, item, size);
1225                    }
1226                    keys[index] = key;
1227                    values[index] = storeDirectly(size) ? item : new SoftReference<Object>(item);
1228                    return item;
1229                }
1230                // Collision: Add a child level, move the old item there,
1231                // and then insert the current item.
1232                Level level = new Level(levelBitsList >> 4, shift + (levelBitsList & 0xf));
1233                int i = (k >> level.shift) & level.mask;
1234                level.keys[i] = k;
1235                level.values[i] = values[index];
1236                keys[index] = 0;
1237                values[index] = level;
1238                return level.putIfAbsent(key, item, size);
1239            }
1240        }
1241
1242        ResourceCache(int maxOffset) {
1243            assert maxOffset != 0;
1244            maxOffsetBits = 28;
1245            while(maxOffset <= 0x7ffffff) {
1246                maxOffset <<= 1;
1247                --maxOffsetBits;
1248            }
1249            int keyBits = maxOffsetBits + 2;  // +2 for mini type: at most 30 bits used in a key
1250            // Precompute for each level the number of bits it handles.
1251            if(keyBits <= ROOT_BITS) {
1252                levelBitsList = keyBits;
1253            } else if(keyBits < (ROOT_BITS + 3)) {
1254                levelBitsList = 0x30 | (keyBits - 3);
1255            } else {
1256                levelBitsList = ROOT_BITS;
1257                keyBits -= ROOT_BITS;
1258                int shift = 4;
1259                for(;;) {
1260                    if(keyBits <= NEXT_BITS) {
1261                        levelBitsList |= keyBits << shift;
1262                        break;
1263                    } else if(keyBits < (NEXT_BITS + 3)) {
1264                        levelBitsList |= (0x30 | (keyBits - 3)) << shift;
1265                        break;
1266                    } else {
1267                        levelBitsList |= NEXT_BITS << shift;
1268                        keyBits -= NEXT_BITS;
1269                        shift += 4;
1270                    }
1271                }
1272            }
1273        }
1274
1275        /**
1276         * Turns a resource integer (with unused bits in the middle)
1277         * into a key with fewer bits (at most keyBits).
1278         */
1279        private int makeKey(int res) {
1280            // It is possible for resources of different types in the 16-bit array
1281            // to share a start offset; distinguish between those with a 2-bit value,
1282            // as a tie-breaker in the bits just above the highest possible offset.
1283            // It is not possible for "regular" resources of different types
1284            // to share a start offset with each other,
1285            // but offsets for 16-bit and "regular" resources overlap;
1286            // use 2-bit value 0 for "regular" resources.
1287            int type = RES_GET_TYPE(res);
1288            int miniType =
1289                    (type == ICUResourceBundle.STRING_V2) ? 1 :
1290                        (type == ICUResourceBundle.TABLE16) ? 3 :
1291                            (type == ICUResourceBundle.ARRAY16) ? 2 : 0;
1292            return RES_GET_OFFSET(res) | (miniType << maxOffsetBits);
1293        }
1294
1295        private int findSimple(int key) {
1296            // With Java 6, return Arrays.binarySearch(keys, 0, length, key).
1297            int start = 0;
1298            int limit = length;
1299            while((limit - start) > 8) {
1300                int mid = (start + limit) / 2;
1301                if(key < keys[mid]) {
1302                    limit = mid;
1303                } else {
1304                    start = mid;
1305                }
1306            }
1307            // For a small number of items, linear search should be a little faster.
1308            while(start < limit) {
1309                int k = keys[start];
1310                if(key < k) {
1311                    return ~start;
1312                }
1313                if(key == k) {
1314                    return start;
1315                }
1316                ++start;
1317            }
1318            return ~start;
1319        }
1320
1321        @SuppressWarnings("unchecked")
1322        synchronized Object get(int res) {
1323            // Integers and empty resources need not be cached.
1324            // The cache itself uses res=0 for "no match".
1325            assert RES_GET_OFFSET(res) != 0;
1326            Object value;
1327            if(length >= 0) {
1328                int index = findSimple(res);
1329                if(index >= 0) {
1330                    value = values[index];
1331                } else {
1332                    return null;
1333                }
1334            } else {
1335                value = rootLevel.get(makeKey(res));
1336                if(value == null) {
1337                    return null;
1338                }
1339            }
1340            if(value instanceof SoftReference) {
1341                value = ((SoftReference<Object>)value).get();
1342            }
1343            return value;  // null if the reference was cleared
1344        }
1345
1346        synchronized Object putIfAbsent(int res, Object item, int size) {
1347            if(length >= 0) {
1348                int index = findSimple(res);
1349                if(index >= 0) {
1350                    return putIfCleared(values, index, item, size);
1351                } else if(length < SIMPLE_LENGTH) {
1352                    index = ~index;
1353                    if(index < length) {
1354                        System.arraycopy(keys, index, keys, index + 1, length - index);
1355                        System.arraycopy(values, index, values, index + 1, length - index);
1356                    }
1357                    ++length;
1358                    keys[index] = res;
1359                    values[index] = storeDirectly(size) ? item : new SoftReference<Object>(item);
1360                    return item;
1361                } else /* not found && length == SIMPLE_LENGTH */ {
1362                    // Grow to become trie-like.
1363                    rootLevel = new Level(levelBitsList, 0);
1364                    for(int i = 0; i < SIMPLE_LENGTH; ++i) {
1365                        rootLevel.putIfAbsent(makeKey(keys[i]), values[i], 0);
1366                    }
1367                    keys = null;
1368                    values = null;
1369                    length = -1;
1370                }
1371            }
1372            return rootLevel.putIfAbsent(makeKey(res), item, size);
1373        }
1374    }
1375
1376    private static final String ICU_RESOURCE_SUFFIX = ".res";
1377
1378    /**
1379     * Gets the full name of the resource with suffix.
1380     */
1381    public static String getFullName(String baseName, String localeName) {
1382        if (baseName == null || baseName.length() == 0) {
1383            if (localeName.length() == 0) {
1384                return localeName = ULocale.getDefault().toString();
1385            }
1386            return localeName + ICU_RESOURCE_SUFFIX;
1387        } else {
1388            if (baseName.indexOf('.') == -1) {
1389                if (baseName.charAt(baseName.length() - 1) != '/') {
1390                    return baseName + "/" + localeName + ICU_RESOURCE_SUFFIX;
1391                } else {
1392                    return baseName + localeName + ICU_RESOURCE_SUFFIX;
1393                }
1394            } else {
1395                baseName = baseName.replace('.', '/');
1396                if (localeName.length() == 0) {
1397                    return baseName + ICU_RESOURCE_SUFFIX;
1398                } else {
1399                    return baseName + "_" + localeName + ICU_RESOURCE_SUFFIX;
1400                }
1401            }
1402        }
1403    }
1404}
1405