ICU-5293 DateFormat perfomance improvement, including a fix for timezone parsing bug #5290

X-SVN-Rev: 19960
This commit is contained in:
Yoshito Umaoka 2006-08-02 20:44:55 +00:00
parent a98c968d72
commit 86ff19728b
12 changed files with 1334 additions and 306 deletions

View File

@ -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

View File

@ -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");
}

View File

@ -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());

View File

@ -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.");
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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 <code>com.ibm.icu.impl.LRUMap</code> 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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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(){

View File

@ -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<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;
}
String[] strings = new String[TIMEZONE_COUNT];
try{
strings[TIMEZONE_SHORT_GENERIC] = zoneTable.getStringWithFallback(SHORT_GENERIC);
}catch( MissingResourceException ex){
// throw away the exception
}
try{
strings[TIMEZONE_SHORT_STANDARD] = zoneTable.getStringWithFallback(SHORT_STANDARD);
}catch( MissingResourceException ex){
// throw away the exception
}
try{
strings[TIMEZONE_SHORT_DAYLIGHT] = zoneTable.getStringWithFallback(SHORT_DAYLIGHT);
}catch( MissingResourceException ex){
// throw away the exception
}
try{
strings[TIMEZONE_LONG_GENERIC] = zoneTable.getStringWithFallback(LONG_GENERIC);
}catch( MissingResourceException ex){
// throw away the exception
}
try{
strings[TIMEZONE_LONG_STANDARD] = zoneTable.getStringWithFallback(LONG_STANDARD);
}catch( MissingResourceException ex){
// throw away the exception
}
try{
strings[TIMEZONE_LONG_DAYLIGHT] = zoneTable.getStringWithFallback(LONG_DAYLIGHT);
}catch( MissingResourceException ex){
// throw away the exception
}
try{
String city = zoneTable.getStringWithFallback(EXEMPLAR_CITY);
strings[TIMEZONE_EXEMPLAR_CITY] = ZoneMeta.displayFallback(key, city, tempLocale);
}catch( MissingResourceException ex){
// throw away the exception
}
if(!zoneStringsHash.containsKey(key)){
zoneStringsHash.put(key, strings); // only add if we don't have already
zoneStringsKeyList.add(key);
}
}
}
}
private ArrayList zoneStringsKeyList;
private void initZoneStrings(String[][] newZoneStrings){
if(newZoneStrings==null){
return;
}
zoneStringsKeyList = new ArrayList();
for(int row=0; row<newZoneStrings.length; row++){
String key = newZoneStrings[row][0];
String[] strings = (String[])zoneStringsHash.get(key);
if(strings == null){
strings = new String[TIMEZONE_COUNT];
}
int colCount = zoneStrings[row].length;
for (int col=1; col<colCount; ++col) {
// fastCopyFrom() - see assignArray comments
switch (col){
case 1:
strings[TIMEZONE_LONG_STANDARD] = newZoneStrings[row][col];
break;
case 2:
strings[TIMEZONE_SHORT_STANDARD] = newZoneStrings[row][col];
break;
case 3:
strings[TIMEZONE_LONG_DAYLIGHT] = newZoneStrings[row][col];
break;
case 4:
strings[TIMEZONE_SHORT_DAYLIGHT] = newZoneStrings[row][col];
break;
case 5:
if(colCount==6 || colCount==8){
strings[TIMEZONE_EXEMPLAR_CITY] = newZoneStrings[row][col];
}else{
strings[TIMEZONE_LONG_GENERIC] = newZoneStrings[row][col];
}
break;
case 6:
if(colCount==8){
strings[TIMEZONE_LONG_GENERIC] = newZoneStrings[row][col];
}else{
strings[TIMEZONE_SHORT_GENERIC] = newZoneStrings[row][col];
}
break;
case 7:
strings[TIMEZONE_SHORT_GENERIC] = newZoneStrings[row][col];
break;
default:
throw new IllegalArgumentException();
}
}
zoneStringsHash.put(key, strings);
zoneStringsKeyList.add(key);
}
}
Iterator getZoneStringIDs(){
return zoneStringsHash.keySet().iterator();
}
String getZoneString(String zid, int type){
if(zoneStringsHash == null){
//lazy initialization
initZoneStringsHash();
/**
* Gets the zone string from the specified zone item info
*/
private String getZoneString(ZoneItemInfo zinfo, String zid, int type) {
if (zinfo == null) {
return null;
}
String[] stringsArray = (String[])zoneStringsHash.get(zid);
if(stringsArray != null){
return stringsArray[type];
}
return null;
}
String getZoneID(String zid){
if(zoneStringsHash == null){
initZoneStringsHash();
}
String[] strings = (String[])zoneStringsHash.get(zid);
if (strings != null) {
return zid;
}
try{
// Do a search through the equivalency group for the given ID
int n = TimeZone.countEquivalentIDs(zid);
if (n > 1) {
int i;
for (i=0; i<n; ++i) {
String equivID = TimeZone.getEquivalentID(zid, i);
if (equivID != zid) {
strings = (String[])zoneStringsHash.get(equivID);
if (strings != null) {
return equivID;
}
}
}
}
}catch(MissingResourceException ex){
// throw away the exception
String[] names = (String[])zinfo.tzidMap.get(zid);
if (names != null) {
// get name for the type
int index = -1;
switch (type) {
case TIMEZONE_LONG_STANDARD:
index = 1;
break;
case TIMEZONE_SHORT_STANDARD:
index = 2;
break;
case TIMEZONE_LONG_DAYLIGHT:
index = 3;
break;
case TIMEZONE_SHORT_DAYLIGHT:
index = 4;
break;
case TIMEZONE_EXEMPLAR_CITY:
if (names.length == 6 || names.length == 8) {
index = 5;
}
break;
case TIMEZONE_LONG_GENERIC:
if (names.length == 8) {
index = 6;
} else {
index = 5;
}
break;
case TIMEZONE_SHORT_GENERIC:
if (names.length == 8) {
index = 7;
} else {
index = 6;
}
}
if (index < names.length) {
return names[index];
}
}
return null;
}
class ZoneItem{
String value;
int type;
String zid;
}
ZoneItem getZoneItem(String zid, String text, int start){
if(zoneStringsHash == null){
initZoneStringsHash();
}
ZoneItem item = new ZoneItem();
String[] strings = (String[])zoneStringsHash.get(zid);
if(strings != null){
for(int j=0; j<TIMEZONE_COUNT; j++){
if(strings[j] != null && text.regionMatches(true, start, strings[j], 0, strings[j].length())){
item.type = j;
item.value = strings[j];
item.zid = zid;
return item;
}
/**
* Package private: used by SimpleDateformat
* Gets the ZoneItem instance which has zone strings
* which matches the specified text.
* @param text The text which contains a zone string
* @param start The start position of zone string in the text
* @return A ZonItem instance for the longest matching zone
* string.
*/
ZoneItem findZoneIDTypeValue(String text, int start){
ZoneItem item = null;
int textLength = text.length() - start;
if (lastZoneItem != null && textLength == lastZoneItem.value.length()) {
if (text.regionMatches(true, start, lastZoneItem.value, 0, textLength)) {
item = new ZoneItem();
item.type = lastZoneItem.type;
item.value = lastZoneItem.value;
item.zid = lastZoneItem.zid;
return item;
}
}
return null;
}
ZoneItem findZoneIDTypeValue(String text, int start){
if(zoneStringsHash == null){
initZoneStringsHash();
ZoneItemInfo zinfo = getLocalZoneItemInfo();
if (zinfo != null) {
// look up the zone string in localZoneItemInfo first
item = (ZoneItem)zinfo.tzStringMap.get(text, start);
}
ZoneItem item = new ZoneItem();
for (Iterator it = zoneStringsHash.keySet().iterator(); it.hasNext();) {
String key = (String)it.next();
String[] strings = (String[])zoneStringsHash.get(key);
if(strings != null){
for(int j=0; j<TIMEZONE_COUNT; j++){
if(strings[j] != null && text.regionMatches(true, start, strings[j], 0, strings[j].length())){
item.type = j;
item.value = strings[j];
item.zid = key;
return item;
// look up the zone string in default ZoneItemInfo for the locale
zinfo = getDefaultZoneItemInfo();
ZoneItem itemForLocale = (ZoneItem)zinfo.tzStringMap.get(text, start);
if (itemForLocale != null) {
// we want to use longer match
if (item == null || itemForLocale.value.length() > 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);
}

View File

@ -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. <b>Note:</b> 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 ?