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