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 */
9package com.ibm.icu.impl;
10
11import java.nio.ByteBuffer;
12
13import com.ibm.icu.util.UResourceBundle;
14import 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 */
22public 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