Home | History | Annotate | Download | only in impl
      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          */
     43         public Key() {
     44             s = "";
     45         }
     46 
     47         /**
     48          * Constructs a resource key object equal to the given string.
     49          */
     50         public Key(String s) {
     51             setString(s);
     52         }
     53 
     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          */
     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          */
     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          */
     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
    116         public Key clone() {
    117             try {
    118                 return (Key)super.clone();
    119             } catch (CloneNotSupportedException cannotOccur) {
    120                 return null;
    121             }
    122         }
    123 
    124         @Override
    125         public char charAt(int i) {
    126             assert(0 <= i && i < length);
    127             return (char)bytes[offset + i];
    128         }
    129 
    130         @Override
    131         public int length() {
    132             return length;
    133         }
    134 
    135         @Override
    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
    146         public String toString() {
    147             if (s == null) {
    148                 s = internalSubString(0, length);
    149             }
    150             return s;
    151         }
    152 
    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          */
    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          */
    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 
    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 
    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
    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 
    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 
    218         public boolean startsWith(CharSequence cs) {
    219             int csLength = cs.length();
    220             return csLength <= length && regionMatches(0, cs, csLength);
    221         }
    222 
    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          */
    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
    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
    253         public int compareTo(Key other) {
    254             return compareTo((CharSequence)other);
    255         }
    256 
    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          */
    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          */
    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          */
    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          */
    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 {
    311         protected Value() {}
    312 
    313         /**
    314          * @return ICU resource type like {@link UResourceBundle#getType()},
    315          *     for example, {@link UResourceBundle#STRING}
    316          */
    317         public abstract int getType();
    318 
    319         /**
    320          * @see UResourceBundle#getString()
    321          * @throws UResourceTypeMismatchException if this is not a string resource
    322          */
    323         public abstract String getString();
    324 
    325         /**
    326          * @throws UResourceTypeMismatchException if this is not an alias resource
    327          */
    328         public abstract String getAliasString();
    329 
    330         /**
    331          * @see UResourceBundle#getInt()
    332          * @throws UResourceTypeMismatchException if this is not an integer resource
    333          */
    334         public abstract int getInt();
    335 
    336         /**
    337          * @see UResourceBundle#getUInt()
    338          * @throws UResourceTypeMismatchException if this is not an integer resource
    339          */
    340         public abstract int getUInt();
    341 
    342         /**
    343          * @see UResourceBundle#getIntVector()
    344          * @throws UResourceTypeMismatchException if this is not an intvector resource
    345          */
    346         public abstract int[] getIntVector();
    347 
    348         /**
    349          * @see UResourceBundle#getBinary()
    350          * @throws UResourceTypeMismatchException if this is not a binary-blob resource
    351          */
    352         public abstract ByteBuffer getBinary();
    353 
    354         /**
    355          * @throws UResourceTypeMismatchException if this is not an array resource
    356          */
    357         public abstract Array getArray();
    358 
    359         /**
    360          * @throws UResourceTypeMismatchException if this is not a table resource
    361          */
    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          */
    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          */
    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          */
    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          */
    413         public abstract String getStringOrFirstOfArray();
    414 
    415         /**
    416          * Only for debugging.
    417          */
    418         @Override
    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          */
    467         public abstract void put(Key key, Value value, boolean noFallback);
    468     }
    469 }
    470