blob: c8f1d7935b7cce9c3f6ff7871f0df4d8a03ce0e6 [file] [log] [blame]
/*
*******************************************************************************
* Copyright (C) 2015, 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() {}
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 void setBytes(byte[] keyBytes, int keyOffset) {
bytes = keyBytes;
offset = keyOffset;
for (length = 0; keyBytes[keyOffset + length] != 0; ++length) {}
s = null;
}
/**
* Mutates this key to an empty resource key string.
*/
public void setToEmpty() {
bytes = null;
offset = length = 0;
s = null;
}
/**
* {@inheritDoc}
* Does not clone the byte array.
*/
@Override
public Key clone() {
try {
return (Key)super.clone();
} catch (CloneNotSupportedException cannotOccur) {
return null;
}
}
// TODO: Java 6: @Override
public char charAt(int i) {
assert(0 <= i && i < length);
return (char)bytes[offset + i];
}
// TODO: Java 6: @Override
public int length() {
return length;
}
// TODO: Java 6: @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.
*/
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;
}
// TODO: Java 6: @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 = (int)charAt(i) - (int)cs.charAt(i);
if (diff != 0) {
return diff;
}
}
return length - csLength;
}
}
/**
* 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();
/**
* 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: // should not occur
return "(array)";
case UResourceBundle.TABLE: // should not occur
return "(table)";
default: // should not occur
return "???";
}
}
}
/**
* Sink for ICU resource array contents.
* The base class does nothing.
*
* <p>Nested arrays and tables are stored as nested sinks,
* never put() as {@link Value} items.
*/
public static class ArraySink {
/**
* Adds a value from a resource array.
*
* @param index of the resource array item
* @param value resource value
*/
public void put(int index, Value value) {}
/**
* Returns a nested resource array at the array index as another sink.
* Creates the sink if none exists for the key.
* Returns null if nested arrays are not supported.
* The default implementation always returns null.
*
* @param index of the resource array item
* @param size number of array items
* @return nested-array sink, or null
*/
public ArraySink getOrCreateArraySink(int index, int size) {
return null;
}
/**
* Returns a nested resource table at the array index as another sink.
* Creates the sink if none exists for the key.
* Returns null if nested tables are not supported.
* The default implementation always returns null.
*
* @param index of the resource array item
* @param initialSize size hint for creating the sink if necessary
* @return nested-table sink, or null
*/
public TableSink getOrCreateTableSink(int index, int initialSize) {
return null;
}
/**
* "Leaves" the array.
* Indicates that all of the resources and sub-resources of the current array
* have been enumerated.
*/
public void leave() {}
}
/**
* Sink for ICU resource table contents.
* The base class does nothing.
*
* <p>Nested arrays and tables are stored as nested sinks,
* never put() as {@link Value} items.
*/
public static class TableSink {
/**
* Adds a key-value pair from a resource table.
*
* @param key resource key string
* @param value resource value
*/
public void put(Key key, Value value) {}
/**
* Adds a no-fallback/no-inheritance marker for this key.
* Used for CLDR no-fallback data values of "∅∅∅"
* when enumerating tables with fallback from the specific resource bundle to root.
*
* <p>The default implementation does nothing.
*
* @param key to be removed
*/
public void putNoFallback(Key key) {}
/**
* Returns a nested resource array for the key as another sink.
* Creates the sink if none exists for the key.
* Returns null if nested arrays are not supported.
* The default implementation always returns null.
*
* @param key resource key string
* @param size number of array items
* @return nested-array sink, or null
*/
public ArraySink getOrCreateArraySink(Key key, int size) {
return null;
}
/**
* Returns a nested resource table for the key as another sink.
* Creates the sink if none exists for the key.
* Returns null if nested tables are not supported.
* The default implementation always returns null.
*
* @param key resource key string
* @param initialSize size hint for creating the sink if necessary
* @return nested-table sink, or null
*/
public TableSink getOrCreateTableSink(Key key, int initialSize) {
return null;
}
/**
* "Leaves" the table.
* Indicates that all of the resources and sub-resources of the current table
* have been enumerated.
*/
public void leave() {}
}
}