]> granicus.if.org Git - icu/commitdiff
ICU-11879 efficient enumeration of time zone names rather than fetching each name...
authorMarkus Scherer <markus.icu@gmail.com>
Tue, 1 Sep 2015 04:18:01 +0000 (04:18 +0000)
committerMarkus Scherer <markus.icu@gmail.com>
Tue, 1 Sep 2015 04:18:01 +0000 (04:18 +0000)
X-SVN-Rev: 37859

icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResource.java [new file with mode: 0644]
icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundle.java
icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundleImpl.java
icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundleReader.java
icu4j/main/classes/core/src/com/ibm/icu/impl/TimeZoneNamesImpl.java
icu4j/main/classes/core/src/com/ibm/icu/text/TimeZoneNames.java

diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResource.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResource.java
new file mode 100644 (file)
index 0000000..248fa0b
--- /dev/null
@@ -0,0 +1,398 @@
+/*
+ *******************************************************************************
+ * 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;
+
+/**
+ * ICU resource bundle key and value types.
+ */
+public final class ICUResource {
+    /**
+     * 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);
+        }
+
+        @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();
+
+        /**
+         * @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;
+        }
+    }
+
+    /**
+     * 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) {}
+
+        /**
+         * Removes any value for this key.
+         * Typically used for CLDR no-fallback data values of "∅∅∅"
+         * when enumerating tables with fallback from root to the specific resource bundle.
+         *
+         * <p>The default implementation does nothing.
+         *
+         * @param key to be removed
+         */
+        public void remove(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;
+        }
+    }
+}
index e07a765f3a13271b6300e6001eabf568fd0f93bc..49a18f87fde89683090ac57cb1ac96ef2f3bafec 100644 (file)
@@ -23,6 +23,7 @@ import java.util.MissingResourceException;
 import java.util.ResourceBundle;
 import java.util.Set;
 
+import com.ibm.icu.impl.ICUResourceBundleReader.ReaderValue;
 import com.ibm.icu.impl.URLHandler.URLVisitor;
 import com.ibm.icu.util.ULocale;
 import com.ibm.icu.util.UResourceBundle;
@@ -99,6 +100,9 @@ public  class ICUResourceBundle extends UResourceBundle {
     @Deprecated
     public static final String ICU_ZONE_BASE_NAME = ICUData.ICU_ZONE_BASE_NAME;
 
+    /**
+     * CLDR string value "∅∅∅" prevents fallback to the parent bundle.
+     */
     private static final String NO_INHERITANCE_MARKER = "\u2205\u2205\u2205";
 
     /**
@@ -443,6 +447,87 @@ public  class ICUResourceBundle extends UResourceBundle {
         return result;
     }
 
+    public void getAllArrayItemsWithFallback(String path, ICUResource.ArraySink sink)
+            throws MissingResourceException {
+        getAllContainerItemsWithFallback(path, sink, null);
+    }
+
+    public void getAllTableItemsWithFallback(String path, ICUResource.TableSink sink)
+            throws MissingResourceException {
+        getAllContainerItemsWithFallback(path, null, sink);
+    }
+
+    private void getAllContainerItemsWithFallback(
+            String path, ICUResource.ArraySink arraySink, ICUResource.TableSink tableSink)
+            throws MissingResourceException {
+        // Collect existing and parsed key objects into an array of keys,
+        // rather than assembling and parsing paths.
+        int numPathKeys = countPathKeys(path);  // How much deeper does the path go?
+        ICUResourceBundle rb;
+        if (numPathKeys == 0) {
+            rb = this;
+        } else {
+            // Get the keys for finding the target.
+            int depth = getResDepth();  // How deep are we in this bundle?
+            String[] pathKeys = new String[depth + numPathKeys];
+            getResPathKeys(path, numPathKeys, pathKeys, depth);
+            rb = findResourceWithFallback(pathKeys, depth, this, null);
+            if (rb == null) {
+                throw new MissingResourceException(
+                    "Can't find resource for bundle "
+                    + this.getClass().getName() + ", key " + getType(),
+                    path, getKey());
+            }
+        }
+        int expectedType = arraySink != null ? ARRAY : TABLE;
+        if (rb.getType() != expectedType) {
+            throw new UResourceTypeMismatchException("");
+        }
+        // Get all table items with fallback.
+        ICUResource.Key key = new ICUResource.Key();
+        ReaderValue readerValue = new ReaderValue();
+        rb.getAllContainerItemsWithFallback(key, readerValue, arraySink, tableSink);
+    }
+
+    private void getAllContainerItemsWithFallback(
+            ICUResource.Key key, ReaderValue readerValue,
+            ICUResource.ArraySink arraySink, ICUResource.TableSink tableSink) {
+        // We recursively enumerate parent-first,
+        // overriding parent items with child items.
+        // When we see the no-inheritance marker, then we remove the parent's item.
+        //
+        // It would be possible to recursively enumerate child-first,
+        // only storing parent items in the absence of child items,
+        // but then we would need to store the no-inheritance marker
+        // (or some placeholder for it)
+        // to prevent a parent item from being stored.
+        int expectedType = arraySink != null ? ARRAY : TABLE;
+        if (parent != null) {
+            ICUResourceBundle parentBundle = (ICUResourceBundle)parent;
+            ICUResourceBundle rb;
+            int depth = getResDepth();
+            if (depth == 0) {
+                rb = parentBundle;
+            } else {
+                // Re-fetch the path keys: They may differ from the original ones
+                // if we had followed an alias.
+                String[] pathKeys = new String[depth];
+                getResPathKeys(pathKeys, depth);
+                rb = findResourceWithFallback(pathKeys, 0, parentBundle, null);
+            }
+            if (rb != null && rb.getType() == expectedType) {
+                rb.getAllContainerItemsWithFallback(key, readerValue, arraySink, tableSink);
+            }
+        }
+        if (getType() == expectedType) {
+            if (arraySink != null) {
+                ((ICUResourceBundleImpl.ResourceArray)this).getAllItems(key, readerValue, arraySink);
+            } else if (tableSink != null) {
+                ((ICUResourceBundleImpl.ResourceTable)this).getAllItems(key, readerValue, tableSink);
+            }
+        }
+    }
+
     /**
      * Return a set of the locale names supported by a collection of resource
      * bundles.
@@ -807,11 +892,6 @@ public  class ICUResourceBundle extends UResourceBundle {
         if (path.length() == 0) {
             return null;
         }
-        ICUResourceBundle sub = null;
-        if (requested == null) {
-            requested = actualBundle;
-        }
-
         ICUResourceBundle base = (ICUResourceBundle) actualBundle;
         // Collect existing and parsed key objects into an array of keys,
         // rather than assembling and parsing paths.
@@ -820,11 +900,20 @@ public  class ICUResourceBundle extends UResourceBundle {
         assert numPathKeys > 0;
         String[] keys = new String[depth + numPathKeys];
         getResPathKeys(path, numPathKeys, keys, depth);
+        return findResourceWithFallback(keys, depth, base, requested);
+    }
+
+    private static final ICUResourceBundle findResourceWithFallback(
+            String[] keys, int depth,
+            ICUResourceBundle base, UResourceBundle requested) {
+        if (requested == null) {
+            requested = base;
+        }
 
         for (;;) {  // Iterate over the parent bundles.
             for (;;) {  // Iterate over the keys on the requested path, within a bundle.
                 String subKey = keys[depth++];
-                sub = (ICUResourceBundle) base.handleGet(subKey, null, requested);
+                ICUResourceBundle sub = (ICUResourceBundle) base.handleGet(subKey, null, requested);
                 if (sub == null) {
                     --depth;
                     break;
index ac7864e9543adef534a8da3bedc6d877bc118625..86ff6f4c00c12b8259304b19d60dfeef3658201a 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *******************************************************************************
- * Copyright (C) 2004-2014, International Business Machines Corporation and
+ * Copyright (C) 2004-2015, International Business Machines Corporation and
  * others. All Rights Reserved.
  *******************************************************************************
  */
@@ -11,6 +11,7 @@ import java.util.HashMap;
 import java.util.Set;
 import java.util.TreeSet;
 
+import com.ibm.icu.impl.ICUResourceBundleReader.ReaderValue;
 import com.ibm.icu.util.UResourceBundle;
 import com.ibm.icu.util.UResourceTypeMismatchException;
 
@@ -158,7 +159,7 @@ class ICUResourceBundleImpl extends ICUResourceBundle {
             super(wholeBundle);
         }
     }
