-//##header\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//#else\r
-/*\r
- *******************************************************************************\r
- * Copyright (C) 1996-2009, International Business Machines Corporation and *\r
- * others. All Rights Reserved. *\r
- *******************************************************************************\r
- */\r
-package com.ibm.icu.dev.test.util;\r
-\r
-import java.io.*;\r
-import java.util.*;\r
-\r
-import com.ibm.icu.impl.Utility;\r
-import com.ibm.icu.text.UTF16;\r
-import com.ibm.icu.text.UnicodeSet;\r
-import com.ibm.icu.text.UnicodeSetIterator;\r
-import com.ibm.icu.util.Freezable;\r
-/**\r
- * Class for mapping Unicode characters to values\r
- * Much smaller storage than using HashMap, and much faster and more compact than\r
- * a list of UnicodeSets.\r
- * @author Davis\r
- */\r
-\r
-public final class UnicodeMap implements Cloneable, Freezable, Externalizable {\r
- /**\r
- * For serialization\r
- */\r
- private static final long serialVersionUID = -6540936876295804105L;\r
- static final boolean ASSERTIONS = false;\r
- static final long GROWTH_PERCENT = 200; // 100 is no growth!\r
- static final long GROWTH_GAP = 10; // extra bump!\r
-\r
- private int length;\r
- // two parallel arrays to save memory. Wish Java had structs.\r
- private int[] transitions;\r
- private Object[] values;\r
- \r
- private LinkedHashSet availableValues = new LinkedHashSet();\r
- private transient boolean staleAvailableValues;\r
-\r
- private transient boolean errorOnReset;\r
- private transient boolean locked;\r
- private int lastIndex;\r
- \r
- { clear(); }\r
- \r
- public UnicodeMap clear() {\r
- if (locked) throw new UnsupportedOperationException("Attempt to modify locked object");\r
- length = 2;\r
- transitions = new int[] {0,0x110000,0,0,0,0,0,0,0,0};\r
- values = new Object[10];\r
- \r
- availableValues.clear();\r
- staleAvailableValues = false;\r
-\r
- errorOnReset = false;\r
- lastIndex = 0;\r
- return this;\r
- }\r
- \r
- /* Boilerplate */\r
- public boolean equals(Object other) {\r
- if (other == null) return false;\r
- try {\r
- UnicodeMap that = (UnicodeMap) other;\r
- if (length != that.length) return false;\r
- for (int i = 0; i < length-1; ++i) {\r
- if (transitions[i] != that.transitions[i]) return false;\r
- if (!areEqual(values[i], that.values[i])) return false;\r
- }\r
- return true;\r
- } catch (ClassCastException e) {\r
- return false;\r
- }\r
- }\r
- \r
- public int getHashCode(Object o) {\r
- return o.hashCode();\r
- //equator.getHashCode\r
- }\r
- \r
- public static boolean areEqual(Object a , Object b) {\r
- if (a == b) return true;\r
- if (a == null || b == null) return false;\r
- return a.equals(b);\r
- }\r
- \r
- public int hashCode() {\r
- int result = length;\r
- // TODO might want to abbreviate this for speed.\r
- for (int i = 0; i < length-1; ++i) {\r
- result = 37*result + transitions[i];\r
- result = 37*result + getHashCode(values[i]);\r
- }\r
- return result;\r
- }\r
- \r
- /**\r
- * Standard clone. Warning, as with Collections, does not do deep clone.\r
- */\r
- public Object cloneAsThawed() {\r
- UnicodeMap that = new UnicodeMap();\r
- that.length = length;\r
- that.transitions = (int[]) transitions.clone();\r
- that.values = (Object[]) values.clone();\r
- that.availableValues = new LinkedHashSet(availableValues);\r
- that.locked = false;\r
- return that;\r
- }\r
- \r
- /* for internal consistency checking */\r
- \r
- void _checkInvariants() {\r
- if (length < 2\r
- || length > transitions.length\r
- || transitions.length != values.length) {\r
- throw new IllegalArgumentException("Invariant failed: Lengths bad");\r
- }\r
- for (int i = 1; i < length-1; ++i) {\r
- if (areEqual(values[i-1], values[i])) {\r
- throw new IllegalArgumentException("Invariant failed: values shared at " \r
- + "\t" + Utility.hex(i-1) + ": <" + values[i-1] + ">"\r
- + "\t" + Utility.hex(i) + ": <" + values[i] + ">"\r
- );\r
- }\r
- }\r
- if (transitions[0] != 0 || transitions[length-1] != 0x110000) {\r
- throw new IllegalArgumentException("Invariant failed: bounds set wrong");\r
- }\r
- for (int i = 1; i < length-1; ++i) {\r
- if (transitions[i-1] >= transitions[i]) {\r
- throw new IllegalArgumentException("Invariant failed: not monotonic"\r
- + "\t" + Utility.hex(i-1) + ": " + transitions[i-1]\r
- + "\t" + Utility.hex(i) + ": " + transitions[i]\r
- );\r
- }\r
- }\r
- }\r
- \r
- /**\r
- * Finds an index such that inversionList[i] <= codepoint < inversionList[i+1]\r
- * Assumes that 0 <= codepoint <= 0x10FFFF\r
- * @param codepoint\r
- * @return the index\r
- */\r
- private int _findIndex(int c) {\r
- int lo = 0;\r
- int hi = length - 1;\r
- int i = (lo + hi) >>> 1;\r
- // invariant: c >= list[lo]\r
- // invariant: c < list[hi]\r
- while (i != lo) {\r
- if (c < transitions[i]) {\r
- hi = i;\r
- } else {\r
- lo = i;\r
- }\r
- i = (lo + hi) >>> 1;\r
- }\r
- if (ASSERTIONS) _checkFind(c, lo);\r
- return lo;\r
- }\r
- \r
- private void _checkFind(int codepoint, int value) {\r
- int other = __findIndex(codepoint);\r
- if (other != value) {\r
- throw new IllegalArgumentException("Invariant failed: binary search"\r
- + "\t" + Utility.hex(codepoint) + ": " + value\r
- + "\tshould be: " + other); \r
- }\r
- }\r
- \r
- private int __findIndex(int codepoint) {\r
- // TODO use binary search\r
- for (int i = length-1; i > 0; --i) {\r
- if (transitions[i] <= codepoint) return i;\r
- }\r
- return 0;\r
- }\r
- \r
- /*\r
- * Try indexed lookup\r
- \r
- static final int SHIFT = 8;\r
- int[] starts = new int[0x10FFFF>>SHIFT]; // lowest transition index where codepoint>>x can be found\r
- boolean startsValid = false;\r
- private int findIndex(int codepoint) {\r
- if (!startsValid) {\r
- int start = 0;\r
- for (int i = 1; i < length; ++i) {\r
- \r
- }\r
- }\r
- for (int i = length-1; i > 0; --i) {\r
- if (transitions[i] <= codepoint) return i;\r
- }\r
- return 0;\r
- }\r
- */\r
- \r
- /**\r
- * Remove the items from index through index+count-1.\r
- * Logically reduces the size of the internal arrays.\r
- * @param index\r
- * @param count\r
- */\r
- private void _removeAt(int index, int count) {\r
- for (int i = index + count; i < length; ++i) {\r
- transitions[i-count] = transitions[i];\r
- values[i-count] = values[i];\r
- }\r
- length -= count;\r
- }\r
- /**\r
- * Add a gap from index to index+count-1.\r
- * The values there are undefined, and must be set.\r
- * Logically grows arrays to accomodate. Actual growth is limited\r
- * @param index\r
- * @param count\r
- */\r
- private void _insertGapAt(int index, int count) {\r
- int newLength = length + count;\r
- int[] oldtransitions = transitions;\r
- Object[] oldvalues = values;\r
- if (newLength > transitions.length) {\r
- int allocation = (int) (GROWTH_GAP + (newLength * GROWTH_PERCENT) / 100);\r
- transitions = new int[allocation];\r
- values = new Object[allocation];\r
- for (int i = 0; i < index; ++i) {\r
- transitions[i] = oldtransitions[i];\r
- values[i] = oldvalues[i];\r
- }\r
- } \r
- for (int i = length - 1; i >= index; --i) {\r
- transitions[i+count] = oldtransitions[i];\r
- values[i+count] = oldvalues[i];\r
- }\r
- length = newLength;\r
- }\r
- \r
- /**\r
- * Associates code point with value. Removes any previous association.\r
- * @param codepoint\r
- * @param value\r
- * @return this, for chaining\r
- */\r
- private UnicodeMap _put(int codepoint, Object value) {\r
- // Warning: baseIndex is an invariant; must\r
- // be defined such that transitions[baseIndex] < codepoint\r
- // at end of this routine.\r
- int baseIndex;\r
- if (transitions[lastIndex] <= codepoint \r
- && codepoint < transitions[lastIndex+1]) {\r
- baseIndex = lastIndex;\r
- } else { \r
- baseIndex = _findIndex(codepoint);\r
- }\r
- int limitIndex = baseIndex + 1;\r
- // cases are (a) value is already set\r
- if (areEqual(values[baseIndex], value)) return this;\r
- if (locked) throw new UnsupportedOperationException("Attempt to modify locked object");\r
- if (errorOnReset && values[baseIndex] != null) {\r
- throw new IllegalArgumentException("Attempt to reset value for " + Utility.hex(codepoint)\r
- + " when that is disallowed. Old: " + values[baseIndex] + "; New: " + value);\r
- }\r
-\r
- // adjust the available values\r
- staleAvailableValues = true;\r
- availableValues.add(value); // add if not there already \r
-\r
- int baseCP = transitions[baseIndex];\r
- int limitCP = transitions[limitIndex];\r
- // we now start walking through the difference case,\r
- // based on whether we are at the start or end of range\r
- // and whether the range is a single character or multiple\r
- \r
- if (baseCP == codepoint) {\r
- // CASE: At very start of range\r
- boolean connectsWithPrevious = \r
- baseIndex != 0 && areEqual(value, values[baseIndex-1]); \r
- \r
- if (limitCP == codepoint + 1) {\r
- // CASE: Single codepoint range\r
- boolean connectsWithFollowing =\r
- baseIndex < length - 1 && areEqual(value, values[limitIndex]);\r
- \r
- if (connectsWithPrevious) {\r
- // A1a connects with previous & following, so remove index\r
- if (connectsWithFollowing) {\r
- _removeAt(baseIndex, 2);\r
- } else {\r
- _removeAt(baseIndex, 1); // extend previous\r
- }\r
- --baseIndex; // fix up\r
- } else if (connectsWithFollowing) {\r
- _removeAt(baseIndex, 1); // extend following backwards\r
- transitions[baseIndex] = codepoint; \r
- } else {\r
- // doesn't connect on either side, just reset\r
- values[baseIndex] = value;\r
- }\r
- } else if (connectsWithPrevious) { \r
- // A.1: start of multi codepoint range\r
- // if connects\r
- ++transitions[baseIndex]; // extend previous\r
- } else {\r
- // otherwise insert new transition\r
- transitions[baseIndex] = codepoint+1; // fix following range\r
- _insertGapAt(baseIndex, 1);\r
- values[baseIndex] = value;\r
- transitions[baseIndex] = codepoint;\r
- }\r
- } else if (limitCP == codepoint + 1) {\r
- // CASE: at end of range \r
- // if connects, just back up range\r
- boolean connectsWithFollowing =\r
- baseIndex < length - 1 && areEqual(value, values[limitIndex]);\r
-\r
- if (connectsWithFollowing) {\r
- --transitions[limitIndex]; \r
- return this; \r
- } else {\r
- _insertGapAt(limitIndex, 1);\r
- transitions[limitIndex] = codepoint;\r
- values[limitIndex] = value;\r
- }\r
- } else {\r
- // CASE: in middle of range\r
- // insert gap, then set the new range\r
- _insertGapAt(++baseIndex,2);\r
- transitions[baseIndex] = codepoint;\r
- values[baseIndex] = value;\r
- transitions[baseIndex+1] = codepoint + 1;\r
- values[baseIndex+1] = values[baseIndex-1]; // copy lower range values\r
- }\r
- lastIndex = baseIndex; // store for next time\r
- return this;\r
- }\r
- private UnicodeMap _putAll(int startCodePoint, int endCodePoint, Object value) {\r
- for (int i = startCodePoint; i <= endCodePoint; ++i) {\r
- _put(i, value);\r
- }\r
- return this;\r
- }\r
- /**\r
- * Sets the codepoint value.\r
- * @param codepoint\r
- * @param value\r
- * @return this (for chaining)\r
- */\r
- public UnicodeMap put(int codepoint, Object value) {\r
- if (codepoint < 0 || codepoint > 0x10FFFF) {\r
- throw new IllegalArgumentException("Codepoint out of range: " + codepoint);\r
- }\r
- _put(codepoint, value);\r
- if (ASSERTIONS) _checkInvariants();\r
- return this;\r
- }\r
- /**\r
- * Adds bunch o' codepoints; otherwise like put.\r
- * @param codepoints\r
- * @param value\r
- * @return this (for chaining)\r
- */\r
- public UnicodeMap putAll(UnicodeSet codepoints, Object value) {\r
- // TODO optimize\r
- UnicodeSetIterator it = new UnicodeSetIterator(codepoints);\r
- while (it.nextRange()) {\r
- _putAll(it.codepoint, it.codepointEnd, value);\r
- }\r
- return this;\r
- }\r
- \r
- /**\r
- * Adds bunch o' codepoints; otherwise like add.\r
- * @param startCodePoint\r
- * @param endCodePoint\r
- * @param value\r
- * @return this (for chaining)\r
- */\r
- public UnicodeMap putAll(int startCodePoint, int endCodePoint, Object value) {\r
- if (startCodePoint < 0 || endCodePoint > 0x10FFFF) {\r
- throw new IllegalArgumentException("Codepoint out of range: "\r
- + Utility.hex(startCodePoint) + ".." + Utility.hex(endCodePoint));\r
- }\r
- // TODO optimize\r
- for (int i = startCodePoint; i <= endCodePoint; ++i) {\r
- _put(i, value);\r
- }\r
- return this;\r
- }\r
- /**\r
- * Add all the (main) values from a Unicode property\r
- * @param prop the property to add to the map\r
- * @return this (for chaining)\r
- */\r
- public UnicodeMap putAll(UnicodeProperty prop) {\r
- // TODO optimize\r
- for (int i = 0; i <= 0x10FFFF; ++i) {\r
- _put(i, prop.getValue(i));\r
- }\r
- return this;\r
- }\r
-\r
- /**\r
- * Add all the (main) values from a Unicode property\r
- * @param prop the property to add to the map\r
- * @return this (for chaining)\r
- */\r
- public UnicodeMap putAll(UnicodeMap prop) {\r
- // TODO optimize\r
- for (int i = 0; i <= 0x10FFFF; ++i) {\r
- _put(i, prop.getValue(i));\r
- }\r
- return this;\r
- }\r
-\r
- /**\r
- * Set the currently unmapped Unicode code points to the given value.\r
- * @param value the value to set\r
- * @return this (for chaining)\r
- */\r
- public UnicodeMap setMissing(Object value) {\r
- // fast path, if value not yet present\r
- if (!getAvailableValues().contains(value)) {\r
- staleAvailableValues = true;\r
- availableValues.add(value);\r
- for (int i = 0; i < length; ++i) {\r
- if (values[i] == null) values[i] = value;\r
- }\r
- return this;\r
- } else {\r
- return putAll(getSet(null), value);\r
- }\r
- }\r
- /**\r
- * Returns the set associated with a given value. Deposits into\r
- * result if it is not null. Remember to clear if you just want\r
- * the new values.\r
- * @param value\r
- * @param result\r
- * @return result\r
- */\r
- public UnicodeSet getSet(Object value, UnicodeSet result) {\r
- if (result == null) result = new UnicodeSet();\r
- for (int i = 0; i < length - 1; ++i) {\r
- if (areEqual(value, values[i])) {\r
- result.add(transitions[i], transitions[i+1]-1);\r
- } \r
- }\r
- return result;\r
- }\r
-\r
- public UnicodeSet getSet(Object value) {\r
- return getSet(value,null);\r
- }\r
-\r
- public UnicodeSet keySet() {\r
- return getSet(null,null).complement();\r
- }\r
- /**\r
- * Returns the list of possible values. Deposits each non-null value into\r
- * result. Creates result if it is null. Remember to clear result if\r
- * you are not appending to existing collection.\r
- * @param result\r
- * @return result\r
- */\r
- public Collection getAvailableValues(Collection result) {\r
- if (staleAvailableValues) {\r
- // collect all the current values\r
- // retain them in the availableValues\r
- Set temp = new HashSet();\r
- for (int i = 0; i < length - 1; ++i) {\r
- if (values[i] != null) temp.add(values[i]);\r
- }\r
- availableValues.retainAll(temp);\r
- staleAvailableValues = false;\r
- }\r
- if (result == null) result = new ArrayList(availableValues.size());\r
- result.addAll(availableValues);\r
- return result;\r
- }\r
- \r
- /**\r
- * Convenience method\r
- */\r
- public Collection getAvailableValues() {\r
- return getAvailableValues(null);\r
- }\r
- /**\r
- * Gets the value associated with a given code point.\r
- * Returns null, if there is no such value.\r
- * @param codepoint\r
- * @return the value\r
- */\r
- public Object getValue(int codepoint) {\r
- if (codepoint < 0 || codepoint > 0x10FFFF) {\r
- throw new IllegalArgumentException("Codepoint out of range: " + codepoint);\r
- }\r
- return values[_findIndex(codepoint)];\r
- }\r
- \r
- /**\r
- * Change a new string from the source string according to the mappings. For each code point cp, if getValue(cp) is null, append the character, otherwise append getValue(cp).toString()\r
- * @param source\r
- * @return\r
- */\r
- public String fold(String source) {\r
- StringBuffer result = new StringBuffer();\r
- int cp;\r
- for (int i = 0; i < source.length(); i += UTF16.getCharCount(cp)) {\r
- cp = UTF16.charAt(source, i);\r
- Object mResult = getValue(cp);\r
- if (mResult != null) {\r
- result.append(mResult);\r
- } else {\r
- UTF16.append(result, cp);\r
- }\r
- }\r
- return result.toString();\r
- }\r
- \r
- public interface Composer {\r
- Object compose(int codePoint, Object a, Object b);\r
- }\r
- \r
- public UnicodeMap composeWith(UnicodeMap other, Composer composer) {\r
- for (int i = 0; i <= 0x10FFFF; ++i) {\r
- Object v1 = getValue(i);\r
- Object v2 = other.getValue(i);\r
- Object v3 = composer.compose(i, v1, v2);\r
- if (v1 != v3 && (v1 == null || !v1.equals(v3))) put(i, v3);\r
- }\r
- return this;\r
- }\r
- \r
- public UnicodeMap composeWith(UnicodeSet set, Object value, Composer composer) {\r
- for (UnicodeSetIterator it = new UnicodeSetIterator(set); it.next();) {\r
- int i = it.codepoint;\r
- Object v1 = getValue(i);\r
- Object v3 = composer.compose(i, v1, value);\r
- if (v1 != v3 && (v1 == null || !v1.equals(v3))) put(i, v3);\r
- }\r
- return this;\r
- }\r
- \r
- /**\r
- * Follow the style used by UnicodeSetIterator\r
- */\r
- public static class MapIterator {\r
- public int codepoint;\r
- public int codepointEnd;\r
- public Object value;\r
- \r
- private UnicodeMap map;\r
- private int index;\r
- private int startRange;\r
- private int endRange;\r
- private Object lastValue;\r
- \r
- public MapIterator(UnicodeMap map) {\r
- reset(map);\r
- }\r
- // note: length of 2 means {0, 110000}. Only want to index up to 0!\r
- public boolean nextRange() {\r
- if (index < 0 || index >= map.length - 1) return false;\r
- value = map.values[index];\r
- codepoint = startRange = map.transitions[index++];\r
- codepointEnd = endRange = map.transitions[index] - 1; // -1 to make limit into end\r
- return true;\r
- }\r
- public boolean next() {\r
- if (startRange > endRange) {\r
- //System.out.println("***" + Utility.hex(startRange) + ".." + Utility.hex(endRange));\r
- if (!nextRange()) return false;\r
- // index now points AFTER the start of the range\r
- lastValue = map.values[index-1];\r
- //System.out.println("***" + Utility.hex(codepoint) + ".." + Utility.hex(codepointEnd) + " => " + lastValue);\r
- }\r
- value = lastValue;\r
- codepoint = codepointEnd = startRange++; // set to first, and iterate\r
- return true;\r
- }\r
-\r
- public MapIterator reset() {\r
- index = 0;\r
- startRange = 0;\r
- endRange = -1;\r
- return this;\r
- }\r
- public MapIterator reset(UnicodeMap newMap) {\r
- this.map = newMap;\r
- return reset();\r
- }\r
- }\r
- \r
- public String toString() {\r
- return toString(null);\r
- }\r
- public String toString(Comparator collected) {\r
- StringBuffer result = new StringBuffer(); \r
- if (collected == null) {\r
- for (int i = 0; i < length-1; ++i) {\r
- Object value = values[i];\r
- if (value == null) continue;\r
- int start = transitions[i];\r
- int end = transitions[i+1]-1;\r
- result.append(Utility.hex(start));\r
- if (start != end) result.append("..")\r
- .append(Utility.hex(end));\r
- result.append("\t=> ")\r
- .append(values[i] == null ? "null" : values[i].toString())\r
- .append("\r\n");\r
- }\r
- } else {\r
- Set set = (Set) getAvailableValues(new TreeSet(collected));\r
- for (Iterator it = set.iterator(); it.hasNext();) {\r
- Object value = it.next();\r
- UnicodeSet s = getSet(value);\r
- result.append(value)\r
- .append("\t=> ")\r
- .append(s.toPattern(true))\r
- .append("\r\n");\r
- }\r
- }\r
- return result.toString();\r
- }\r
- /**\r
- * @return Returns the errorOnReset.\r
- */\r
- public boolean getErrorOnReset() {\r
- return errorOnReset;\r
- }\r
- /**\r
- * @param errorOnReset The errorOnReset to set.\r
- */\r
- public void setErrorOnReset(boolean errorOnReset) {\r
- this.errorOnReset = errorOnReset;\r
- }\r
-\r
- /* (non-Javadoc)\r
- * @see com.ibm.icu.dev.test.util.Lockable#isLocked()\r
- */\r
- public boolean isFrozen() {\r
- // TODO Auto-generated method stub\r
- return locked;\r
- }\r
-\r
- /* (non-Javadoc)\r
- * @see com.ibm.icu.dev.test.util.Lockable#lock()\r
- */\r
- public Object freeze() {\r
- locked = true;\r
- return this;\r
- }\r
- \r
- static final boolean DEBUG_WRITE = false;\r
- \r
- // TODO Fix to serialize more than just strings.\r
- // Only if all the items are strings will we do the following compression\r
- // Otherwise we'll just use Java Serialization, bulky as it is\r
- public void writeExternal(ObjectOutput out1) throws IOException {\r
- DataOutputCompressor sc = new DataOutputCompressor(out1);\r
- // if all objects are strings\r
- Collection availableVals = getAvailableValues();\r
- boolean allStrings = allAreString(availableVals);\r
- sc.writeBoolean(allStrings);\r
- Map object_index = new LinkedHashMap();\r
- if (allAreString(availableVals)) {\r
- sc.writeStringSet(new TreeSet(availableVals), object_index);\r
- } else {\r
- sc.writeCollection(availableVals, object_index); \r
- }\r
- sc.writeUInt(length);\r
- int lastTransition = -1;\r
- int lastValueNumber = 0;\r
- if (DEBUG_WRITE) System.out.println("Trans count: " + length);\r
- for (int i = 0; i < length; ++i) {\r
- int valueNumber = ((Integer)object_index.get(values[i])).intValue();\r
- if (DEBUG_WRITE) System.out.println("Trans: " + transitions[i] + ",\t" + valueNumber);\r
- \r
- int deltaTransition = transitions[i] - lastTransition;\r
- lastTransition = transitions[i];\r
- int deltaValueNumber = valueNumber - lastValueNumber;\r
- lastValueNumber = valueNumber;\r
- \r
- deltaValueNumber <<= 1; // make room for one bit\r
- boolean canCombine = deltaTransition == 1;\r
- if (canCombine) deltaValueNumber |= 1;\r
- sc.writeInt(deltaValueNumber);\r
- if (DEBUG_WRITE) System.out.println("deltaValueNumber: " + deltaValueNumber);\r
- if (!canCombine) {\r
- sc.writeUInt(deltaTransition);\r
- if (DEBUG_WRITE) System.out.println("deltaTransition: " + deltaTransition);\r
- }\r
- }\r
- sc.flush();\r
- }\r
-\r
- /**\r
- * \r
- */\r
- private boolean allAreString(Collection availableValues2) {\r
- //if (true) return false;\r
- for (Iterator it = availableValues2.iterator(); it.hasNext();) {\r
- if (!(it.next() instanceof String)) return false;\r
- }\r
- return true;\r
- }\r
-\r
- public void readExternal(ObjectInput in1) throws IOException, ClassNotFoundException {\r
- DataInputCompressor sc = new DataInputCompressor(in1);\r
- boolean allStrings = sc.readBoolean();\r
- Object[] valuesList;\r
- availableValues = new LinkedHashSet();\r
- if (allStrings) {\r
- valuesList = sc.readStringSet(availableValues);\r
- } else {\r
- valuesList = sc.readCollection(availableValues); \r
- }\r
- length = sc.readUInt();\r
- transitions = new int[length];\r
- if (DEBUG_WRITE) System.out.println("Trans count: " + length);\r
- values = new Object[length];\r
- int currentTransition = -1;\r
- int currentValue = 0;\r
- int deltaTransition;\r
- for (int i = 0; i < length; ++i) {\r
- int temp = sc.readInt();\r
- if (DEBUG_WRITE) System.out.println("deltaValueNumber: " + temp);\r
- boolean combined = (temp & 1) != 0;\r
- temp >>= 1;\r
- values[i] = valuesList[currentValue += temp];\r
- if (!combined) {\r
- deltaTransition = sc.readUInt();\r
- if (DEBUG_WRITE) System.out.println("deltaTransition: " + deltaTransition);\r
- } else {\r
- deltaTransition = 1;\r
- }\r
- transitions[i] = currentTransition += deltaTransition; // delta value\r
- if (DEBUG_WRITE) System.out.println("Trans: " + transitions[i] + ",\t" + currentValue);\r
- }\r
- }\r
-\r
- /**\r
- * \r
- */\r
- static int findCommon(String last, String s) {\r
- int minLen = Math.min(last.length(), s.length());\r
- for (int i = 0; i < minLen; ++i) {\r
- if (last.charAt(i) != s.charAt(i)) return i;\r
- }\r
- return minLen;\r
- }\r
-\r
-\r
-// /**\r
-// * @param sc\r
-// * @throws IOException\r
-// * \r
-// */\r
-// private void showSize(String title, ObjectOutput out, StreamCompressor sc) throws IOException {\r
-// sc.showSize(this, title, out);\r
-// }\r
-// //public void readObject(ObjectInputStream in) throws IOException {\r
-// public static class StreamCompressor {\r
-// transient byte[] buffer = new byte[1];\r
-// transient StringBuffer stringBuffer = new StringBuffer();\r
-// \r
-// transient byte[] readWriteBuffer = new byte[8];\r
-// int position = 0;\r
-// DataOutput out;\r
-// DataInput in;\r
-//\r
-// /**\r
-// * Format is:\r
-// * @throws IOException\r
-// */\r
-// public void writeInt(int i) throws IOException {\r
-// while (true) {\r
-// if (position == readWriteBuffer.length) {\r
-// out.write(readWriteBuffer);\r
-// position = 0;\r
-// }\r
-// if ((i & ~0x7F) == 0) {\r
-// readWriteBuffer[position++] = (byte)i;\r
-// break;\r
-// }\r
-// readWriteBuffer[position++] = (byte)(0x80 | i);\r
-// i >>>= 7;\r
-// }\r
-// }\r
-// /**\r
-// * @throws IOException\r
-// * \r
-// */\r
-// public int readNInt(ObjectInput in) throws IOException {\r
-// int result = readInt(in);\r
-// boolean negative = (result & 1) != 0;\r
-// result >>>= 1;\r
-// if (negative) result = ~result;\r
-// return result;\r
-// }\r
-// /**\r
-// * @throws IOException\r
-// * \r
-// */\r
-// public void writeNInt(int input) throws IOException {\r
-// int flag = 0;\r
-// if (input < 0) {\r
-// input = ~input;\r
-// flag = 1;\r
-// }\r
-// input = (input << 1) | flag;\r
-// writeInt(out, input);\r
-// }\r
-// /**\r
-// * @throws IOException\r
-// * \r
-// */\r
-// public void flush() throws IOException {\r
-// out.write(readWriteBuffer);\r
-// position = 0;\r
-// }\r
-// \r
-// int readPosition = readWriteBuffer.length;\r
-// \r
-// public int readInt(ObjectInput in) throws IOException {\r
-// int result = 0;\r
-// int offset = 0;\r
-// while (true) {\r
-// if (readPosition == readWriteBuffer.length) {\r
-// in.read(readWriteBuffer);\r
-// readPosition = 0;\r
-// }\r
-// //in.read(buffer);\r
-// int input = readWriteBuffer[readPosition++]; // buffer[0];\r
-// result |= (input & 0x7F) << offset;\r
-// if ((input & 0x80) == 0) {\r
-// return result;\r
-// }\r
-// offset += 7;\r
-// } \r
-// }\r
-//\r
-// /**\r
-// * @throws IOException\r
-// * \r
-// */\r
-// public void writeString(String s) throws IOException {\r
-// writeInt(UTF16.countCodePoint(s));\r
-// writeCodePoints(s);\r
-// }\r
-// /**\r
-// * \r
-// */\r
-// private void writeCodePoints(String s) throws IOException {\r
-// int cp = 0;\r
-// for (int i = 0; i < s.length(); i += UTF16.getCharCount(cp)) {\r
-// cp = UTF16.charAt(s, i);\r
-// writeInt(cp);\r
-// }\r
-// }\r
-// /**\r
-// * @throws IOException\r
-// * \r
-// */\r
-// public String readString() throws IOException {\r
-// int len = readInt(in);\r
-// return readCodePoints(in, len);\r
-// }\r
-// /**\r
-// * \r
-// */\r
-// private String readCodePoints(int len) throws IOException {\r
-// stringBuffer.setLength(0);\r
-// for (int i = 0; i < len; ++i) {\r
-// int cp = readInt(in);\r
-// UTF16.append(stringBuffer, cp);\r
-// }\r
-// return stringBuffer.toString();\r
-// }\r
-// /**\r
-// * @param this\r
-// * @throws IOException\r
-// * \r
-// */\r
-// private void showSize(UnicodeMap map, String title, ObjectOutput out) throws IOException {\r
-// out.flush();\r
-// System.out.println(title + ": " + (map.debugOut.size() + position));\r
-// }\r
-// }\r
-}\r
-//#endif\r
+//##header J2SE15
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//#else
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2009, International Business Machines Corporation and *
+ * others. All Rights Reserved. *
+ *******************************************************************************
+ */
+package com.ibm.icu.dev.test.util;
+
+import java.io.*;
+import java.util.*;
+
+import com.ibm.icu.impl.Utility;
+import com.ibm.icu.text.UTF16;
+import com.ibm.icu.text.UnicodeSet;
+import com.ibm.icu.text.UnicodeSetIterator;
+import com.ibm.icu.util.Freezable;
+/**
+ * Class for mapping Unicode characters to values
+ * Much smaller storage than using HashMap, and much faster and more compact than
+ * a list of UnicodeSets.
+ * @author Davis
+ */
+
+public final class UnicodeMap implements Cloneable, Freezable, Externalizable {
+ /**
+ * For serialization
+ */
+ private static final long serialVersionUID = -6540936876295804105L;
+ static final boolean ASSERTIONS = false;
+ static final long GROWTH_PERCENT = 200; // 100 is no growth!
+ static final long GROWTH_GAP = 10; // extra bump!
+
+ private int length;
+ // two parallel arrays to save memory. Wish Java had structs.
+ private int[] transitions;
+ private Object[] values;
+
+ private LinkedHashSet availableValues = new LinkedHashSet();
+ private transient boolean staleAvailableValues;
+
+ private transient boolean errorOnReset;
+ private transient boolean locked;
+ private int lastIndex;
+
+ { clear(); }
+
+ public UnicodeMap clear() {
+ if (locked) throw new UnsupportedOperationException("Attempt to modify locked object");
+ length = 2;
+ transitions = new int[] {0,0x110000,0,0,0,0,0,0,0,0};
+ values = new Object[10];
+
+ availableValues.clear();
+ staleAvailableValues = false;
+
+ errorOnReset = false;
+ lastIndex = 0;
+ return this;
+ }
+
+ /* Boilerplate */
+ public boolean equals(Object other) {
+ if (other == null) return false;
+ try {
+ UnicodeMap that = (UnicodeMap) other;
+ if (length != that.length) return false;
+ for (int i = 0; i < length-1; ++i) {
+ if (transitions[i] != that.transitions[i]) return false;
+ if (!areEqual(values[i], that.values[i])) return false;
+ }
+ return true;
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ public int getHashCode(Object o) {
+ return o.hashCode();
+ //equator.getHashCode
+ }
+
+ public static boolean areEqual(Object a , Object b) {
+ if (a == b) return true;
+ if (a == null || b == null) return false;
+ return a.equals(b);
+ }
+
+ public int hashCode() {
+ int result = length;
+ // TODO might want to abbreviate this for speed.
+ for (int i = 0; i < length-1; ++i) {
+ result = 37*result + transitions[i];
+ result = 37*result + getHashCode(values[i]);
+ }
+ return result;
+ }
+
+ /**
+ * Standard clone. Warning, as with Collections, does not do deep clone.
+ */
+ public Object cloneAsThawed() {
+ UnicodeMap that = new UnicodeMap();
+ that.length = length;
+ that.transitions = (int[]) transitions.clone();
+ that.values = (Object[]) values.clone();
+ that.availableValues = new LinkedHashSet(availableValues);
+ that.locked = false;
+ return that;
+ }
+
+ /* for internal consistency checking */
+
+ void _checkInvariants() {
+ if (length < 2
+ || length > transitions.length
+ || transitions.length != values.length) {
+ throw new IllegalArgumentException("Invariant failed: Lengths bad");
+ }
+ for (int i = 1; i < length-1; ++i) {
+ if (areEqual(values[i-1], values[i])) {
+ throw new IllegalArgumentException("Invariant failed: values shared at "
+ + "\t" + Utility.hex(i-1) + ": <" + values[i-1] + ">"
+ + "\t" + Utility.hex(i) + ": <" + values[i] + ">"
+ );
+ }
+ }
+ if (transitions[0] != 0 || transitions[length-1] != 0x110000) {
+ throw new IllegalArgumentException("Invariant failed: bounds set wrong");
+ }
+ for (int i = 1; i < length-1; ++i) {
+ if (transitions[i-1] >= transitions[i]) {
+ throw new IllegalArgumentException("Invariant failed: not monotonic"
+ + "\t" + Utility.hex(i-1) + ": " + transitions[i-1]
+ + "\t" + Utility.hex(i) + ": " + transitions[i]
+ );
+ }
+ }
+ }
+
+ /**
+ * Finds an index such that inversionList[i] <= codepoint < inversionList[i+1]
+ * Assumes that 0 <= codepoint <= 0x10FFFF
+ * @param codepoint
+ * @return the index
+ */
+ private int _findIndex(int c) {
+ int lo = 0;
+ int hi = length - 1;
+ int i = (lo + hi) >>> 1;
+ // invariant: c >= list[lo]
+ // invariant: c < list[hi]
+ while (i != lo) {
+ if (c < transitions[i]) {
+ hi = i;
+ } else {
+ lo = i;
+ }
+ i = (lo + hi) >>> 1;
+ }
+ if (ASSERTIONS) _checkFind(c, lo);
+ return lo;
+ }
+
+ private void _checkFind(int codepoint, int value) {
+ int other = __findIndex(codepoint);
+ if (other != value) {
+ throw new IllegalArgumentException("Invariant failed: binary search"
+ + "\t" + Utility.hex(codepoint) + ": " + value
+ + "\tshould be: " + other);
+ }
+ }
+
+ private int __findIndex(int codepoint) {
+ // TODO use binary search
+ for (int i = length-1; i > 0; --i) {
+ if (transitions[i] <= codepoint) return i;
+ }
+ return 0;
+ }
+
+ /*
+ * Try indexed lookup
+
+ static final int SHIFT = 8;
+ int[] starts = new int[0x10FFFF>>SHIFT]; // lowest transition index where codepoint>>x can be found
+ boolean startsValid = false;
+ private int findIndex(int codepoint) {
+ if (!startsValid) {
+ int start = 0;
+ for (int i = 1; i < length; ++i) {
+
+ }
+ }
+ for (int i = length-1; i > 0; --i) {
+ if (transitions[i] <= codepoint) return i;
+ }
+ return 0;
+ }
+ */
+
+ /**
+ * Remove the items from index through index+count-1.
+ * Logically reduces the size of the internal arrays.
+ * @param index
+ * @param count
+ */
+ private void _removeAt(int index, int count) {
+ for (int i = index + count; i < length; ++i) {
+ transitions[i-count] = transitions[i];
+ values[i-count] = values[i];
+ }
+ length -= count;
+ }
+ /**
+ * Add a gap from index to index+count-1.
+ * The values there are undefined, and must be set.
+ * Logically grows arrays to accomodate. Actual growth is limited
+ * @param index
+ * @param count
+ */
+ private void _insertGapAt(int index, int count) {
+ int newLength = length + count;
+ int[] oldtransitions = transitions;
+ Object[] oldvalues = values;
+ if (newLength > transitions.length) {
+ int allocation = (int) (GROWTH_GAP + (newLength * GROWTH_PERCENT) / 100);
+ transitions = new int[allocation];
+ values = new Object[allocation];
+ for (int i = 0; i < index; ++i) {
+ transitions[i] = oldtransitions[i];
+ values[i] = oldvalues[i];
+ }
+ }
+ for (int i = length - 1; i >= index; --i) {
+ transitions[i+count] = oldtransitions[i];
+ values[i+count] = oldvalues[i];
+ }
+ length = newLength;
+ }
+
+ /**
+ * Associates code point with value. Removes any previous association.
+ * @param codepoint
+ * @param value
+ * @return this, for chaining
+ */
+ private UnicodeMap _put(int codepoint, Object value) {
+ // Warning: baseIndex is an invariant; must
+ // be defined such that transitions[baseIndex] < codepoint
+ // at end of this routine.
+ int baseIndex;
+ if (transitions[lastIndex] <= codepoint
+ && codepoint < transitions[lastIndex+1]) {
+ baseIndex = lastIndex;
+ } else {
+ baseIndex = _findIndex(codepoint);
+ }
+ int limitIndex = baseIndex + 1;
+ // cases are (a) value is already set
+ if (areEqual(values[baseIndex], value)) return this;
+ if (locked) throw new UnsupportedOperationException("Attempt to modify locked object");
+ if (errorOnReset && values[baseIndex] != null) {
+ throw new IllegalArgumentException("Attempt to reset value for " + Utility.hex(codepoint)
+ + " when that is disallowed. Old: " + values[baseIndex] + "; New: " + value);
+ }
+
+ // adjust the available values
+ staleAvailableValues = true;
+ availableValues.add(value); // add if not there already
+
+ int baseCP = transitions[baseIndex];
+ int limitCP = transitions[limitIndex];
+ // we now start walking through the difference case,
+ // based on whether we are at the start or end of range
+ // and whether the range is a single character or multiple
+
+ if (baseCP == codepoint) {
+ // CASE: At very start of range
+ boolean connectsWithPrevious =
+ baseIndex != 0 && areEqual(value, values[baseIndex-1]);
+
+ if (limitCP == codepoint + 1) {
+ // CASE: Single codepoint range
+ boolean connectsWithFollowing =
+ baseIndex < length - 1 && areEqual(value, values[limitIndex]);
+
+ if (connectsWithPrevious) {
+ // A1a connects with previous & following, so remove index
+ if (connectsWithFollowing) {
+ _removeAt(baseIndex, 2);
+ } else {
+ _removeAt(baseIndex, 1); // extend previous
+ }
+ --baseIndex; // fix up
+ } else if (connectsWithFollowing) {
+ _removeAt(baseIndex, 1); // extend following backwards
+ transitions[baseIndex] = codepoint;
+ } else {
+ // doesn't connect on either side, just reset
+ values[baseIndex] = value;
+ }
+ } else if (connectsWithPrevious) {
+ // A.1: start of multi codepoint range
+ // if connects
+ ++transitions[baseIndex]; // extend previous
+ } else {
+ // otherwise insert new transition
+ transitions[baseIndex] = codepoint+1; // fix following range
+ _insertGapAt(baseIndex, 1);
+ values[baseIndex] = value;
+ transitions[baseIndex] = codepoint;
+ }
+ } else if (limitCP == codepoint + 1) {
+ // CASE: at end of range
+ // if connects, just back up range
+ boolean connectsWithFollowing =
+ baseIndex < length - 1 && areEqual(value, values[limitIndex]);
+
+ if (connectsWithFollowing) {
+ --transitions[limitIndex];
+ return this;
+ } else {
+ _insertGapAt(limitIndex, 1);
+ transitions[limitIndex] = codepoint;
+ values[limitIndex] = value;
+ }
+ } else {
+ // CASE: in middle of range
+ // insert gap, then set the new range
+ _insertGapAt(++baseIndex,2);
+ transitions[baseIndex] = codepoint;
+ values[baseIndex] = value;
+ transitions[baseIndex+1] = codepoint + 1;
+ values[baseIndex+1] = values[baseIndex-1]; // copy lower range values
+ }
+ lastIndex = baseIndex; // store for next time
+ return this;
+ }
+ private UnicodeMap _putAll(int startCodePoint, int endCodePoint, Object value) {
+ for (int i = startCodePoint; i <= endCodePoint; ++i) {
+ _put(i, value);
+ }
+ return this;
+ }
+ /**
+ * Sets the codepoint value.
+ * @param codepoint
+ * @param value
+ * @return this (for chaining)
+ */
+ public UnicodeMap put(int codepoint, Object value) {
+ if (codepoint < 0 || codepoint > 0x10FFFF) {
+ throw new IllegalArgumentException("Codepoint out of range: " + codepoint);
+ }
+ _put(codepoint, value);
+ if (ASSERTIONS) _checkInvariants();
+ return this;
+ }
+ /**
+ * Adds bunch o' codepoints; otherwise like put.
+ * @param codepoints
+ * @param value
+ * @return this (for chaining)
+ */
+ public UnicodeMap putAll(UnicodeSet codepoints, Object value) {
+ // TODO optimize
+ UnicodeSetIterator it = new UnicodeSetIterator(codepoints);
+ while (it.nextRange()) {
+ _putAll(it.codepoint, it.codepointEnd, value);
+ }
+ return this;
+ }
+
+ /**
+ * Adds bunch o' codepoints; otherwise like add.
+ * @param startCodePoint
+ * @param endCodePoint
+ * @param value
+ * @return this (for chaining)
+ */
+ public UnicodeMap putAll(int startCodePoint, int endCodePoint, Object value) {
+ if (startCodePoint < 0 || endCodePoint > 0x10FFFF) {
+ throw new IllegalArgumentException("Codepoint out of range: "
+ + Utility.hex(startCodePoint) + ".." + Utility.hex(endCodePoint));
+ }
+ // TODO optimize
+ for (int i = startCodePoint; i <= endCodePoint; ++i) {
+ _put(i, value);
+ }
+ return this;
+ }
+ /**
+ * Add all the (main) values from a Unicode property
+ * @param prop the property to add to the map
+ * @return this (for chaining)
+ */
+ public UnicodeMap putAll(UnicodeProperty prop) {
+ // TODO optimize
+ for (int i = 0; i <= 0x10FFFF; ++i) {
+ _put(i, prop.getValue(i));
+ }
+ return this;
+ }
+
+ /**
+ * Add all the (main) values from a Unicode property
+ * @param prop the property to add to the map
+ * @return this (for chaining)
+ */
+ public UnicodeMap putAll(UnicodeMap prop) {
+ // TODO optimize
+ for (int i = 0; i <= 0x10FFFF; ++i) {
+ _put(i, prop.getValue(i));
+ }
+ return this;
+ }
+
+ /**
+ * Set the currently unmapped Unicode code points to the given value.
+ * @param value the value to set
+ * @return this (for chaining)
+ */
+ public UnicodeMap setMissing(Object value) {
+ // fast path, if value not yet present
+ if (!getAvailableValues().contains(value)) {
+ staleAvailableValues = true;
+ availableValues.add(value);
+ for (int i = 0; i < length; ++i) {
+ if (values[i] == null) values[i] = value;
+ }
+ return this;
+ } else {
+ return putAll(getSet(null), value);
+ }
+ }
+ /**
+ * Returns the set associated with a given value. Deposits into
+ * result if it is not null. Remember to clear if you just want
+ * the new values.
+ * @param value
+ * @param result
+ * @return result
+ */
+ public UnicodeSet getSet(Object value, UnicodeSet result) {
+ if (result == null) result = new UnicodeSet();
+ for (int i = 0; i < length - 1; ++i) {
+ if (areEqual(value, values[i])) {
+ result.add(transitions[i], transitions[i+1]-1);
+ }
+ }
+ return result;
+ }
+
+ public UnicodeSet getSet(Object value) {
+ return getSet(value,null);
+ }
+
+ public UnicodeSet keySet() {
+ return getSet(null,null).complement();
+ }
+ /**
+ * Returns the list of possible values. Deposits each non-null value into
+ * result. Creates result if it is null. Remember to clear result if
+ * you are not appending to existing collection.
+ * @param result
+ * @return result
+ */
+ public Collection getAvailableValues(Collection result) {
+ if (staleAvailableValues) {
+ // collect all the current values
+ // retain them in the availableValues
+ Set temp = new HashSet();
+ for (int i = 0; i < length - 1; ++i) {
+ if (values[i] != null) temp.add(values[i]);
+ }
+ availableValues.retainAll(temp);
+ staleAvailableValues = false;
+ }
+ if (result == null) result = new ArrayList(availableValues.size());
+ result.addAll(availableValues);
+ return result;
+ }
+
+ /**
+ * Convenience method
+ */
+ public Collection getAvailableValues() {
+ return getAvailableValues(null);
+ }
+ /**
+ * Gets the value associated with a given code point.
+ * Returns null, if there is no such value.
+ * @param codepoint
+ * @return the value
+ */
+ public Object getValue(int codepoint) {
+ if (codepoint < 0 || codepoint > 0x10FFFF) {
+ throw new IllegalArgumentException("Codepoint out of range: " + codepoint);
+ }
+ return values[_findIndex(codepoint)];
+ }
+
+ /**
+ * Change a new string from the source string according to the mappings. For each code point cp, if getValue(cp) is null, append the character, otherwise append getValue(cp).toString()
+ * @param source
+ * @return
+ */
+ public String fold(String source) {
+ StringBuffer result = new StringBuffer();
+ int cp;
+ for (int i = 0; i < source.length(); i += UTF16.getCharCount(cp)) {
+ cp = UTF16.charAt(source, i);
+ Object mResult = getValue(cp);
+ if (mResult != null) {
+ result.append(mResult);
+ } else {
+ UTF16.append(result, cp);
+ }
+ }
+ return result.toString();
+ }
+
+ public interface Composer {
+ Object compose(int codePoint, Object a, Object b);
+ }
+
+ public UnicodeMap composeWith(UnicodeMap other, Composer composer) {
+ for (int i = 0; i <= 0x10FFFF; ++i) {
+ Object v1 = getValue(i);
+ Object v2 = other.getValue(i);
+ Object v3 = composer.compose(i, v1, v2);
+ if (v1 != v3 && (v1 == null || !v1.equals(v3))) put(i, v3);
+ }
+ return this;
+ }
+
+ public UnicodeMap composeWith(UnicodeSet set, Object value, Composer composer) {
+ for (UnicodeSetIterator it = new UnicodeSetIterator(set); it.next();) {
+ int i = it.codepoint;
+ Object v1 = getValue(i);
+ Object v3 = composer.compose(i, v1, value);
+ if (v1 != v3 && (v1 == null || !v1.equals(v3))) put(i, v3);
+ }
+ return this;
+ }
+
+ /**
+ * Follow the style used by UnicodeSetIterator
+ */
+ public static class MapIterator {
+ public int codepoint;
+ public int codepointEnd;
+ public Object value;
+
+ private UnicodeMap map;
+ private int index;
+ private int startRange;
+ private int endRange;
+ private Object lastValue;
+
+ public MapIterator(UnicodeMap map) {
+ reset(map);
+ }
+ // note: length of 2 means {0, 110000}. Only want to index up to 0!
+ public boolean nextRange() {
+ if (index < 0 || index >= map.length - 1) return false;
+ value = map.values[index];
+ codepoint = startRange = map.transitions[index++];
+ codepointEnd = endRange = map.transitions[index] - 1; // -1 to make limit into end
+ return true;
+ }
+ public boolean next() {
+ if (startRange > endRange) {
+ //System.out.println("***" + Utility.hex(startRange) + ".." + Utility.hex(endRange));
+ if (!nextRange()) return false;
+ // index now points AFTER the start of the range
+ lastValue = map.values[index-1];
+ //System.out.println("***" + Utility.hex(codepoint) + ".." + Utility.hex(codepointEnd) + " => " + lastValue);
+ }
+ value = lastValue;
+ codepoint = codepointEnd = startRange++; // set to first, and iterate
+ return true;
+ }
+
+ public MapIterator reset() {
+ index = 0;
+ startRange = 0;
+ endRange = -1;
+ return this;
+ }
+ public MapIterator reset(UnicodeMap newMap) {
+ this.map = newMap;
+ return reset();
+ }
+ }
+
+ public String toString() {
+ return toString(null);
+ }
+ public String toString(Comparator collected) {
+ StringBuffer result = new StringBuffer();
+ if (collected == null) {
+ for (int i = 0; i < length-1; ++i) {
+ Object value = values[i];
+ if (value == null) continue;
+ int start = transitions[i];
+ int end = transitions[i+1]-1;
+ result.append(Utility.hex(start));
+ if (start != end) result.append("..")
+ .append(Utility.hex(end));
+ result.append("\t=> ")
+ .append(values[i] == null ? "null" : values[i].toString())
+ .append("\r\n");
+ }
+ } else {
+ Set set = (Set) getAvailableValues(new TreeSet(collected));
+ for (Iterator it = set.iterator(); it.hasNext();) {
+ Object value = it.next();
+ UnicodeSet s = getSet(value);
+ result.append(value)
+ .append("\t=> ")
+ .append(s.toPattern(true))
+ .append("\r\n");
+ }
+ }
+ return result.toString();
+ }
+ /**
+ * @return Returns the errorOnReset.
+ */
+ public boolean getErrorOnReset() {
+ return errorOnReset;
+ }
+ /**
+ * @param errorOnReset The errorOnReset to set.
+ */
+ public void setErrorOnReset(boolean errorOnReset) {
+ this.errorOnReset = errorOnReset;
+ }
+
+ /* (non-Javadoc)
+ * @see com.ibm.icu.dev.test.util.Lockable#isLocked()
+ */
+ public boolean isFrozen() {
+ // TODO Auto-generated method stub
+ return locked;
+ }
+
+ /* (non-Javadoc)
+ * @see com.ibm.icu.dev.test.util.Lockable#lock()
+ */
+ public Object freeze() {
+ locked = true;
+ return this;
+ }
+
+ static final boolean DEBUG_WRITE = false;
+
+ // TODO Fix to serialize more than just strings.
+ // Only if all the items are strings will we do the following compression
+ // Otherwise we'll just use Java Serialization, bulky as it is
+ public void writeExternal(ObjectOutput out1) throws IOException {
+ DataOutputCompressor sc = new DataOutputCompressor(out1);
+ // if all objects are strings
+ Collection availableVals = getAvailableValues();
+ boolean allStrings = allAreString(availableVals);
+ sc.writeBoolean(allStrings);
+ Map object_index = new LinkedHashMap();
+ if (allAreString(availableVals)) {
+ sc.writeStringSet(new TreeSet(availableVals), object_index);
+ } else {
+ sc.writeCollection(availableVals, object_index);
+ }
+ sc.writeUInt(length);
+ int lastTransition = -1;
+ int lastValueNumber = 0;
+ if (DEBUG_WRITE) System.out.println("Trans count: " + length);
+ for (int i = 0; i < length; ++i) {
+ int valueNumber = ((Integer)object_index.get(values[i])).intValue();
+ if (DEBUG_WRITE) System.out.println("Trans: " + transitions[i] + ",\t" + valueNumber);
+
+ int deltaTransition = transitions[i] - lastTransition;
+ lastTransition = transitions[i];
+ int deltaValueNumber = valueNumber - lastValueNumber;
+ lastValueNumber = valueNumber;
+
+ deltaValueNumber <<= 1; // make room for one bit
+ boolean canCombine = deltaTransition == 1;
+ if (canCombine) deltaValueNumber |= 1;
+ sc.writeInt(deltaValueNumber);
+ if (DEBUG_WRITE) System.out.println("deltaValueNumber: " + deltaValueNumber);
+ if (!canCombine) {
+ sc.writeUInt(deltaTransition);
+ if (DEBUG_WRITE) System.out.println("deltaTransition: " + deltaTransition);
+ }
+ }
+ sc.flush();
+ }
+
+ /**
+ *
+ */
+ private boolean allAreString(Collection availableValues2) {
+ //if (true) return false;
+ for (Iterator it = availableValues2.iterator(); it.hasNext();) {
+ if (!(it.next() instanceof String)) return false;
+ }
+ return true;
+ }
+
+ public void readExternal(ObjectInput in1) throws IOException, ClassNotFoundException {
+ DataInputCompressor sc = new DataInputCompressor(in1);
+ boolean allStrings = sc.readBoolean();
+ Object[] valuesList;
+ availableValues = new LinkedHashSet();
+ if (allStrings) {
+ valuesList = sc.readStringSet(availableValues);
+ } else {
+ valuesList = sc.readCollection(availableValues);
+ }
+ length = sc.readUInt();
+ transitions = new int[length];
+ if (DEBUG_WRITE) System.out.println("Trans count: " + length);
+ values = new Object[length];
+ int currentTransition = -1;
+ int currentValue = 0;
+ int deltaTransition;
+ for (int i = 0; i < length; ++i) {
+ int temp = sc.readInt();
+ if (DEBUG_WRITE) System.out.println("deltaValueNumber: " + temp);
+ boolean combined = (temp & 1) != 0;
+ temp >>= 1;
+ values[i] = valuesList[currentValue += temp];
+ if (!combined) {
+ deltaTransition = sc.readUInt();
+ if (DEBUG_WRITE) System.out.println("deltaTransition: " + deltaTransition);
+ } else {
+ deltaTransition = 1;
+ }
+ transitions[i] = currentTransition += deltaTransition; // delta value
+ if (DEBUG_WRITE) System.out.println("Trans: " + transitions[i] + ",\t" + currentValue);
+ }
+ }
+
+ /**
+ *
+ */
+ static int findCommon(String last, String s) {
+ int minLen = Math.min(last.length(), s.length());
+ for (int i = 0; i < minLen; ++i) {
+ if (last.charAt(i) != s.charAt(i)) return i;
+ }
+ return minLen;
+ }
+
+
+// /**
+// * @param sc
+// * @throws IOException
+// *
+// */
+// private void showSize(String title, ObjectOutput out, StreamCompressor sc) throws IOException {
+// sc.showSize(this, title, out);
+// }
+// //public void readObject(ObjectInputStream in) throws IOException {
+// public static class StreamCompressor {
+// transient byte[] buffer = new byte[1];
+// transient StringBuffer stringBuffer = new StringBuffer();
+//
+// transient byte[] readWriteBuffer = new byte[8];
+// int position = 0;
+// DataOutput out;
+// DataInput in;
+//
+// /**
+// * Format is:
+// * @throws IOException
+// */
+// public void writeInt(int i) throws IOException {
+// while (true) {
+// if (position == readWriteBuffer.length) {
+// out.write(readWriteBuffer);
+// position = 0;
+// }
+// if ((i & ~0x7F) == 0) {
+// readWriteBuffer[position++] = (byte)i;
+// break;
+// }
+// readWriteBuffer[position++] = (byte)(0x80 | i);
+// i >>>= 7;
+// }
+// }
+// /**
+// * @throws IOException
+// *
+// */
+// public int readNInt(ObjectInput in) throws IOException {
+// int result = readInt(in);
+// boolean negative = (result & 1) != 0;
+// result >>>= 1;
+// if (negative) result = ~result;
+// return result;
+// }
+// /**
+// * @throws IOException
+// *
+// */
+// public void writeNInt(int input) throws IOException {
+// int flag = 0;
+// if (input < 0) {
+// input = ~input;
+// flag = 1;
+// }
+// input = (input << 1) | flag;
+// writeInt(out, input);
+// }
+// /**
+// * @throws IOException
+// *
+// */
+// public void flush() throws IOException {
+// out.write(readWriteBuffer);
+// position = 0;
+// }
+//
+// int readPosition = readWriteBuffer.length;
+//
+// public int readInt(ObjectInput in) throws IOException {
+// int result = 0;
+// int offset = 0;
+// while (true) {
+// if (readPosition == readWriteBuffer.length) {
+// in.read(readWriteBuffer);
+// readPosition = 0;
+// }
+// //in.read(buffer);
+// int input = readWriteBuffer[readPosition++]; // buffer[0];
+// result |= (input & 0x7F) << offset;
+// if ((input & 0x80) == 0) {
+// return result;
+// }
+// offset += 7;
+// }
+// }
+//
+// /**
+// * @throws IOException
+// *
+// */
+// public void writeString(String s) throws IOException {
+// writeInt(UTF16.countCodePoint(s));
+// writeCodePoints(s);
+// }
+// /**
+// *
+// */
+// private void writeCodePoints(String s) throws IOException {
+// int cp = 0;
+// for (int i = 0; i < s.length(); i += UTF16.getCharCount(cp)) {
+// cp = UTF16.charAt(s, i);
+// writeInt(cp);
+// }
+// }
+// /**
+// * @throws IOException
+// *
+// */
+// public String readString() throws IOException {
+// int len = readInt(in);
+// return readCodePoints(in, len);
+// }
+// /**
+// *
+// */
+// private String readCodePoints(int len) throws IOException {
+// stringBuffer.setLength(0);
+// for (int i = 0; i < len; ++i) {
+// int cp = readInt(in);
+// UTF16.append(stringBuffer, cp);
+// }
+// return stringBuffer.toString();
+// }
+// /**
+// * @param this
+// * @throws IOException
+// *
+// */
+// private void showSize(UnicodeMap map, String title, ObjectOutput out) throws IOException {
+// out.flush();
+// System.out.println(title + ": " + (map.debugOut.size() + position));
+// }
+// }
+}
+//#endif