Home | History | Annotate | Download | only in util
      1 /*
      2  *******************************************************************************
      3  * Copyright (C) 1996-2014, International Business Machines Corporation and    *
      4  * others. All Rights Reserved.                                                *
      5  *******************************************************************************
      6  */
      7 package com.ibm.icu.dev.util;
      8 
      9 import java.io.PrintWriter;
     10 import java.io.StringWriter;
     11 import java.text.ParsePosition;
     12 import java.util.ArrayList;
     13 import java.util.Arrays;
     14 import java.util.Collection;
     15 import java.util.Comparator;
     16 import java.util.HashMap;
     17 import java.util.Iterator;
     18 import java.util.LinkedHashSet;
     19 import java.util.List;
     20 import java.util.Map;
     21 import java.util.TreeMap;
     22 import java.util.regex.Pattern;
     23 
     24 import com.ibm.icu.dev.util.CollectionUtilities.InverseMatcher;
     25 import com.ibm.icu.dev.util.CollectionUtilities.ObjectMatcher;
     26 import com.ibm.icu.impl.Utility;
     27 import com.ibm.icu.text.SymbolTable;
     28 import com.ibm.icu.text.UFormat;
     29 import com.ibm.icu.text.UTF16;
     30 import com.ibm.icu.text.UnicodeMatcher;
     31 import com.ibm.icu.text.UnicodeSet;
     32 import com.ibm.icu.text.UnicodeSetIterator;
     33 
     34 public abstract class UnicodeProperty extends UnicodeLabel {
     35 
     36     public static final UnicodeSet NONCHARACTERS = new UnicodeSet("[:noncharactercodepoint:]").freeze();
     37     public static final UnicodeSet PRIVATE_USE = new UnicodeSet("[:gc=privateuse:]").freeze();
     38     public static final UnicodeSet SURROGATE = new UnicodeSet("[:gc=surrogate:]").freeze();
     39 
     40     public static final UnicodeSet HIGH_SURROGATES = new UnicodeSet("[\\uD800-\\uDB7F]").freeze();
     41     public static final int SAMPLE_HIGH_SURROGATE = HIGH_SURROGATES.charAt(0);
     42     public static final UnicodeSet HIGH_PRIVATE_USE_SURROGATES = new UnicodeSet("[\\uDB80-\\uDBFF]").freeze();
     43     public static final int SAMPLE_HIGH_PRIVATE_USE_SURROGATE = HIGH_PRIVATE_USE_SURROGATES.charAt(0);
     44     public static final UnicodeSet LOW_SURROGATES = new UnicodeSet("[\\uDC00-\\uDFFF]").freeze();
     45     public static final int SAMPLE_LOW_SURROGATE = LOW_SURROGATES.charAt(0);
     46 
     47     public static final UnicodeSet PRIVATE_USE_AREA = new UnicodeSet("[\\uE000-\\uF8FF]").freeze();
     48     public static final int SAMPLE_PRIVATE_USE_AREA = PRIVATE_USE_AREA.charAt(0);
     49     public static final UnicodeSet PRIVATE_USE_AREA_A = new UnicodeSet("[\\U000F0000-\\U000FFFFD]").freeze();
     50     public static final int SAMPLE_PRIVATE_USE_AREA_A = PRIVATE_USE_AREA_A.charAt(0);
     51     public static final UnicodeSet PRIVATE_USE_AREA_B = new UnicodeSet("[\\U00100000-\\U0010FFFD]").freeze();
     52     public static final int SAMPLE_PRIVATE_USE_AREA_B = PRIVATE_USE_AREA_B.charAt(0);
     53 
     54     // The following are special. They are used for performance, but must be changed if the version of Unicode for the UnicodeProperty changes.
     55     private static UnicodeSet UNASSIGNED;
     56     private static int SAMPLE_UNASSIGNED;
     57     private static UnicodeSet SPECIALS;
     58     private static UnicodeSet STUFF_TO_TEST;
     59     private static UnicodeSet STUFF_TO_TEST_WITH_UNASSIGNED;
     60 
     61     public static synchronized UnicodeSet getUNASSIGNED() {
     62         if (UNASSIGNED == null) {
     63             UNASSIGNED = new UnicodeSet("[:gc=unassigned:]").freeze();
     64         }
     65         return UNASSIGNED;
     66     }
     67 
     68     public static synchronized UnicodeSet contractUNASSIGNED(UnicodeSet toBeUnassigned) {
     69         UnicodeSet temp = UNASSIGNED;
     70         ResetCacheProperties();
     71         UNASSIGNED = temp == null ? toBeUnassigned.freeze() : new UnicodeSet(temp).retainAll(toBeUnassigned).freeze();
     72         return UNASSIGNED;
     73     }
     74 
     75     public static synchronized int getSAMPLE_UNASSIGNED() {
     76         if (SAMPLE_UNASSIGNED == 0) {
     77             SAMPLE_UNASSIGNED = getUNASSIGNED().charAt(0);
     78         }
     79         return SAMPLE_UNASSIGNED;
     80     }
     81 
     82     public static synchronized UnicodeSet getSPECIALS() {
     83         if (SPECIALS == null) {
     84             SPECIALS = new UnicodeSet(getUNASSIGNED()).addAll(PRIVATE_USE).addAll(SURROGATE).freeze();
     85         }
     86         return SPECIALS;
     87     }
     88 
     89     public static synchronized UnicodeSet getSTUFF_TO_TEST() {
     90         if (STUFF_TO_TEST == null) {
     91             STUFF_TO_TEST = new UnicodeSet(getSPECIALS()).complement()
     92             .addAll(NONCHARACTERS)
     93             .add(getSAMPLE_UNASSIGNED())
     94             .add(SAMPLE_HIGH_SURROGATE)
     95             .add(SAMPLE_HIGH_PRIVATE_USE_SURROGATE)
     96             .add(SAMPLE_LOW_SURROGATE)
     97             .add(SAMPLE_PRIVATE_USE_AREA)
     98             .add(SAMPLE_PRIVATE_USE_AREA_A)
     99             .add(SAMPLE_PRIVATE_USE_AREA_B)
    100             .freeze();
    101         }
    102         return STUFF_TO_TEST;
    103     }
    104 
    105     public static synchronized UnicodeSet getSTUFF_TO_TEST_WITH_UNASSIGNED() {
    106         if (STUFF_TO_TEST_WITH_UNASSIGNED == null) {
    107             STUFF_TO_TEST_WITH_UNASSIGNED = new UnicodeSet(getSTUFF_TO_TEST()).addAll(getUNASSIGNED()).freeze();
    108         }
    109         return STUFF_TO_TEST_WITH_UNASSIGNED;
    110     }
    111 
    112     /**
    113      * Reset the cache properties. Must be done if the version of Unicode is different than the ICU one, AND any UnicodeProperty has already been instantiated.
    114      * TODO make this a bit more robust.
    115      * @internal
    116      */
    117     public static synchronized void ResetCacheProperties() {
    118         UNASSIGNED = null;
    119         SAMPLE_UNASSIGNED = 0;
    120         SPECIALS = null;
    121         STUFF_TO_TEST = null;
    122         STUFF_TO_TEST_WITH_UNASSIGNED = null;
    123     }
    124 
    125     public static boolean DEBUG = false;
    126 
    127     public static String CHECK_NAME = "FC_NFKC_Closure";
    128 
    129     public static int CHECK_VALUE = 0x037A;
    130 
    131     private String name;
    132 
    133     private String firstNameAlias = null;
    134 
    135     private int type;
    136 
    137     private Map valueToFirstValueAlias = null;
    138 
    139     private boolean hasUniformUnassigned = true;
    140 
    141     /*
    142      * Name: Unicode_1_Name Name: ISO_Comment Name: Name Name: Unicode_1_Name
    143      *
    144      */
    145 
    146     public static final int UNKNOWN = 0, BINARY = 2, EXTENDED_BINARY = 3,
    147     ENUMERATED = 4, EXTENDED_ENUMERATED = 5, CATALOG = 6,
    148     EXTENDED_CATALOG = 7, MISC = 8, EXTENDED_MISC = 9, STRING = 10,
    149     EXTENDED_STRING = 11, NUMERIC = 12, EXTENDED_NUMERIC = 13,
    150     START_TYPE = 2, LIMIT_TYPE = 14, EXTENDED_MASK = 1,
    151     CORE_MASK = ~EXTENDED_MASK, BINARY_MASK = (1 << BINARY)
    152     | (1 << EXTENDED_BINARY), STRING_MASK = (1 << STRING)
    153     | (1 << EXTENDED_STRING),
    154     STRING_OR_MISC_MASK = (1 << STRING) | (1 << EXTENDED_STRING)
    155     | (1 << MISC) | (1 << EXTENDED_MISC),
    156     ENUMERATED_OR_CATALOG_MASK = (1 << ENUMERATED)
    157     | (1 << EXTENDED_ENUMERATED) | (1 << CATALOG)
    158     | (1 << EXTENDED_CATALOG);
    159 
    160     private static final String[] TYPE_NAMES = { "Unknown", "Unknown",
    161         "Binary", "Extended Binary", "Enumerated", "Extended Enumerated",
    162         "Catalog", "Extended Catalog", "Miscellaneous",
    163         "Extended Miscellaneous", "String", "Extended String", "Numeric",
    164         "Extended Numeric", };
    165 
    166     public static String getTypeName(int propType) {
    167         return TYPE_NAMES[propType];
    168     }
    169 
    170     public final String getName() {
    171         return name;
    172     }
    173 
    174     public final int getType() {
    175         return type;
    176     }
    177 
    178     public String getTypeName() {
    179         return TYPE_NAMES[type];
    180     }
    181 
    182     public final boolean isType(int mask) {
    183         return ((1 << type) & mask) != 0;
    184     }
    185 
    186     protected final void setName(String string) {
    187         if (string == null)
    188             throw new IllegalArgumentException("Name must not be null");
    189         name = string;
    190     }
    191 
    192     protected final void setType(int i) {
    193         type = i;
    194     }
    195 
    196     public String getVersion() {
    197         return _getVersion();
    198     }
    199 
    200     public String getValue(int codepoint) {
    201         if (DEBUG && CHECK_VALUE == codepoint && CHECK_NAME.equals(getName())) {
    202             String value = _getValue(codepoint);
    203             System.out.println(getName() + "(" + Utility.hex(codepoint) + "):"
    204                     + (getType() == STRING ? Utility.hex(value) : value));
    205             return value;
    206         }
    207         return _getValue(codepoint);
    208     }
    209 
    210     // public String getValue(int codepoint, boolean isShort) {
    211     // return getValue(codepoint);
    212     // }
    213 
    214     public List<String> getNameAliases(List<String> result) {
    215         if (result == null)
    216             result = new ArrayList(1);
    217         return _getNameAliases(result);
    218     }
    219 
    220     public List<String> getValueAliases(String valueAlias, List<String> result) {
    221         if (result == null)
    222             result = new ArrayList(1);
    223         result = _getValueAliases(valueAlias, result);
    224         if (!result.contains(valueAlias)) { // FIX && type < NUMERIC
    225             result = _getValueAliases(valueAlias, result); // for debugging
    226             throw new IllegalArgumentException("Internal error: " + getName()
    227                     + " doesn't contain " + valueAlias + ": "
    228                     + new BagFormatter().join(result));
    229         }
    230         return result;
    231     }
    232 
    233     public List<String> getAvailableValues(List<String> result) {
    234         if (result == null)
    235             result = new ArrayList(1);
    236         return _getAvailableValues(result);
    237     }
    238 
    239     protected abstract String _getVersion();
    240 
    241     protected abstract String _getValue(int codepoint);
    242 
    243     protected abstract List<String> _getNameAliases(List<String> result);
    244 
    245     protected abstract List<String> _getValueAliases(String valueAlias, List<String> result);
    246 
    247     protected abstract List<String> _getAvailableValues(List<String> result);
    248 
    249     // conveniences
    250     public final List<String> getNameAliases() {
    251         return getNameAliases(null);
    252     }
    253 
    254     public final List<String> getValueAliases(String valueAlias) {
    255         return getValueAliases(valueAlias, null);
    256     }
    257 
    258     public final List<String> getAvailableValues() {
    259         return getAvailableValues(null);
    260     }
    261 
    262     public final String getValue(int codepoint, boolean getShortest) {
    263         String result = getValue(codepoint);
    264         if (type >= MISC || result == null || !getShortest)
    265             return result;
    266         return getFirstValueAlias(result);
    267     }
    268 
    269     public final String getFirstNameAlias() {
    270         if (firstNameAlias == null) {
    271             firstNameAlias = (String) getNameAliases().get(0);
    272         }
    273         return firstNameAlias;
    274     }
    275 
    276     public final String getFirstValueAlias(String value) {
    277         if (valueToFirstValueAlias == null)
    278             _getFirstValueAliasCache();
    279         return valueToFirstValueAlias.get(value).toString();
    280     }
    281 
    282     private void _getFirstValueAliasCache() {
    283         maxValueWidth = 0;
    284         maxFirstValueAliasWidth = 0;
    285         valueToFirstValueAlias = new HashMap(1);
    286         Iterator it = getAvailableValues().iterator();
    287         while (it.hasNext()) {
    288             String value = (String) it.next();
    289             String first = (String) getValueAliases(value).get(0);
    290             if (first == null) { // internal error
    291                 throw new IllegalArgumentException(
    292                         "Value not in value aliases: " + value);
    293             }
    294             if (DEBUG && CHECK_NAME.equals(getName())) {
    295                 System.out.println("First Alias: " + getName() + ": " + value
    296                         + " => " + first
    297                         + new BagFormatter().join(getValueAliases(value)));
    298             }
    299             valueToFirstValueAlias.put(value, first);
    300             if (value.length() > maxValueWidth) {
    301                 maxValueWidth = value.length();
    302             }
    303             if (first.length() > maxFirstValueAliasWidth) {
    304                 maxFirstValueAliasWidth = first.length();
    305             }
    306         }
    307     }
    308 
    309     private int maxValueWidth = -1;
    310 
    311     private int maxFirstValueAliasWidth = -1;
    312 
    313     public int getMaxWidth(boolean getShortest) {
    314         if (maxValueWidth < 0)
    315             _getFirstValueAliasCache();
    316         if (getShortest)
    317             return maxFirstValueAliasWidth;
    318         return maxValueWidth;
    319     }
    320 
    321     public final UnicodeSet getSet(String propertyValue) {
    322         return getSet(propertyValue, null);
    323     }
    324 
    325     public final UnicodeSet getSet(PatternMatcher matcher) {
    326         return getSet(matcher, null);
    327     }
    328 
    329     /** Adds the property value set to the result. Clear the result first if you don't want to keep the original contents.
    330      */
    331     public final UnicodeSet getSet(String propertyValue, UnicodeSet result) {
    332         return getSet(new SimpleMatcher(propertyValue,
    333                 isType(STRING_OR_MISC_MASK) ? null : PROPERTY_COMPARATOR),
    334                 result);
    335     }
    336 
    337     private UnicodeMap unicodeMap = null;
    338 
    339     public static final String UNUSED = "??";
    340 
    341     public UnicodeSet getSet(PatternMatcher matcher, UnicodeSet result) {
    342         if (result == null)
    343             result = new UnicodeSet();
    344         boolean uniformUnassigned = hasUniformUnassigned();
    345         if (isType(STRING_OR_MISC_MASK)) {
    346             for (UnicodeSetIterator usi = getStuffToTest(uniformUnassigned); usi.next();) { // int i = 0; i <= 0x10FFFF; ++i
    347                 int i = usi.codepoint;
    348                 String value = getValue(i);
    349                 if (value != null && matcher.matches(value)) {
    350                     result.add(i);
    351                 }
    352             }
    353             return addUntested(result, uniformUnassigned);
    354         }
    355         List temp = new ArrayList(1); // to avoid reallocating...
    356         UnicodeMap um = getUnicodeMap_internal();
    357         Iterator it = um.getAvailableValues(null).iterator();
    358         main: while (it.hasNext()) {
    359             String value = (String) it.next();
    360             temp.clear();
    361             Iterator it2 = getValueAliases(value, temp).iterator();
    362             while (it2.hasNext()) {
    363                 String value2 = (String) it2.next();
    364                 // System.out.println("Values:" + value2);
    365                 if (matcher.matches(value2)
    366                         || matcher.matches(toSkeleton(value2))) {
    367                     um.keySet(value, result);
    368                     continue main;
    369                 }
    370             }
    371         }
    372         return result;
    373     }
    374 
    375     /*
    376      * public UnicodeSet getMatchSet(UnicodeSet result) { if (result == null)
    377      * result = new UnicodeSet(); addAll(matchIterator, result); return result; }
    378      *
    379      * public void setMatchSet(UnicodeSet set) { matchIterator = new
    380      * UnicodeSetIterator(set); }
    381      */
    382 
    383     /**
    384      * Utility for debugging
    385      */
    386     public static String getStack() {
    387         Exception e = new Exception();
    388         StringWriter sw = new StringWriter();
    389         PrintWriter pw = new PrintWriter(sw);
    390         e.printStackTrace(pw);
    391         pw.flush();
    392         return "Showing Stack with fake " + sw.getBuffer().toString();
    393     }
    394 
    395     // TODO use this instead of plain strings
    396     public static class Name implements Comparable {
    397         private String skeleton;
    398 
    399         private String pretty;
    400 
    401         public final int RAW = 0, TITLE = 1, NORMAL = 2;
    402 
    403         public Name(String name, int style) {
    404             if (name == null)
    405                 name = "";
    406             if (style == RAW) {
    407                 skeleton = pretty = name;
    408             } else {
    409                 pretty = regularize(name, style == TITLE);
    410                 skeleton = toSkeleton(pretty);
    411             }
    412         }
    413 
    414         public int compareTo(Object o) {
    415             return skeleton.compareTo(((Name) o).skeleton);
    416         }
    417 
    418         public boolean equals(Object o) {
    419             return skeleton.equals(((Name) o).skeleton);
    420         }
    421 
    422         public int hashCode() {
    423             return skeleton.hashCode();
    424         }
    425 
    426         public String toString() {
    427             return pretty;
    428         }
    429     }
    430 
    431     /**
    432      * @return the unicode map
    433      */
    434     public UnicodeMap getUnicodeMap() {
    435         return getUnicodeMap(false);
    436     }
    437 
    438     /**
    439      * @return the unicode map
    440      */
    441     public UnicodeMap getUnicodeMap(boolean getShortest) {
    442         if (!getShortest)
    443             return (UnicodeMap) getUnicodeMap_internal().cloneAsThawed();
    444         UnicodeMap result = new UnicodeMap();
    445         boolean uniformUnassigned = hasUniformUnassigned();
    446 
    447         for (UnicodeSetIterator usi = getStuffToTest(uniformUnassigned); usi.next();) { // int i = 0; i <= 0x10FFFF; ++i
    448             int i = usi.codepoint;
    449             // if (DEBUG && i == 0x41) System.out.println(i + "\t" +
    450             // getValue(i));
    451             String value = getValue(i, true);
    452             result.put(i, value);
    453         }
    454         return addUntested(result, uniformUnassigned);
    455     }
    456 
    457     /**
    458      * @return the unicode map
    459      */
    460     public UnicodeMap getUnicodeMap_internal() {
    461         if (unicodeMap == null)
    462             unicodeMap = _getUnicodeMap();
    463         return unicodeMap;
    464     }
    465 
    466     protected UnicodeMap _getUnicodeMap() {
    467         UnicodeMap result = new UnicodeMap();
    468         HashMap myIntern = new HashMap();
    469         boolean uniformUnassigned = hasUniformUnassigned();
    470 
    471         for (UnicodeSetIterator usi = getStuffToTest(uniformUnassigned); usi.next();) { // int i = 0; i <= 0x10FFFF; ++i
    472             int i = usi.codepoint;
    473             // if (DEBUG && i == 0x41) System.out.println(i + "\t" +
    474             // getValue(i));
    475             String value = getValue(i);
    476             String iValue = (String) myIntern.get(value);
    477             if (iValue == null)
    478                 myIntern.put(value, iValue = value);
    479             result.put(i, iValue);
    480         }
    481         addUntested(result, uniformUnassigned);
    482 
    483         if (DEBUG) {
    484             for (UnicodeSetIterator usi = getStuffToTest(uniformUnassigned); usi.next();) { // int i = 0; i <= 0x10FFFF; ++i
    485                 int i = usi.codepoint;
    486                 // if (DEBUG && i == 0x41) System.out.println(i + "\t" +
    487                 // getValue(i));
    488                 String value = getValue(i);
    489                 String resultValue = (String) result.getValue(i);
    490                 if (!value.equals(resultValue)) {
    491                     throw new RuntimeException("Value failure at: "
    492                             + Utility.hex(i));
    493                 }
    494             }
    495         }
    496         if (DEBUG && CHECK_NAME.equals(getName())) {
    497             System.out.println(getName() + ":\t" + getClass().getName() + "\t"
    498                     + getVersion());
    499             System.out.println(getStack());
    500             System.out.println(result);
    501         }
    502         return result;
    503     }
    504 
    505     private static UnicodeSetIterator getStuffToTest(boolean uniformUnassigned) {
    506         return new UnicodeSetIterator(uniformUnassigned ? getSTUFF_TO_TEST() : getSTUFF_TO_TEST_WITH_UNASSIGNED());
    507     }
    508 
    509     /**
    510      * Really ought to create a Collection UniqueList, that forces uniqueness.
    511      * But for now...
    512      */
    513     public static Collection addUnique(Object obj, Collection result) {
    514         if (obj != null && !result.contains(obj))
    515             result.add(obj);
    516         return result;
    517     }
    518 
    519     /**
    520      * Utility for managing property & non-string value aliases
    521      */
    522     public static final Comparator PROPERTY_COMPARATOR = new Comparator() {
    523         public int compare(Object o1, Object o2) {
    524             return compareNames((String) o1, (String) o2);
    525         }
    526     };
    527 
    528     /**
    529      * Utility for managing property & non-string value aliases
    530      *
    531      */
    532     // TODO optimize
    533     public static boolean equalNames(String a, String b) {
    534         if (a == b)
    535             return true;
    536         if (a == null)
    537             return false;
    538         return toSkeleton(a).equals(toSkeleton(b));
    539     }
    540 
    541     /**
    542      * Utility for managing property & non-string value aliases
    543      */
    544     // TODO optimize
    545     public static int compareNames(String a, String b) {
    546         if (a == b)
    547             return 0;
    548         if (a == null)
    549             return -1;
    550         if (b == null)
    551             return 1;
    552         return toSkeleton(a).compareTo(toSkeleton(b));
    553     }
    554 
    555     /**
    556      * Utility for managing property & non-string value aliases
    557      */
    558     // TODO account for special names, tibetan, hangul
    559     public static String toSkeleton(String source) {
    560         if (source == null)
    561             return null;
    562         StringBuffer skeletonBuffer = new StringBuffer();
    563         boolean gotOne = false;
    564         // remove spaces, '_', '-'
    565         // we can do this with char, since no surrogates are involved
    566         for (int i = 0; i < source.length(); ++i) {
    567             char ch = source.charAt(i);
    568             if (i > 0 && (ch == '_' || ch == ' ' || ch == '-')) {
    569                 gotOne = true;
    570             } else {
    571                 char ch2 = Character.toLowerCase(ch);
    572                 if (ch2 != ch) {
    573                     gotOne = true;
    574                     skeletonBuffer.append(ch2);
    575                 } else {
    576                     skeletonBuffer.append(ch);
    577                 }
    578             }
    579         }
    580         if (!gotOne)
    581             return source; // avoid string creation
    582         return skeletonBuffer.toString();
    583     }
    584 
    585     // get the name skeleton
    586     public static String toNameSkeleton(String source) {
    587         if (source == null)
    588             return null;
    589         StringBuffer result = new StringBuffer();
    590         // remove spaces, medial '-'
    591         // we can do this with char, since no surrogates are involved
    592         for (int i = 0; i < source.length(); ++i) {
    593             char ch = source.charAt(i);
    594             if (('0' <= ch && ch <= '9') || ('A' <= ch && ch <= 'Z')
    595                     || ch == '<' || ch == '>') {
    596                 result.append(ch);
    597             } else if (ch == ' ') {
    598                 // don't copy ever
    599             } else if (ch == '-') {
    600                 // only copy non-medials AND trailing O-E
    601                 if (0 == i
    602                         || i == source.length() - 1
    603                         || source.charAt(i - 1) == ' '
    604                             || source.charAt(i + 1) == ' '
    605                                 || (i == source.length() - 2
    606                                         && source.charAt(i - 1) == 'O' && source
    607                                         .charAt(i + 1) == 'E')) {
    608                     System.out.println("****** EXCEPTION " + source);
    609                     result.append(ch);
    610                 }
    611                 // otherwise don't copy
    612             } else {
    613                 throw new IllegalArgumentException("Illegal Name Char: U+"
    614                         + Utility.hex(ch) + ", " + ch);
    615             }
    616         }
    617         return result.toString();
    618     }
    619 
    620     /**
    621      * These routines use the Java functions, because they only need to act on
    622      * ASCII Changes space, - into _, inserts _ between lower and UPPER.
    623      */
    624     public static String regularize(String source, boolean titlecaseStart) {
    625         if (source == null)
    626             return source;
    627         /*
    628          * if (source.equals("noBreak")) { // HACK if (titlecaseStart) return
    629          * "NoBreak"; return source; }
    630          */
    631         StringBuffer result = new StringBuffer();
    632         int lastCat = -1;
    633         boolean haveFirstCased = true;
    634         for (int i = 0; i < source.length(); ++i) {
    635             char c = source.charAt(i);
    636             if (c == ' ' || c == '-' || c == '_') {
    637                 c = '_';
    638                 haveFirstCased = true;
    639             }
    640             if (c == '=')
    641                 haveFirstCased = true;
    642             int cat = Character.getType(c);
    643             if (lastCat == Character.LOWERCASE_LETTER
    644                     && cat == Character.UPPERCASE_LETTER) {
    645                 result.append('_');
    646             }
    647             if (haveFirstCased
    648                     && (cat == Character.LOWERCASE_LETTER
    649                             || cat == Character.TITLECASE_LETTER || cat == Character.UPPERCASE_LETTER)) {
    650                 if (titlecaseStart) {
    651                     c = Character.toUpperCase(c);
    652                 }
    653                 haveFirstCased = false;
    654             }
    655             result.append(c);
    656             lastCat = cat;
    657         }
    658         return result.toString();
    659     }
    660 
    661     /**
    662      * Utility function for comparing codepoint to string without generating new
    663      * string.
    664      *
    665      * @param codepoint
    666      * @param other
    667      * @return true if the codepoint equals the string
    668      */
    669     public static final boolean equals(int codepoint, String other) {
    670         if (other == null) return false;
    671         if (other.length() == 1) {
    672             return codepoint == other.charAt(0);
    673         }
    674         if (other.length() == 2) {
    675             return other.equals(UTF16.valueOf(codepoint));
    676         }
    677         return false;
    678     }
    679 
    680     /**
    681      * Utility function for comparing objects that may be null
    682      * string.
    683      */
    684     public static final <T extends Object> boolean equals(T a, T b) {
    685         return a == null ? b == null
    686                 : b == null ? false
    687                         : a.equals(b);
    688     }
    689 
    690     /**
    691      * Utility that should be on UnicodeSet
    692      *
    693      * @param source
    694      * @param result
    695      */
    696     static public void addAll(UnicodeSetIterator source, UnicodeSet result) {
    697         while (source.nextRange()) {
    698             if (source.codepoint == UnicodeSetIterator.IS_STRING) {
    699                 result.add(source.string);
    700             } else {
    701                 result.add(source.codepoint, source.codepointEnd);
    702             }
    703         }
    704     }
    705 
    706     /**
    707      * Really ought to create a Collection UniqueList, that forces uniqueness.
    708      * But for now...
    709      */
    710     public static Collection addAllUnique(Collection source, Collection result) {
    711         for (Iterator it = source.iterator(); it.hasNext();) {
    712             addUnique(it.next(), result);
    713         }
    714         return result;
    715     }
    716 
    717     /**
    718      * Really ought to create a Collection UniqueList, that forces uniqueness.
    719      * But for now...
    720      */
    721     public static Collection addAllUnique(Object[] source, Collection result) {
    722         for (int i = 0; i < source.length; ++i) {
    723             addUnique(source[i], result);
    724         }
    725         return result;
    726     }
    727 
    728     static public class Factory {
    729         static boolean DEBUG = false;
    730 
    731         Map<String, UnicodeProperty> canonicalNames = new TreeMap<String, UnicodeProperty>();
    732 
    733         Map skeletonNames = new TreeMap();
    734 
    735         Map propertyCache = new HashMap(1);
    736 
    737         public final Factory add(UnicodeProperty sp) {
    738             String name2 = sp.getName();
    739             if (name2.length() == 0) {
    740                 throw new IllegalArgumentException();
    741             }
    742             canonicalNames.put(name2, sp);
    743             skeletonNames.put(toSkeleton(name2), sp);
    744             List c = sp.getNameAliases(new ArrayList(1));
    745             Iterator it = c.iterator();
    746             while (it.hasNext()) {
    747                 skeletonNames.put(toSkeleton((String) it.next()), sp);
    748             }
    749             return this;
    750         }
    751 
    752         public UnicodeProperty getProperty(String propertyAlias) {
    753             return (UnicodeProperty) skeletonNames
    754             .get(toSkeleton(propertyAlias));
    755         }
    756 
    757         public final List<String> getAvailableNames() {
    758             return getAvailableNames(null);
    759         }
    760 
    761         public final List<String> getAvailableNames(List<String> result) {
    762             if (result == null)
    763                 result = new ArrayList(1);
    764             Iterator it = canonicalNames.keySet().iterator();
    765             while (it.hasNext()) {
    766                 addUnique(it.next(), result);
    767             }
    768             return result;
    769         }
    770 
    771         public final List getAvailableNames(int propertyTypeMask) {
    772             return getAvailableNames(propertyTypeMask, null);
    773         }
    774 
    775         public final List getAvailableNames(int propertyTypeMask, List result) {
    776             if (result == null)
    777                 result = new ArrayList(1);
    778             Iterator it = canonicalNames.keySet().iterator();
    779             while (it.hasNext()) {
    780                 String item = (String) it.next();
    781                 UnicodeProperty property = getProperty(item);
    782                 if (DEBUG)
    783                     System.out.println("Properties: " + item + ","
    784                             + property.getType());
    785                 if (!property.isType(propertyTypeMask)) {
    786                     // System.out.println("Masking: " + property.getType() + ","
    787                     // + propertyTypeMask);
    788                     continue;
    789                 }
    790                 addUnique(property.getName(), result);
    791             }
    792             return result;
    793         }
    794 
    795         InversePatternMatcher inverseMatcher = new InversePatternMatcher();
    796 
    797         /**
    798          * Format is: propname ('=' | '!=') propvalue ( '|' propValue )*
    799          */
    800         public final UnicodeSet getSet(String propAndValue,
    801                 PatternMatcher matcher, UnicodeSet result) {
    802             int equalPos = propAndValue.indexOf('=');
    803             String prop = propAndValue.substring(0, equalPos);
    804             String value = propAndValue.substring(equalPos + 1);
    805             boolean negative = false;
    806             if (prop.endsWith("!")) {
    807                 prop = prop.substring(0, prop.length() - 1);
    808                 negative = true;
    809             }
    810             prop = prop.trim();
    811             UnicodeProperty up = getProperty(prop);
    812             if (matcher == null) {
    813                 matcher = new SimpleMatcher(value, up
    814                         .isType(STRING_OR_MISC_MASK) ? null
    815                                 : PROPERTY_COMPARATOR);
    816             }
    817             if (negative) {
    818                 inverseMatcher.set(matcher);
    819                 matcher = inverseMatcher;
    820             }
    821             return up.getSet(matcher.set(value), result);
    822         }
    823 
    824         public final UnicodeSet getSet(String propAndValue,
    825                 PatternMatcher matcher) {
    826             return getSet(propAndValue, matcher, null);
    827         }
    828 
    829         public final UnicodeSet getSet(String propAndValue) {
    830             return getSet(propAndValue, null, null);
    831         }
    832 
    833         public final SymbolTable getSymbolTable(String prefix) {
    834             return new PropertySymbolTable(prefix);
    835         }
    836 
    837         private class MyXSymbolTable extends UnicodeSet.XSymbolTable {
    838             public boolean applyPropertyAlias(String propertyName,
    839                     String propertyValue, UnicodeSet result) {
    840                 if (false)
    841                     System.out.println(propertyName + "=" + propertyValue);
    842                 UnicodeProperty prop = getProperty(propertyName);
    843                 if (prop == null)
    844                     return false;
    845                 result.clear();
    846                 UnicodeSet x = prop.getSet(propertyValue, result);
    847                 return x.size() != 0;
    848             }
    849         }
    850 
    851         public final UnicodeSet.XSymbolTable getXSymbolTable() {
    852             return new MyXSymbolTable();
    853         }
    854 
    855         private class PropertySymbolTable implements SymbolTable {
    856             static final boolean DEBUG = false;
    857 
    858             private String prefix;
    859 
    860             RegexMatcher regexMatcher = new RegexMatcher();
    861 
    862             PropertySymbolTable(String prefix) {
    863                 this.prefix = prefix;
    864             }
    865 
    866             public char[] lookup(String s) {
    867                 if (DEBUG)
    868                     System.out.println("\t(" + prefix + ")Looking up " + s);
    869                 // ensure, again, that prefix matches
    870                 int start = prefix.length();
    871                 if (!s.regionMatches(true, 0, prefix, 0, start))
    872                     return null;
    873 
    874                 int pos = s.indexOf(':', start);
    875                 if (pos < 0) { // should never happen
    876                     throw new IllegalArgumentException(
    877                             "Internal Error: missing =: " + s + "\r\n");
    878                 }
    879                 UnicodeProperty prop = getProperty(s.substring(start, pos));
    880                 if (prop == null) {
    881                     throw new IllegalArgumentException("Invalid Property in: "
    882                             + s + "\r\nUse " + showSet(getAvailableNames()));
    883                 }
    884                 String value = s.substring(pos + 1);
    885                 UnicodeSet set;
    886                 if (value.startsWith("\u00AB")) { // regex!
    887                     set = prop.getSet(regexMatcher.set(value.substring(1, value
    888                             .length() - 1)));
    889                 } else {
    890                     set = prop.getSet(value);
    891                 }
    892                 if (set.size() == 0) {
    893                     throw new IllegalArgumentException(
    894                             "Empty Property-Value in: " + s + "\r\nUse "
    895                             + showSet(prop.getAvailableValues()));
    896                 }
    897                 if (DEBUG)
    898                     System.out.println("\t(" + prefix + ")Returning "
    899                             + set.toPattern(true));
    900                 return set.toPattern(true).toCharArray(); // really ugly
    901             }
    902 
    903             private String showSet(List list) {
    904                 StringBuffer result = new StringBuffer("[");
    905                 boolean first = true;
    906                 for (Iterator it = list.iterator(); it.hasNext();) {
    907                     if (!first)
    908                         result.append(", ");
    909                     else
    910                         first = false;
    911                     result.append(it.next().toString());
    912                 }
    913                 result.append("]");
    914                 return result.toString();
    915             }
    916 
    917             public UnicodeMatcher lookupMatcher(int ch) {
    918                 return null;
    919             }
    920 
    921             public String parseReference(String text, ParsePosition pos,
    922                     int limit) {
    923                 if (DEBUG)
    924                     System.out.println("\t(" + prefix + ")Parsing <"
    925                             + text.substring(pos.getIndex(), limit) + ">");
    926                 int start = pos.getIndex();
    927                 // ensure that it starts with 'prefix'
    928                 if (!text
    929                         .regionMatches(true, start, prefix, 0, prefix.length()))
    930                     return null;
    931                 start += prefix.length();
    932                 // now see if it is of the form identifier:identifier
    933                 int i = getIdentifier(text, start, limit);
    934                 if (i == start)
    935                     return null;
    936                 String prop = text.substring(start, i);
    937                 String value = "true";
    938                 if (i < limit) {
    939                     if (text.charAt(i) == ':') {
    940                         int j;
    941                         if (text.charAt(i + 1) == '\u00AB') { // regular
    942                             // expression
    943                             j = text.indexOf('\u00BB', i + 2) + 1; // include
    944                             // last
    945                             // character
    946                             if (j <= 0)
    947                                 return null;
    948                         } else {
    949                             j = getIdentifier(text, i + 1, limit);
    950                         }
    951                         value = text.substring(i + 1, j);
    952                         i = j;
    953                     }
    954                 }
    955                 pos.setIndex(i);
    956                 if (DEBUG)
    957                     System.out.println("\t(" + prefix + ")Parsed <" + prop
    958                             + ">=<" + value + ">");
    959                 return prefix + prop + ":" + value;
    960             }
    961 
    962             private int getIdentifier(String text, int start, int limit) {
    963                 if (DEBUG)
    964                     System.out.println("\tGetID <"
    965                             + text.substring(start, limit) + ">");
    966                 int cp = 0;
    967                 int i;
    968                 for (i = start; i < limit; i += UTF16.getCharCount(cp)) {
    969                     cp = UTF16.charAt(text, i);
    970                     if (!com.ibm.icu.lang.UCharacter
    971                             .isUnicodeIdentifierPart(cp)
    972                             && cp != '.') {
    973                         break;
    974                     }
    975                 }
    976                 if (DEBUG)
    977                     System.out.println("\tGotID <" + text.substring(start, i)
    978                             + ">");
    979                 return i;
    980             }
    981         }
    982     }
    983 
    984     public static class FilteredProperty extends UnicodeProperty {
    985         private UnicodeProperty property;
    986 
    987         protected StringFilter filter;
    988 
    989         protected UnicodeSetIterator matchIterator = new UnicodeSetIterator(
    990                 new UnicodeSet(0, 0x10FFFF));
    991 
    992         protected HashMap backmap;
    993 
    994         boolean allowValueAliasCollisions = false;
    995 
    996         public FilteredProperty(UnicodeProperty property, StringFilter filter) {
    997             this.property = property;
    998             this.filter = filter;
    999         }
   1000 
   1001         public StringFilter getFilter() {
   1002             return filter;
   1003         }
   1004 
   1005         public UnicodeProperty setFilter(StringFilter filter) {
   1006             this.filter = filter;
   1007             return this;
   1008         }
   1009 
   1010         List temp = new ArrayList(1);
   1011 
   1012         public List _getAvailableValues(List result) {
   1013             temp.clear();
   1014             return filter.addUnique(property.getAvailableValues(temp), result);
   1015         }
   1016 
   1017         public List _getNameAliases(List result) {
   1018             temp.clear();
   1019             return filter.addUnique(property.getNameAliases(temp), result);
   1020         }
   1021 
   1022         public String _getValue(int codepoint) {
   1023             return filter.remap(property.getValue(codepoint));
   1024         }
   1025 
   1026         public List _getValueAliases(String valueAlias, List result) {
   1027             if (backmap == null) {
   1028                 backmap = new HashMap(1);
   1029                 temp.clear();
   1030                 Iterator it = property.getAvailableValues(temp).iterator();
   1031                 while (it.hasNext()) {
   1032                     String item = (String) it.next();
   1033                     String mappedItem = filter.remap(item);
   1034                     if (backmap.get(mappedItem) != null
   1035                             && !allowValueAliasCollisions) {
   1036                         throw new IllegalArgumentException(
   1037                                 "Filter makes values collide! " + item + ", "
   1038                                 + mappedItem);
   1039                     }
   1040                     backmap.put(mappedItem, item);
   1041                 }
   1042             }
   1043             valueAlias = (String) backmap.get(valueAlias);
   1044             temp.clear();
   1045             return filter.addUnique(property.getValueAliases(valueAlias, temp),
   1046                     result);
   1047         }
   1048 
   1049         public String _getVersion() {
   1050             return property.getVersion();
   1051         }
   1052 
   1053         public boolean isAllowValueAliasCollisions() {
   1054             return allowValueAliasCollisions;
   1055         }
   1056 
   1057         public FilteredProperty setAllowValueAliasCollisions(boolean b) {
   1058             allowValueAliasCollisions = b;
   1059             return this;
   1060         }
   1061 
   1062     }
   1063 
   1064     public static abstract class StringFilter implements Cloneable {
   1065         public abstract String remap(String original);
   1066 
   1067         public final List addUnique(Collection source, List result) {
   1068             if (result == null)
   1069                 result = new ArrayList(1);
   1070             Iterator it = source.iterator();
   1071             while (it.hasNext()) {
   1072                 UnicodeProperty.addUnique(remap((String) it.next()), result);
   1073             }
   1074             return result;
   1075         }
   1076         /*
   1077          * public Object clone() { try { return super.clone(); } catch
   1078          * (CloneNotSupportedException e) { throw new
   1079          * IllegalStateException("Should never happen."); } }
   1080          */
   1081     }
   1082 
   1083     public static class MapFilter extends StringFilter {
   1084         private Map valueMap;
   1085 
   1086         public MapFilter(Map valueMap) {
   1087             this.valueMap = valueMap;
   1088         }
   1089 
   1090         public String remap(String original) {
   1091             Object changed = valueMap.get(original);
   1092             return changed == null ? original : (String) changed;
   1093         }
   1094 
   1095         public Map getMap() {
   1096             return valueMap;
   1097         }
   1098     }
   1099 
   1100     public interface PatternMatcher extends ObjectMatcher {
   1101         public PatternMatcher set(String pattern);
   1102     }
   1103 
   1104     public static class InversePatternMatcher extends InverseMatcher implements
   1105     PatternMatcher {
   1106         PatternMatcher other;
   1107 
   1108         public PatternMatcher set(PatternMatcher toInverse) {
   1109             other = toInverse;
   1110             return this;
   1111         }
   1112 
   1113         public boolean matches(Object value) {
   1114             return !other.matches(value);
   1115         }
   1116 
   1117         public PatternMatcher set(String pattern) {
   1118             other.set(pattern);
   1119             return this;
   1120         }
   1121     }
   1122 
   1123     public static class SimpleMatcher implements PatternMatcher {
   1124         Comparator comparator;
   1125 
   1126         String pattern;
   1127 
   1128         public SimpleMatcher(String pattern, Comparator comparator) {
   1129             this.comparator = comparator;
   1130             this.pattern = pattern;
   1131         }
   1132 
   1133         public boolean matches(Object value) {
   1134             if (comparator == null)
   1135                 return pattern.equals(value);
   1136             return comparator.compare(pattern, value) == 0;
   1137         }
   1138 
   1139         public PatternMatcher set(String pattern) {
   1140             this.pattern = pattern;
   1141             return this;
   1142         }
   1143     }
   1144 
   1145     public static class RegexMatcher implements UnicodeProperty.PatternMatcher {
   1146         private java.util.regex.Matcher matcher;
   1147 
   1148         public UnicodeProperty.PatternMatcher set(String pattern) {
   1149             matcher = Pattern.compile(pattern).matcher("");
   1150             return this;
   1151         }
   1152         UFormat foo;
   1153         public boolean matches(Object value) {
   1154             matcher.reset(value.toString());
   1155             return matcher.find();
   1156         }
   1157     }
   1158 
   1159     public enum AliasAddAction {IGNORE_IF_MISSING, REQUIRE_MAIN_ALIAS, ADD_MAIN_ALIAS}
   1160 
   1161     public static abstract class BaseProperty extends UnicodeProperty {
   1162         private static final String[] NO_VALUES = {"No", "N", "F", "False"};
   1163 
   1164         private static final String[] YES_VALUES = {"Yes", "Y", "T", "True"};
   1165 
   1166         /**
   1167          *
   1168          */
   1169         private static final String[][] YES_NO_ALIASES = new String[][] {YES_VALUES, NO_VALUES};
   1170 
   1171         protected List propertyAliases = new ArrayList(1);
   1172 
   1173         protected Map toValueAliases;
   1174 
   1175         protected String version;
   1176 
   1177         public BaseProperty setMain(String alias, String shortAlias,
   1178                 int propertyType, String version) {
   1179             setName(alias);
   1180             setType(propertyType);
   1181             propertyAliases.add(shortAlias);
   1182             propertyAliases.add(alias);
   1183             if (propertyType == BINARY) {
   1184                 addValueAliases(YES_NO_ALIASES, AliasAddAction.ADD_MAIN_ALIAS);
   1185             }
   1186             this.version = version;
   1187             return this;
   1188         }
   1189 
   1190         public String _getVersion() {
   1191             return version;
   1192         }
   1193 
   1194         public List _getNameAliases(List result) {
   1195             addAllUnique(propertyAliases, result);
   1196             return result;
   1197         }
   1198 
   1199         public BaseProperty addValueAliases(String[][] valueAndAlternates,
   1200                 AliasAddAction aliasAddAction) {
   1201             if (toValueAliases == null)
   1202                 _fixValueAliases();
   1203             for (int i = 0; i < valueAndAlternates.length; ++i) {
   1204                 for (int j = 1; j < valueAndAlternates[0].length; ++j) {
   1205                     addValueAlias(valueAndAlternates[i][0],
   1206                             valueAndAlternates[i][j], aliasAddAction);
   1207                 }
   1208             }
   1209             return this;
   1210         }
   1211 
   1212         public void addValueAlias(String value, String valueAlias,
   1213                 AliasAddAction aliasAddAction) {
   1214             List result = (List) toValueAliases.get(value);
   1215             if (result == null) {
   1216                 switch(aliasAddAction) {
   1217                 case IGNORE_IF_MISSING: return;
   1218                 case REQUIRE_MAIN_ALIAS: throw new IllegalArgumentException("Can't add alias for mising value: " + value);
   1219                 case ADD_MAIN_ALIAS:
   1220                     toValueAliases.put(value, result = new ArrayList(0));
   1221                     break;
   1222                 }
   1223             }
   1224             addUnique(value, result);
   1225             addUnique(valueAlias, result);
   1226         }
   1227 
   1228         protected List _getValueAliases(String valueAlias, List result) {
   1229             if (toValueAliases == null)
   1230                 _fixValueAliases();
   1231             List a = (List) toValueAliases.get(valueAlias);
   1232             if (a != null)
   1233                 addAllUnique(a, result);
   1234             return result;
   1235         }
   1236 
   1237         protected void _fixValueAliases() {
   1238             if (toValueAliases == null)
   1239                 toValueAliases = new HashMap(1);
   1240             for (Iterator it = getAvailableValues().iterator(); it.hasNext();) {
   1241                 Object value = it.next();
   1242                 _ensureValueInAliases(value);
   1243             }
   1244         }
   1245 
   1246         protected void _ensureValueInAliases(Object value) {
   1247             List result = (List) toValueAliases.get(value);
   1248             if (result == null)
   1249                 toValueAliases.put(value, result = new ArrayList(1));
   1250             addUnique(value, result);
   1251         }
   1252 
   1253         public BaseProperty swapFirst2ValueAliases() {
   1254             for (Iterator it = toValueAliases.keySet().iterator(); it.hasNext();) {
   1255                 List list = (List) toValueAliases.get(it.next());
   1256                 if (list.size() < 2)
   1257                     continue;
   1258                 Object first = list.get(0);
   1259                 list.set(0, list.get(1));
   1260                 list.set(1, first);
   1261             }
   1262             return this;
   1263         }
   1264 
   1265         /**
   1266          * @param string
   1267          * @return
   1268          */
   1269         public UnicodeProperty addName(String string) {
   1270             throw new UnsupportedOperationException();
   1271         }
   1272 
   1273     }
   1274 
   1275     public static abstract class SimpleProperty extends BaseProperty {
   1276         LinkedHashSet values;
   1277 
   1278         public UnicodeProperty addName(String alias) {
   1279             propertyAliases.add(alias);
   1280             return this;
   1281         }
   1282 
   1283         public SimpleProperty setValues(String valueAlias) {
   1284             _addToValues(valueAlias, null);
   1285             return this;
   1286         }
   1287 
   1288         public SimpleProperty addAliases(String valueAlias, String... aliases) {
   1289             _addToValues(valueAlias, null);
   1290             return this;
   1291         }
   1292 
   1293         public SimpleProperty setValues(String[] valueAliases,
   1294                 String[] alternateValueAliases) {
   1295             for (int i = 0; i < valueAliases.length; ++i) {
   1296                 if (valueAliases[i].equals(UNUSED))
   1297                     continue;
   1298                 _addToValues(
   1299                         valueAliases[i],
   1300                         alternateValueAliases != null ? alternateValueAliases[i]
   1301                                                                               : null);
   1302             }
   1303             return this;
   1304         }
   1305 
   1306         public SimpleProperty setValues(List valueAliases) {
   1307             this.values = new LinkedHashSet(valueAliases);
   1308             for (Iterator it = this.values.iterator(); it.hasNext();) {
   1309                 _addToValues((String) it.next(), null);
   1310             }
   1311             return this;
   1312         }
   1313 
   1314         public List _getAvailableValues(List result) {
   1315             if (values == null)
   1316                 _fillValues();
   1317             result.addAll(values);
   1318             return result;
   1319         }
   1320 
   1321         protected void _fillValues() {
   1322             List newvalues = (List) getUnicodeMap_internal()
   1323             .getAvailableValues(new ArrayList());
   1324             for (Iterator it = newvalues.iterator(); it.hasNext();) {
   1325                 _addToValues((String) it.next(), null);
   1326             }
   1327         }
   1328 
   1329         private void _addToValues(String item, String alias) {
   1330             if (values == null)
   1331                 values = new LinkedHashSet();
   1332             if (toValueAliases == null)
   1333                 _fixValueAliases();
   1334             addUnique(item, values);
   1335             _ensureValueInAliases(item);
   1336             addValueAlias(item, alias, AliasAddAction.REQUIRE_MAIN_ALIAS);
   1337         }
   1338         /*        public String _getVersion() {
   1339          return version;
   1340          }
   1341          */
   1342     }
   1343 
   1344     public static class UnicodeMapProperty extends BaseProperty {
   1345         /*
   1346          * Example of usage:
   1347          * new UnicodeProperty.UnicodeMapProperty() {
   1348          {
   1349          unicodeMap = new UnicodeMap();
   1350          unicodeMap.setErrorOnReset(true);
   1351          unicodeMap.put(0xD, "CR");
   1352          unicodeMap.put(0xA, "LF");
   1353          UnicodeProperty cat = getProperty("General_Category");
   1354          UnicodeSet temp = cat.getSet("Line_Separator")
   1355          .addAll(cat.getSet("Paragraph_Separator"))
   1356          .addAll(cat.getSet("Control"))
   1357          .addAll(cat.getSet("Format"))
   1358          .remove(0xD).remove(0xA).remove(0x200C).remove(0x200D);
   1359          unicodeMap.putAll(temp, "Control");
   1360          UnicodeSet graphemeExtend = getProperty("Grapheme_Extend").getSet("true");
   1361          unicodeMap.putAll(graphemeExtend,"Extend");
   1362          UnicodeProperty hangul = getProperty("Hangul_Syllable_Type");
   1363          unicodeMap.putAll(hangul.getSet("L"),"L");
   1364          unicodeMap.putAll(hangul.getSet("V"),"V");
   1365          unicodeMap.putAll(hangul.getSet("T"),"T");
   1366          unicodeMap.putAll(hangul.getSet("LV"),"LV");
   1367          unicodeMap.putAll(hangul.getSet("LVT"),"LVT");
   1368          unicodeMap.setMissing("Other");
   1369          }
   1370          }.setMain("Grapheme_Cluster_Break", "GCB", UnicodeProperty.ENUMERATED, version)
   1371          */
   1372         protected UnicodeMap unicodeMap;
   1373 
   1374         protected UnicodeMap _getUnicodeMap() {
   1375             return unicodeMap;
   1376         }
   1377 
   1378         public UnicodeMapProperty set(UnicodeMap map) {
   1379             unicodeMap = map.freeze();
   1380             return this;
   1381         }
   1382 
   1383         protected String _getValue(int codepoint) {
   1384             return (String) unicodeMap.getValue(codepoint);
   1385         }
   1386 
   1387         /* protected List _getValueAliases(String valueAlias, List result) {
   1388          if (!unicodeMap.getAvailableValues().contains(valueAlias)) return result;
   1389          result.add(valueAlias);
   1390          return result; // no other aliases
   1391          }
   1392          */protected List _getAvailableValues(List result) {
   1393              unicodeMap.getAvailableValues(result);
   1394              if (toValueAliases != null) {
   1395                  for (Object s : toValueAliases.keySet()) {
   1396                      if (!result.contains(s)) {
   1397                          result.add(s);
   1398                      }
   1399                  }
   1400              }
   1401              return result;
   1402          }
   1403     }
   1404 
   1405     public boolean isValidValue(String propertyValue) {
   1406         if (isType(STRING_OR_MISC_MASK)) {
   1407             return true;
   1408         }
   1409         Collection<String> values = (Collection<String>) getAvailableValues();
   1410         for (String valueAlias : values) {
   1411             if (UnicodeProperty.compareNames(valueAlias, propertyValue) == 0) {
   1412                 return true;
   1413             }
   1414             for (String valueAlias2 : (Collection<String>) getValueAliases(valueAlias)) {
   1415                 if (UnicodeProperty.compareNames(valueAlias2, propertyValue) == 0) {
   1416                     return true;
   1417                 }
   1418             }
   1419         }
   1420         return false;
   1421     }
   1422 
   1423     public List<String> getValueAliases() {
   1424         List<String> result = new ArrayList();
   1425         if (isType(STRING_OR_MISC_MASK)) {
   1426             return result;
   1427         }
   1428         Collection<String> values = (Collection<String>) getAvailableValues();
   1429         for (String valueAlias : values) {
   1430             UnicodeProperty.addAllUnique(getValueAliases(valueAlias), result);
   1431         }
   1432         result.removeAll(values);
   1433         return result;
   1434     }
   1435 
   1436 
   1437     public static UnicodeSet addUntested(UnicodeSet result, boolean uniformUnassigned) {
   1438         if (uniformUnassigned && result.contains(UnicodeProperty.getSAMPLE_UNASSIGNED())) {
   1439             result.addAll(UnicodeProperty.getUNASSIGNED());
   1440         }
   1441 
   1442         if (result.contains(UnicodeProperty.SAMPLE_HIGH_SURROGATE)) {
   1443             result.addAll(UnicodeProperty.HIGH_SURROGATES);
   1444         }
   1445         if (result.contains(UnicodeProperty.SAMPLE_HIGH_PRIVATE_USE_SURROGATE)) {
   1446             result.addAll(UnicodeProperty.HIGH_PRIVATE_USE_SURROGATES);
   1447         }
   1448         if (result.contains(UnicodeProperty.SAMPLE_LOW_SURROGATE)) {
   1449             result.addAll(UnicodeProperty.LOW_SURROGATES);
   1450         }
   1451 
   1452         if (result.contains(UnicodeProperty.SAMPLE_PRIVATE_USE_AREA)) {
   1453             result.addAll(UnicodeProperty.PRIVATE_USE_AREA);
   1454         }
   1455         if (result.contains(UnicodeProperty.SAMPLE_PRIVATE_USE_AREA_A)) {
   1456             result.addAll(UnicodeProperty.PRIVATE_USE_AREA_A);
   1457         }
   1458         if (result.contains(UnicodeProperty.SAMPLE_PRIVATE_USE_AREA_B)) {
   1459             result.addAll(UnicodeProperty.PRIVATE_USE_AREA_B);
   1460         }
   1461 
   1462         return result;
   1463     }
   1464 
   1465     public static UnicodeMap addUntested(UnicodeMap result, boolean uniformUnassigned) {
   1466         Object temp;
   1467         if (uniformUnassigned && null != (temp = result.get(UnicodeProperty.getSAMPLE_UNASSIGNED()))) {
   1468             result.putAll(UnicodeProperty.getUNASSIGNED(), temp);
   1469         }
   1470 
   1471         if (null != (temp = result.get(UnicodeProperty.SAMPLE_HIGH_SURROGATE))) {
   1472             result.putAll(UnicodeProperty.HIGH_SURROGATES, temp);
   1473         }
   1474         if (null != (temp = result.get(UnicodeProperty.SAMPLE_HIGH_PRIVATE_USE_SURROGATE))) {
   1475             result.putAll(UnicodeProperty.HIGH_PRIVATE_USE_SURROGATES, temp);
   1476         }
   1477         if (null != (temp = result.get(UnicodeProperty.SAMPLE_LOW_SURROGATE))) {
   1478             result.putAll(UnicodeProperty.LOW_SURROGATES, temp);
   1479         }
   1480 
   1481         if (null != (temp = result.get(UnicodeProperty.SAMPLE_PRIVATE_USE_AREA))) {
   1482             result.putAll(UnicodeProperty.PRIVATE_USE_AREA, temp);
   1483         }
   1484         if (null != (temp = result.get(UnicodeProperty.SAMPLE_PRIVATE_USE_AREA_A))) {
   1485             result.putAll(UnicodeProperty.PRIVATE_USE_AREA_A, temp);
   1486         }
   1487         if (null != (temp = result.get(UnicodeProperty.SAMPLE_PRIVATE_USE_AREA_B))) {
   1488             result.putAll(UnicodeProperty.PRIVATE_USE_AREA_B, temp);
   1489         }
   1490         return result;
   1491     }
   1492 
   1493     public boolean isDefault(int cp) {
   1494         String value = getValue(cp);
   1495         if (isType(STRING_OR_MISC_MASK)) {
   1496             return equals(cp, value);
   1497         }
   1498         String defaultValue = getValue(getSAMPLE_UNASSIGNED());
   1499         return defaultValue == null ? value == null : defaultValue.equals(value);
   1500     }
   1501 
   1502     public boolean hasUniformUnassigned() {
   1503         return hasUniformUnassigned;
   1504     }
   1505     protected UnicodeProperty setUniformUnassigned(boolean hasUniformUnassigned) {
   1506         this.hasUniformUnassigned = hasUniformUnassigned;
   1507         return this;
   1508     }
   1509 
   1510     public static class UnicodeSetProperty extends BaseProperty {
   1511         protected UnicodeSet unicodeSet;
   1512         private static final String[] YESNO_ARRAY = new String[]{"Yes", "No"};
   1513         private static final List YESNO = Arrays.asList(YESNO_ARRAY);
   1514 
   1515         public UnicodeSetProperty set(UnicodeSet set) {
   1516             unicodeSet = set.freeze();
   1517             return this;
   1518         }
   1519 
   1520         public UnicodeSetProperty set(String string) {
   1521             // TODO Auto-generated method stub
   1522             return set(new UnicodeSet(string).freeze());
   1523         }
   1524 
   1525         protected String _getValue(int codepoint) {
   1526             return YESNO_ARRAY[unicodeSet.contains(codepoint) ? 0 : 1];
   1527         }
   1528 
   1529         protected List _getAvailableValues(List result) {
   1530             return YESNO;
   1531         }
   1532     }
   1533 
   1534     //    private static class StringTransformProperty extends SimpleProperty {
   1535     //        Transform<String,String> transform;
   1536     //
   1537     //        public StringTransformProperty(Transform<String,String> transform, boolean hasUniformUnassigned) {
   1538     //            this.transform = transform;
   1539     //            setUniformUnassigned(hasUniformUnassigned);
   1540     //        }
   1541     //        protected String _getValue(int codepoint) {
   1542     //            return transform.transform(UTF16.valueOf(codepoint));
   1543     //        }
   1544     //    }
   1545     //
   1546     //    private static class CodepointTransformProperty extends SimpleProperty {
   1547     //        Transform<Integer,String> transform;
   1548     //
   1549     //        public CodepointTransformProperty(Transform<Integer,String> transform, boolean hasUniformUnassigned) {
   1550     //            this.transform = transform;
   1551     //            setUniformUnassigned(hasUniformUnassigned);
   1552     //        }
   1553     //        protected String _getValue(int codepoint) {
   1554     //            return transform.transform(codepoint);
   1555     //        }
   1556     //    }
   1557 }
   1558 
   1559