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