1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 /* 4 ******************************************************************************* 5 * Copyright (C) 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 array resource. 294 */ getSize()295 public int getSize(); 296 /** 297 * @param i Array 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 305 /** 306 * Represents a resource bundle item's value. 307 * Avoids object creations as much as possible. 308 * Mutable, not thread-safe. 309 */ 310 public static abstract class Value { Value()311 protected Value() {} 312 313 /** 314 * @return ICU resource type like {@link UResourceBundle#getType()}, 315 * for example, {@link UResourceBundle#STRING} 316 */ getType()317 public abstract int getType(); 318 319 /** 320 * @see UResourceBundle#getString() 321 * @throws UResourceTypeMismatchException if this is not a string resource 322 */ getString()323 public abstract String getString(); 324 325 /** 326 * @throws UResourceTypeMismatchException if this is not an alias resource 327 */ getAliasString()328 public abstract String getAliasString(); 329 330 /** 331 * @see UResourceBundle#getInt() 332 * @throws UResourceTypeMismatchException if this is not an integer resource 333 */ getInt()334 public abstract int getInt(); 335 336 /** 337 * @see UResourceBundle#getUInt() 338 * @throws UResourceTypeMismatchException if this is not an integer resource 339 */ getUInt()340 public abstract int getUInt(); 341 342 /** 343 * @see UResourceBundle#getIntVector() 344 * @throws UResourceTypeMismatchException if this is not an intvector resource 345 */ getIntVector()346 public abstract int[] getIntVector(); 347 348 /** 349 * @see UResourceBundle#getBinary() 350 * @throws UResourceTypeMismatchException if this is not a binary-blob resource 351 */ getBinary()352 public abstract ByteBuffer getBinary(); 353 354 /** 355 * @throws UResourceTypeMismatchException if this is not an array resource 356 */ getArray()357 public abstract Array getArray(); 358 359 /** 360 * @throws UResourceTypeMismatchException if this is not a table resource 361 */ getTable()362 public abstract Table getTable(); 363 364 /** 365 * Is this a no-fallback/no-inheritance marker string? 366 * Such a marker is used for CLDR no-fallback data values of "∅∅∅" 367 * when enumerating tables with fallback from the specific resource bundle to root. 368 * 369 * @return true if this is a no-inheritance marker string 370 */ isNoInheritanceMarker()371 public abstract boolean isNoInheritanceMarker(); 372 373 /** 374 * @return the array of strings in this array resource. 375 * @see UResourceBundle#getStringArray() 376 * @throws UResourceTypeMismatchException if this is not an array resource 377 * or if any of the array items is not a string 378 */ getStringArray()379 public abstract String[] getStringArray(); 380 381 /** 382 * Same as 383 * <pre> 384 * if (getType() == STRING) { 385 * return new String[] { getString(); } 386 * } else { 387 * return getStringArray(); 388 * } 389 * </pre> 390 * 391 * @see #getString() 392 * @see #getStringArray() 393 * @throws UResourceTypeMismatchException if this is 394 * neither a string resource nor an array resource containing strings 395 */ getStringArrayOrStringAsArray()396 public abstract String[] getStringArrayOrStringAsArray(); 397 398 /** 399 * Same as 400 * <pre> 401 * if (getType() == STRING) { 402 * return getString(); 403 * } else { 404 * return getStringArray()[0]; 405 * } 406 * </pre> 407 * 408 * @see #getString() 409 * @see #getStringArray() 410 * @throws UResourceTypeMismatchException if this is 411 * neither a string resource nor an array resource containing strings 412 */ getStringOrFirstOfArray()413 public abstract String getStringOrFirstOfArray(); 414 415 /** 416 * Only for debugging. 417 */ 418 @Override toString()419 public String toString() { 420 switch(getType()) { 421 case UResourceBundle.STRING: 422 return getString(); 423 case UResourceBundle.INT: 424 return Integer.toString(getInt()); 425 case UResourceBundle.INT_VECTOR: 426 int[] iv = getIntVector(); 427 StringBuilder sb = new StringBuilder("["); 428 sb.append(iv.length).append("]{"); 429 if (iv.length != 0) { 430 sb.append(iv[0]); 431 for (int i = 1; i < iv.length; ++i) { 432 sb.append(", ").append(iv[i]); 433 } 434 } 435 return sb.append('}').toString(); 436 case UResourceBundle.BINARY: 437 return "(binary blob)"; 438 case UResourceBundle.ARRAY: 439 return "(array)"; 440 case UResourceBundle.TABLE: 441 return "(table)"; 442 default: // should not occur 443 return "???"; 444 } 445 } 446 } 447 448 /** 449 * Sink for ICU resource bundle contents. 450 */ 451 public static abstract class Sink { 452 /** 453 * Called once for each bundle (child-parent-...-root). 454 * The value is normally an array or table resource, 455 * and implementations of this method normally iterate over the 456 * tree of resource items stored there. 457 * 458 * @param key Initially the key string of the enumeration-start resource. 459 * Empty if the enumeration starts at the top level of the bundle. 460 * Reuse for output values from Array and Table getters. 461 * @param value Call getArray() or getTable() as appropriate. 462 * Then reuse for output values from Array and Table getters. 463 * @param noFallback true if the bundle has no parent; 464 * that is, its top-level table has the nofallback attribute, 465 * or it is the root bundle of a locale tree. 466 */ put(Key key, Value value, boolean noFallback)467 public abstract void put(Key key, Value value, boolean noFallback); 468 } 469 } 470