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