-    private static class ResourceArray extends ResourceContainer {
+    static class ResourceArray extends ResourceContainer {
         public int getType() {
             return ARRAY;
         }
@@ -189,6 +190,16 @@ class ICUResourceBundleImpl extends ICUResourceBundle {
                                             UResourceBundle requested) {
             return createBundleObject(index, Integer.toString(index), aliasesVisited, requested);
         }
+        /**
+         * @param key will be set during enumeration; input contents is ignored
+         * @param readerValue will be set during enumeration; input contents is ignored
+         * @param sink receives all array item values
+         */
+        void getAllItems(ICUResource.Key key, ReaderValue readerValue, ICUResource.ArraySink sink) {
+            ICUResourceBundleReader reader = wholeBundle.reader;
+            readerValue.reader = reader;
+            ((ICUResourceBundleReader.Array)value).getAllItems(reader, key, readerValue, sink);
+        }
         ResourceArray(ICUResourceBundleImpl container, String key, int resource) {
             super(container, key);
             value = wholeBundle.reader.getArray(resource);
@@ -276,6 +287,16 @@ class ICUResourceBundleImpl extends ICUResourceBundle {
             }
             return reader.getString(value.getContainerResource(reader, index));
         }
+        /**
+         * @param key will be set during enumeration; input contents is ignored
+         * @param readerValue will be set during enumeration; input contents is ignored
+         * @param sink receives all table item key-value pairs
+         */
+        void getAllItems(ICUResource.Key key, ReaderValue readerValue, ICUResource.TableSink sink) {
+            ICUResourceBundleReader reader = wholeBundle.reader;
+            readerValue.reader = reader;
+            ((ICUResourceBundleReader.Table)value).getAllItems(reader, key, readerValue, sink);
+        }
         ResourceTable(ICUResourceBundleImpl container, String key, int resource) {
             super(container, key);
             value = wholeBundle.reader.getTable(resource);
index 00886221c51d6391e8b711356eb8a2bbd765e962..1d93f9cc167bfa092e8116b2060c2e834bc888e0 100644 (file)
@@ -13,10 +13,13 @@ import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.IntBuffer;
 
+import com.ibm.icu.impl.ICUResource.ArraySink;
+import com.ibm.icu.impl.ICUResource.TableSink;
 import com.ibm.icu.util.ICUException;
 import com.ibm.icu.util.ICUUncheckedIOException;
 import com.ibm.icu.util.ULocale;
 import com.ibm.icu.util.UResourceBundle;
+import com.ibm.icu.util.UResourceTypeMismatchException;
 import com.ibm.icu.util.VersionInfo;
 
 /**
@@ -373,7 +376,7 @@ public final class ICUResourceBundleReader {
     private static final char[] emptyChars = new char[0];
     private static final int[] emptyInts = new int[0];
     private static final String emptyString = "";
-    private static final Container EMPTY_ARRAY = new Container();
+    private static final Array EMPTY_ARRAY = new Array();
     private static final Table EMPTY_TABLE = new Table();
 
     private char[] getChars(int offset, int count) {
@@ -463,6 +466,20 @@ public final class ICUResourceBundleReader {
             return makeKeyStringFromBytes(poolBundleReader.keyBytes, keyOffset & 0x7fffffff);
         }
     }
+    private void setKeyFromKey16(int keyOffset, ICUResource.Key key) {
+        if(keyOffset < localKeyLimit) {
+            key.setBytes(keyBytes, keyOffset);
+        } else {
+            key.setBytes(poolBundleReader.keyBytes, keyOffset - localKeyLimit);
+        }
+    }
+    private void setKeyFromKey32(int keyOffset, ICUResource.Key key) {
+        if(keyOffset >= 0) {
+            key.setBytes(keyBytes, keyOffset);
+        } else {
+            key.setBytes(poolBundleReader.keyBytes, keyOffset & 0x7fffffff);
+        }
+    }
     private int compareKeys(CharSequence key, char keyOffset) {
         if(keyOffset < localKeyLimit) {
             return ICUBinary.compareKeys(key, keyBytes, keyOffset);
@@ -566,6 +583,43 @@ public final class ICUResourceBundleReader {
         return (String)resourceCache.putIfAbsent(res, s, s.length() * 2);
     }
 
+    /**
+     * CLDR string value "∅∅∅"=="\u2205\u2205\u2205" prevents fallback to the parent bundle.
+     */
+    private boolean isNoInheritanceMarker(int res) {
+        int offset = RES_GET_OFFSET(res);
+        if (offset == 0) {
+            // empty string
+        } else if (res == offset) {
+            offset = getResourceByteOffset(offset);
+            return getInt(offset) == 3 && bytes.getChar(offset + 4) == 0x2205 &&
+                    bytes.getChar(offset + 6) == 0x2205 && bytes.getChar(offset + 8) == 0x2205;
+        } else if (RES_GET_TYPE(res) == ICUResourceBundle.STRING_V2) {
+            if (offset < poolStringIndexLimit) {
+                return poolBundleReader.isStringV2NoInheritanceMarker(offset);
+            } else {
+                return isStringV2NoInheritanceMarker(offset - poolStringIndexLimit);
+            }
+        }
+        return false;
+    }
+
+    private boolean isStringV2NoInheritanceMarker(int offset) {
+        int first = b16BitUnits.charAt(offset);
+        if (first == 0x2205) {  // implicit length
+            return b16BitUnits.charAt(offset + 1) == 0x2205 &&
+                    b16BitUnits.charAt(offset + 2) == 0x2205 &&
+                    b16BitUnits.charAt(offset + 3) == 0;
+        } else if (first == 0xdc03) {  // explicit length 3 (should not occur)
+            return b16BitUnits.charAt(offset + 1) == 0x2205 &&
+                    b16BitUnits.charAt(offset + 2) == 0x2205 &&
+                    b16BitUnits.charAt(offset + 3) == 0x2205;
+        } else {
+            // Assume that the string has not been stored with more length units than necessary.
+            return false;
+        }
+    }
+
     String getAlias(int res) {
         int offset=RES_GET_OFFSET(res);
         int length;
@@ -672,7 +726,23 @@ public final class ICUResourceBundleReader {
         }
     }
 
-    Container getArray(int res) {
+    private int getArrayLength(int res) {
+        int offset = RES_GET_OFFSET(res);
+        if(offset == 0) {
+            return 0;
+        }
+        int type = RES_GET_TYPE(res);
+        if(type == UResourceBundle.ARRAY) {
+            offset = getResourceByteOffset(offset);
+            return getInt(offset);
+        } else if(type == ICUResourceBundle.ARRAY16) {
+            return b16BitUnits.charAt(offset);
+        } else {
+            return 0;
+        }
+    }
+
+    Array getArray(int res) {
         int type=RES_GET_TYPE(res);
         if(!URES_IS_ARRAY(type)) {
             return null;
@@ -683,11 +753,30 @@ public final class ICUResourceBundleReader {
         }
         Object value = resourceCache.get(res);
         if(value != null) {
-            return (Container)value;
+            return (Array)value;
+        }
+        Array array = (type == UResourceBundle.ARRAY) ?
+                new Array32(this, offset) : new Array16(this, offset);
+        return (Array)resourceCache.putIfAbsent(res, array, 0);
+    }
+
+    private int getTableLength(int res) {
+        int offset = RES_GET_OFFSET(res);
+        if(offset == 0) {
+            return 0;
+        }
+        int type = RES_GET_TYPE(res);
+        if(type == UResourceBundle.TABLE) {
+            offset = getResourceByteOffset(offset);
+            return bytes.getChar(offset);
+        } else if(type == ICUResourceBundle.TABLE16) {
+            return b16BitUnits.charAt(offset);
+        } else if(type == ICUResourceBundle.TABLE32) {
+            offset = getResourceByteOffset(offset);
+            return getInt(offset);
+        } else {
+            return 0;
         }
-        Container array = (type == UResourceBundle.ARRAY) ?
-                new Array(this, offset) : new Array16(this, offset);
-        return (Container)resourceCache.putIfAbsent(res, array, 0);
     }
 
     Table getTable(int res) {
@@ -718,6 +807,86 @@ public final class ICUResourceBundleReader {
         return (Table)resourceCache.putIfAbsent(res, table, size);
     }
 
+    // ICUResource.Value --------------------------------------------------- ***
+
+    /**
+     * From C++ uresdata.c gPublicTypes[URES_LIMIT].
+     */
+    private static int PUBLIC_TYPES[] = {
+        UResourceBundle.STRING,
+        UResourceBundle.BINARY,
+        UResourceBundle.TABLE,
+        ICUResourceBundle.ALIAS,
+
+        UResourceBundle.TABLE,     /* URES_TABLE32 */
+        UResourceBundle.TABLE,     /* URES_TABLE16 */
+        UResourceBundle.STRING,    /* URES_STRING_V2 */
+        UResourceBundle.INT,
+
+        UResourceBundle.ARRAY,
+        UResourceBundle.ARRAY,     /* URES_ARRAY16 */
+        UResourceBundle.NONE,
+        UResourceBundle.NONE,
+
+        UResourceBundle.NONE,
+        UResourceBundle.NONE,
+        UResourceBundle.INT_VECTOR,
+        UResourceBundle.NONE
+    };
+
+    static class ReaderValue extends ICUResource.Value {
+        ICUResourceBundleReader reader;
+        private int res;
+
+        @Override
+        public int getType() {
+            return PUBLIC_TYPES[RES_GET_TYPE(res)];
+        }
+
+        @Override
+        public String getString() {
+            String s = reader.getString(res);
+            if (s == null) {
+                throw new UResourceTypeMismatchException("");
+            }
+            return s;
+        }
+
+        @Override
+        public int getInt() {
+            if (RES_GET_TYPE(res) != UResourceBundle.INT) {
+                throw new UResourceTypeMismatchException("");
+            }
+            return RES_GET_INT(res);
+        }
+
+        @Override
+        public int getUInt() {
+            if (RES_GET_TYPE(res) != UResourceBundle.INT) {
+                throw new UResourceTypeMismatchException("");
+            }
+            return RES_GET_UINT(res);
+        }
+
+        @Override
+        public int[] getIntVector() {
+            int[] iv = reader.getIntVector(res);
+            if (iv == null) {
+                throw new UResourceTypeMismatchException("");
+            }
+            return iv;
+        }
+
+        @Override
+        public ByteBuffer getBinary() {
+            ByteBuffer bb = reader.getBinary(res);
+            if (bb == null) {
+                throw new UResourceTypeMismatchException("");
+            }
+            return bb;
+        }
+    }
+
     // Container value classes --------------------------------------------- ***
 
     static class Container {
@@ -756,18 +925,51 @@ public final class ICUResourceBundleReader {
         Container() {
         }
     }
-    private static final class Array extends Container {
+    static class Array extends Container {
+        Array() {}
+        void getAllItems(ICUResourceBundleReader reader,
+                ICUResource.Key key, ReaderValue value, ArraySink sink) {
+            for (int i = 0; i < size; ++i) {
+                int res = getContainerResource(reader, i);
+                int type = RES_GET_TYPE(res);
+                if (URES_IS_ARRAY(type)) {
+                    int numItems = reader.getArrayLength(res);
+                    ArraySink subSink = sink.getOrCreateArraySink(i, numItems);
+                    if (subSink != null) {
+                        Array array = reader.getArray(res);
+                        assert(array.size == numItems);
+                        array.getAllItems(reader, key, value, subSink);
+                    }
+                } else if (URES_IS_TABLE(type)) {
+                    int numItems = reader.getTableLength(res);
+                    TableSink subSink = sink.getOrCreateTableSink(i, numItems);
+                    if (subSink != null) {
+                        Table table = reader.getTable(res);
+                        assert(table.size == numItems);
+                        table.getAllItems(reader, key, value, subSink);
+                    }
+                } else if (type == ICUResourceBundle.ALIAS) {
+                    throw new UnsupportedOperationException(
+                            "aliases not handled in resource enumeration");
+                } else {
+                    value.res = res;
+                    sink.put(i, value);
+                }
+            }
+        }
+    }
+    private static final class Array32 extends Array {
         @Override
         int getContainerResource(ICUResourceBundleReader reader, int index) {
             return getContainer32Resource(reader, index);
         }
-        Array(ICUResourceBundleReader reader, int offset) {
+        Array32(ICUResourceBundleReader reader, int offset) {
             offset = reader.getResourceByteOffset(offset);
             size = reader.getInt(offset);
             itemsOffset = offset + 4;
         }
     }
-    private static final class Array16 extends Container {
+    private static final class Array16 extends Array {
         @Override
         int getContainerResource(ICUResourceBundleReader reader, int index) {
             return getContainer16Resource(reader, index);
@@ -819,6 +1021,43 @@ public final class ICUResourceBundleReader {
         int getResource(ICUResourceBundleReader reader, String resKey) {
             return getContainerResource(reader, findTableItem(reader, resKey));
         }
+        void getAllItems(ICUResourceBundleReader reader,
+                ICUResource.Key key, ReaderValue value, TableSink sink) {
+            for (int i = 0; i < size; ++i) {
+                if (keyOffsets != null) {
+                    reader.setKeyFromKey16(keyOffsets[i], key);
+                } else {
+                    reader.setKeyFromKey32(key32Offsets[i], key);
+                }
+                int res = getContainerResource(reader, i);
+                int type = RES_GET_TYPE(res);
+                if (URES_IS_ARRAY(type)) {
+                    int numItems = reader.getArrayLength(res);
+                    ArraySink subSink = sink.getOrCreateArraySink(key, numItems);
+                    if (subSink != null) {
+                        Array array = reader.getArray(res);
+                        assert(array.size == numItems);
+                        array.getAllItems(reader, key, value, subSink);
+                    }
+                } else if (URES_IS_TABLE(type)) {
+                    int numItems = reader.getTableLength(res);
+                    TableSink subSink = sink.getOrCreateTableSink(key, numItems);
+                    if (subSink != null) {
+                        Table table = reader.getTable(res);
+                        assert(table.size == numItems);
+                        table.getAllItems(reader, key, value, subSink);
+                    }
+                } else if (type == ICUResourceBundle.ALIAS) {
+                    throw new UnsupportedOperationException(
+                            "aliases not handled in resource enumeration");
+                } else if (reader.isNoInheritanceMarker(res)) {
+                    sink.remove(key);
+                } else {
+                    value.res = res;
+                    sink.put(key, value);
+                }
+            }
+        }
         Table() {
         }
     }
index 9381c0f6bc34716f82fa7b60df583edd7525bfaf..63aa836a8548ba49f1a10cbad97f6b340eabb033 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *******************************************************************************
- * Copyright (C) 2011-2014, International Business Machines Corporation and    *
- * others. All Rights Reserved.                                                *
+ * Copyright (C) 2011-2015, International Business Machines Corporation and
+ * others. All Rights Reserved.
  *******************************************************************************
  */
 package com.ibm.icu.impl;
@@ -24,6 +24,7 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Pattern;
 
+import com.ibm.icu.impl.ICUResource.TableSink;
 import com.ibm.icu.impl.TextTrieMap.ResultHandler;
 import com.ibm.icu.text.TimeZoneNames;
 import com.ibm.icu.util.TimeZone;
@@ -40,6 +41,7 @@ public class TimeZoneNamesImpl extends TimeZoneNames {
 
     private static final String ZONE_STRINGS_BUNDLE = "zoneStrings";
     private static final String MZ_PREFIX = "meta:";
+    private static final NameType[] NAME_TYPE_VALUES = NameType.values();
 
     private static volatile Set<String> METAZONE_IDS;
     private static final TZ2MZsCache TZ_TO_MZS_CACHE = new TZ2MZsCache();
@@ -52,7 +54,8 @@ public class TimeZoneNamesImpl extends TimeZoneNames {
     // and it's stored in SoftCache, so we do not need to worry about the
     // footprint much.
     private transient ConcurrentHashMap<String, ZNames> _mzNamesMap;
-    private transient ConcurrentHashMap<String, TZNames> _tzNamesMap;
+    private transient ConcurrentHashMap<String, ZNames> _tzNamesMap;
+    private transient boolean _namesFullyLoaded;
 
     private transient TextTrieMap<NameInfo> _namesTrie;
     private transient boolean _namesTrieFullyLoaded;
@@ -162,7 +165,7 @@ public class TimeZoneNamesImpl extends TimeZoneNames {
         if (mzID == null || mzID.length() == 0) {
             return null;
         }
-        return loadMetaZoneNames(mzID).getName(type);
+        return loadMetaZoneNames(null, mzID).getName(type);
     }
 
     /*
@@ -174,7 +177,7 @@ public class TimeZoneNamesImpl extends TimeZoneNames {
         if (tzID == null || tzID.length() == 0) {
             return null;
         }
-        return loadTimeZoneNames(tzID).getName(type);
+        return loadTimeZoneNames(null, tzID).getName(type);
     }
 
     /* (non-Javadoc)
@@ -185,7 +188,7 @@ public class TimeZoneNamesImpl extends TimeZoneNames {
         if (tzID == null || tzID.length() == 0) {
             return null;
         }
-        String locName = loadTimeZoneNames(tzID).getName(NameType.EXEMPLAR_LOCATION);
+        String locName = loadTimeZoneNames(null, tzID).getName(NameType.EXEMPLAR_LOCATION);
         return locName;
     }
 
@@ -205,17 +208,19 @@ public class TimeZoneNamesImpl extends TimeZoneNames {
         }
 
         // All names are not yet loaded into the trie
+        internalLoadAllDisplayNames();
+        addAllNamesIntoTrie();
 
-        // time zone names
+        // Set default time zone location names
+        // for time zones without explicit display names.
         Set<String> tzIDs = TimeZone.getAvailableIDs(SystemTimeZoneType.CANONICAL, null, null);
         for (String tzID : tzIDs) {
-            loadTimeZoneNames(tzID);
-        }
-
-        // meta zone names
-        Set<String> mzIDs = getAvailableMetaZoneIDs();
-        for (String mzID : mzIDs) {
-            loadMetaZoneNames(mzID);
+            if (!_tzNamesMap.containsKey(tzID)) {
+                tzID = tzID.intern();
+                ZNames tznames = ZNames.getInstance(null, tzID);
+                tznames.addNamesIntoTrie(null, tzID, _namesTrie);
+                _tzNamesMap.put(tzID, tznames);
+            }
         }
         _namesTrieFullyLoaded = true;
 
@@ -225,6 +230,140 @@ public class TimeZoneNamesImpl extends TimeZoneNames {
         return handler.getMatches();
     }
 
+    @Override
+    public synchronized void loadAllDisplayNames() {
+        internalLoadAllDisplayNames();
+    }
+
+    @Override
+    public String[] getDisplayNames(String tzID, long date, NameType... types) {
+        String[] names = new String[types.length];
+        if (tzID == null || tzID.length() == 0) {
+            return names;
+        }
+        ZNames tzNames = loadTimeZoneNames(null, tzID);
+        ZNames mzNames = null;
+        for (int i = 0; i < types.length; ++i) {
+            NameType type = types[i];
+            String name = tzNames.getName(type);
+            if (name == null) {
+                if (mzNames == null) {
+                    String mzID = getMetaZoneID(tzID, date);
+                    if (mzID == null || mzID.length() == 0) {
+                        mzNames = ZNames.EMPTY_ZNAMES;
+                    } else {
+                        mzNames = loadMetaZoneNames(null, mzID);
+                    }
+                }
+                name = mzNames.getName(type);
+            }
+            names[i] = name;
+        }
+        return names;
+    }
+
+    /** Caller must synchronize. */
+    private void internalLoadAllDisplayNames() {
+        if (!_namesFullyLoaded) {
+            new ZoneStringsLoader().load();
+            _namesFullyLoaded = true;
+        }
+    }
+
+    /** Caller must synchronize. */
+    private void addAllNamesIntoTrie() {
+        for (Map.Entry<String, ZNames> entry : _tzNamesMap.entrySet()) {
+            entry.getValue().addNamesIntoTrie(null, entry.getKey(), _namesTrie);
+        }
+        for (Map.Entry<String, ZNames> entry : _mzNamesMap.entrySet()) {
+            entry.getValue().addNamesIntoTrie(entry.getKey(), null, _namesTrie);
+        }
+    }
+
+    /**
+     * Loads all meta zone and time zone names for this TimeZoneNames' locale.
+     */
+    private final class ZoneStringsLoader extends ICUResource.TableSink {
+        /**
+         * Prepare for several hundred time zones and meta zones.
+         * _zoneStrings.getSize() is ineffective in a sparsely populated locale like en-GB.
+         */
+        private static final int INITIAL_NUM_ZONES = 300;
+        private HashMap<ICUResource.Key, ZNamesLoader> keyToLoader =
+                new HashMap<ICUResource.Key, ZNamesLoader>(INITIAL_NUM_ZONES);
+        private StringBuilder sb = new StringBuilder(32);
+
+        /** Caller must synchronize. */
+        void load() {
+            _zoneStrings.getAllTableItemsWithFallback("", this);
+            for (Map.Entry<ICUResource.Key, ZNamesLoader> entry : keyToLoader.entrySet()) {
+                ICUResource.Key key = entry.getKey();
+                ZNamesLoader loader = entry.getValue();
+                if (key.startsWith(MZ_PREFIX)) {
+                    String mzID = mzIDFromKey(key).intern();
+                    ZNames mzNames = ZNames.getInstance(loader.getNames(), null);
+                    _mzNamesMap.put(mzID, mzNames);
+                } else {
+                    String tzID = tzIDFromKey(key).intern();
+                    ZNames tzNames = ZNames.getInstance(loader.getNames(), tzID);
+                    _tzNamesMap.put(tzID, tzNames);
+                }
+            }
+        }
+
+        @Override
+        public TableSink getOrCreateTableSink(ICUResource.Key key, int initialSize) {
+            ZNamesLoader loader = keyToLoader.get(key);
+            if (loader != null) {
+                return loader;
+            }
+            if (key.startsWith(MZ_PREFIX)) {
+                String mzID = mzIDFromKey(key);
+                if (_mzNamesMap.containsKey(mzID)) {
+                    return null;  // We have already loaded the names for this meta zone.
+                }
+                loader = ZNamesLoader.forMetaZoneNames();
+            } else {
+                String tzID = tzIDFromKey(key);
+                if (_tzNamesMap.containsKey(tzID)) {
+                    return null;  // We have already loaded the names for this time zone.
+                }
+                loader = ZNamesLoader.forTimeZoneNames();
+            }
+            keyToLoader.put(key.clone(), loader);
+            return loader;
+        }
+
+        @Override
+        public void remove(ICUResource.Key key) {
+            keyToLoader.remove(key);
+        }
+
+        /**
+         * Equivalent to key.substring(MZ_PREFIX.length())
+         * except reuses our StringBuilder.
+         */
+        private String mzIDFromKey(ICUResource.Key key) {
+            sb.setLength(0);
+            for (int i = MZ_PREFIX.length(); i < key.length(); ++i) {
+                sb.append(key.charAt(i));
+            }
+            return sb.toString();
+        }
+
+        private String tzIDFromKey(ICUResource.Key key) {
+            sb.setLength(0);
+            for (int i = 0; i < key.length(); ++i) {
+                char c = key.charAt(i);
+                if (c == ':') {
+                    c = '/';
+                }
+                sb.append(c);
+            }
+            return sb.toString();
+        }
+    }
+
     /**
      * Initialize the transient fields, called from the constructor and
      * readObject.
@@ -236,8 +375,10 @@ public class TimeZoneNamesImpl extends TimeZoneNames {
                 ICUResourceBundle.ICU_ZONE_BASE_NAME, locale);
         _zoneStrings = (ICUResourceBundle)bundle.get(ZONE_STRINGS_BUNDLE);
 
-        _tzNamesMap = new ConcurrentHashMap<String, TZNames>();
+        // TODO: Access is synchronized, can we use a non-concurrent map?
+        _tzNamesMap = new ConcurrentHashMap<String, ZNames>();
         _mzNamesMap = new ConcurrentHashMap<String, ZNames>();
+        _namesFullyLoaded = false;
 
         _namesTrie = new TextTrieMap<NameInfo>(true);
         _namesTrieFullyLoaded = false;
@@ -260,12 +401,14 @@ public class TimeZoneNamesImpl extends TimeZoneNames {
         if (tzCanonicalID == null || tzCanonicalID.length() == 0) {
             return;
         }
-        loadTimeZoneNames(tzCanonicalID);
+        loadTimeZoneNames(null, tzCanonicalID);
 
+        ZNamesLoader loader = ZNamesLoader.forMetaZoneNames();
         Set<String> mzIDs = getAvailableMetaZoneIDs(tzCanonicalID);
         for (String mzID : mzIDs) {
-            loadMetaZoneNames(mzID);
+            loadMetaZoneNames(loader, mzID);
         }
+        addAllNamesIntoTrie();
     }
 
     /*
@@ -292,23 +435,18 @@ public class TimeZoneNamesImpl extends TimeZoneNames {
      * @param mzID the meta zone ID
      * @return An instance of ZNames that includes a set of meta zone display names.
      */
-    private synchronized ZNames loadMetaZoneNames(String mzID) {
+    private synchronized ZNames loadMetaZoneNames(ZNamesLoader loader, String mzID) {
         ZNames znames = _mzNamesMap.get(mzID);
         if (znames == null) {
-            znames = ZNames.getInstance(_zoneStrings, MZ_PREFIX + mzID);
-            // put names into the trie
+            if (loader == null) {
+                loader = ZNamesLoader.forMetaZoneNames();
+            }
+            znames = ZNames.getInstance(loader, _zoneStrings, MZ_PREFIX + mzID, null);
             mzID = mzID.intern();
-            for (NameType t : NameType.values()) {
-                String name = znames.getName(t);
-                if (name != null) {
-                    NameInfo info = new NameInfo();
-                    info.mzID = mzID;
-                    info.type = t;
-                    _namesTrie.put(name, info);
-                }
+            if (_namesTrieFullyLoaded) {
+                znames.addNamesIntoTrie(mzID, null, _namesTrie);
             }
-            ZNames tmpZnames = _mzNamesMap.putIfAbsent(mzID, znames);
-            znames = (tmpZnames == null) ? znames : tmpZnames;
+            _mzNamesMap.put(mzID, znames);
         }
         return znames;
     }
@@ -319,23 +457,18 @@ public class TimeZoneNamesImpl extends TimeZoneNames {
      * @param tzID the canonical time zone ID
      * @return An instance of TZNames that includes a set of time zone display names.
      */
-    private synchronized TZNames loadTimeZoneNames(String tzID) {
-        TZNames tznames = _tzNamesMap.get(tzID);
+    private synchronized ZNames loadTimeZoneNames(ZNamesLoader loader, String tzID) {
+        ZNames tznames = _tzNamesMap.get(tzID);
         if (tznames == null) {
-            tznames = TZNames.getInstance(_zoneStrings, tzID.replace('/', ':'), tzID);
-            // put names into the trie
+            if (loader == null) {
+                loader = ZNamesLoader.forTimeZoneNames();
+            }
+            tznames = ZNames.getInstance(loader, _zoneStrings, tzID.replace('/', ':'), tzID);
             tzID = tzID.intern();
-            for (NameType t : NameType.values()) {
-                String name = tznames.getName(t);
-                if (name != null) {
-                    NameInfo info = new NameInfo();
-                    info.tzID = tzID;
-                    info.type = t;
-                    _namesTrie.put(name, info);
-                }
+            if (_namesTrieFullyLoaded) {
+                tznames.addNamesIntoTrie(null, tzID, _namesTrie);
             }
-            TZNames tmpTznames = _tzNamesMap.putIfAbsent(tzID, tznames);
-            tznames = (tmpTznames == null) ? tznames : tmpTznames;
+            _tzNamesMap.put(tzID, tznames);
         }
         return tznames;
     }
@@ -416,139 +549,174 @@ public class TimeZoneNamesImpl extends TimeZoneNames {
         }
     }
 
-    /**
-     * This class stores name data for a meta zone
-     */
-    private static class ZNames {
-        private static final ZNames EMPTY_ZNAMES = new ZNames(null);
+    private static final class ZNamesLoader extends ICUResource.TableSink {
+        private static int NUM_META_ZONE_NAMES = 6;
+        private static int NUM_TIME_ZONE_NAMES = 7;  // incl. EXEMPLAR_LOCATION
 
-        private String[] _names;
+        private String[] names;
+        private int numNames;
 
-        private static final String[] KEYS = {"lg", "ls", "ld", "sg", "ss", "sd"};
+        private ZNamesLoader(int numNames) {
+            this.numNames = numNames;
+        }
 
-        protected ZNames(String[] names) {
-            _names = names;
+        static ZNamesLoader forMetaZoneNames() {
+            return new ZNamesLoader(NUM_META_ZONE_NAMES);
         }
 
-        public static ZNames getInstance(ICUResourceBundle zoneStrings, String key) {
-            String[] names = loadData(zoneStrings, key);
-            if (names == null) {
-                return EMPTY_ZNAMES;
-            }
-            return new ZNames(names);
+        static ZNamesLoader forTimeZoneNames() {
+            return new ZNamesLoader(NUM_TIME_ZONE_NAMES);
         }
 
-        public String getName(NameType type) {
-            if (_names == null) {
+        String[] load(ICUResourceBundle zoneStrings, String key) {
+            if (zoneStrings == null || key == null || key.length() == 0) {
                 return null;
             }
-            String name = null;
-            switch (type) {
-            case LONG_GENERIC:
-                name = _names[0];
-                break;
-            case LONG_STANDARD:
-                name = _names[1];
-                break;
-            case LONG_DAYLIGHT:
-                name = _names[2];
-                break;
-            case SHORT_GENERIC:
-                name = _names[3];
-                break;
-            case SHORT_STANDARD:
-                name = _names[4];
-                break;
-            case SHORT_DAYLIGHT:
-                name = _names[5];
-                break;
-            case EXEMPLAR_LOCATION:
-                name = null;    // implemented by subclass
-                break;
+
+            try {
+                zoneStrings.getAllTableItemsWithFallback(key, this);
+            } catch (MissingResourceException e) {
+                return null;
             }
 
-            return name;
+            return getNames();
         }
 
-        protected static String[] loadData(ICUResourceBundle zoneStrings, String key) {
-            if (zoneStrings == null || key == null || key.length() == 0) {
+        private static NameType nameTypeFromKey(ICUResource.Key key) {
+            // Avoid key.toString() object creation.
+            if (key.length() != 2) {
                 return null;
             }
+            char c0 = key.charAt(0);
+            char c1 = key.charAt(1);
+            if (c0 == 'l') {
+                return c1 == 'g' ? NameType.LONG_GENERIC :
+                        c1 == 's' ? NameType.LONG_STANDARD :
+                            c1 == 'd' ? NameType.LONG_DAYLIGHT : null;
+            } else if (c0 == 's') {
+                return c1 == 'g' ? NameType.SHORT_GENERIC :
+                        c1 == 's' ? NameType.SHORT_STANDARD :
+                            c1 == 'd' ? NameType.SHORT_DAYLIGHT : null;
+            } else if (c0 == 'e' && c1 == 'c') {
+                return NameType.EXEMPLAR_LOCATION;
+            }
+            return null;
+        }
 
-            ICUResourceBundle table = null;
-            try {
-                table = zoneStrings.getWithFallback(key);
-            } catch (MissingResourceException e) {
-                return null;
+        @Override
+        public void put(ICUResource.Key key, ICUResource.Value value) {
+            if (value.getType() == UResourceBundle.STRING) {
+                NameType type = nameTypeFromKey(key);
+                if (type != null && type.ordinal() < numNames) {
+                    if (names == null) {
+                        names = new String[numNames];
+                    }
+                    names[type.ordinal()] = value.getString();
+                }
             }
+        }
 
-            boolean isEmpty = true;
-            String[] names = new String[KEYS.length];
-            for (int i = 0; i < names.length; i++) {
-                try {
-                    names[i] = table.getStringWithFallback(KEYS[i]);
-                    isEmpty = false;
-                } catch (MissingResourceException e) {
-                    names[i] = null;
+        @Override
+        public void remove(ICUResource.Key key) {
+            if (names != null) {
+                NameType type = nameTypeFromKey(key);
+                if (type != null && type.ordinal() < numNames) {
+                    names[type.ordinal()] = null;
                 }
             }
+        }
 
-            if (isEmpty) {
+        private String[] getNames() {
+            if (names == null) {
                 return null;
             }
-
-            return names;
+            int length = names.length;
+            while (names[length - 1] == null) {
+                if (--length == 0) {
+                    return null;  // no names
+                }
+            }
+            if (length == names.length || numNames == NUM_TIME_ZONE_NAMES) {
+                // Return the full array if the last name is set.
+                // Also return the full *time* zone names array,
+                // so that the exemplar location can be set.
+                String[] result = names;
+                names = null;
+                return result;
+            }
+            // Return a shorter array for permanent storage.
+            // *Move* all names into a minimal array.
+            String[] result = new String[length];
+            do {
+                --length;
+                result[length] = names[length];
+                names[length] = null;  // Reset for loading another set of names.
+            } while (length > 0);
+            return result;
         }
     }
 
     /**
-     * This class stores name data for a single time zone
+     * This class stores name data for a meta zone or time zone.
      */
-    private static class TZNames extends ZNames {
-        private String _locationName;
-
-        private static final TZNames EMPTY_TZNAMES = new TZNames(null, null);
+    private static class ZNames {
+        private static final ZNames EMPTY_ZNAMES = new ZNames(null);
+        // A meta zone names instance never has an exemplar location string.
+        private static final int EX_LOC_INDEX = NameType.EXEMPLAR_LOCATION.ordinal();
 
-        public static TZNames getInstance(ICUResourceBundle zoneStrings, String key, String tzID) {
-            if (zoneStrings == null || key == null || key.length() == 0) {
-                return EMPTY_TZNAMES;
-            }
+        private String[] _names;
 
-            String[] names = loadData(zoneStrings, key);
-            String locationName = null;
+        protected ZNames(String[] names) {
+            _names = names;
+        }
 
-            ICUResourceBundle table = null;
-            try {
-                table = zoneStrings.getWithFallback(key);
-                locationName = table.getStringWithFallback("ec");
-            } catch (MissingResourceException e) {
-                // fall through
+        public static ZNames getInstance(String[] names, String tzID) {
+            if (tzID != null && (names == null || names[EX_LOC_INDEX] == null)) {
+                String locationName = getDefaultExemplarLocationName(tzID);
+                if (locationName != null) {
+                    if (names == null) {
+                        names = new String[EX_LOC_INDEX + 1];
+                    }
+                    names[EX_LOC_INDEX] = locationName;
+                }
             }
 
-            if (locationName == null) {
-                locationName = getDefaultExemplarLocationName(tzID);
+            if (names == null) {
+                return EMPTY_ZNAMES;
             }
+            return new ZNames(names);
+        }
 
-            if (locationName == null && names == null) {
-                return EMPTY_TZNAMES;
-            }
-            return new TZNames(names, locationName);
+        public static ZNames getInstance(ZNamesLoader loader,
+                ICUResourceBundle zoneStrings, String key, String tzID) {
+            return getInstance(loader.load(zoneStrings, key), tzID);
         }
 
         public String getName(NameType type) {
-            if (type == NameType.EXEMPLAR_LOCATION) {
-                return _locationName;
+            if (_names != null && type.ordinal() < _names.length) {
+                return _names[type.ordinal()];
+            } else {
+                return null;
             }
-            return super.getName(type);
         }
 
-        private TZNames(String[] names, String locationName) {
-            super(names);
-            _locationName = locationName;
+        public void addNamesIntoTrie(String mzID, String tzID, TextTrieMap<NameInfo> trie) {
+            if (_names == null) {
+                return;
+            }
+            for (int i = 0; i < _names.length; ++ i) {
+                String name = _names[i];
+                if (name != null) {
+                    NameInfo info = new NameInfo();
+                    info.mzID = mzID;
+                    info.tzID = tzID;
+                    info.type = NAME_TYPE_VALUES[i];
+                    trie.put(name, info);
+                }
+            }
         }
     }
 
-
     //
     // Canonical time zone ID -> meta zone ID
     //
index 4caa6a77ca0f587ebb7b96ac2418d1e1e4d23af0..e9d793301344fc389574cee4ce97f616f0d4230d 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *******************************************************************************
- * Copyright (C) 2011-2014, International Business Machines Corporation and    *
- * others. All Rights Reserved.                                                *
+ * Copyright (C) 2011-2015, International Business Machines Corporation and
+ * others. All Rights Reserved.
  *******************************************************************************
  */
 package com.ibm.icu.text;
@@ -440,6 +440,36 @@ public abstract class TimeZoneNames implements Serializable {
         }
     }
 
+    /**
+     * @author Markus
+     * @internal For specific users only until proposed publicly.
+     */
+    public void loadAllDisplayNames() {}
+
+    /**
+     * @author Markus
+     * @internal For specific users only until proposed publicly.
+     */
+    public String[] getDisplayNames(String tzID, long date, NameType... types) {
+        String[] names = new String[types.length];
+        if (tzID == null || tzID.length() == 0) {
+            return names;
+        }
+        String mzID = null;
+        for (int i = 0; i < types.length; ++i) {
+            NameType type = types[i];
+            String name = getTimeZoneDisplayName(tzID, type);
+            if (name == null) {
+                if (mzID == null) {
+                    mzID = getMetaZoneID(tzID, date);
+                }
+                name = getMetaZoneDisplayName(mzID, type);
+            }
+            names[i] = name;
+        }
+        return names;
+    }
+
     /**
      * Sole constructor for invocation by subclass constructors.
      *