]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/dev/test/util/UnicodeProperty.java
go
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / dev / test / util / UnicodeProperty.java
1 //##header J2SE15
2 //#if defined(FOUNDATION10) || defined(J2SE13)
3 //#else
4 /*
5  *******************************************************************************
6  * Copyright (C) 1996-2009, International Business Machines Corporation and    *
7  * others. All Rights Reserved.                                                *
8  *******************************************************************************
9  */
10 package com.ibm.icu.dev.test.util;
11
12 import java.io.PrintWriter;
13 import java.io.StringWriter;
14 import java.text.ParsePosition;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Comparator;
18 import java.util.HashMap;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.TreeMap;
23 import java.util.regex.Pattern;
24
25 import com.ibm.icu.dev.test.util.CollectionUtilities.InverseMatcher;
26 import com.ibm.icu.dev.test.util.CollectionUtilities.ObjectMatcher;
27 import com.ibm.icu.impl.Utility;
28 import com.ibm.icu.text.SymbolTable;
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 boolean DEBUG = false;
37
38     public static String CHECK_NAME = "FC_NFKC_Closure";
39
40     public static int CHECK_VALUE = 0x037A;
41
42     private String name;
43
44     private String firstNameAlias = null;
45
46     private int type;
47
48     private Map valueToFirstValueAlias = null;
49
50     /*
51      * Name: Unicode_1_Name Name: ISO_Comment Name: Name Name: Unicode_1_Name
52      * 
53      */
54
55     public static final int UNKNOWN = 0, BINARY = 2, EXTENDED_BINARY = 3,
56             ENUMERATED = 4, EXTENDED_ENUMERATED = 5, CATALOG = 6,
57             EXTENDED_CATALOG = 7, MISC = 8, EXTENDED_MISC = 9, STRING = 10,
58             EXTENDED_STRING = 11, NUMERIC = 12, EXTENDED_NUMERIC = 13,
59             START_TYPE = 2, LIMIT_TYPE = 14, EXTENDED_MASK = 1,
60             CORE_MASK = ~EXTENDED_MASK, BINARY_MASK = (1 << BINARY)
61                     | (1 << EXTENDED_BINARY), STRING_MASK = (1 << STRING)
62                     | (1 << EXTENDED_STRING),
63             STRING_OR_MISC_MASK = (1 << STRING) | (1 << EXTENDED_STRING)
64                     | (1 << MISC) | (1 << EXTENDED_MISC),
65             ENUMERATED_OR_CATALOG_MASK = (1 << ENUMERATED)
66                     | (1 << EXTENDED_ENUMERATED) | (1 << CATALOG)
67                     | (1 << EXTENDED_CATALOG);
68
69     private static final String[] TYPE_NAMES = { "Unknown", "Unknown",
70             "Binary", "Extended Binary", "Enumerated", "Extended Enumerated",
71             "Catalog", "Extended Catalog", "Miscellaneous",
72             "Extended Miscellaneous", "String", "Extended String", "Numeric",
73             "Extended Numeric", };
74
75     public static String getTypeName(int propType) {
76         return TYPE_NAMES[propType];
77     }
78
79     public final String getName() {
80         return name;
81     }
82
83     public final int getType() {
84         return type;
85     }
86
87     public final boolean isType(int mask) {
88         return ((1 << type) & mask) != 0;
89     }
90
91     protected final void setName(String string) {
92         if (string == null)
93             throw new IllegalArgumentException("Name must not be null");
94         name = string;
95     }
96
97     protected final void setType(int i) {
98         type = i;
99     }
100
101     public String getVersion() {
102         return _getVersion();
103     }
104
105     public String getValue(int codepoint) {
106         if (DEBUG && CHECK_VALUE == codepoint && CHECK_NAME.equals(getName())) {
107             String value = _getValue(codepoint);
108             System.out.println(getName() + "(" + Utility.hex(codepoint) + "):"
109                     + (getType() == STRING ? Utility.hex(value) : value));
110             return value;
111         }
112         return _getValue(codepoint);
113     }
114
115     // public String getValue(int codepoint, boolean isShort) {
116     // return getValue(codepoint);
117     // }
118
119     public List getNameAliases(List result) {
120         if (result == null)
121             result = new ArrayList(1);
122         return _getNameAliases(result);
123     }
124
125     public List getValueAliases(String valueAlias, List result) {
126         if (result == null)
127             result = new ArrayList(1);
128         result = _getValueAliases(valueAlias, result);
129         if (!result.contains(valueAlias)) { // FIX && type < NUMERIC
130             result = _getValueAliases(valueAlias, result); // for debugging
131             throw new IllegalArgumentException("Internal error: " + getName()
132                     + " doesn't contain " + valueAlias + ": "
133                     + new BagFormatter().join(result));
134         }
135         return result;
136     }
137
138     public List getAvailableValues(List result) {
139         if (result == null)
140             result = new ArrayList(1);
141         return _getAvailableValues(result);
142     }
143
144     protected abstract String _getVersion();
145
146     protected abstract String _getValue(int codepoint);
147
148     protected abstract List _getNameAliases(List result);
149
150     protected abstract List _getValueAliases(String valueAlias, List result);
151
152     protected abstract List _getAvailableValues(List result);
153
154     // conveniences
155     public final List getNameAliases() {
156         return getNameAliases(null);
157     }
158
159     public final List getValueAliases(String valueAlias) {
160         return getValueAliases(valueAlias, null);
161     }
162
163     public final List getAvailableValues() {
164         return getAvailableValues(null);
165     }
166
167     public final String getValue(int codepoint, boolean getShortest) {
168         String result = getValue(codepoint);
169         if (type >= MISC || result == null || !getShortest)
170             return result;
171         return getFirstValueAlias(result);
172     }
173
174     public final String getFirstNameAlias() {
175         if (firstNameAlias == null) {
176             firstNameAlias = (String) getNameAliases().get(0);
177         }
178         return firstNameAlias;
179     }
180
181     public final String getFirstValueAlias(String value) {
182         if (valueToFirstValueAlias == null)
183             _getFirstValueAliasCache();
184         return (String) valueToFirstValueAlias.get(value);
185     }
186
187     private void _getFirstValueAliasCache() {
188         maxValueWidth = 0;
189         maxFirstValueAliasWidth = 0;
190         valueToFirstValueAlias = new HashMap(1);
191         Iterator it = getAvailableValues().iterator();
192         while (it.hasNext()) {
193             String value = (String) it.next();
194             String first = (String) getValueAliases(value).get(0);
195             if (first == null) { // internal error
196                 throw new IllegalArgumentException(
197                         "Value not in value aliases: " + value);
198             }
199             if (DEBUG && CHECK_NAME.equals(getName())) {
200                 System.out.println("First Alias: " + getName() + ": " + value
201                         + " => " + first
202                         + new BagFormatter().join(getValueAliases(value)));
203             }
204             valueToFirstValueAlias.put(value, first);
205             if (value.length() > maxValueWidth) {
206                 maxValueWidth = value.length();
207             }
208             if (first.length() > maxFirstValueAliasWidth) {
209                 maxFirstValueAliasWidth = first.length();
210             }
211         }
212     }
213
214     private int maxValueWidth = -1;
215
216     private int maxFirstValueAliasWidth = -1;
217
218     public int getMaxWidth(boolean getShortest) {
219         if (maxValueWidth < 0)
220             _getFirstValueAliasCache();
221         if (getShortest)
222             return maxFirstValueAliasWidth;
223         return maxValueWidth;
224     }
225
226     public final UnicodeSet getSet(String propertyValue) {
227         return getSet(propertyValue, null);
228     }
229
230     public final UnicodeSet getSet(PatternMatcher matcher) {
231         return getSet(matcher, null);
232     }
233
234     public final UnicodeSet getSet(String propertyValue, UnicodeSet result) {
235         return getSet(new SimpleMatcher(propertyValue,
236                 isType(STRING_OR_MISC_MASK) ? null : PROPERTY_COMPARATOR),
237                 result);
238     }
239
240     private UnicodeMap unicodeMap = null;
241
242     public static final String UNUSED = "??";
243
244     public final UnicodeSet getSet(PatternMatcher matcher, UnicodeSet result) {
245         if (result == null)
246             result = new UnicodeSet();
247         if (isType(STRING_OR_MISC_MASK)) {
248             for (int i = 0; i <= 0x10FFFF; ++i) {
249                 String value = getValue(i);
250                 if (value != null && matcher.matches(value)) {
251                     result.add(i);
252                 }
253             }
254             return result;
255         }
256         List temp = new ArrayList(1); // to avoid reallocating...
257         UnicodeMap um = getUnicodeMap_internal();
258         Iterator it = um.getAvailableValues(null).iterator();
259         main: while (it.hasNext()) {
260             String value = (String) it.next();
261             temp.clear();
262             Iterator it2 = getValueAliases(value, temp).iterator();
263             while (it2.hasNext()) {
264                 String value2 = (String) it2.next();
265                 // System.out.println("Values:" + value2);
266                 if (matcher.matches(value2)
267                         || matcher.matches(toSkeleton(value2))) {
268                     um.getSet(value, result);
269                     continue main;
270                 }
271             }
272         }
273         return result;
274     }
275
276     /*
277      * public UnicodeSet getMatchSet(UnicodeSet result) { if (result == null)
278      * result = new UnicodeSet(); addAll(matchIterator, result); return result; }
279      * 
280      * public void setMatchSet(UnicodeSet set) { matchIterator = new
281      * UnicodeSetIterator(set); }
282      */
283
284     /**
285      * Utility for debugging
286      */
287     public static String getStack() {
288         Exception e = new Exception();
289         StringWriter sw = new StringWriter();
290         PrintWriter pw = new PrintWriter(sw);
291         e.printStackTrace(pw);
292         pw.flush();
293         return "Showing Stack with fake " + sw.getBuffer().toString();
294     }
295
296     // TODO use this instead of plain strings
297     public static class Name implements Comparable {
298         private String skeleton;
299
300         private String pretty;
301
302         public final int RAW = 0, TITLE = 1, NORMAL = 2;
303
304         public Name(String name, int style) {
305             if (name == null)
306                 name = "";
307             if (style == RAW) {
308                 skeleton = pretty = name;
309             } else {
310                 pretty = regularize(name, style == TITLE);
311                 skeleton = toSkeleton(pretty);
312             }
313         }
314
315         public int compareTo(Object o) {
316             return skeleton.compareTo(((Name) o).skeleton);
317         }
318
319         public boolean equals(Object o) {
320             return skeleton.equals(((Name) o).skeleton);
321         }
322
323         public int hashCode() {
324             return skeleton.hashCode();
325         }
326
327         public String toString() {
328             return pretty;
329         }
330     }
331
332     /**
333      * @return the unicode map
334      */
335     public UnicodeMap getUnicodeMap() {
336         return getUnicodeMap(false);
337     }
338
339     /**
340      * @return the unicode map
341      */
342     public UnicodeMap getUnicodeMap(boolean getShortest) {
343         if (!getShortest)
344             return (UnicodeMap) getUnicodeMap_internal().cloneAsThawed();
345         UnicodeMap result = new UnicodeMap();
346         for (int i = 0; i <= 0x10FFFF; ++i) {
347             // if (DEBUG && i == 0x41) System.out.println(i + "\t" +
348             // getValue(i));
349             String value = getValue(i, true);
350             result.put(i, value);
351         }
352         return result;
353     }
354
355     /**
356      * @return the unicode map
357      */
358     protected UnicodeMap getUnicodeMap_internal() {
359         if (unicodeMap == null)
360             unicodeMap = _getUnicodeMap();
361         return unicodeMap;
362     }
363
364     protected UnicodeMap _getUnicodeMap() {
365         UnicodeMap result = new UnicodeMap();
366         HashMap myIntern = new HashMap();
367         for (int i = 0; i <= 0x10FFFF; ++i) {
368             // if (DEBUG && i == 0x41) System.out.println(i + "\t" +
369             // getValue(i));
370             String value = getValue(i);
371             String iValue = (String) myIntern.get(value);
372             if (iValue == null)
373                 myIntern.put(value, iValue = value);
374             result.put(i, iValue);
375         }
376         if (DEBUG) {
377             for (int i = 0; i <= 0x10FFFF; ++i) {
378                 // if (DEBUG && i == 0x41) System.out.println(i + "\t" +
379                 // getValue(i));
380                 String value = getValue(i);
381                 String resultValue = (String) result.getValue(i);
382                 if (!value.equals(resultValue)) {
383                     throw new RuntimeException("Value failure at: "
384                             + Utility.hex(i));
385                 }
386             }
387         }
388         if (DEBUG && CHECK_NAME.equals(getName())) {
389             System.out.println(getName() + ":\t" + getClass().getName() + "\t"
390                     + getVersion());
391             System.out.println(getStack());
392             System.out.println(result);
393         }
394         return result;
395     }
396
397     /**
398      * Really ought to create a Collection UniqueList, that forces uniqueness.
399      * But for now...
400      */
401     public static Collection addUnique(Object obj, Collection result) {
402         if (obj != null && !result.contains(obj))
403             result.add(obj);
404         return result;
405     }
406
407     /**
408      * Utility for managing property & non-string value aliases
409      */
410     public static final Comparator PROPERTY_COMPARATOR = new Comparator() {
411         public int compare(Object o1, Object o2) {
412             return compareNames((String) o1, (String) o2);
413         }
414     };
415
416     /**
417      * Utility for managing property & non-string value aliases
418      * 
419      */
420     // TODO optimize
421     public static boolean equalNames(String a, String b) {
422         if (a == b)
423             return true;
424         if (a == null)
425             return false;
426         return toSkeleton(a).equals(toSkeleton(b));
427     }
428
429     /**
430      * Utility for managing property & non-string value aliases
431      */
432     // TODO optimize
433     public static int compareNames(String a, String b) {
434         if (a == b)
435             return 0;
436         if (a == null)
437             return -1;
438         if (b == null)
439             return 1;
440         return toSkeleton(a).compareTo(toSkeleton(b));
441     }
442
443     /**
444      * Utility for managing property & non-string value aliases
445      */
446     // TODO account for special names, tibetan, hangul
447     public static String toSkeleton(String source) {
448         if (source == null)
449             return null;
450         StringBuffer skeletonBuffer = new StringBuffer();
451         boolean gotOne = false;
452         // remove spaces, '_', '-'
453         // we can do this with char, since no surrogates are involved
454         for (int i = 0; i < source.length(); ++i) {
455             char ch = source.charAt(i);
456             if (i > 0 && (ch == '_' || ch == ' ' || ch == '-')) {
457                 gotOne = true;
458             } else {
459                 char ch2 = Character.toLowerCase(ch);
460                 if (ch2 != ch) {
461                     gotOne = true;
462                     skeletonBuffer.append(ch2);
463                 } else {
464                     skeletonBuffer.append(ch);
465                 }
466             }
467         }
468         if (!gotOne)
469             return source; // avoid string creation
470         return skeletonBuffer.toString();
471     }
472
473     // get the name skeleton
474     public static String toNameSkeleton(String source) {
475         if (source == null)
476             return null;
477         StringBuffer result = new StringBuffer();
478         // remove spaces, medial '-'
479         // we can do this with char, since no surrogates are involved
480         for (int i = 0; i < source.length(); ++i) {
481             char ch = source.charAt(i);
482             if (('0' <= ch && ch <= '9') || ('A' <= ch && ch <= 'Z')
483                     || ch == '<' || ch == '>') {
484                 result.append(ch);
485             } else if (ch == ' ') {
486                 // don't copy ever
487             } else if (ch == '-') {
488                 // only copy non-medials AND trailing O-E
489                 if (0 == i
490                         || i == source.length() - 1
491                         || source.charAt(i - 1) == ' '
492                         || source.charAt(i + 1) == ' '
493                         || (i == source.length() - 2
494                                 && source.charAt(i - 1) == 'O' && source
495                                 .charAt(i + 1) == 'E')) {
496                     System.out.println("****** EXCEPTION " + source);
497                     result.append(ch);
498                 }
499                 // otherwise don't copy
500             } else {
501                 throw new IllegalArgumentException("Illegal Name Char: U+"
502                         + Utility.hex(ch) + ", " + ch);
503             }
504         }
505         return result.toString();
506     }
507
508     /**
509      * These routines use the Java functions, because they only need to act on
510      * ASCII Changes space, - into _, inserts _ between lower and UPPER.
511      */
512     public static String regularize(String source, boolean titlecaseStart) {
513         if (source == null)
514             return source;
515         /*
516          * if (source.equals("noBreak")) { // HACK if (titlecaseStart) return
517          * "NoBreak"; return source; }
518          */
519         StringBuffer result = new StringBuffer();
520         int lastCat = -1;
521         boolean haveFirstCased = true;
522         for (int i = 0; i < source.length(); ++i) {
523             char c = source.charAt(i);
524             if (c == ' ' || c == '-' || c == '_') {
525                 c = '_';
526                 haveFirstCased = true;
527             }
528             if (c == '=')
529                 haveFirstCased = true;
530             int cat = Character.getType(c);
531             if (lastCat == Character.LOWERCASE_LETTER
532                     && cat == Character.UPPERCASE_LETTER) {
533                 result.append('_');
534             }
535             if (haveFirstCased
536                     && (cat == Character.LOWERCASE_LETTER
537                             || cat == Character.TITLECASE_LETTER || cat == Character.UPPERCASE_LETTER)) {
538                 if (titlecaseStart) {
539                     c = Character.toUpperCase(c);
540                 }
541                 haveFirstCased = false;
542             }
543             result.append(c);
544             lastCat = cat;
545         }
546         return result.toString();
547     }
548
549     /**
550      * Utility function for comparing codepoint to string without generating new
551      * string.
552      * 
553      * @param codepoint
554      * @param other
555      * @return true if the codepoint equals the string
556      */
557     public static final boolean equals(int codepoint, String other) {
558         if (other.length() == 1) {
559             return codepoint == other.charAt(0);
560         }
561         if (other.length() == 2) {
562             return other.equals(UTF16.valueOf(codepoint));
563         }
564         return false;
565     }
566
567     /**
568      * Utility that should be on UnicodeSet
569      * 
570      * @param source
571      * @param result
572      */
573     static public void addAll(UnicodeSetIterator source, UnicodeSet result) {
574         while (source.nextRange()) {
575             if (source.codepoint == UnicodeSetIterator.IS_STRING) {
576                 result.add(source.string);
577             } else {
578                 result.add(source.codepoint, source.codepointEnd);
579             }
580         }
581     }
582
583     /**
584      * Really ought to create a Collection UniqueList, that forces uniqueness.
585      * But for now...
586      */
587     public static Collection addAllUnique(Collection source, Collection result) {
588         for (Iterator it = source.iterator(); it.hasNext();) {
589             addUnique(it.next(), result);
590         }
591         return result;
592     }
593
594     /**
595      * Really ought to create a Collection UniqueList, that forces uniqueness.
596      * But for now...
597      */
598     public static Collection addAllUnique(Object[] source, Collection result) {
599         for (int i = 0; i < source.length; ++i) {
600             addUnique(source[i], result);
601         }
602         return result;
603     }
604
605     static public class Factory {
606         static boolean DEBUG = false;
607
608         Map canonicalNames = new TreeMap();
609
610         Map skeletonNames = new TreeMap();
611
612         Map propertyCache = new HashMap(1);
613
614         public final Factory add(UnicodeProperty sp) {
615             canonicalNames.put(sp.getName(), sp);
616             List c = sp.getNameAliases(new ArrayList(1));
617             Iterator it = c.iterator();
618             while (it.hasNext()) {
619                 skeletonNames.put(toSkeleton((String) it.next()), sp);
620             }
621             return this;
622         }
623
624         public final UnicodeProperty getProperty(String propertyAlias) {
625             return (UnicodeProperty) skeletonNames
626                     .get(toSkeleton(propertyAlias));
627         }
628
629         public final List getAvailableNames() {
630             return getAvailableNames(null);
631         }
632
633         public final List getAvailableNames(List result) {
634             if (result == null)
635                 result = new ArrayList(1);
636             Iterator it = canonicalNames.keySet().iterator();
637             while (it.hasNext()) {
638                 addUnique(it.next(), result);
639             }
640             return result;
641         }
642
643         public final List getAvailableNames(int propertyTypeMask) {
644             return getAvailableNames(propertyTypeMask, null);
645         }
646
647         public final List getAvailableNames(int propertyTypeMask, List result) {
648             if (result == null)
649                 result = new ArrayList(1);
650             Iterator it = canonicalNames.keySet().iterator();
651             while (it.hasNext()) {
652                 String item = (String) it.next();
653                 UnicodeProperty property = getProperty(item);
654                 if (DEBUG)
655                     System.out.println("Properties: " + item + ","
656                             + property.getType());
657                 if (!property.isType(propertyTypeMask)) {
658                     // System.out.println("Masking: " + property.getType() + ","
659                     // + propertyTypeMask);
660                     continue;
661                 }
662                 addUnique(property.getName(), result);
663             }
664             return result;
665         }
666
667         InversePatternMatcher inverseMatcher = new InversePatternMatcher();
668
669         /**
670          * Format is: propname ('=' | '!=') propvalue ( '|' propValue )*
671          */
672         public final UnicodeSet getSet(String propAndValue,
673                 PatternMatcher matcher, UnicodeSet result) {
674             int equalPos = propAndValue.indexOf('=');
675             String prop = propAndValue.substring(0, equalPos);
676             String value = propAndValue.substring(equalPos + 1);
677             boolean negative = false;
678             if (prop.endsWith("!")) {
679                 prop = prop.substring(0, prop.length() - 1);
680                 negative = true;
681             }
682             prop = prop.trim();
683             UnicodeProperty up = getProperty(prop);
684             if (matcher == null) {
685                 matcher = new SimpleMatcher(value, up
686                         .isType(STRING_OR_MISC_MASK) ? null
687                         : PROPERTY_COMPARATOR);
688             }
689             if (negative) {
690                 inverseMatcher.set(matcher);
691                 matcher = inverseMatcher;
692             }
693             return up.getSet(matcher.set(value), result);
694         }
695
696         public final UnicodeSet getSet(String propAndValue,
697                 PatternMatcher matcher) {
698             return getSet(propAndValue, matcher, null);
699         }
700
701         public final UnicodeSet getSet(String propAndValue) {
702             return getSet(propAndValue, null, null);
703         }
704
705         public final SymbolTable getSymbolTable(String prefix) {
706             return new PropertySymbolTable(prefix);
707         }
708
709         private class MyXSymbolTable extends UnicodeSet.XSymbolTable {
710             public boolean applyPropertyAlias(String propertyName,
711                     String propertyValue, UnicodeSet result) {
712                 if (false)
713                     System.out.println(propertyName + "=" + propertyValue);
714                 UnicodeProperty prop = getProperty(propertyName);
715                 if (prop == null)
716                     return false;
717                 result.clear();
718                 UnicodeSet x = prop.getSet(propertyValue, result);
719                 return x.size() != 0;
720             }
721         }
722
723         public final UnicodeSet.XSymbolTable getXSymbolTable() {
724             return new MyXSymbolTable();
725         }
726
727         private class PropertySymbolTable implements SymbolTable {
728             static final boolean DEBUG = false;
729
730             private String prefix;
731
732             RegexMatcher regexMatcher = new RegexMatcher();
733
734             PropertySymbolTable(String prefix) {
735                 this.prefix = prefix;
736             }
737
738             public char[] lookup(String s) {
739                 if (DEBUG)
740                     System.out.println("\t(" + prefix + ")Looking up " + s);
741                 // ensure, again, that prefix matches
742                 int start = prefix.length();
743                 if (!s.regionMatches(true, 0, prefix, 0, start))
744                     return null;
745
746                 int pos = s.indexOf(':', start);
747                 if (pos < 0) { // should never happen
748                     throw new IllegalArgumentException(
749                             "Internal Error: missing =: " + s + "\r\n");
750                 }
751                 UnicodeProperty prop = getProperty(s.substring(start, pos));
752                 if (prop == null) {
753                     throw new IllegalArgumentException("Invalid Property in: "
754                             + s + "\r\nUse " + showSet(getAvailableNames()));
755                 }
756                 String value = s.substring(pos + 1);
757                 UnicodeSet set;
758                 if (value.startsWith("\u00AB")) { // regex!
759                     set = prop.getSet(regexMatcher.set(value.substring(1, value
760                             .length() - 1)));
761                 } else {
762                     set = prop.getSet(value);
763                 }
764                 if (set.size() == 0) {
765                     throw new IllegalArgumentException(
766                             "Empty Property-Value in: " + s + "\r\nUse "
767                                     + showSet(prop.getAvailableValues()));
768                 }
769                 if (DEBUG)
770                     System.out.println("\t(" + prefix + ")Returning "
771                             + set.toPattern(true));
772                 return set.toPattern(true).toCharArray(); // really ugly
773             }
774
775             private String showSet(List list) {
776                 StringBuffer result = new StringBuffer("[");
777                 boolean first = true;
778                 for (Iterator it = list.iterator(); it.hasNext();) {
779                     if (!first)
780                         result.append(", ");
781                     else
782                         first = false;
783                     result.append(it.next().toString());
784                 }
785                 result.append("]");
786                 return result.toString();
787             }
788
789             public UnicodeMatcher lookupMatcher(int ch) {
790                 return null;
791             }
792
793             public String parseReference(String text, ParsePosition pos,
794                     int limit) {
795                 if (DEBUG)
796                     System.out.println("\t(" + prefix + ")Parsing <"
797                             + text.substring(pos.getIndex(), limit) + ">");
798                 int start = pos.getIndex();
799                 // ensure that it starts with 'prefix'
800                 if (!text
801                         .regionMatches(true, start, prefix, 0, prefix.length()))
802                     return null;
803                 start += prefix.length();
804                 // now see if it is of the form identifier:identifier
805                 int i = getIdentifier(text, start, limit);
806                 if (i == start)
807                     return null;
808                 String prop = text.substring(start, i);
809                 String value = "true";
810                 if (i < limit) {
811                     if (text.charAt(i) == ':') {
812                         int j;
813                         if (text.charAt(i + 1) == '\u00AB') { // regular
814                                                                 // expression
815                             j = text.indexOf('\u00BB', i + 2) + 1; // include
816                                                                     // last
817                                                                     // character
818                             if (j <= 0)
819                                 return null;
820                         } else {
821                             j = getIdentifier(text, i + 1, limit);
822                         }
823                         value = text.substring(i + 1, j);
824                         i = j;
825                     }
826                 }
827                 pos.setIndex(i);
828                 if (DEBUG)
829                     System.out.println("\t(" + prefix + ")Parsed <" + prop
830                             + ">=<" + value + ">");
831                 return prefix + prop + ":" + value;
832             }
833
834             private int getIdentifier(String text, int start, int limit) {
835                 if (DEBUG)
836                     System.out.println("\tGetID <"
837                             + text.substring(start, limit) + ">");
838                 int cp = 0;
839                 int i;
840                 for (i = start; i < limit; i += UTF16.getCharCount(cp)) {
841                     cp = UTF16.charAt(text, i);
842                     if (!com.ibm.icu.lang.UCharacter
843                             .isUnicodeIdentifierPart(cp)
844                             && cp != '.') {
845                         break;
846                     }
847                 }
848                 if (DEBUG)
849                     System.out.println("\tGotID <" + text.substring(start, i)
850                             + ">");
851                 return i;
852             }
853         }
854     }
855
856     public static class FilteredProperty extends UnicodeProperty {
857         private UnicodeProperty property;
858
859         protected StringFilter filter;
860
861         protected UnicodeSetIterator matchIterator = new UnicodeSetIterator(
862                 new UnicodeSet(0, 0x10FFFF));
863
864         protected HashMap backmap;
865
866         boolean allowValueAliasCollisions = false;
867
868         public FilteredProperty(UnicodeProperty property, StringFilter filter) {
869             this.property = property;
870             this.filter = filter;
871         }
872
873         public StringFilter getFilter() {
874             return filter;
875         }
876
877         public UnicodeProperty setFilter(StringFilter filter) {
878             this.filter = filter;
879             return this;
880         }
881
882         List temp = new ArrayList(1);
883
884         public List _getAvailableValues(List result) {
885             temp.clear();
886             return filter.addUnique(property.getAvailableValues(temp), result);
887         }
888
889         public List _getNameAliases(List result) {
890             temp.clear();
891             return filter.addUnique(property.getNameAliases(temp), result);
892         }
893
894         public String _getValue(int codepoint) {
895             return filter.remap(property.getValue(codepoint));
896         }
897
898         public List _getValueAliases(String valueAlias, List result) {
899             if (backmap == null) {
900                 backmap = new HashMap(1);
901                 temp.clear();
902                 Iterator it = property.getAvailableValues(temp).iterator();
903                 while (it.hasNext()) {
904                     String item = (String) it.next();
905                     String mappedItem = filter.remap(item);
906                     if (backmap.get(mappedItem) != null
907                             && !allowValueAliasCollisions) {
908                         throw new IllegalArgumentException(
909                                 "Filter makes values collide! " + item + ", "
910                                         + mappedItem);
911                     }
912                     backmap.put(mappedItem, item);
913                 }
914             }
915             valueAlias = (String) backmap.get(valueAlias);
916             temp.clear();
917             return filter.addUnique(property.getValueAliases(valueAlias, temp),
918                     result);
919         }
920
921         public String _getVersion() {
922             return property.getVersion();
923         }
924
925         public boolean isAllowValueAliasCollisions() {
926             return allowValueAliasCollisions;
927         }
928
929         public FilteredProperty setAllowValueAliasCollisions(boolean b) {
930             allowValueAliasCollisions = b;
931             return this;
932         }
933
934     }
935
936     public static abstract class StringFilter implements Cloneable {
937         public abstract String remap(String original);
938
939         public final List addUnique(Collection source, List result) {
940             if (result == null)
941                 result = new ArrayList(1);
942             Iterator it = source.iterator();
943             while (it.hasNext()) {
944                 UnicodeProperty.addUnique(remap((String) it.next()), result);
945             }
946             return result;
947         }
948         /*
949          * public Object clone() { try { return super.clone(); } catch
950          * (CloneNotSupportedException e) { throw new
951          * IllegalStateException("Should never happen."); } }
952          */
953     }
954
955     public static class MapFilter extends StringFilter {
956         private Map valueMap;
957
958         public MapFilter(Map valueMap) {
959             this.valueMap = valueMap;
960         }
961
962         public String remap(String original) {
963             Object changed = valueMap.get(original);
964             return changed == null ? original : (String) changed;
965         }
966
967         public Map getMap() {
968             return valueMap;
969         }
970     }
971
972     public interface PatternMatcher extends ObjectMatcher {
973         public PatternMatcher set(String pattern);
974     }
975
976     public static class InversePatternMatcher extends InverseMatcher implements
977             PatternMatcher {
978         PatternMatcher other;
979
980         public PatternMatcher set(PatternMatcher toInverse) {
981             other = toInverse;
982             return this;
983         }
984
985         public boolean matches(Object value) {
986             return !other.matches(value);
987         }
988
989         public PatternMatcher set(String pattern) {
990             other.set(pattern);
991             return this;
992         }
993     }
994
995     public static class SimpleMatcher implements PatternMatcher {
996         Comparator comparator;
997
998         String pattern;
999
1000         public SimpleMatcher(String pattern, Comparator comparator) {
1001             this.comparator = comparator;
1002             this.pattern = pattern;
1003         }
1004
1005         public boolean matches(Object value) {
1006             if (comparator == null)
1007                 return pattern.equals(value);
1008             return comparator.compare(pattern, value) == 0;
1009         }
1010
1011         public PatternMatcher set(String pattern) {
1012             this.pattern = pattern;
1013             return this;
1014         }
1015     }
1016
1017     public static class RegexMatcher implements UnicodeProperty.PatternMatcher {
1018         private java.util.regex.Matcher matcher;
1019
1020         public UnicodeProperty.PatternMatcher set(String pattern) {
1021             matcher = Pattern.compile(pattern).matcher("");
1022             return this;
1023         }
1024
1025         public boolean matches(Object value) {
1026             matcher.reset(value.toString());
1027             return matcher.matches();
1028         }
1029     }
1030
1031     public static abstract class BaseProperty extends UnicodeProperty {
1032         protected List propertyAliases = new ArrayList(1);
1033
1034         protected Map toValueAliases;
1035
1036         protected String version;
1037
1038         public BaseProperty setMain(String alias, String shortAlias,
1039                 int propertyType, String version) {
1040             setName(alias);
1041             setType(propertyType);
1042             propertyAliases.add(shortAlias);
1043             propertyAliases.add(alias);
1044             this.version = version;
1045             return this;
1046         }
1047
1048         public String _getVersion() {
1049             return version;
1050         }
1051
1052         public List _getNameAliases(List result) {
1053             addAllUnique(propertyAliases, result);
1054             return result;
1055         }
1056
1057         public BaseProperty addValueAliases(String[][] valueAndAlternates,
1058                 boolean errorIfCant) {
1059             if (toValueAliases == null)
1060                 _fixValueAliases();
1061             for (int i = 0; i < valueAndAlternates.length; ++i) {
1062                 for (int j = 1; j < valueAndAlternates[0].length; ++j) {
1063                     addValueAlias(valueAndAlternates[i][0],
1064                             valueAndAlternates[i][j], errorIfCant);
1065                 }
1066             }
1067             return this;
1068         }
1069
1070         public void addValueAlias(String value, String valueAlias,
1071                 boolean errorIfCant) {
1072             List result = (List) toValueAliases.get(value);
1073             if (result == null && !errorIfCant)
1074                 return;
1075             addUnique(value, result);
1076             addUnique(valueAlias, result);
1077         }
1078
1079         protected List _getValueAliases(String valueAlias, List result) {
1080             if (toValueAliases == null)
1081                 _fixValueAliases();
1082             List a = (List) toValueAliases.get(valueAlias);
1083             if (a != null)
1084                 addAllUnique(a, result);
1085             return result;
1086         }
1087
1088         protected void _fixValueAliases() {
1089             if (toValueAliases == null)
1090                 toValueAliases = new HashMap(1);
1091             for (Iterator it = getAvailableValues().iterator(); it.hasNext();) {
1092                 Object value = it.next();
1093                 _ensureValueInAliases(value);
1094             }
1095         }
1096
1097         protected void _ensureValueInAliases(Object value) {
1098             List result = (List) toValueAliases.get(value);
1099             if (result == null)
1100                 toValueAliases.put(value, result = new ArrayList(1));
1101             addUnique(value, result);
1102         }
1103
1104         public BaseProperty swapFirst2ValueAliases() {
1105             for (Iterator it = toValueAliases.keySet().iterator(); it.hasNext();) {
1106                 List list = (List) toValueAliases.get(it.next());
1107                 if (list.size() < 2)
1108                     continue;
1109                 Object first = list.get(0);
1110                 list.set(0, list.get(1));
1111                 list.set(1, first);
1112             }
1113             return this;
1114         }
1115
1116         /**
1117          * @param string
1118          * @return
1119          */
1120         public UnicodeProperty addName(String string) {
1121             throw new UnsupportedOperationException();
1122         }
1123
1124     }
1125
1126     public static abstract class SimpleProperty extends BaseProperty {
1127         List values;
1128
1129         public UnicodeProperty addName(String alias) {
1130             propertyAliases.add(alias);
1131             return this;
1132         }
1133
1134         public SimpleProperty setValues(String valueAlias) {
1135             _addToValues(valueAlias, null);
1136             return this;
1137         }
1138
1139         public SimpleProperty setValues(String[] valueAliases,
1140                 String[] alternateValueAliases) {
1141             for (int i = 0; i < valueAliases.length; ++i) {
1142                 if (valueAliases[i].equals(UNUSED))
1143                     continue;
1144                 _addToValues(
1145                         valueAliases[i],
1146                         alternateValueAliases != null ? alternateValueAliases[i]
1147                                 : null);
1148             }
1149             return this;
1150         }
1151
1152         public SimpleProperty setValues(List valueAliases) {
1153             this.values = new ArrayList(valueAliases);
1154             for (Iterator it = this.values.iterator(); it.hasNext();) {
1155                 _addToValues((String) it.next(), null);
1156             }
1157             return this;
1158         }
1159
1160         public List _getAvailableValues(List result) {
1161             if (values == null)
1162                 _fillValues();
1163             result.addAll(values);
1164             return result;
1165         }
1166
1167         protected void _fillValues() {
1168             List newvalues = (List) getUnicodeMap_internal()
1169                     .getAvailableValues(new ArrayList());
1170             for (Iterator it = newvalues.iterator(); it.hasNext();) {
1171                 _addToValues((String) it.next(), null);
1172             }
1173         }
1174
1175         private void _addToValues(String item, String alias) {
1176             if (values == null)
1177                 values = new ArrayList(1);
1178             if (toValueAliases == null)
1179                 _fixValueAliases();
1180             addUnique(item, values);
1181             _ensureValueInAliases(item);
1182             addValueAlias(item, alias, true);
1183         }
1184         /*        public String _getVersion() {
1185          return version;
1186          }
1187          */
1188     }
1189
1190     public static class UnicodeMapProperty extends BaseProperty {
1191         /*
1192          * Example of usage:
1193          * new UnicodeProperty.UnicodeMapProperty() {
1194          {
1195          unicodeMap = new UnicodeMap();
1196          unicodeMap.setErrorOnReset(true);
1197          unicodeMap.put(0xD, "CR");
1198          unicodeMap.put(0xA, "LF");
1199          UnicodeProperty cat = getProperty("General_Category");
1200          UnicodeSet temp = cat.getSet("Line_Separator")
1201          .addAll(cat.getSet("Paragraph_Separator"))
1202          .addAll(cat.getSet("Control"))
1203          .addAll(cat.getSet("Format"))
1204          .remove(0xD).remove(0xA).remove(0x200C).remove(0x200D);
1205          unicodeMap.putAll(temp, "Control");
1206          UnicodeSet graphemeExtend = getProperty("Grapheme_Extend").getSet("true");
1207          unicodeMap.putAll(graphemeExtend,"Extend");
1208          UnicodeProperty hangul = getProperty("Hangul_Syllable_Type");
1209          unicodeMap.putAll(hangul.getSet("L"),"L");
1210          unicodeMap.putAll(hangul.getSet("V"),"V");
1211          unicodeMap.putAll(hangul.getSet("T"),"T");
1212          unicodeMap.putAll(hangul.getSet("LV"),"LV");
1213          unicodeMap.putAll(hangul.getSet("LVT"),"LVT");
1214          unicodeMap.setMissing("Other");
1215          }
1216          }.setMain("Grapheme_Cluster_Break", "GCB", UnicodeProperty.ENUMERATED, version)
1217          */
1218         protected UnicodeMap unicodeMap;
1219
1220         public UnicodeMapProperty set(UnicodeMap map) {
1221             unicodeMap = map;
1222             return this;
1223         }
1224
1225         protected String _getValue(int codepoint) {
1226             return (String) unicodeMap.getValue(codepoint);
1227         }
1228
1229         /* protected List _getValueAliases(String valueAlias, List result) {
1230          if (!unicodeMap.getAvailableValues().contains(valueAlias)) return result;
1231          result.add(valueAlias);
1232          return result; // no other aliases
1233          }
1234          */protected List _getAvailableValues(List result) {
1235             return (List) unicodeMap.getAvailableValues(result);
1236         }
1237     }
1238 }
1239 //#endif
1240