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