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