]> gitweb.fperrin.net Git - Dictionary.git/blobdiff - jars/icu4j-4_2_1-src/src/com/ibm/icu/dev/test/util/UnicodeMap.java
go
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / dev / test / util / UnicodeMap.java
old mode 100755 (executable)
new mode 100644 (file)
index c77e9c7..3f5fa57
-//##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