1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2015-2016, International Business Machines Corporation and 6 * others. All Rights Reserved. 7 ******************************************************************************* 8 */ 9 package com.ibm.icu.impl; 10 11 import java.nio.ByteBuffer; 12 13 import com.ibm.icu.util.UResourceBundle; 14 import com.ibm.icu.util.UResourceTypeMismatchException; 15 16 // Class UResource is named consistently with the public class UResourceBundle, 17 // in case we want to make it public at some point. 18 19 /** 20 * ICU resource bundle key and value types. 21 */ 22 public final class UResource { 23 /** 24 * Represents a resource bundle item's key string. 25 * Avoids object creations as much as possible. 26 * Mutable, not thread-safe. 27 * For permanent storage, use clone() or toString(). 28 */ 29 public static final class Key implements CharSequence, Cloneable, Comparable<Key> { 30 // Stores a reference to the resource bundle key string bytes array, 31 // with an offset of the key, to avoid creating a String object 32 // until one is really needed. 33 // Alternatively, we could try to always just get the key String object, 34 // and cache it in the reader, and see if that performs better or worse. 35 private byte[] bytes; 36 private int offset; 37 private int length; 38 private String s; 39 40 /** 41 * Constructs an empty resource key string object. 42 */ Key()43 public Key() { 44 s = ""; 45 } 46 47 /** 48 * Constructs a resource key object equal to the given string. 49 */ Key(String s)50 public Key(String s) { 51 setString(s); 52 } 53 Key(byte[] keyBytes, int keyOffset, int keyLength)54 private Key(byte[] keyBytes, int keyOffset, int keyLength) { 55 bytes = keyBytes; 56 offset = keyOffset; 57 length = keyLength; 58 } 59 60 /** 61 * Mutates this key for a new NUL-terminated resource key string. 62 * The corresponding ASCII-character bytes are not copied and 63 * must not be changed during the lifetime of this key 64 * (or until the next setBytes() call) 65 * and lifetimes of subSequences created from this key. 66 * 67 * @param keyBytes new key string byte array 68 * @param keyOffset new key string offset 69 */ setBytes(byte[] keyBytes, int keyOffset)70 public Key setBytes(byte[] keyBytes, int keyOffset) { 71 bytes = keyBytes; 72 offset = keyOffset; 73 for (length = 0; keyBytes[keyOffset + length] != 0; ++length) {} 74 s = null; 75 return this; 76 } 77 78 /** 79 * Mutates this key to an empty resource key string. 80 */ setToEmpty()81 public Key setToEmpty() { 82 bytes = null; 83 offset = length = 0; 84 s = ""; 85 return this; 86 } 87 88 /** 89 * Mutates this key to be equal to the given string. 90 */ setString(String s)91 public Key setString(String s) { 92 if (s.isEmpty()) { 93 setToEmpty(); 94 } else { 95 bytes = new byte[s.length()]; 96 offset = 0; 97 length = s.length(); 98 for (int i = 0; i < length; ++i) { 99 char c = s.charAt(i); 100 if (c <= 0x7f) { 101 bytes[i] = (byte)c; 102 } else { 103 throw new IllegalArgumentException('\"' + s + "\" is not an ASCII string"); 104 } 105 } 106 this.s = s; 107 } 108 return this; 109 } 110 111 /** 112 * {@inheritDoc} 113 * Does not clone the byte array. 114 */ 115 @Override clone()116 public Key clone() { 117 try { 118 return (Key)super.clone(); 119 } catch (CloneNotSupportedException cannotOccur) { 120 return null; 121 } 122 } 123 124 @Override charAt(int i)125 public char charAt(int i) { 126 assert(0 <= i && i < length); 127 return (char)bytes[offset + i]; 128 } 129 130 @Override length()131 public int length() { 132 return length; 133 } 134 135 @Override subSequence(int start, int end)136 public Key subSequence(int start, int end) { 137 assert(0 <= start && start < length); 138 assert(start <= end && end <= length); 139 return new Key(bytes, offset + start, end - start); 140 } 141 142 /** 143 * Creates/caches/returns this resource key string as a Java String. 144 */ 145 @Override toString()146 public String toString() { 147 if (s == null) { 148 s = internalSubString(0, length); 149 } 150 return s; 151 } 152 internalSubString(int start, int end)153 private String internalSubString(int start, int end) { 154 StringBuilder sb = new StringBuilder(end - start); 155 for (int i = start; i < end; ++i) { 156 sb.append((char)bytes[offset + i]); 157 } 158 return sb.toString(); 159 } 160 161 /** 162 * Creates a new Java String for a sub-sequence of this resource key string. 163 */ substring(int start)164 public String substring(int start) { 165 assert(0 <= start && start < length); 166 return internalSubString(start, length); 167 } 168 169 /** 170 * Creates a new Java String for a sub-sequence of this resource key string. 171 */ substring(int start, int end)172 public String substring(int start, int end) { 173 assert(0 <= start && start < length); 174 assert(start <= end && end <= length); 175 return internalSubString(start, end); 176 } 177 regionMatches(byte[] otherBytes, int otherOffset, int n)178 private boolean regionMatches(byte[] otherBytes, int otherOffset, int n) { 179 for (int i = 0; i < n; ++i) { 180 if (bytes[offset + i] != otherBytes[otherOffset + i]) { 181 return false; 182 } 183 } 184 return true; 185 } 186 regionMatches(int start, CharSequence cs, int n)187 private boolean regionMatches(int start, CharSequence cs, int n) { 188 for (int i = 0; i < n; ++i) { 189 if (bytes[offset + start + i] != cs.charAt(i)) { 190 return false; 191 } 192 } 193 return true; 194 } 195 196 @Override equals(Object other)197 public boolean equals(Object other) { 198 if (other == null) { 199 return false; 200 } else if (this == other) { 201 return true; 202 } else if (other instanceof Key) { 203 Key otherKey = (Key)other; 204 return length == otherKey.length && 205 regionMatches(otherKey.bytes, otherKey.offset, length); 206 } else { 207 return false; 208 } 209 } 210 contentEquals(CharSequence cs)211 public boolean contentEquals(CharSequence cs) { 212 if (cs == null) { 213 return false; 214 } 215 return this == cs || (cs.length() == length && regionMatches(0, cs, length)); 216 } 217 startsWith(CharSequence cs)218 public boolean startsWith(CharSequence cs) { 219 int csLength = cs.length(); 220 return csLength <= length && regionMatches(0, cs, csLength); 221 } 222 endsWith(CharSequence cs)223 public boolean endsWith(CharSequence cs) { 224 int csLength = cs.length(); 225 return csLength <= length && regionMatches(length - csLength, cs, csLength); 226 } 227 228 /** 229 * @return true if the substring of this key starting from the offset 230 * contains the same characters as the other sequence. 231 */ regionMatches(int start, CharSequence cs)232 public boolean regionMatches(int start, CharSequence cs) { 233 int csLength = cs.length(); 234 return csLength == (length - start) && regionMatches(start, cs, csLength); 235 } 236 237 @Override hashCode()238 public int hashCode() { 239 // Never return s.hashCode(), so that 240 // Key.hashCode() is the same whether we have cached s or not. 241 if (length == 0) { 242 return 0; 243 } 244 245 int h = bytes[offset]; 246 for (int i = 1; i < length; ++i) { 247 h = 37 * h + bytes[offset]; 248 } 249 return h; 250 } 251 252 @Override compareTo(Key other)253 public int compareTo(Key other) { 254 return compareTo((CharSequence)other); 255 } 256 compareTo(CharSequence cs)257 public int compareTo(CharSequence cs) { 258 int csLength = cs.length(); 259 int minLength = length <= csLength ? length : csLength; 260 for (int i = 0; i < minLength; ++i) { 261 int diff = charAt(i) - cs.charAt(i); 262 if (diff != 0) { 263 return diff; 264 } 265 } 266 return length - csLength; 267 } 268 } 269 270 /** 271 * Interface for iterating over a resource bundle array resource. 272 * Does not use Java Iterator to reduce object creations. 273 */ 274 public interface Array { 275 /** 276 * @return The number of items in the array resource. 277 */ getSize()278 public int getSize(); 279 /** 280 * @param i Array item index. 281 * @param value Output-only, receives the value of the i'th item. 282 * @return true if i is non-negative and less than getSize(). 283 */ getValue(int i, Value value)284 public boolean getValue(int i, Value value); 285 } 286 287 /** 288 * Interface for iterating over a resource bundle table resource. 289 * Does not use Java Iterator to reduce object creations. 290 */ 291 public interface Table { 292 /** 293 * @return The number of items in the table resource. 294 */ getSize()295 public int getSize(); 296 /** 297 * @param i Table item index. 298 * @param key Output-only, receives the key of the i'th item. 299 * @param value Output-only, receives the value of the i'th item. 300 * @return true if i is non-negative and less than getSize(). 301 */ getKeyAndValue(int i, Key key, Value value)302 public boolean getKeyAndValue(int i, Key key, Value value); 303 /** 304 * @param key Key string to find in the table. 305 * @param value Output-only, receives the value of the item with that key. 306 * @return true if the table contains the key. 307 */ findValue(CharSequence key, Value value)308 public boolean findValue(CharSequence key, Value value); 309 } 310 311 /** 312 * Represents a resource bundle item's value. 313 * Avoids object creations as much as possible. 314 * Mutable, not thread-safe. 315 */ 316 public static abstract class Value { Value()317 protected Value() {} 318 319 /** 320 * @return ICU resource type like {@link UResourceBundle#getType()}, 321 * for example, {@link UResourceBundle#STRING} 322 */ getType()323 public abstract int getType(); 324 325 /** 326 * @see UResourceBundle#getString() 327 * @throws UResourceTypeMismatchException if this is not a string resource 328 */ getString()329 public abstract String getString(); 330 331 /** 332 * @throws UResourceTypeMismatchException if this is not an alias resource 333 */ getAliasString()334 public abstract String getAliasString(); 335 336 /** 337 * @see UResourceBundle#getInt() 338 * @throws UResourceTypeMismatchException if this is not an integer resource 339 */ getInt()340 public abstract int getInt(); 341 342 /** 343 * @see UResourceBundle#getUInt() 344 * @throws UResourceTypeMismatchException if this is not an integer resource 345 */ getUInt()346 public abstract int getUInt(); 347 348 /** 349 * @see UResourceBundle#getIntVector() 350 * @throws UResourceTypeMismatchException if this is not an intvector resource 351 */ getIntVector()352 public abstract int[] getIntVector(); 353 354 /** 355 * @see UResourceBundle#getBinary() 356 * @throws UResourceTypeMismatchException if this is not a binary-blob resource 357 */ getBinary()358 public abstract ByteBuffer getBinary(); 359 360 /** 361 * @throws UResourceTypeMismatchException if this is not an array resource 362 */ getArray()363 public abstract Array getArray(); 364 365 /** 366 * @throws UResourceTypeMismatchException if this is not a table resource 367 */ getTable()368 public abstract Table getTable(); 369 370 /** 371 * Is this a no-fallback/no-inheritance marker string? 372 * Such a marker is used for CLDR no-fallback data values of "∅∅∅" 373 * when enumerating tables with fallback from the specific resource bundle to root. 374 * 375 * @return true if this is a no-inheritance marker string 376 */ isNoInheritanceMarker()377 public abstract boolean isNoInheritanceMarker(); 378 379 /** 380 * @return the array of strings in this array resource. 381 * @see UResourceBundle#getStringArray() 382 * @throws UResourceTypeMismatchException if this is not an array resource 383 * or if any of the array items is not a string 384 */ getStringArray()385 public abstract String[] getStringArray(); 386 387 /** 388 * Same as 389 * <pre> 390 * if (getType() == STRING) { 391 * return new String[] { getString(); } 392 * } else { 393 * return getStringArray(); 394 * } 395 * </pre> 396 * 397 * @see #getString() 398 * @see #getStringArray() 399 * @throws UResourceTypeMismatchException if this is 400 * neither a string resource nor an array resource containing strings 401 */ getStringArrayOrStringAsArray()402 public abstract String[] getStringArrayOrStringAsArray(); 403 404 /** 405 * Same as 406 * <pre> 407 * if (getType() == STRING) { 408 * return getString(); 409 * } else { 410 * return getStringArray()[0]; 411 * } 412 * </pre> 413 * 414 * @see #getString() 415 * @see #getStringArray() 416 * @throws UResourceTypeMismatchException if this is 417 * neither a string resource nor an array resource containing strings 418 */ getStringOrFirstOfArray()419 public abstract String getStringOrFirstOfArray(); 420 421 /** 422 * Only for debugging. 423 */ 424 @Override toString()425 public String toString() { 426 switch(getType()) { 427 case UResourceBundle.STRING: 428 return getString(); 429 case UResourceBundle.INT: 430 return Integer.toString(getInt()); 431 case UResourceBundle.INT_VECTOR: 432 int[] iv = getIntVector(); 433 StringBuilder sb = new StringBuilder("["); 434 sb.append(iv.length).append("]{"); 435 if (iv.length != 0) { 436 sb.append(iv[0]); 437 for (int i = 1; i < iv.length; ++i) { 438 sb.append(", ").append(iv[i]); 439 } 440 } 441 return sb.append('}').toString(); 442 case UResourceBundle.BINARY: 443 return "(binary blob)"; 444 case UResourceBundle.ARRAY: 445 return "(array)"; 446 case UResourceBundle.TABLE: 447 return "(table)"; 448 default: // should not occur 449 return "???"; 450 } 451 } 452 } 453 454 /** 455 * Sink for ICU resource bundle contents. 456 */ 457 public static abstract class Sink { 458 /** 459 * Called once for each bundle (child-parent-...-root). 460 * The value is normally an array or table resource, 461 * and implementations of this method normally iterate over the 462 * tree of resource items stored there. 463 * 464 * @param key Initially the key string of the enumeration-start resource. 465 * Empty if the enumeration starts at the top level of the bundle. 466 * Reuse for output values from Array and Table getters. 467 * @param value Call getArray() or getTable() as appropriate. 468 * Then reuse for output values from Array and Table getters. 469 * @param noFallback true if the bundle has no parent; 470 * that is, its top-level table has the nofallback attribute, 471 * or it is the root bundle of a locale tree. 472 */ put(Key key, Value value, boolean noFallback)473 public abstract void put(Key key, Value value, boolean noFallback); 474 } 475 } 476