| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| /* |
| ******************************************************************************* |
| * Copyright (C) 2015-2016, International Business Machines Corporation and |
| * others. All Rights Reserved. |
| ******************************************************************************* |
| */ |
| package com.ibm.icu.impl; |
| |
| import java.nio.ByteBuffer; |
| |
| import com.ibm.icu.util.UResourceBundle; |
| import com.ibm.icu.util.UResourceTypeMismatchException; |
| |
| // Class UResource is named consistently with the public class UResourceBundle, |
| // in case we want to make it public at some point. |
| |
| /** |
| * ICU resource bundle key and value types. |
| */ |
| public final class UResource { |
| /** |
| * Represents a resource bundle item's key string. |
| * Avoids object creations as much as possible. |
| * Mutable, not thread-safe. |
| * For permanent storage, use clone() or toString(). |
| */ |
| public static final class Key implements CharSequence, Cloneable, Comparable<Key> { |
| // Stores a reference to the resource bundle key string bytes array, |
| // with an offset of the key, to avoid creating a String object |
| // until one is really needed. |
| // Alternatively, we could try to always just get the key String object, |
| // and cache it in the reader, and see if that performs better or worse. |
| private byte[] bytes; |
| private int offset; |
| private int length; |
| private String s; |
| |
| /** |
| * Constructs an empty resource key string object. |
| */ |
| public Key() { |
| s = ""; |
| } |
| |
| /** |
| * Constructs a resource key object equal to the given string. |
| */ |
| public Key(String s) { |
| setString(s); |
| } |
| |
| private Key(byte[] keyBytes, int keyOffset, int keyLength) { |
| bytes = keyBytes; |
| offset = keyOffset; |
| length = keyLength; |
| } |
| |
| /** |
| * Mutates this key for a new NUL-terminated resource key string. |
| * The corresponding ASCII-character bytes are not copied and |
| * must not be changed during the lifetime of this key |
| * (or until the next setBytes() call) |
| * and lifetimes of subSequences created from this key. |
| * |
| * @param keyBytes new key string byte array |
| * @param keyOffset new key string offset |
| */ |
| public Key setBytes(byte[] keyBytes, int keyOffset) { |
| bytes = keyBytes; |
| offset = keyOffset; |
| for (length = 0; keyBytes[keyOffset + length] != 0; ++length) {} |
| s = null; |
| return this; |
| } |
| |
| /** |
| * Mutates this key to an empty resource key string. |
| */ |
| public Key setToEmpty() { |
| bytes = null; |
| offset = length = 0; |
| s = ""; |
| return this; |
| } |
| |
| /** |
| * Mutates this key to be equal to the given string. |
| */ |
| public Key setString(String s) { |
| if (s.isEmpty()) { |
| setToEmpty(); |
| } else { |
| bytes = new byte[s.length()]; |
| offset = 0; |
| length = s.length(); |
| for (int i = 0; i < length; ++i) { |
| char c = s.charAt(i); |
| if (c <= 0x7f) { |
| bytes[i] = (byte)c; |
| } else { |
| throw new IllegalArgumentException('\"' + s + "\" is not an ASCII string"); |
| } |
| } |
| this.s = s; |
| } |
| return this; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * Does not clone the byte array. |
| */ |
| @Override |
| public Key clone() { |
| try { |
| return (Key)super.clone(); |
| } catch (CloneNotSupportedException cannotOccur) { |
| return null; |
| } |
| } |
| |
| @Override |
| public char charAt(int i) { |
| assert(0 <= i && i < length); |
| return (char)bytes[offset + i]; |
| } |
| |
| @Override |
| public int length() { |
| return length; |
| } |
| |
| @Override |
| public Key subSequence(int start, int end) { |
| assert(0 <= start && start < length); |
| assert(start <= end && end <= length); |
| return new Key(bytes, offset + start, end - start); |
| } |
| |
| /** |
| * Creates/caches/returns this resource key string as a Java String. |
| */ |
| @Override |
| public String toString() { |
| if (s == null) { |
| s = internalSubString(0, length); |
| } |
| return s; |
| } |
| |
| private String internalSubString(int start, int end) { |
| StringBuilder sb = new StringBuilder(end - start); |
| for (int i = start; i < end; ++i) { |
| sb.append((char)bytes[offset + i]); |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Creates a new Java String for a sub-sequence of this resource key string. |
| */ |
| public String substring(int start) { |
| assert(0 <= start && start < length); |
| return internalSubString(start, length); |
| } |
| |
| /** |
| * Creates a new Java String for a sub-sequence of this resource key string. |
| */ |
| public String substring(int start, int end) { |
| assert(0 <= start && start < length); |
| assert(start <= end && end <= length); |
| return internalSubString(start, end); |
| } |
| |
| private boolean regionMatches(byte[] otherBytes, int otherOffset, int n) { |
| for (int i = 0; i < n; ++i) { |
| if (bytes[offset + i] != otherBytes[otherOffset + i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private boolean regionMatches(int start, CharSequence cs, int n) { |
| for (int i = 0; i < n; ++i) { |
| if (bytes[offset + start + i] != cs.charAt(i)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (other == null) { |
| return false; |
| } else if (this == other) { |
| return true; |
| } else if (other instanceof Key) { |
| Key otherKey = (Key)other; |
| return length == otherKey.length && |
| regionMatches(otherKey.bytes, otherKey.offset, length); |
| } else { |
| return false; |
| } |
| } |
| |
| public boolean contentEquals(CharSequence cs) { |
| if (cs == null) { |
| return false; |
| } |
| return this == cs || (cs.length() == length && regionMatches(0, cs, length)); |
| } |
| |
| public boolean startsWith(CharSequence cs) { |
| int csLength = cs.length(); |
| return csLength <= length && regionMatches(0, cs, csLength); |
| } |
| |
| public boolean endsWith(CharSequence cs) { |
| int csLength = cs.length(); |
| return csLength <= length && regionMatches(length - csLength, cs, csLength); |
| } |
| |
| /** |
| * @return true if the substring of this key starting from the offset |
| * contains the same characters as the other sequence. |
| */ |
| public boolean regionMatches(int start, CharSequence cs) { |
| int csLength = cs.length(); |
| return csLength == (length - start) && regionMatches(start, cs, csLength); |
| } |
| |
| @Override |
| public int hashCode() { |
| // Never return s.hashCode(), so that |
| // Key.hashCode() is the same whether we have cached s or not. |
| if (length == 0) { |
| return 0; |
| } |
| |
| int h = bytes[offset]; |
| for (int i = 1; i < length; ++i) { |
| h = 37 * h + bytes[offset]; |
| } |
| return h; |
| } |
| |
| @Override |
| public int compareTo(Key other) { |
| return compareTo((CharSequence)other); |
| } |
| |
| public int compareTo(CharSequence cs) { |
| int csLength = cs.length(); |
| int minLength = length <= csLength ? length : csLength; |
| for (int i = 0; i < minLength; ++i) { |
| int diff = charAt(i) - cs.charAt(i); |
| if (diff != 0) { |
| return diff; |
| } |
| } |
| return length - csLength; |
| } |
| } |
| |
| /** |
| * Interface for iterating over a resource bundle array resource. |
| * Does not use Java Iterator to reduce object creations. |
| */ |
| public interface Array { |
| /** |
| * @return The number of items in the array resource. |
| */ |
| public int getSize(); |
| /** |
| * @param i Array item index. |
| * @param value Output-only, receives the value of the i'th item. |
| * @return true if i is non-negative and less than getSize(). |
| */ |
| public boolean getValue(int i, Value value); |
| } |
| |
| /** |
| * Interface for iterating over a resource bundle table resource. |
| * Does not use Java Iterator to reduce object creations. |
| */ |
| public interface Table { |
| /** |
| * @return The number of items in the table resource. |
| */ |
| public int getSize(); |
| /** |
| * @param i Table item index. |
| * @param key Output-only, receives the key of the i'th item. |
| * @param value Output-only, receives the value of the i'th item. |
| * @return true if i is non-negative and less than getSize(). |
| */ |
| public boolean getKeyAndValue(int i, Key key, Value value); |
| /** |
| * @param key Key string to find in the table. |
| * @param value Output-only, receives the value of the item with that key. |
| * @return true if the table contains the key. |
| */ |
| public boolean findValue(CharSequence key, Value value); |
| } |
| |
| /** |
| * Represents a resource bundle item's value. |
| * Avoids object creations as much as possible. |
| * Mutable, not thread-safe. |
| */ |
| public static abstract class Value { |
| protected Value() {} |
| |
| /** |
| * @return ICU resource type like {@link UResourceBundle#getType()}, |
| * for example, {@link UResourceBundle#STRING} |
| */ |
| public abstract int getType(); |
| |
| /** |
| * @see UResourceBundle#getString() |
| * @throws UResourceTypeMismatchException if this is not a string resource |
| */ |
| public abstract String getString(); |
| |
| /** |
| * @throws UResourceTypeMismatchException if this is not an alias resource |
| */ |
| public abstract String getAliasString(); |
| |
| /** |
| * @see UResourceBundle#getInt() |
| * @throws UResourceTypeMismatchException if this is not an integer resource |
| */ |
| public abstract int getInt(); |
| |
| /** |
| * @see UResourceBundle#getUInt() |
| * @throws UResourceTypeMismatchException if this is not an integer resource |
| */ |
| public abstract int getUInt(); |
| |
| /** |
| * @see UResourceBundle#getIntVector() |
| * @throws UResourceTypeMismatchException if this is not an intvector resource |
| */ |
| public abstract int[] getIntVector(); |
| |
| /** |
| * @see UResourceBundle#getBinary() |
| * @throws UResourceTypeMismatchException if this is not a binary-blob resource |
| */ |
| public abstract ByteBuffer getBinary(); |
| |
| /** |
| * @throws UResourceTypeMismatchException if this is not an array resource |
| */ |
| public abstract Array getArray(); |
| |
| /** |
| * @throws UResourceTypeMismatchException if this is not a table resource |
| */ |
| public abstract Table getTable(); |
| |
| /** |
| * Is this a no-fallback/no-inheritance marker string? |
| * Such a marker is used for CLDR no-fallback data values of "∅∅∅" |
| * when enumerating tables with fallback from the specific resource bundle to root. |
| * |
| * @return true if this is a no-inheritance marker string |
| */ |
| public abstract boolean isNoInheritanceMarker(); |
| |
| /** |
| * @return the array of strings in this array resource. |
| * @see UResourceBundle#getStringArray() |
| * @throws UResourceTypeMismatchException if this is not an array resource |
| * or if any of the array items is not a string |
| */ |
| public abstract String[] getStringArray(); |
| |
| /** |
| * Same as |
| * <pre> |
| * if (getType() == STRING) { |
| * return new String[] { getString(); } |
| * } else { |
| * return getStringArray(); |
| * } |
| * </pre> |
| * |
| * @see #getString() |
| * @see #getStringArray() |
| * @throws UResourceTypeMismatchException if this is |
| * neither a string resource nor an array resource containing strings |
| */ |
| public abstract String[] getStringArrayOrStringAsArray(); |
| |
| /** |
| * Same as |
| * <pre> |
| * if (getType() == STRING) { |
| * return getString(); |
| * } else { |
| * return getStringArray()[0]; |
| * } |
| * </pre> |
| * |
| * @see #getString() |
| * @see #getStringArray() |
| * @throws UResourceTypeMismatchException if this is |
| * neither a string resource nor an array resource containing strings |
| */ |
| public abstract String getStringOrFirstOfArray(); |
| |
| /** |
| * Only for debugging. |
| */ |
| @Override |
| public String toString() { |
| switch(getType()) { |
| case UResourceBundle.STRING: |
| return getString(); |
| case UResourceBundle.INT: |
| return Integer.toString(getInt()); |
| case UResourceBundle.INT_VECTOR: |
| int[] iv = getIntVector(); |
| StringBuilder sb = new StringBuilder("["); |
| sb.append(iv.length).append("]{"); |
| if (iv.length != 0) { |
| sb.append(iv[0]); |
| for (int i = 1; i < iv.length; ++i) { |
| sb.append(", ").append(iv[i]); |
| } |
| } |
| return sb.append('}').toString(); |
| case UResourceBundle.BINARY: |
| return "(binary blob)"; |
| case UResourceBundle.ARRAY: |
| return "(array)"; |
| case UResourceBundle.TABLE: |
| return "(table)"; |
| default: // should not occur |
| return "???"; |
| } |
| } |
| } |
| |
| /** |
| * Sink for ICU resource bundle contents. |
| */ |
| public static abstract class Sink { |
| /** |
| * Called once for each bundle (child-parent-...-root). |
| * The value is normally an array or table resource, |
| * and implementations of this method normally iterate over the |
| * tree of resource items stored there. |
| * |
| * @param key Initially the key string of the enumeration-start resource. |
| * Empty if the enumeration starts at the top level of the bundle. |
| * Reuse for output values from Array and Table getters. |
| * @param value Call getArray() or getTable() as appropriate. |
| * Then reuse for output values from Array and Table getters. |
| * @param noFallback true if the bundle has no parent; |
| * that is, its top-level table has the nofallback attribute, |
| * or it is the root bundle of a locale tree. |
| */ |
| public abstract void put(Key key, Value value, boolean noFallback); |
| } |
| } |