• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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