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