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