From 86ff19728b7e6f2835400897d4bc9263d028739a Mon Sep 17 00:00:00 2001 From: Yoshito Umaoka Date: Wed, 2 Aug 2006 20:44:55 +0000 Subject: [PATCH] ICU-5293 DateFormat perfomance improvement, including a fix for timezone parsing bug #5290 X-SVN-Rev: 19960 --- icu4j/eclipseCoreArgs.txt | 3 +- .../com/ibm/icu/dev/test/impl/TestAll.java | 6 +- .../test/serializable/SerializableTest.java | 45 +- .../com/ibm/icu/dev/test/util/LRUMapTest.java | 116 ++++ .../icu/dev/test/util/TextTrieMapTest.java | 180 ++++++ icu4j/src/com/ibm/icu/impl/LRUMap.java | 53 ++ icu4j/src/com/ibm/icu/impl/LinkedHashMap.java | 175 +++++ icu4j/src/com/ibm/icu/impl/SoftCache.java | 118 ++++ icu4j/src/com/ibm/icu/impl/TextTrieMap.java | 222 +++++++ icu4j/src/com/ibm/icu/impl/ZoneMeta.java | 23 +- .../com/ibm/icu/text/DateFormatSymbols.java | 597 ++++++++++-------- .../com/ibm/icu/text/SimpleDateFormat.java | 102 +-- 12 files changed, 1334 insertions(+), 306 deletions(-) create mode 100644 icu4j/src/com/ibm/icu/dev/test/util/LRUMapTest.java create mode 100644 icu4j/src/com/ibm/icu/dev/test/util/TextTrieMapTest.java create mode 100644 icu4j/src/com/ibm/icu/impl/LRUMap.java create mode 100644 icu4j/src/com/ibm/icu/impl/LinkedHashMap.java create mode 100644 icu4j/src/com/ibm/icu/impl/SoftCache.java create mode 100644 icu4j/src/com/ibm/icu/impl/TextTrieMap.java diff --git a/icu4j/eclipseCoreArgs.txt b/icu4j/eclipseCoreArgs.txt index 263e950d21..356e33baea 100644 --- a/icu4j/eclipseCoreArgs.txt +++ b/icu4j/eclipseCoreArgs.txt @@ -1,9 +1,10 @@ -# Copyright (C) 2005, International Business Machines Corporation and +# Copyright (C) 2005-2006, International Business Machines Corporation and # others. All Rights Reserved. src/com/ibm/icu/impl/CollectionUtilities.java src/com/ibm/icu/impl/ICUResourceBundle.java src/com/ibm/icu/impl/ICUResourceBundleImpl.java src/com/ibm/icu/impl/ICUResourceBundleReader.java +src/com/ibm/icu/impl/LRUMap.java src/com/ibm/icu/impl/Utility.java src/com/ibm/icu/lang/UCharacter.java src/com/ibm/icu/math/BigDecimal.java diff --git a/icu4j/src/com/ibm/icu/dev/test/impl/TestAll.java b/icu4j/src/com/ibm/icu/dev/test/impl/TestAll.java index 719d23295e..dc92e0c90d 100644 --- a/icu4j/src/com/ibm/icu/dev/test/impl/TestAll.java +++ b/icu4j/src/com/ibm/icu/dev/test/impl/TestAll.java @@ -1,6 +1,6 @@ /* ******************************************************************************* - * Copyright (C) 1996-2004, International Business Machines Corporation and * + * Copyright (C) 1996-2006, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ @@ -21,7 +21,9 @@ public class TestAll extends TestGroup { new String[] { "ICUServiceTest", "ICUServiceThreadTest", - "ICUBinaryTest" + "ICUBinaryTest", + "LRUMapTest", + "TextTrieMapTest" }, "Test miscellaneous implementation utilities"); } diff --git a/icu4j/src/com/ibm/icu/dev/test/serializable/SerializableTest.java b/icu4j/src/com/ibm/icu/dev/test/serializable/SerializableTest.java index 3c868755b5..ad88cb6db0 100644 --- a/icu4j/src/com/ibm/icu/dev/test/serializable/SerializableTest.java +++ b/icu4j/src/com/ibm/icu/dev/test/serializable/SerializableTest.java @@ -1,6 +1,6 @@ /* ******************************************************************************* - * Copyright (C) 1996-2005, International Business Machines Corporation and * + * Copyright (C) 1996-2006, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* * @@ -13,6 +13,8 @@ import java.util.HashMap; import com.ibm.icu.dev.test.TestFmwk; import com.ibm.icu.impl.JDKTimeZone; +import com.ibm.icu.impl.LinkedHashMap; +import com.ibm.icu.impl.LRUMap; import com.ibm.icu.impl.OlsonTimeZone; import com.ibm.icu.impl.TimeZoneAdapter; import com.ibm.icu.math.BigDecimal; @@ -369,6 +371,45 @@ public class SerializableTest extends TestFmwk.TestGroup } } + private static class LRUMapHandler implements Handler + { + public Object[] getTestObjects() + { + LRUMap[] maps = new LRUMap[1]; + maps[0] = new LRUMap(); + maps[0].put("1", "a"); + maps[0].put("2", "b"); + return maps; + } + public boolean hasSameBehavior(Object a, Object b) + { + LRUMap mapA = (LRUMap) a; + LRUMap mapB = (LRUMap) b; + return mapA.equals(mapB); + } + } + + private static class LinkedHashMapHandler implements Handler + { + public Object[] getTestObjects() + { + LinkedHashMap[] maps = new LinkedHashMap[2]; + maps[0] = new LinkedHashMap(); + maps[1] = new LinkedHashMap(16, 0.75F, true); + for (int i = 0; i < 2; i++) { + maps[i].put("1", "a"); + maps[i].put("2", "b"); + } + return maps; + } + public boolean hasSameBehavior(Object a, Object b) + { + LinkedHashMap mapA = (LinkedHashMap) a; + LinkedHashMap mapB = (LinkedHashMap) b; + return mapA.equals(mapB); + } + } + private static HashMap map = new HashMap(); static { @@ -377,6 +418,8 @@ public class SerializableTest extends TestFmwk.TestGroup map.put("com.ibm.icu.util.ULocale", new ULocaleHandler()); map.put("com.ibm.icu.util.Currency", new CurrencyHandler()); map.put("com.ibm.icu.impl.JDKTimeZone", new JDKTimeZoneHandler()); + map.put("com.ibm.icu.impl.LinkedHashMap", new LinkedHashMapHandler()); + map.put("com.ibm.icu.impl.LRUMap", new LRUMapHandler()); map.put("com.ibm.icu.impl.OlsonTimeZone", new OlsonTimeZoneHandler()); map.put("com.ibm.icu.impl.TimeZoneAdapter", new TimeZoneAdapterHandler()); map.put("com.ibm.icu.math.BigDecimal", new BigDecimalHandler()); diff --git a/icu4j/src/com/ibm/icu/dev/test/util/LRUMapTest.java b/icu4j/src/com/ibm/icu/dev/test/util/LRUMapTest.java new file mode 100644 index 0000000000..a50a7c76a6 --- /dev/null +++ b/icu4j/src/com/ibm/icu/dev/test/util/LRUMapTest.java @@ -0,0 +1,116 @@ +/* + ******************************************************************************* + * Copyright (C) 2006, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* +*/ +package com.ibm.icu.dev.test.util; + +import com.ibm.icu.dev.test.TestFmwk; +import com.ibm.icu.impl.LRUMap; + +public class LRUMapTest extends TestFmwk { + public static void main(String[] args) throws Exception { + LRUMapTest test = new LRUMapTest(); + test.run(args); + } + + public void TestLRUMap() { + // Default size - max 64 + logln("Testing LRUMap with the default size"); + LRUMap map = new LRUMap(); + execute(map, 64); + + // max size - 16 + logln("Testing LRUMap with initial/max size - 4/16"); + map = new LRUMap(4, 16); + execute(map, 16); + } + + private void execute(LRUMap map, int maxSize /* maxSize > 6 */) { + Integer num; + String numStr; + + for (int i = 0; i <= maxSize; i++) { + num = new Integer(i); + numStr = num.toString(); + map.put(numStr, num); + } + // The first key/value should be removed, because + // we already put 65 entries + num = (Integer)map.get("0"); + if (num == null) { + logln("OK: The entry '0' was removed."); + } + else { + errln("The entry '0' is still available."); + } + // Access the second entry '1', which is currently + // the eldest. + num = (Integer)map.get("1"); + if (num == null) { + errln("The eldest entry '1' was removed."); + } + else { + logln("OK: The eldest entry '1' is available."); + } + // Put another entry - because the entry '1' was + // accessed above, the entry '2' is the eldest and + // putting new entry should remove the entry. + num = new Integer(maxSize + 1); + map.put(num.toString(), num); + num = (Integer)map.get("2"); + if (num == null) { + logln("OK: The entry '2' was removed."); + } + else { + errln("The entry '2' is still available."); + } + // The entry '3' is the eldest for now. + boolean b = map.containsKey("3"); + if (b) { + logln("OK: The eldest entry '3' is available."); + } + else { + errln("The eldest entry '3' was removed."); + } + // contansKey should not affect the access order + num = new Integer(maxSize + 2); + map.put(num.toString(), num); + num = (Integer)map.get("3"); + if (num == null) { + logln("OK: The entry '3' was removed."); + } + else { + errln("The entry '3' is still available."); + } + // Putting existing entry with new value + num = (Integer)map.put("4", new Integer(-4)); + if (num == null) { + errln("The entry '4' no longer exists"); + } + if (num.intValue() != 4) { + errln("The value for '4' was not 4"); + } + // The entry '5' is the eldest for now, because + // the entry '4' was updated above. + num = new Integer(maxSize + 3); + map.put(num.toString(), num); + num = (Integer)map.get("5"); + if (num == null) { + logln("OK: The entry '5' was removed."); + } + else { + errln("The entry '5' is still available."); + } + // Clear the map + map.clear(); + num = (Integer)map.get("6"); + if (num == null) { + logln("OK: The entry '6' was removed."); + } + else { + errln("The entry '6' is still available."); + } + } +} diff --git a/icu4j/src/com/ibm/icu/dev/test/util/TextTrieMapTest.java b/icu4j/src/com/ibm/icu/dev/test/util/TextTrieMapTest.java new file mode 100644 index 0000000000..ccb230fc3b --- /dev/null +++ b/icu4j/src/com/ibm/icu/dev/test/util/TextTrieMapTest.java @@ -0,0 +1,180 @@ +/* + ******************************************************************************* + * Copyright (C) 2006, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* +*/ +package com.ibm.icu.dev.test.util; + +import com.ibm.icu.dev.test.TestFmwk; +import com.ibm.icu.impl.TextTrieMap; + +public class TextTrieMapTest extends TestFmwk { + + private static final Integer SUN = new Integer(1); + private static final Integer MON = new Integer(2); + private static final Integer TUE = new Integer(3); + private static final Integer WED = new Integer(4); + private static final Integer THU = new Integer(5); + private static final Integer FRI = new Integer(6); + private static final Integer SAT = new Integer(7); + + private static final Integer FOO = new Integer(-1); + private static final Integer BAR = new Integer(-2); + + private static final Object[][] TESTDATA = { + {"Sunday", SUN}, + {"Monday", MON}, + {"Tuesday", TUE}, + {"Wednesday", WED}, + {"Thursday", THU}, + {"Friday", FRI}, + {"Saturday", SAT}, + {"Sun", SUN}, + {"Mon", MON}, + {"Tue", TUE}, + {"Wed", WED}, + {"Thu", THU}, + {"Fri", FRI}, + {"Sat", SAT}, + {"S", SUN}, + {"M", MON}, + {"T", TUE}, + {"W", WED}, + {"T", THU}, + {"F", FRI}, + {"S", SAT} + }; + + private static final Object[][] TESTCASES = { + {"Sunday", SUN, SUN}, + {"sunday", null, SUN}, + {"Mo", MON, MON}, + {"mo", null, MON}, + {"Thursday Friday", THU, THU}, + {"T", THU, THU}, + {"TEST", THU, THU}, + {"SUN", SAT, SUN}, + {"super", null, SAT}, + {"NO", null, null} + }; + + public static void main(String[] args) throws Exception { + TextTrieMapTest test = new TextTrieMapTest(); + test.run(args); + } + + public void TestCaseSensitive() { + TextTrieMap map = new TextTrieMap(false); + for (int i = 0; i < TESTDATA.length; i++) { + map.put((String)TESTDATA[i][0], TESTDATA[i][1]); + } + + logln("Test for get(String)"); + for (int i = 0; i < TESTCASES.length; i++) { + Object value = map.get((String)TESTCASES[i][0]); + if (!eql(value, TESTCASES[i][1])) { + errln("Invalid search results - Expected:" + TESTCASES[i][1] + " Actual:" + value); + } + } + + logln("Test for get(String, int)"); + StringBuffer textBuf = new StringBuffer(); + for (int i = 0; i < TESTCASES.length; i++) { + textBuf.setLength(0); + for (int j = 0; j < i; j++) { + textBuf.append('X'); + } + textBuf.append(TESTCASES[i][0]); + Object value = map.get(textBuf.toString(), i); + if (!eql(value, TESTCASES[i][1])) { + errln("Invalid search results - Expected:" + TESTCASES[i][1] + " Actual:" + value); + } + } + + // Add duplicated entry + Object prev = map.put("Sunday", FOO); + if (!eql(prev, SUN)) { + errln("The previous value of duplicated entry is not valid - Expected:" + SUN + " Actual:" + prev); + } + // Make sure the value is updated + Object value = map.get("Sunday"); + if (!eql(value, FOO)) { + errln("The map value is not valid - Expected:" + FOO + " Actual:" + value); + } + + // Add duplicated entry with different casing + prev = map.put("sunday", BAR); + if (!eql(prev, null)) { + errln("The value should be new in the trie map - Expected:null" + " Actual:" + prev); + } + // Make sure the value is valid + value = map.get("sunday"); + if (!eql(value, BAR)) { + errln("The map value is not valid - Expected:" + BAR + " Actual:" + value); + } + + } + + public void TestCaseInsensitive() { + TextTrieMap map = new TextTrieMap(true); + for (int i = 0; i < TESTDATA.length; i++) { + map.put((String)TESTDATA[i][0], TESTDATA[i][1]); + } + + logln("Test for get(String)"); + for (int i = 0; i < TESTCASES.length; i++) { + Object value = map.get((String)TESTCASES[i][0]); + if (!eql(value, TESTCASES[i][2])) { + errln("Invalid search results - Expected:" + TESTCASES[i][2] + " Actual:" + value); + } + } + + logln("Test for get(String, int)"); + StringBuffer textBuf = new StringBuffer(); + for (int i = 0; i < TESTCASES.length; i++) { + textBuf.setLength(0); + for (int j = 0; j < i; j++) { + textBuf.append('X'); + } + textBuf.append(TESTCASES[i][0]); + Object value = map.get(textBuf.toString(), i); + if (!eql(value, TESTCASES[i][2])) { + errln("Invalid search results - Expected:" + TESTCASES[i][2] + " Actual:" + value); + } + } + + // Add duplicated entry + Object prev = map.put("Sunday", FOO); + if (!eql(prev, SUN)) { + errln("The previous value of duplicated entry is not valid - Expected:" + SUN + " Actual:" + prev); + } + // Make sure the value is updated + Object value = map.get("Sunday"); + if (!eql(value, FOO)) { + errln("The map value is not valid - Expected:" + FOO + " Actual:" + value); + } + + // Add duplicated entry with different casing + prev = map.put("sunday", BAR); + if (!eql(prev, FOO)) { + errln("The value should be new in the trie map - Expected:" + FOO + " Actual:" + prev); + } + // Make sure the value is updated + value = map.get("sunday"); + if (!eql(value, BAR)) { + errln("The map value is not valid - Expected:" + BAR + " Actual:" + value); + } + + } + + private boolean eql(Object o1, Object o2) { + if (o1 == null || o2 == null) { + if (o1 == null && o2 == null) { + return true; + } + return false; + } + return o1.equals(o2); + } +} diff --git a/icu4j/src/com/ibm/icu/impl/LRUMap.java b/icu4j/src/com/ibm/icu/impl/LRUMap.java new file mode 100644 index 0000000000..e7628a7a45 --- /dev/null +++ b/icu4j/src/com/ibm/icu/impl/LRUMap.java @@ -0,0 +1,53 @@ +/* + * ***************************************************************************** + * Copyright (C) 2006, International Business Machines Corporation and others. + * All Rights Reserved. + * ***************************************************************************** + */ +package com.ibm.icu.impl; + +//#ifndef FOUNDATION +import java.util.LinkedHashMap; +//#endif +import java.util.Map; + +/* + * Simple LRU (Least Recent Used) Map implementation + */ +public class LRUMap extends LinkedHashMap { + private static final long serialVersionUID = -8178106459089682120L; + + private static final int DEFAULT_MAXCAPACITY = 64; + private static final int DEFAULT_INITIALCAPACITY = 16; + private static final float DEFAULT_LOADFACTOR = 0.75F; + + private final int maxCapacity; + + /** + * Construct a new LRU map with the default initial + * capacity(16) and the maximum capacity(64). + */ + public LRUMap() { + super(DEFAULT_INITIALCAPACITY, DEFAULT_LOADFACTOR, true); + maxCapacity = DEFAULT_MAXCAPACITY; + } + + /** + * Construct a new LRU map with the specified initial + * capacity and the maximum capacity + * + * @param initialCapacity initial capacity of the map + * @param maxCapacity maximum capacity of the map + */ + public LRUMap(int initialCapacity, int maxCapacity) { + super(initialCapacity, DEFAULT_LOADFACTOR, true); + this.maxCapacity = maxCapacity; + } + + /* + * Delete the eldest entry when the size exceeds the limit + */ + protected boolean removeEldestEntry(Map.Entry eldest) { + return (size() > maxCapacity); + } +} diff --git a/icu4j/src/com/ibm/icu/impl/LinkedHashMap.java b/icu4j/src/com/ibm/icu/impl/LinkedHashMap.java new file mode 100644 index 0000000000..339e532d1e --- /dev/null +++ b/icu4j/src/com/ibm/icu/impl/LinkedHashMap.java @@ -0,0 +1,175 @@ +/* + * ***************************************************************************** + * Copyright (C) 2006, International Business Machines Corporation and others. + * All Rights Reserved. + * ***************************************************************************** + */ +package com.ibm.icu.impl; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; + +/** + * JDK1.4 LinkedHashMap equivalent implementation for + * Java foundation profile support. This class is used + * by com.ibm.icu.impl.LRUMap on eclipse + * distributions, which require JDK1.3/Java Foundation + * profile support. + */ +public class LinkedHashMap extends HashMap { + private static final long serialVersionUID = -2497823480436618075L; + + private boolean accessOrder = false; + private LinkedList keyList = new LinkedList(); + + public LinkedHashMap() { + super(); + } + + public LinkedHashMap(int initialCapacity) { + super(initialCapacity); + } + + public LinkedHashMap(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor); + } + + public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { + super(initialCapacity, loadFactor); + this.accessOrder = accessOrder; + } + + public LinkedHashMap(Map m) { + super(); + putAll(m); + } + + public void clear() { + super.clear(); + keyList.clear(); + } + + public Object remove(Object key) { + Object value = super.remove(key); + if (value == null) { + // null might be returned for a map entry + // for null value. So we need to check if + // the key is actually available or not. + // If the key list contains the key, then + // remove the key from the list. + int index = getKeyIndex(key); + if (index >= 0) { + keyList.remove(index); + } + } + return value; + } + + public Object get(Object key) { + Object value = super.get(key); + if (accessOrder) { + // When accessOrder is true, move the key + // to the end of the list + int index = getKeyIndex(key); + if (index >= 0) { + if (index != keyList.size() - 1) { + keyList.remove(index); + keyList.addLast(key); + } + } + } + return value; + } + + public void putAll(Map m) { + Set keySet = m.keySet(); + Iterator it = keySet.iterator(); + while (it.hasNext()) { + Object key = it.next(); + Object value = m.get(key); + put(key, value); + } + } + + public Object put(Object key, Object value) { + Object oldValue = super.put(key, value); + + // Move the key to the end of key list + // if it exists. If not, append the key + // to the end of key list + int index = getKeyIndex(key); + if (index >= 0) { + if (index != keyList.size() - 1) { + keyList.remove(index); + keyList.addLast(key); + } + } + else { + keyList.addLast(key); + } + + // Check if we need to remove the eldest + // entry. + Object eldestKey = keyList.getFirst(); + Object eldestValue = super.get(eldestKey); + MapEntry entry = new MapEntry(eldestKey, eldestValue); + if (removeEldestEntry(entry)) { + keyList.removeFirst(); + super.remove(eldestKey); + } + + return oldValue; + } + + protected boolean removeEldestEntry(Map.Entry eldest) { + return false; + } + + private int getKeyIndex(Object key) { + int index = -1; + for (int i = 0; i < keyList.size(); i++) { + Object o = keyList.get(i); + if (o.equals(key)) { + index = i; + break; + } + } + return index; + } + + protected static class MapEntry implements Map.Entry { + private Object key; + private Object value; + + private MapEntry(Object key, Object value) { + this.key = key; + this.value = value; + } + + public boolean equals(Object o) { + Object otherKey = ((Map.Entry)o).getKey(); + Object otherValue = ((Map.Entry)o).getValue(); + if (key.equals(otherKey) && value.equals(otherValue)) { + return true; + } + return false; + } + + public Object getKey() { + return key; + } + + public Object getValue() { + return value; + } + + public Object setValue(Object value) { + Object oldValue = this.value; + this.value = value; + return oldValue; + } + } +} diff --git a/icu4j/src/com/ibm/icu/impl/SoftCache.java b/icu4j/src/com/ibm/icu/impl/SoftCache.java new file mode 100644 index 0000000000..fb2c808c64 --- /dev/null +++ b/icu4j/src/com/ibm/icu/impl/SoftCache.java @@ -0,0 +1,118 @@ +/* + * ***************************************************************************** + * Copyright (C) 2006, International Business Machines Corporation and others. + * All Rights Reserved. + * ***************************************************************************** + */ +package com.ibm.icu.impl; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; + +/** + * LRU hash map using softly-referenced values + */ +public class SoftCache { + private final LRUMap map; + private final ReferenceQueue queue = new ReferenceQueue(); + + /** + * Construct a SoftCache with default cache size + */ + public SoftCache() { + map = new LRUMap(); + } + + /** + * Construct a SoftCache with sepcified initial/max size + * + * @param initialSize the initial cache size + * @param maxSize the maximum cache size + */ + public SoftCache(int initialSize, int maxSize) { + map = new LRUMap(initialSize, maxSize); + } + + /** + * Put an object to the cache + * @param key key object + * @param value value object + * @return the value previously put, null when not matching key is found. + */ + public synchronized Object put(Object key, Object value) { + if (key == null || value == null) { + throw new IllegalArgumentException("Key and value must not be null"); + } + ProcessQueue(); + Object obj = map.put(key, new SoftMapEntry(key, value, queue)); + return obj; + } + + /** + * Get an object from the cache + * @param key key object + * @return the cached value, null when the value is not found. + */ + public synchronized Object get(Object key) { + ProcessQueue(); + Object obj = null; + SoftMapEntry entry = (SoftMapEntry)map.get(key); + if (entry != null) { + obj = entry.get(); + if (obj == null) { + // It is unlikely to enter into this block, because + // ProcessQueue() should already remove a map entrie + // whose value was deleted by the garbage collactor. + map.remove(key); + } + } + return obj; + } + + /** + * Remove a cache entry from the cache + * @param key key object + * @return the value of cache entry which is removed from this cache, + * or null when no entry for the key was no found. + */ + public synchronized Object remove(Object key) { + return map.remove(key); + } + + /** + * Clear the cache contents + */ + public synchronized void clear() { + ProcessQueue(); + map.clear(); + } + + /** + * Remove map entries which no longer have value + */ + private void ProcessQueue() { + while (true) { + SoftMapEntry entry = (SoftMapEntry)queue.poll(); + if (entry == null) { + break; + } + map.remove(entry.getKey()); + } + } + + /** + * A class for map entry with soft-referenced value + */ + private static class SoftMapEntry extends SoftReference { + private final Object key; + + private SoftMapEntry(Object key, Object value, ReferenceQueue queue) { + super(value, queue); + this.key = key; + } + + private Object getKey() { + return key; + } + } +} \ No newline at end of file diff --git a/icu4j/src/com/ibm/icu/impl/TextTrieMap.java b/icu4j/src/com/ibm/icu/impl/TextTrieMap.java new file mode 100644 index 0000000000..6514b3e8e3 --- /dev/null +++ b/icu4j/src/com/ibm/icu/impl/TextTrieMap.java @@ -0,0 +1,222 @@ +/* + * ***************************************************************************** + * Copyright (C) 2006, International Business Machines Corporation and others. + * All Rights Reserved. + * ***************************************************************************** + */ +package com.ibm.icu.impl; + +import java.util.ArrayList; +import java.util.List; + +import com.ibm.icu.lang.UCharacter; +import com.ibm.icu.text.UTF16; + +/** + * TextTrieMap is a trie implementation for supporting + * fast prefix match for the key. + */ +public class TextTrieMap { + /** + * Costructs a TextTrieMap object. + * + * @param ignoreCase true to use case insensitive match + */ + public TextTrieMap(boolean ignoreCase) { + this.ignoreCase = ignoreCase; + } + + /** + * Adds the text key and its associated object in this object. + * + * @param text The text. + * @param o The object associated with the text. + * @return The previous value associated with specified text, + * or null if there was no mapping for the text. + */ + public synchronized Object put(String text, Object o) { + CharacterNode node = root; + for (int i = 0; i < text.length(); i++) { + int ch = UTF16.charAt(text, i); + node = node.addChildNode(ch); + if (UTF16.getCharCount(ch) == 2) { + i++; + } + } + Object prevObj = node.getObject(); + node.setObject(o); + return prevObj; + } + + /** + * Gets the object associated with the longest prefix + * matching string key. + * + * @param text The text to be matched with prefixes. + * @return The object associated with the longet prefix matching + * matching key, or null if no matching entry is found. + */ + public Object get(String text) { + return get(root, text, 0); + } + + /** + * Gets the object associated with the longest prefix + * matching string key starting at the specified position. + * + * @param text The text to be matched with prefixes. + * @param start The start index of of the text + * @return The object associated with the longet prefix matching + * matching key, or null if no matching entry is found. + */ + public Object get(String text, int start) { + return get(root, text, start); + } + + /** + * Gets the object associated with the longet prefix + * matching string key under the specified node. + * + * @param node The character node in this trie. + * @param text The text to be matched with prefixes. + * @param index The current index within the text. + * @return The object associated with the longest prefix + * match under the node. + */ + private synchronized Object get(CharacterNode node, String text, int index) { + Object obj = node.getObject(); + if (index < text.length()) { + List childNodes = node.getChildNodes(); + if (childNodes == null) { + return obj; + } + int ch = UTF16.charAt(text, index); + int chLen = UTF16.getCharCount(ch); + for (int i = 0; i < childNodes.size(); i++) { + CharacterNode child = (CharacterNode)childNodes.get(i); + if (compare(ch, child.getCharacter())) { + Object tmp = get(child, text, index + chLen); + if (tmp != null) { + obj = tmp; + } + break; + } + } + } + return obj; + } + + /** + * A private method used for comparing two characters. + * + * @param ch1 The first character. + * @param ch2 The second character. + * @return true if the first character matches the second. + */ + private boolean compare(int ch1, int ch2) { + if (ch1 == ch2) { + return true; + } + else if (ignoreCase) { + if (UCharacter.toLowerCase(ch1) == UCharacter.toLowerCase(ch2)) { + return true; + } + else if (UCharacter.toUpperCase(ch1) == UCharacter.toUpperCase(ch2)) { + return true; + } + } + return false; + } + + // The root node of this trie + private CharacterNode root = new CharacterNode(0); + + // Character matching option + boolean ignoreCase; + + /** + * Inner class representing a character node in the trie. + */ + private class CharacterNode { + int character; + List children; + Object obj; + + /** + * Constructs a node for the character. + * + * @param ch The character associated with this node. + */ + public CharacterNode(int ch) { + character = ch; + } + + /** + * Gets the character associated with this node. + * + * @return The character + */ + public int getCharacter() { + return character; + } + + /** + * Sets the object to the node. Only a leaf node has + * the reference of an object associated with a key. + * + * @param obj The object set in the leaf node. + */ + public void setObject(Object obj) { + this.obj = obj; + } + + /** + * Gets the object associated the leaf node. + * + * @return The object. + */ + public Object getObject() { + return obj; + } + + /** + * Adds a child node for the characer under this character + * node in the trie. When the matching child node already + * exists, the reference of the existing child node is + * returned. + * + * @param ch The character associated with a child node. + * @return The child node. + */ + public CharacterNode addChildNode(int ch) { + if (children == null) { + children = new ArrayList(); + CharacterNode newNode = new CharacterNode(ch); + children.add(newNode); + return newNode; + } + CharacterNode node = null; + for (int i = 0; i < children.size(); i++) { + CharacterNode cur = (CharacterNode)children.get(i); + if (compare(ch, cur.getCharacter())) { + node = cur; + break; + } + } + if (node == null) { + node = new CharacterNode(ch); + children.add(node); + } + return node; + } + + /** + * Gets the list of child nodes under this node. + * + * @return The list of child nodes. + */ + public List getChildNodes() { + return children; + } + } +} diff --git a/icu4j/src/com/ibm/icu/impl/ZoneMeta.java b/icu4j/src/com/ibm/icu/impl/ZoneMeta.java index fa6c064227..438501417b 100644 --- a/icu4j/src/com/ibm/icu/impl/ZoneMeta.java +++ b/icu4j/src/com/ibm/icu/impl/ZoneMeta.java @@ -1,6 +1,6 @@ /* ********************************************************************** -* Copyright (c) 2003-2005, International Business Machines +* Copyright (c) 2003-2006, International Business Machines * Corporation and others. All Rights Reserved. ********************************************************************** * Author: Alan Liu @@ -466,6 +466,7 @@ public final class ZoneMeta { private static final String kCUSTOM_ID= "Custom"; //private static ICUResourceBundle zoneBundle = null; private static java.util.Enumeration idEnum = null; + private static SoftCache zoneCache = new SoftCache(); /** * The Olson data is stored the "zoneinfo" resource bundle. * Sub-resources are organized into three ranges of data: Zones, final @@ -505,15 +506,19 @@ public final class ZoneMeta { * found, return 0. */ public static TimeZone getSystemTimeZone(String id) { - try{ - ICUResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER); - ICUResourceBundle res = openOlsonResource(id); - TimeZone z = new OlsonTimeZone(top, res); - z.setID(id); - return z; - }catch(Exception ex){ - return null; + TimeZone z = (TimeZone)zoneCache.get(id); + if (z == null) { + try{ + ICUResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER); + ICUResourceBundle res = openOlsonResource(id); + z = new OlsonTimeZone(top, res); + z.setID(id); + zoneCache.put(id, z); + }catch(Exception ex){ + return null; + } } + return (TimeZone)z.clone(); } public static TimeZone getGMT(){ diff --git a/icu4j/src/com/ibm/icu/text/DateFormatSymbols.java b/icu4j/src/com/ibm/icu/text/DateFormatSymbols.java index 82b7db52e3..9d5342a414 100755 --- a/icu4j/src/com/ibm/icu/text/DateFormatSymbols.java +++ b/icu4j/src/com/ibm/icu/text/DateFormatSymbols.java @@ -9,6 +9,7 @@ package com.ibm.icu.text; import com.ibm.icu.impl.ICUResourceBundle; import com.ibm.icu.impl.CalendarData; +import com.ibm.icu.impl.TextTrieMap; import com.ibm.icu.impl.Utility; import com.ibm.icu.util.Calendar; import com.ibm.icu.util.GregorianCalendar; @@ -16,10 +17,12 @@ import com.ibm.icu.util.TimeZone; import com.ibm.icu.util.UResourceBundle; import com.ibm.icu.impl.ZoneMeta; import com.ibm.icu.util.ULocale; +import com.ibm.icu.impl.SoftCache; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.Locale; import java.util.MissingResourceException; @@ -774,10 +777,13 @@ public class DateFormatSymbols implements Serializable, Cloneable { * @stable ICU 2.0 */ public String[][] getZoneStrings() { - if(zoneStrings==null){ - initZoneStrings(); + String[][] strings = zoneStrings; + if(strings == null){ + // get the default zone strings + ZoneItemInfo zii = getDefaultZoneItemInfo(); + strings = zii.tzStrings; } - return duplicate(zoneStrings); + return duplicate(strings); } /** @@ -787,12 +793,8 @@ public class DateFormatSymbols implements Serializable, Cloneable { */ public void setZoneStrings(String[][] newZoneStrings) { zoneStrings = duplicate(newZoneStrings); - if(zoneStringsHash==null){ - zoneStringsHash = new HashMap(); - } - initZoneStrings(newZoneStrings); - // reset the zone strings. - zoneStrings=null; + // need to update local zone item info + localZoneItemInfo = null; } /** @@ -839,17 +841,17 @@ public class DateFormatSymbols implements Serializable, Cloneable { public int hashCode() { int hashcode = 0; hashcode ^= requestedLocale.toString().hashCode(); - if(zoneStringsHash!=null){ - for(Iterator iter=zoneStringsHash.keySet().iterator(); iter.hasNext();){ - String key = (String)iter.next(); - String[] strings = (String[])zoneStringsHash.get(key); - hashcode ^= key.hashCode(); - for(int i=0; i< strings.length; i++){ - if(strings[i]!=null){ - hashcode ^= strings[i].hashCode(); - } - } - } + String[][] tzStrings = zoneStrings; + if (tzStrings == null){ + ZoneItemInfo zii = getDefaultZoneItemInfo(); + tzStrings = zii.tzStrings; + } + for(int i = 0; i < tzStrings.length; i++) { + for (int j = 0; j < tzStrings[i].length; j++) { + if (tzStrings[i][j] != null) { + hashcode ^= tzStrings[i][j].hashCode(); + } + } } return hashcode; } @@ -878,7 +880,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { && Utility.arrayEquals(standaloneShortWeekdays, that.standaloneShortWeekdays) && Utility.arrayEquals(standaloneNarrowWeekdays, that.standaloneNarrowWeekdays) && Utility.arrayEquals(ampms, that.ampms) - && hashEquals(zoneStringsHash, that.zoneStringsHash) + && arrayOfArrayEquals(zoneStrings, that.zoneStrings) // getDiplayName maps deprecated country and language codes to the current ones // too bad there is no way to get the current codes! // I thought canolicalize() would map the codes but .. alas! it doesn't. @@ -1085,263 +1087,349 @@ public class DateFormatSymbols implements Serializable, Cloneable { ULocale uloc = rb.getULocale(); setLocale(uloc, uloc); } - private static final boolean hashEquals(HashMap h1, HashMap h2){ - if(h1==h2){ // both are null - return true; + + private static final boolean arrayOfArrayEquals(Object[][] aa1, Object[][]aa2) { + if (aa1 == aa2) { // both are null + return true; } - if(h1==null || h2==null){ // one is null and the other is not + if (aa1 == null || aa2 == null) { // one is null and the other is not return false; } - if(h1.size() != h2.size()){ + if (aa1.length != aa2.length) { return false; } - Iterator i1 = h1.keySet().iterator(); - Iterator i2 = h2.keySet().iterator(); + boolean equal = true; + for (int i = 0; i < aa1.length; i++) { + equal = Utility.arrayEquals(aa1[i], aa2[i]); + if (!equal) { + break; + } + } + return equal; + } - while(i1.hasNext()){ - String key = (String) i1.next(); - String[] s1 = (String[])h1.get(key); - String[] s2 = (String[])h2.get(key); - if(Utility.arrayEquals(s1, s2)==false){ - return false; - } + /** + * Package private: used by SimpleDateFormat. + * Gets the string for the specified time zone. + * @param zid The time zone ID + * @param type The type of zone string + * @return The zone string, or null if not available. + */ + String getZoneString(String zid, int type) { + // Try local zone item info first + String zoneString = getZoneString(getLocalZoneItemInfo(), zid, type); + if (zoneString == null) { + // Fallback to the default info + zoneString = getZoneString(getDefaultZoneItemInfo(), zid, type); } - return true; + return zoneString; } - private void initZoneStrings(){ - if(zoneStringsHash==null){ - initZoneStringsHash(); - } - zoneStrings = new String[zoneStringsHash.size()][8]; - int i = 0; - for (Iterator it = zoneStringsKeyList.iterator(); it.hasNext();) { - String key = (String)it.next(); - String[] strings = (String[])zoneStringsHash.get(key); - zoneStrings[i][0] = key; - zoneStrings[i][1] = strings[TIMEZONE_LONG_STANDARD]; - zoneStrings[i][2] = strings[TIMEZONE_SHORT_STANDARD]; - zoneStrings[i][3] = strings[TIMEZONE_LONG_DAYLIGHT]; - zoneStrings[i][4] = strings[TIMEZONE_SHORT_DAYLIGHT]; - zoneStrings[i][5] = strings[TIMEZONE_EXEMPLAR_CITY]; - if(zoneStrings[i][5]==null){ - zoneStrings[i][5] = strings[TIMEZONE_LONG_GENERIC]; - }else{ - zoneStrings[i][6] = strings[TIMEZONE_LONG_GENERIC]; - } - if(zoneStrings[i][6]==null){ - zoneStrings[i][6] = strings[TIMEZONE_SHORT_GENERIC]; - }else{ - zoneStrings[i][7] = strings[TIMEZONE_SHORT_GENERIC]; - } - i++; - } - } - private void initZoneStringsHash(){ - - zoneStringsHash = new HashMap(); - zoneStringsKeyList = new ArrayList(); - for (ULocale tempLocale = requestedLocale; tempLocale != null; tempLocale = tempLocale.getFallback()) { - ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, tempLocale); - ICUResourceBundle zoneStringsBundle = bundle.getWithFallback("zoneStrings"); - for(int i=0; i 1) { - int i; - for (i=0; i item.value.length()) { + item = itemForLocale; + } + } + + if (item != null && textLength == item.value.length()) { + // clone the last match for next time + // only when the substring completely matches + // with the value resolved + lastZoneItem = new ZoneItem(); + lastZoneItem.type = item.type; + lastZoneItem.value = item.value; + lastZoneItem.zid = item.zid; + } + return item; + } + + /** + * A class holds zone strings and searchable indice + */ + private class ZoneItemInfo { + String[][] tzStrings; + HashMap tzidMap; + TextTrieMap tzStringMap; + } + + /** + * A cache for ZoneItemInfo objects, shared by class instances. + */ + private static SoftCache zoneItemInfoCache = new SoftCache(); + + /** + * A ZoneItemInfo instance which holds custom timezone strings + */ + private transient ZoneItemInfo localZoneItemInfo; + + /** + * Single entry cache for findZoneTypeValue() + */ + private transient ZoneItem lastZoneItem; + + /** + * Gets the ZoneItemInfo instance for the locale used by this object. + * If it does not exist, create new one and register in the static cache. + */ + private ZoneItemInfo getDefaultZoneItemInfo() { + ZoneItemInfo zii = (ZoneItemInfo)zoneItemInfoCache.get(requestedLocale); + if (zii != null) { + return zii; + } + zii = getZoneItemInfo(getDefaultZoneStrings(requestedLocale)); + // Add fallback display names + String[] zoneIDs = TimeZone.getAvailableIDs(); + for (int i = 0; i < zoneIDs.length; i++) { + Object o = zii.tzidMap.get(zoneIDs[i]); + if (o != null) { + // Already has names + continue; + } + String value = ZoneMeta.displayFallback(zoneIDs[i], null, requestedLocale); + if (value != null) { + String[] strings = new String[8]; + strings[5] = value; + zii.tzidMap.put(zoneIDs[i], strings); + + ZoneItem item = new ZoneItem(); + item.zid = zoneIDs[i]; + item.value = value; + item.type = TIMEZONE_EXEMPLAR_CITY; + zii.tzStringMap.put(item.value, item); + } + } + zoneItemInfoCache.put(requestedLocale, zii); + return zii; + } + + /** + * Gets the array of zone strings for the specified locale. + */ + private static String[][] getDefaultZoneStrings(ULocale locale) { + ArrayList tmpList = new ArrayList(); + HashSet tmpSet = new HashSet(); + for (ULocale tempLocale = locale; tempLocale != null; tempLocale = tempLocale.getFallback()) { + ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, tempLocale); + ICUResourceBundle zoneStringsBundle = bundle.getWithFallback("zoneStrings"); + for(int i = 0; i < zoneStringsBundle.getSize(); i++){ + ICUResourceBundle zoneTable = zoneStringsBundle.get(i); + String key = Utility.replaceAll(zoneTable.getKey(), ":", "/"); + // hack for the root zone strings + if(key.length() == 0|| zoneTable.getType() != ICUResourceBundle.TABLE){ + continue; + } + if (tmpSet.contains(key)) { + // only add if we don't have already + continue; + } + String[] strings = new String[8]; + strings[0] = key; + try { + strings[1] = zoneTable.getStringWithFallback(LONG_STANDARD); + } catch (MissingResourceException ex) { + // throw away the exception + } + try { + strings[2] = zoneTable.getStringWithFallback(SHORT_STANDARD); + } catch (MissingResourceException ex) { + // throw away the exception + } + try { + strings[3] = zoneTable.getStringWithFallback(LONG_DAYLIGHT); + } catch (MissingResourceException ex) { + // throw away the exception + } + try { + strings[4] = zoneTable.getStringWithFallback(SHORT_DAYLIGHT); + } catch (MissingResourceException ex) { + // throw away the exception + } + try { + String city = zoneTable.getStringWithFallback(EXEMPLAR_CITY); + strings[5] = ZoneMeta.displayFallback(key, city, tempLocale); + } catch (MissingResourceException ex) { + // throw away the exception + } + try { + strings[6] = zoneTable.getStringWithFallback(LONG_GENERIC); + } catch (MissingResourceException ex) { + // throw away the exception + } + try { + strings[7] = zoneTable.getStringWithFallback(SHORT_GENERIC); + } catch (MissingResourceException ex) { + // throw away the exception + } + tmpList.add(strings); + } + } + String[][] array = new String[tmpList.size()][8]; + tmpList.toArray(array); + return array; + } + + /** + * Gets the array of zone strings for the custom zone strings + */ + private ZoneItemInfo getLocalZoneItemInfo() { + if (localZoneItemInfo == null && zoneStrings != null) { + localZoneItemInfo = getZoneItemInfo(zoneStrings); + } + return localZoneItemInfo; + } + + /** + * Creates a new ZoneItemInfo instance from the array of time zone + * strings. + */ + private ZoneItemInfo getZoneItemInfo(String[][] strings) { + ZoneItemInfo zii = new ZoneItemInfo(); + zii.tzStrings = strings; + zii.tzidMap = new HashMap(); + zii.tzStringMap = new TextTrieMap(true); + for (int i = 0; i < strings.length; i++) { + String zid = strings[i][0]; + if (zid != null && zid.length() > 0) { + zii.tzidMap.put(zid, strings[i]); + int nameCount = strings[i].length < 8 ? strings[i].length : 8; + for (int j = 1; j < nameCount; j++) { + if (strings[i][j] != null) { + // map zoneStrings array index to timezone name type + int type = -1; + switch (j) { + case 1: + type = TIMEZONE_LONG_STANDARD; + break; + case 2: + type = TIMEZONE_SHORT_STANDARD; + break; + case 3: + type = TIMEZONE_LONG_DAYLIGHT; + break; + case 4: + type = TIMEZONE_SHORT_DAYLIGHT; + break; + case 5: + if (nameCount == 6 || nameCount == 8) { + type = TIMEZONE_EXEMPLAR_CITY; + } else { + type = TIMEZONE_LONG_GENERIC; + } + break; + case 6: + if (nameCount == 8) { + type = TIMEZONE_LONG_GENERIC; + } else { + type = TIMEZONE_SHORT_GENERIC; + } + break; + case 7: + type = TIMEZONE_SHORT_GENERIC; + break; + default: + // never occur + continue; + } + ZoneItem item = new ZoneItem(); + item.zid = zid; + item.value = strings[i][j]; + item.type = type; + zii.tzStringMap.put(strings[i][j], item); } } } } - return null; + return zii; } - private HashMap zoneStringsHash; /** * save the input locale */ @@ -1370,8 +1458,8 @@ public class DateFormatSymbols implements Serializable, Cloneable { TIMEZONE_LONG_DAYLIGHT = 5, TIMEZONE_EXEMPLAR_CITY = 6, TIMEZONE_COUNT = 7; - - /** + + /** * Package private: used by SimpleDateFormat * Gets the index for the given time zone ID to obtain the timezone * strings for formatting. The time zone ID is just for programmatic @@ -1455,11 +1543,10 @@ public class DateFormatSymbols implements Serializable, Cloneable { dst.standaloneShortWeekdays = duplicate(src.standaloneShortWeekdays); dst.standaloneNarrowWeekdays = duplicate(src.standaloneNarrowWeekdays); dst.ampms = duplicate(src.ampms); - if(src.zoneStringsHash != null){ - dst.zoneStringsHash = (HashMap)src.zoneStringsHash.clone(); + if (src.zoneStrings != null) { + dst.zoneStrings = duplicate(src.zoneStrings); } dst.requestedLocale = new ULocale(src.requestedLocale.toString()); - //dst.zoneStrings = duplicate(src.zoneStrings); dst.localPatternChars = new String (src.localPatternChars); } diff --git a/icu4j/src/com/ibm/icu/text/SimpleDateFormat.java b/icu4j/src/com/ibm/icu/text/SimpleDateFormat.java index be62bb191e..d60a5699f1 100755 --- a/icu4j/src/com/ibm/icu/text/SimpleDateFormat.java +++ b/icu4j/src/com/ibm/icu/text/SimpleDateFormat.java @@ -287,6 +287,13 @@ public class SimpleDateFormat extends DateFormat { */ private transient boolean useFastFormat; + /** + * If true, this object supports fast number format + */ + private transient boolean useFastZeroPaddingNumber; + private transient char zeroDigit; + private char[] decimalBuf = new char[10]; // 10 digit is good enough to store Interger.MAX_VALUE + /** * Construct a SimpleDateFormat using the default pattern for the default * locale. Note: Not all locales support SimpleDateFormat; for full @@ -467,11 +474,17 @@ public class SimpleDateFormat extends DateFormat { // TODO: convert to use ULocale APIs when we get to the text package numberFormat = NumberFormat.getInstance(loc); numberFormat.setGroupingUsed(false); + useFastZeroPaddingNumber = false; ///CLOVER:OFF // difficult to test for case where NumberFormat.getInstance does not // return a DecimalFormat - if (numberFormat instanceof DecimalFormat) + if (numberFormat instanceof DecimalFormat) { ((DecimalFormat)numberFormat).setDecimalSeparatorAlwaysShown(false); + zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit(); + if (numberFormat.getClass().getName().equals("com.ibm.icu.text.DecimalFormat")) { + useFastZeroPaddingNumber = true; + } + } ///CLOVER:ON numberFormat.setParseIntegerOnly(true); /* So that dd.MM.yy can be parsed */ numberFormat.setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00" @@ -884,7 +897,7 @@ public class SimpleDateFormat extends DateFormat { } val = (val / 3) * 5 + (val % 60); // minutes => KKmm buf.append(sign); - buf.append(new DecimalFormat("0000").format(val)); + fastZeroPaddingNubmer(buf, (int)val, 4, 4, '0'); } else { // long form, localized GMT pattern // not in 3.4 locale data, need to add, so use same default as for general time zone names @@ -994,12 +1007,53 @@ public class SimpleDateFormat extends DateFormat { */ protected void zeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits) { + if (useFastZeroPaddingNumber) { + fastZeroPaddingNubmer(buf, value, minDigits, maxDigits, zeroDigit); + return; + } FieldPosition pos = new FieldPosition(-1); numberFormat.setMinimumIntegerDigits(minDigits); numberFormat.setMaximumIntegerDigits(maxDigits); numberFormat.format(value, buf, pos); } + /** + * Internal faster method. This method does not use NumberFormat + * to format digits. + * @internal + */ + private void fastZeroPaddingNubmer(StringBuffer buf, int value, + int minDigits, int maxDigits, char zero) { + value = value < 0 ? -value : value; //?? + minDigits = minDigits < maxDigits ? minDigits : maxDigits; + int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits; + int index = limit - 1; + while (true) { + decimalBuf[index] = (char)((value % 10) + zero); + value /= 10; + if (index == 0 || value == 0) { + break; + } + index--; + } + int padding = minDigits - (limit - index); + for (; padding > 0; padding--) { + decimalBuf[--index] = zero; + } + buf.append(decimalBuf, index, limit - index); + } + + public void setNumberFormat(NumberFormat newNumberFormat) { + super.setNumberFormat(newNumberFormat); + if (newNumberFormat instanceof DecimalFormat) { + zeroDigit = ((DecimalFormat)newNumberFormat).getDecimalFormatSymbols().getZeroDigit(); + useFastZeroPaddingNumber = true; + } + else { + useFastZeroPaddingNumber = false; + } + } + /** * Formats a number with the specified minimum and maximum number of digits. * @stable ICU 2.0 @@ -1377,43 +1431,15 @@ public class SimpleDateFormat extends DateFormat { TimeZone tz = null; String zid = null, value = null; int type = -1; - zid = formatData.getZoneID(getTimeZone().getID()); + + DateFormatSymbols.ZoneItem item = formatData.findZoneIDTypeValue(text, start); + if (item != null) { + zid = item.zid; + value = item.value; + type = item.type; + } if (zid != null) { - DateFormatSymbols.ZoneItem item = formatData.getZoneItem(zid, text, start); - if (item != null) { - zid = item.zid; - value = item.value; - type = item.type; - tz = (TimeZone) getTimeZone().clone(); - } - } - - // optimize for default time zone, assume different from caller - if (tz == null) { - TimeZone defaultZone = TimeZone.getDefault(); - zid = formatData.getZoneID(defaultZone.getID()); - if (zid != null) { - DateFormatSymbols.ZoneItem item = formatData.getZoneItem(zid, text, start); - if (item != null) { - zid = item.zid; - value = item.value; - type = item.type; - tz = defaultZone; - } - } - } - - // still no luck, check all time zone strings - if (tz == null) { - DateFormatSymbols.ZoneItem item = formatData.findZoneIDTypeValue(text, start); - if (item != null) { - zid = item.zid; - value = item.value; - type = item.type; - } - if (zid != null) { - tz = TimeZone.getTimeZone(zid); - } + tz = TimeZone.getTimeZone(zid); } if (tz != null) { // Matched any ?