Home | History | Annotate | Download | only in tool
      1 /*
      2  **********************************************************************
      3  * Copyright (c) 2002-2011, International Business Machines
      4  * Corporation and others.  All Rights Reserved.
      5  **********************************************************************
      6  * Author: Mark Davis
      7  **********************************************************************
      8  */
      9 package org.unicode.cldr.tool;
     10 
     11 import java.io.File;
     12 import java.io.PrintWriter;
     13 import java.text.ParseException;
     14 import java.text.ParsePosition;
     15 import java.util.Arrays;
     16 import java.util.Collection;
     17 import java.util.Collections;
     18 import java.util.Comparator;
     19 import java.util.Date;
     20 import java.util.Iterator;
     21 import java.util.LinkedHashMap;
     22 import java.util.LinkedHashSet;
     23 import java.util.List;
     24 import java.util.Locale;
     25 import java.util.Map;
     26 import java.util.Set;
     27 import java.util.TreeMap;
     28 import java.util.TreeSet;
     29 import java.util.regex.Matcher;
     30 
     31 import org.unicode.cldr.draft.FileUtilities;
     32 import org.unicode.cldr.util.Builder;
     33 import org.unicode.cldr.util.CLDRFile;
     34 import org.unicode.cldr.util.CLDRFile.DraftStatus;
     35 import org.unicode.cldr.util.CLDRPaths;
     36 import org.unicode.cldr.util.CldrUtility;
     37 import org.unicode.cldr.util.Factory;
     38 import org.unicode.cldr.util.ICUServiceBuilder;
     39 import org.unicode.cldr.util.LanguageTagParser;
     40 import org.unicode.cldr.util.Log;
     41 import org.unicode.cldr.util.PatternCache;
     42 import org.unicode.cldr.util.SortedBag;
     43 import org.unicode.cldr.util.TimezoneFormatter;
     44 import org.unicode.cldr.util.TransliteratorUtilities;
     45 import org.w3c.dom.NamedNodeMap;
     46 import org.w3c.dom.Node;
     47 
     48 import com.ibm.icu.dev.tool.UOption;
     49 import com.ibm.icu.dev.util.UnicodeMap;
     50 import com.ibm.icu.impl.Relation;
     51 import com.ibm.icu.lang.UCharacter;
     52 import com.ibm.icu.text.Collator;
     53 import com.ibm.icu.text.DateFormat;
     54 import com.ibm.icu.text.DecimalFormat;
     55 import com.ibm.icu.text.Normalizer;
     56 import com.ibm.icu.text.RuleBasedCollator;
     57 import com.ibm.icu.text.SimpleDateFormat;
     58 import com.ibm.icu.text.UTF16;
     59 import com.ibm.icu.text.UnicodeSet;
     60 import com.ibm.icu.text.UnicodeSetIterator;
     61 import com.ibm.icu.util.ULocale;
     62 
     63 //import org.unicode.cldr.tool.GenerateCldrDateTimeTests;
     64 
     65 /**
     66  * Generated tests for CLDR.
     67  *
     68  * @author medavis
     69  */
     70 
     71 public class GenerateCldrTests {
     72 
     73     protected static final boolean METAZONES_WORK = false;
     74 
     75     // static private PrintWriter log;
     76     PrintWriter out;
     77 
     78     private static final int HELP1 = 0, HELP2 = 1, SOURCEDIR = 2, DESTDIR = 3,
     79         LOGDIR = 4, MATCH = 5, NOT_RESOLVED = 6, LANGUAGES = 7,
     80         SHOW = 8;
     81 
     82     private static final UOption[] options = {
     83         UOption.HELP_H(),
     84         UOption.HELP_QUESTION_MARK(),
     85         UOption.SOURCEDIR().setDefault(CLDRPaths.COMMON_DIRECTORY),
     86         UOption.DESTDIR().setDefault(CLDRPaths.GEN_DIRECTORY + "/test/"),
     87         UOption.create("log", 'l', UOption.REQUIRES_ARG).setDefault(CLDRPaths.GEN_DIRECTORY),
     88         UOption.create("match", 'm', UOption.REQUIRES_ARG).setDefault(".*"),
     89         UOption.create("notresolved", 'n', UOption.NO_ARG),
     90         UOption.create("languages", 'g', UOption.NO_ARG),
     91         // "C:\\ICU4J\\icu4j\\src\\com\\ibm\\icu\\dev\\tool\\cldr\\"),
     92         UOption.create("show", 's', UOption.NO_ARG), };
     93 
     94     private static final String VERSION = CLDRFile.GEN_VERSION;
     95 
     96     GenerateCldrCollationTests cldrCollations;
     97 
     98     static String logDir = null, destDir = null;
     99 
    100     public static boolean hasLocalizedLanguageFor(String locale,
    101         String otherLocale) {
    102         String lang = new LanguageTagParser().set(otherLocale).getLanguage();
    103         String localizedVersion = english.getName(locale);
    104         return !lang.equals(localizedVersion);
    105     }
    106 
    107     public static boolean hasLocalizedCountryFor(String locale,
    108         String otherLocale) {
    109         String country = new LanguageTagParser().set(otherLocale).getRegion();
    110         if (country.equals(""))
    111             return true;
    112         String localizedVersion = english.getName(CLDRFile.TERRITORY_NAME, locale);
    113         return !country.equals(localizedVersion);
    114     }
    115 
    116     public static void main(String[] args) throws Exception {
    117         double deltaTime = System.currentTimeMillis();
    118         UOption.parseArgs(args, options);
    119         Log.setLog(options[LOGDIR].value, "log.txt");
    120         // log = FileUtilities.openUTF8Writer(options[LOGDIR].value, "log.txt");
    121         try {
    122             if (options[LANGUAGES].doesOccur) {
    123                 GenerateStatistics.generateSize(
    124                     options[GenerateCldrTests.SOURCEDIR].value + "main/",
    125                     options[GenerateCldrTests.LOGDIR].value,
    126                     options[MATCH].value, true);
    127                 return;
    128             }
    129             // compareAvailable();
    130 
    131             // if (true) return;
    132             // System.out.println(createCaseClosure(new
    133             // UnicodeSet("[a{bc}{def}{oss}]")));
    134             // System.out.println(createCaseClosure(new
    135             // UnicodeSet("[a-z\u00c3\u0178{aa}]")));
    136             GenerateCldrTests t = new GenerateCldrTests();
    137             // t.generate(new ULocale("hu"), null);
    138             t.generate(options[MATCH].value);
    139         } finally {
    140             Log.close();
    141             deltaTime = System.currentTimeMillis() - deltaTime;
    142             System.out.println("Elapsed: " + deltaTime / 1000.0 + " seconds");
    143             System.out.println("Done");
    144         }
    145     }
    146 
    147     /*
    148      * private static void compareAvailable() { String[] cols =
    149      * Collator.getAvailableULocales(); Locale[] alocs =
    150      * NumberFormat.getAvailableLocales(); Set sCols = filter(cols); Set sLocs =
    151      * filter(alocs); Set oldSLocs = new TreeSet(sCols); sLocs.removeAll(sCols);
    152      * log.println("main - collation"); showLocales(sLocs);
    153      * sCols.removeAll(oldSLocs); log.println();
    154      * log.println("collation - main"); showLocales(sCols); }
    155      */
    156 
    157     /**
    158      *
    159      */
    160     /*
    161      * private static void checkLocaleNames() {Stringe[] locales =
    162      * String.getAvailableLocales(); for (int i = 0; i < locales.length; ++i) {
    163      * if (!hasLocalizedCountryFor(String.ENGLISH, locales[i]) ||
    164      * !hasLocalizedLanguageFor(String.ENGLISH, locales[i]) ||
    165      * !hasLocalizedCountryFor(locales[i], locales[i]) ||
    166      * !hasLocalizedLanguageFor(locales[i], locales[i])) {
    167      * Log.getLog().print("FAILURE\t"); } else {
    168      * Log.getLog().print("       \t"); } Log.logln(locales[i] + "\t" +
    169      * locales[i].getDisplayName(String.ENGLISH) + "\t" +
    170      * locales[i].getDisplayName(locales[i])); } }
    171      */
    172     /**
    173      * @param sLocs
    174      */
    175     private static void showLocales(Set<String> sLocs) {
    176         for (Iterator<String> it = sLocs.iterator(); it.hasNext();) {
    177             String s = it.next();
    178             Log.logln(s + "\t" + ULocale.getDisplayLanguage(s, "en"));
    179         }
    180     }
    181 
    182     /**
    183      * @param cols
    184      * @return
    185      */
    186     private static Set<String> filter(Object[] cols) {
    187         Set<String> result = new TreeSet<String>();
    188         for (int i = 0; i < cols.length; ++i) {
    189             String s = cols[i].toString();
    190             if (s.indexOf('_') >= 0)
    191                 continue;
    192             result.add(s);
    193         }
    194         return result;
    195     }
    196 
    197     Set<String> addULocales(Object[] objects, Set<String> target) {
    198         for (int i = 0; i < objects.length; ++i) {
    199             target.add(objects[i].toString());
    200         }
    201         return target;
    202     }
    203 
    204     LanguageTagParser ltp = new LanguageTagParser();
    205 
    206     private void addLocale(String locale) {
    207         String lang;
    208         try {
    209             lang = ltp.set(locale).getLanguageScript();
    210             // lang = locale.getLanguage();
    211             if (lang.length() == 0 || lang.equals("root"))
    212                 return; // skip root
    213         } catch (RuntimeException e) {
    214             return; // illegal locale name, must be supplemental
    215         }
    216         // ULocale parent = new ULocale(lang);
    217         // System.out.println(item + ", " + parent);
    218         parentToLocales.put(lang, locale);
    219         /*
    220          * RuleBasedCollator col = cldrCollations.getInstance(item); if (col ==
    221          * null) { System.out.println("No collator for: " + item); } String
    222          * rules = col.getRules(); //
    223          * ((RuleBasedCollator)Collator.getInstance(item)).getRules();
    224          * rulesToLocales.add(rules, item); localesToRules.put(item, rules);
    225          */
    226     }
    227 
    228     Set<String> collationLocales = new TreeSet<String>(); // =ULocaleComparator
    229     // addULocales(Collator.getAvailableULocales(),
    230     // new
    231     // TreeSet(ULocaleComparator));
    232 
    233     // Set numberLocales = addULocales(NumberFormat.getAvailableLocales(), new
    234     // TreeSet(ULocaleComparator));
    235     // Set dateLocales = addULocales(DateFormat.getAvailableLocales(), new
    236     // TreeSet(ULocaleComparator));
    237     Set<String> allLocales = new TreeSet<String>(); // ULocaleComparator
    238 
    239     // Map localesToRules = new HashMap();
    240 
    241     // Relation rulesToLocales = new Relation(new TreeMap(ULocaleComparator),TreeSet.class);
    242 
    243     Relation<String, String> parentToLocales = Relation.of(new TreeMap<String, Set<String>>(), TreeSet.class);
    244 
    245     /*
    246      * void getLocaleList() { collationLocales = new TreeSet(ULocaleComparator);
    247      * collationLocales.addAll(cldrCollations.getAvailableSet());
    248      *
    249      * collationLocales = addULocales(new String[] { // HACK "ga", "nl", "pt",
    250      * "de@collation=phonebook", "es@collation=traditional",
    251      * "hi@collation=direct", "zh@collation=pinyin", "zh@collation=stroke",
    252      * "zh@collation=traditional", }, collationLocales);
    253      *
    254      * allLocales.addAll(collationLocales); allLocales.addAll(numberLocales);
    255      * allLocales.addAll(dateLocales); // HACK // get all collations with same
    256      * rules
    257      *
    258      * for (Iterator it = allLocales.iterator(); it.hasNext();) {
    259      * addLocale((ULocale) it.next()); }
    260      *
    261      * String[] others = new String[] { "de@collation=phonebook",
    262      * "es@collation=traditional", "hi@collation=direct", "zh@collation=pinyin",
    263      * "zh@collation=stroke", "zh@collation=traditional", }; for (int i = 0; i <
    264      * others.length; ++i) { addLocale(new ULocale(others[i])); }
    265      *
    266      * }
    267      */
    268 
    269     // GenerateCldrDateTimeTests cldrOthers;
    270     Factory mainCldrFactory;
    271 
    272     ICUServiceBuilder icuServiceBuilder;
    273 
    274     private static CLDRFile english;
    275 
    276     // static Transform<String,ULocale> TO_LOCALE = new Transform<String,ULocale>(){
    277     // public ULocale transform(String source) {
    278     // return new ULocale(source);
    279     // }
    280     // };
    281 
    282     void generate(String pat) throws Exception {
    283         mainCldrFactory = Factory.make(options[SOURCEDIR].value + "main"
    284             + File.separator, pat);
    285         english = mainCldrFactory.make("en", true);
    286         Factory collationCldrFactory = Factory.make(options[SOURCEDIR].value
    287             + "collation" + File.separator, pat);
    288         //Factory supplementalCldrFactory = Factory.make(options[SOURCEDIR].value
    289         //    + "supplemental" + File.separator, ".*");
    290 
    291         // allLocales = Builder.with(allLocales)
    292         // .addAll(mainCldrFactory.getAvailable(), TO_LOCALE)
    293         // .addAll(collationCldrFactory.getAvailable(), TO_LOCALE)
    294         // .freeze();
    295         allLocales.addAll(mainCldrFactory.getAvailable());
    296         if (!allLocales.containsAll(collationCldrFactory.getAvailable())) {
    297             System.err.println("Collation locale that is not in main!\t"
    298                 + Builder.with(new TreeSet<String>(collationCldrFactory.getAvailable())).removeAll(allLocales).get());
    299         }
    300         allLocales.addAll(collationCldrFactory.getAvailable());
    301         allLocales = Collections.unmodifiableSet(allLocales);
    302 
    303         cldrCollations = new GenerateCldrCollationTests(options[SOURCEDIR].value
    304             + "collation" + File.separator, pat, allLocales);
    305         if (options[SHOW].doesOccur)
    306             cldrCollations.show();
    307 
    308         for (Iterator<String> it = cldrCollations.getAvailableSet().iterator(); it
    309             .hasNext();) {
    310             collationLocales.add(it.next());
    311         }
    312         if (!allLocales.containsAll(collationLocales)) {
    313             System.err.println("Collation locale that is not in main!\t"
    314                 + Builder.with(new TreeSet<String>(collationLocales)).removeAll(allLocales).get());
    315         }
    316         collationLocales = allLocales;
    317 
    318         // TODO HACK
    319         // collationLocales.remove("ar_IN");
    320         icuServiceBuilder = new ICUServiceBuilder();
    321         /*
    322          * cldrOthers = new GenerateCldrDateTimeTests(options[SOURCEDIR].value +
    323          * "main" + File.separator, pat,
    324          * !options[GenerateCldrTests.NOT_RESOLVED].doesOccur); if
    325          * (options[SHOW].doesOccur) cldrOthers.show();
    326          */
    327         // getLocaleList();
    328         for (Iterator<String> it = collationLocales.iterator(); it.hasNext();) {
    329             addLocale(it.next());
    330         }
    331 
    332         Matcher m = PatternCache.get(pat).matcher("");
    333         for (Iterator<String> it = parentToLocales.keySet().iterator(); it.hasNext();) {
    334             String p = it.next();
    335             if (!m.reset(p).matches())
    336                 continue;
    337             generate2(p);
    338         }
    339     }
    340 
    341     private void generate2(String locale) throws Exception {
    342         System.out.println("Main Generation:\t" + locale);
    343         out = FileUtilities.openUTF8Writer(options[DESTDIR].value, locale + ".xml");
    344         out.println("<?xml version='1.0' encoding='UTF-8' ?>");
    345         out.println(
    346             // "<!DOCTYPE cldrTest SYSTEM 'http://www.unicode.org/cldr/dtd/1.5/cldrTest.dtd'>"
    347             // +
    348             "<!DOCTYPE cldrTest SYSTEM '../common/dtd/cldrTest.dtd'>");
    349         out.println("<!-- For information, see readme.html -->");
    350         out.println(" <cldrTest version='" + VERSION +
    351             "' base='" + locale + "'>");
    352         CLDRFile localeFile = mainCldrFactory.make(locale, true);
    353         out.println(" <!-- "
    354             + TransliteratorUtilities.toXML.transliterate(english.getName(locale)
    355                 + " [" + localeFile.getName(locale))
    356             + "] -->");
    357         generateItems(locale, allLocales, NumberShower);
    358         generateItems(locale, allLocales, DateShower);
    359         generateItems(locale, allLocales, ZoneFieldShower);
    360         generateItems(locale, collationLocales, CollationShower);
    361         out.println(" </cldrTest>");
    362         out.close();
    363         ToolUtilities.generateBat(options[SOURCEDIR].value + "test" + File.separator,
    364             locale + ".xml", options[DESTDIR].value, locale + ".xml",
    365             new CldrUtility.SimpleLineComparator(0));
    366     }
    367 
    368     /*
    369      *
    370      * // first pass through and get all the functional equivalents Map
    371      * uniqueLocales = new TreeMap();
    372      *
    373      * String[] keywords = Collator.getKeywords(); boolean [] isAvailable = new
    374      * boolean[1]; for (int i = 0; i < locales.length; ++i) { add(locales[i],
    375      * uniqueLocales); if (true) continue; // TODO restore once Vladimir fixes
    376      * for (int j = 0; j < keywords.length; ++j) { String[] values =
    377      * Collator.getKeywordValues(keywords[j]); for (int k = 0; k <
    378      * values.length; ++k) { // TODO -- for a full job, would do all
    379      * combinations of different keywords! if (values[k].equals("standard"))
    380      * continue; add(new ULocale(locales[i] + "@" + keywords[j] + "=" +
    381      * values[k]), uniqueLocales); //ULocale other =
    382      * Collator.getFunctionalEquivalent(keywords[j], locales[i], isAvailable); }
    383      * } } for (int i = 0; i < extras.length; ++i) { add(new ULocale(extras[i]),
    384      * uniqueLocales); } // items are now sorted by rules. So resort by locale
    385      * Map toDo = new TreeMap(ULocaleComparator); for (Iterator it =
    386      * uniqueLocales.keySet().iterator(); it.hasNext();) { Object rules =
    387      * it.next(); Set s = (Set) uniqueLocales.get(rules); ULocale ulocale =
    388      * (ULocale) s.iterator().next(); // get first one toDo.put(ulocale, s); }
    389      * for (Iterator it = toDo.keySet().iterator(); it.hasNext();) { ULocale
    390      * ulocale = (ULocale) it.next(); Set s = (Set) toDo.get(ulocale);
    391      * generate(ulocale); }
    392      */
    393 
    394     /**
    395      * add locale into list. Replace old if shorter
    396      *
    397      * @param locale
    398      */
    399     void add(String locale, Map<String, Set<String>> uniqueLocales) {
    400         try {
    401             RuleBasedCollator col = cldrCollations.getInstance(locale); // (RuleBasedCollator)
    402             // Collator.getInstance(locale);
    403             // for our purposes, separate locales if we are using different
    404             // exemplars
    405             String key = col.getRules() + "\uFFFF" + getExemplarSet(locale, 0, DraftStatus.unconfirmed);
    406             Set<String> s = uniqueLocales.get(key);
    407             if (s == null) {
    408                 s = new TreeSet<String>(ULocaleComparator);
    409                 uniqueLocales.put(key, s);
    410             }
    411             System.out.println("Adding " + locale);
    412             s.add(locale);
    413         } catch (Throwable e) { // skip
    414             System.out.println("skipped " + locale);
    415         }
    416     }
    417 
    418     /**
    419      * Work-around
    420      */
    421     public UnicodeSet getExemplarSet(String locale, int options,
    422         DraftStatus minimalDraftStatus) {
    423         String n = locale.toString();
    424         int pos = n.indexOf('@');
    425         if (pos >= 0)
    426             locale = n.substring(0, pos);
    427         CLDRFile cldrFile = mainCldrFactory.make(locale.toString(), true,
    428             minimalDraftStatus);
    429         String v = cldrFile.getStringValue("//ldml/characters/exemplarCharacters");
    430         UnicodeSet result = new UnicodeSet(v);
    431         v = cldrFile
    432             .getStringValue("//ldml/characters/exemplarCharacters[@type=\"auxiliary\"]");
    433         if (v != null) {
    434             result.addAll(new UnicodeSet(v));
    435         }
    436         if (options == 0)
    437             result.closeOver(UnicodeSet.CASE);
    438         return result;
    439     }
    440 
    441     public static final Comparator<Object> ULocaleComparator = new Comparator<Object>() {
    442         public int compare(Object o1, Object o2) {
    443             return o1.toString().compareTo(o2.toString());
    444         }
    445     };
    446 
    447     /*
    448      * public interface Equator { public boolean equals(Object o1, Object o2); }
    449      */
    450     @SuppressWarnings("rawtypes")
    451     static boolean intersects(Collection a, Collection b) {
    452         for (Iterator it = a.iterator(); it.hasNext();) {
    453             if (b.contains(it.next()))
    454                 return true;
    455         }
    456         return false;
    457     }
    458 
    459     /*
    460      * static Collection extract(Object x, Collection a, Equator e, Collection
    461      * output) { List itemsToRemove = new ArrayList(); for (Iterator it =
    462      * a.iterator(); it.hasNext();) { Object item = it.next(); if (e.equals(x,
    463      * item)) { itemsToRemove.add(item); // have to do this because iterator may
    464      * not allow output.add(item); } } a.removeAll(itemsToRemove); return
    465      * output; }
    466      */
    467     class ResultsPrinter {
    468         private Set<Map> listOfSettings = new LinkedHashSet<Map>();
    469 
    470         private transient LinkedHashMap<String, String> settings = new LinkedHashMap<String, String>();
    471 
    472         ResultsPrinter() {
    473         }
    474 
    475         ResultsPrinter(ResultsPrinter rpIncludeDraft, ResultsPrinter rpNoDraft) {
    476             Set<Map> listOfSettings1 = rpIncludeDraft.getListOfSettings();
    477             Set<Map> listOfSettings2 = rpNoDraft.getListOfSettings();
    478             if (listOfSettings1.size() != listOfSettings2.size()) {
    479                 throw new InternalError("can't combine");
    480             }
    481             Iterator<Map> it1 = listOfSettings1.iterator();
    482             Iterator<Map> it2 = listOfSettings2.iterator();
    483             while (it1.hasNext()) {
    484                 Map settings1 = it1.next();
    485                 Map settings2 = it2.next();
    486                 if (settings1.equals(settings2)) {
    487                     settings1.put("draft", "unconfirmed approved");
    488                     addToListOfSettings(settings1);
    489                 } else {
    490                     // they should only differ by result!
    491                     settings1.put("draft", "unconfirmed");
    492                     addToListOfSettings(settings1);
    493                     settings2.put("draft", "approved");
    494                     addToListOfSettings(settings2);
    495                 }
    496             }
    497         }
    498 
    499         private void addToListOfSettings(Map settings1) {
    500             for (Object key : settings1.keySet()) {
    501                 if (key == null || settings1.get(key) == null) {
    502                     throw new IllegalArgumentException("null key or value in settings.");
    503                 }
    504             }
    505             listOfSettings.add(settings1);
    506         }
    507 
    508         void set(String name, String value) {
    509             if (name == null || value == null) {
    510                 throw new IllegalArgumentException("null key or value in settings.");
    511             }
    512             settings.put(name, value);
    513         }
    514 
    515         void setResult(String result) {
    516             if (result == null) {
    517                 throw new IllegalArgumentException("null key or value in settings.");
    518             }
    519             settings.put("result", result);
    520             addToListOfSettings((Map) settings.clone());
    521         }
    522 
    523         void print() {
    524             Map oldSettings = new TreeMap();
    525             for (Iterator it2 = getListOfSettings().iterator(); it2.hasNext();) {
    526                 Map settings = (Map) it2.next();
    527                 String result = (String) settings.get("result");
    528                 out.print("   <result");
    529                 for (Iterator it = settings.keySet().iterator(); it.hasNext();) {
    530                     Object key = it.next();
    531                     if (key.equals("result"))
    532                         continue;
    533                     Object value = settings.get(key);
    534                     if (!value.equals(oldSettings.get(key))) {
    535                         out.print(" " + key + "='"
    536                             + TransliteratorUtilities.toXML.transliterate(value.toString())
    537                             + "'");
    538                     }
    539                 }
    540                 out.println(">" + TransliteratorUtilities.toXML.transliterate(result)
    541                     + "</result>");
    542                 oldSettings = settings;
    543             }
    544         }
    545 
    546         public boolean equals(Object other) {
    547             try {
    548                 ResultsPrinter that = (ResultsPrinter) other;
    549                 return getListOfSettings().equals(that.getListOfSettings());
    550             } catch (Exception e) {
    551                 return false;
    552             }
    553         }
    554 
    555         public int hashCode() {
    556             throw new IllegalArgumentException();
    557         }
    558 
    559         /**
    560          *
    561          */
    562 
    563         private void setListOfSettings(Set listOfSettings) {
    564             this.listOfSettings = listOfSettings;
    565         }
    566 
    567         private Set<Map> getListOfSettings() {
    568             return Collections.unmodifiableSet(listOfSettings);
    569         }
    570     }
    571 
    572     abstract class DataShower {
    573         abstract ResultsPrinter show(String first_locale, DraftStatus minimalDraftStatus);
    574 
    575         ResultsPrinter show(String first) throws Exception {
    576             ResultsPrinter rpIncludeDraft = show(first, DraftStatus.unconfirmed);
    577             ResultsPrinter rpNoDraft = show(first, DraftStatus.approved);
    578             return new ResultsPrinter(rpIncludeDraft, rpNoDraft);
    579         }
    580 
    581         abstract String getElement();
    582     }
    583 
    584     interface DataShower2 {
    585         void show(ULocale first, Collection others) throws Exception;
    586     }
    587 
    588     private void generateItems(String locale, Collection<String> onlyLocales,
    589         DataShower generator) throws Exception {
    590         Set<String> sublocales = new TreeSet<String>(); // ULocaleComparator
    591         sublocales.add(locale);
    592         sublocales.addAll(parentToLocales.getAll(locale));
    593         sublocales.retainAll(onlyLocales);
    594         Map<String, ResultsPrinter> locale_results = new TreeMap<String, ResultsPrinter>(ULocaleComparator);
    595         for (Iterator<String> it = sublocales.iterator(); it.hasNext();) {
    596             String current = it.next();
    597             locale_results.put(current, generator.show(current));
    598         }
    599         // do it this way so that the locales stay in order
    600         Set<String> matchingLocales = new TreeSet<String>(ULocaleComparator);
    601         while (sublocales.size() != 0) {
    602             String first = sublocales.iterator().next();
    603             ResultsPrinter r = locale_results.get(first);
    604             for (Iterator<String> it = sublocales.iterator(); it.hasNext();) {
    605                 String other = it.next();
    606                 ResultsPrinter r2 = locale_results.get(other);
    607                 if (r2.equals(r))
    608                     matchingLocales.add(other);
    609             }
    610             showLocales(generator.getElement(), matchingLocales);
    611             r.print();
    612             out.println("  </" + generator.getElement() + ">");
    613             sublocales.removeAll(matchingLocales);
    614             matchingLocales.clear();
    615         }
    616         //Comparator c;
    617     }
    618 
    619     public void showLocales(String elementName, Collection<String> others) {
    620         // System.out.println(elementName + ": " + locale);
    621         out.println("  <" + elementName + " ");
    622         StringBuffer comment = new StringBuffer();
    623         if (others != null && others.size() != 0) {
    624             out.print("locales='");
    625             boolean first = true;
    626             for (Iterator<String> it = others.iterator(); it.hasNext();) {
    627                 if (first)
    628                     first = false;
    629                 else {
    630                     out.print(" ");
    631                     comment.append("; ");
    632                 }
    633                 String loc = it.next();
    634                 out.print(loc);
    635                 comment.append(english.getName(loc) + " ["
    636                     + getNativeName(loc) + "]");
    637             }
    638             out.print("'");
    639         }
    640         out.println(">");
    641         out.println("<!-- "
    642             + TransliteratorUtilities.toXML.transliterate(comment.toString())
    643             + " -->");
    644     }
    645 
    646     private String getNativeName(String loc) {
    647         int atPos = loc.indexOf('@');
    648         String keywords = "";
    649         if (atPos >= 0) {
    650             keywords = loc.substring(atPos + 1);
    651             loc = loc.substring(0, atPos);
    652         }
    653         return mainCldrFactory.make(loc, true).getName(loc) + "@" + keywords;
    654     }
    655 
    656     DataShower ZoneFieldShower = new DataShower() {
    657 
    658         // Set zones = new
    659         // TreeSet(sc.getAvailableCodes("tzid"));
    660         List<String> zones = Arrays.asList(new String[] { "America/Los_Angeles",
    661             "America/Argentina/Buenos_Aires", "America/Buenos_Aires",
    662             "America/Havana", "Australia/ACT", "Australia/Sydney", "Europe/London",
    663             "Europe/Moscow", "Etc/GMT+3" });
    664 
    665         String[] perZoneSamples = { "Z", "ZZZZ", "z", "zzzz", "v", "vvvv", "V", "VVVV" };
    666 
    667         String[] dates = { "2004-01-15T12:00:00Z", "2004-07-15T12:00:00Z" };
    668 
    669         public ResultsPrinter show(String first, DraftStatus minimalDraftStatus) {
    670             TimezoneFormatter tzf = new TimezoneFormatter(mainCldrFactory, first
    671                 .toString(), minimalDraftStatus);
    672             ResultsPrinter rp = new ResultsPrinter();
    673             if (!METAZONES_WORK) {
    674                 return rp;
    675             }
    676             // TODO Auto-generated
    677             // method stub
    678             ParsePosition parsePosition = new ParsePosition(0);
    679             for (Iterator<String> it = zones.iterator(); it.hasNext();) {
    680                 String tzid = it.next();
    681                 rp.set("zone", tzid);
    682                 for (int j = 0; j < dates.length; ++j) {
    683                     String date = dates[j];
    684                     Date datetime;
    685                     try {
    686                         datetime = ICUServiceBuilder.isoDateParse(date);
    687                     } catch (ParseException e1) {
    688                         throw new IllegalArgumentException(e1);
    689                     }
    690                     rp.set("date", dates[j]);
    691                     for (int i = 0; i < perZoneSamples.length; ++i) {
    692                         try {
    693                             String pattern = perZoneSamples[i];
    694                             if (!METAZONES_WORK && (pattern.contains("z") || pattern.contains("V"))) {
    695                                 continue;
    696                             }
    697                             rp.set("field", pattern);
    698                             String formatted = tzf.getFormattedZone(tzid, pattern, datetime.getTime(), false);
    699                             parsePosition.setIndex(0);
    700                             String parsed = tzf.parse(formatted, parsePosition);
    701                             if (parsed == null) {
    702                                 // for
    703                                 // debugging
    704                                 formatted = tzf.getFormattedZone(tzid, pattern, datetime.getTime(), false);
    705                                 parsePosition.setIndex(0);
    706                                 parsed = tzf.parse(formatted, parsePosition);
    707                             }
    708                             rp.set("parse", parsed);
    709                             rp.setResult(formatted);
    710                         } catch (RuntimeException e) {
    711                             throw (IllegalArgumentException) new IllegalArgumentException(
    712                                 "Failure in " + first).initCause(e);
    713                         }
    714                     }
    715                 }
    716             }
    717             return rp;
    718             /*
    719              * Date datetime = ICUServiceBuilder .isoDateParse (samples[j]);
    720              * rp.set("input", ICUServiceBuilder .isoDateFormat (datetime));
    721              */
    722         }
    723 
    724         public String getElement() {
    725             return "zoneFields";
    726         }
    727     };
    728 
    729     DataShower DateShower = new DataShower() {
    730         public ResultsPrinter show(String locale, DraftStatus minimalDraftStatus) {
    731             String[] samples = { "1900-01-31T00:00:00Z", "1909-02-28T00:00:01Z",
    732                 "1918-03-26T00:59:59Z", "1932-04-24T01:00:00Z",
    733                 "1945-05-20T01:00:01Z", "1952-06-18T11:59:59Z",
    734                 "1973-07-16T12:00:00Z", "1999-08-14T12:00:01Z",
    735                 "2000-09-12T22:59:59Z", "2001-10-08T23:00:00Z",
    736                 "2004-11-04T23:00:01Z", "2010-12-01T23:59:59Z", };
    737             CLDRFile cldrFile = mainCldrFactory.make(locale.toString(), true,
    738                 minimalDraftStatus);
    739             icuServiceBuilder.setCldrFile(cldrFile);
    740             ResultsPrinter rp = new ResultsPrinter();
    741             for (int j = 0; j < samples.length; ++j) {
    742                 Date datetime;
    743                 try {
    744                     datetime = ICUServiceBuilder.isoDateParse(samples[j]);
    745                 } catch (ParseException e) {
    746                     throw new IllegalArgumentException(e);
    747                 }
    748                 rp.set("input", ICUServiceBuilder.isoDateFormat(datetime));
    749                 for (int i = 0; i < ICUServiceBuilder.LIMIT_DATE_FORMAT_INDEX; ++i) {
    750                     rp.set("dateType", ICUServiceBuilder.getDateNames(i));
    751                     for (int k = 0; k < ICUServiceBuilder.LIMIT_DATE_FORMAT_INDEX; ++k) {
    752                         if (i == 0 && k == 0)
    753                             continue;
    754                         DateFormat df = icuServiceBuilder.getDateFormat("gregorian", i, k);
    755                         String pattern = ((SimpleDateFormat) df).toPattern();
    756                         if (!METAZONES_WORK && (pattern.contains("z") || pattern.contains("V"))) {
    757                             continue;
    758                         }
    759                         rp.set("timeType", ICUServiceBuilder.getDateNames(k));
    760                         if (false && i == 2 && k == 0) {
    761                             System.out.println("debug: date "
    762                                 + icuServiceBuilder.getDateNames(i) + ", time "
    763                                 + icuServiceBuilder.getDateNames(k) + " = "
    764                                 + df.format(datetime));
    765                         }
    766                         rp.setResult(df.format(datetime));
    767                     }
    768                 }
    769             }
    770             return rp;
    771         }
    772 
    773         public String getElement() {
    774             return "date";
    775         }
    776     };
    777 
    778     DataShower NumberShower = new DataShower() {
    779         public ResultsPrinter show(String locale, DraftStatus minimalDraftStatus) {
    780             CLDRFile cldrFile = mainCldrFactory.make(locale.toString(), true,
    781                 minimalDraftStatus);
    782             icuServiceBuilder.setCldrFile(cldrFile);
    783 
    784             double[] samples = { 0, 0.01, -0.01, 1, -1, 123.456, -123.456, 123456.78,
    785                 -123456.78, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY,
    786                 Double.NaN };
    787             ResultsPrinter rp = new ResultsPrinter();
    788             for (int j = 0; j < samples.length; ++j) {
    789                 double sample = samples[j];
    790                 rp.set("input", String.valueOf(sample));
    791                 for (int i = 0; i < ICUServiceBuilder.LIMIT_NUMBER_INDEX; ++i) {
    792                     rp.set("numberType", icuServiceBuilder.getNumberNames(i));
    793                     DecimalFormat nf = icuServiceBuilder.getNumberFormat(i);
    794                     String formatted = nf.format(sample);
    795                     if (formatted.indexOf("NaNNaN") >= 0) {
    796                         formatted = nf.format(sample); // for
    797                         // debugging
    798                     }
    799                     rp.setResult(formatted);
    800                 }
    801             }
    802             return rp;
    803         }
    804 
    805         public String getElement() {
    806             return "number";
    807         }
    808     };
    809 
    810     // ========== COLLATION ==========
    811 
    812     /*
    813      * Equator CollationEquator = new Equator() {
    814      *//**
    815        * Must both be ULocales
    816        */
    817     /*
    818      * public boolean equals(Object o1, Object o2) { try { ULocale loc1 =
    819      * (ULocale) o1; ULocale loc2 = (ULocale) o2; if (loc1.equals(loc2)) return
    820      * true; return
    821      * cldrCollations.getInstance(loc1).equals(cldrCollations.getInstance
    822      * (loc2)); } catch (RuntimeException e) { System.out.println("Failed on: "
    823      * + o1 + " ;\t" + o2); throw e; } } };
    824      */static ULocale zhHack = new ULocale("zh"); // FIXME
    825     // hack
    826     // for
    827     // zh
    828 
    829     DataShower CollationShower = new DataShower() {
    830         public ResultsPrinter show(String locale, DraftStatus minimalDraftStatus) {
    831             // if
    832             // (locale.equals(zhHack))
    833             // return;
    834 
    835             Collator col = cldrCollations.getInstance(locale); // Collator.getInstance(locale);
    836 
    837             UnicodeSet tailored = new UnicodeSet();
    838             if (col != null) {
    839                 tailored = col.getTailoredSet();
    840                 if (new LanguageTagParser().set(locale).getLanguage().equals("zh")) {
    841                     tailored.addAll(new UnicodeSet("[[a-z]-[v]]"));
    842                     Log.logln("HACK for Pinyin");
    843                 }
    844                 tailored = createCaseClosure(tailored);
    845                 tailored = nfc(tailored);
    846             } else {
    847                 System.out.println("No collation for: " + locale);
    848                 col = cldrCollations.getInstance("root");
    849             }
    850             // System.out.println(tailored.toPattern(true));
    851 
    852             UnicodeSet exemplars = getExemplarSet(locale, UnicodeSet.CASE,
    853                 minimalDraftStatus);
    854             // add all the exemplars
    855 
    856             exemplars = createCaseClosure(exemplars);
    857             exemplars = nfc(exemplars);
    858             // System.out.println(exemplars.toPattern(true));
    859             tailored.addAll(exemplars);
    860             // UnicodeSet
    861             // tailoredMinusHan = new
    862             // UnicodeSet(tailored).removeAll(SKIP_COLLATION_SET);
    863             if (!exemplars.containsAll(tailored)) {
    864                 // BagFormatter bf =
    865                 // new
    866                 // BagFormatter();
    867                 Log.logln("In Tailored, but not Exemplar; Locale: " + locale + "\t"
    868                     + english.getName(locale));
    869                 Log.logln(new UnicodeSet(tailored).removeAll(exemplars)
    870                     .toPattern(false));
    871                 // bf.(log,"tailored",
    872                 // tailored,
    873                 // "exemplars",
    874                 // exemplars);
    875                 Log.getLog().flush();
    876             }
    877             tailored.addAll(new UnicodeSet("[\\ .02{12}]"));
    878             tailored.removeAll(SKIP_COLLATION_SET);
    879 
    880             SortedBag bag = new SortedBag(col);
    881             return doCollationResult(col, tailored, bag);
    882         }
    883 
    884         public String getElement() {
    885             return "collation";
    886         }
    887     };
    888 
    889     /*
    890      * public void show(ULocale locale, Collection others) {
    891      * showLocales("collation", others);
    892      *
    893      * Collator col = cldrCollations.getInstance(locale); //
    894      * Collator.getInstance(locale);
    895      *
    896      * UnicodeSet tailored = col.getTailoredSet(); if
    897      * (locale.getLanguage().equals("zh")) { tailored.addAll(new
    898      * UnicodeSet("[[a-z]-[v]]")); log.println("HACK for Pinyin"); } tailored =
    899      * createCaseClosure(tailored); tailored = nfc(tailored);
    900      * //System.out.println(tailored.toPattern(true));
    901      *
    902      * UnicodeSet exemplars = getExemplarSet(locale, UnicodeSet.CASE); // add
    903      * all the exemplars if (false) for (Iterator it = others.iterator();
    904      * it.hasNext(); ) { exemplars.addAll(getExemplarSet((ULocale)it.next(),
    905      * UnicodeSet.CASE)); }
    906      *
    907      * exemplars = createCaseClosure(exemplars); exemplars = nfc(exemplars);
    908      * //System.out.println(exemplars.toPattern(true));
    909      * tailored.addAll(exemplars); //UnicodeSet tailoredMinusHan = new
    910      * UnicodeSet(tailored).removeAll(SKIP_COLLATION_SET); if
    911      * (!exemplars.containsAll(tailored)) { //BagFormatter bf = new
    912      * BagFormatter(); log.println("In Tailored, but not Exemplar; Locale: " +
    913      * locale + "\t" + locale.getDisplayName()); log.println(new
    914      * UnicodeSet(tailored).removeAll(exemplars).toPattern(false));
    915      * //bf.(log,"tailored", tailored, "exemplars", exemplars); log.flush(); }
    916      * tailored.addAll(new UnicodeSet("[\\ .02{12}]"));
    917      * tailored.removeAll(SKIP_COLLATION_SET);
    918      *
    919      * SortedBag bag = new SortedBag(col); doCollationResult(col, tailored,
    920      * bag); out.println("  </collation>"); }};
    921      */
    922     static final UnicodeSet SKIP_COLLATION_SET = new UnicodeSet(
    923         "[[:script=han:][:script=hangul:]-[\u4e00-\u4eff \u9f00-\u9fff \uac00-\uacff \ud700-\ud7ff]]");
    924 
    925     /**
    926      * @param col
    927      * @param tailored
    928      * @param bag
    929      */
    930     private ResultsPrinter doCollationResult(Collator col, UnicodeSet tailored,
    931         SortedBag bag) {
    932         for (UnicodeSetIterator usi = new UnicodeSetIterator(tailored); usi.next();) {
    933             String s = usi.getString();
    934             bag.add('x' + s);
    935             bag.add('X' + s);
    936             bag.add('x' + s + 'x');
    937         }
    938         // out.println("   <set locale='" + locale + "'/>");
    939         /*
    940          * if (others != null) for (Iterator it = others.iterator();
    941          * it.hasNext(); ) { ULocale uloc = (ULocale) it.next(); if
    942          * (uloc.equals(locale)) continue; out.println("   <other locale='" +
    943          * uloc + "'/>"); }
    944          */
    945         String last = "";
    946         boolean needEquals = false;
    947         StringBuffer tempResult = new StringBuffer(CldrUtility.LINE_SEPARATOR);
    948         for (Iterator<String> it = bag.iterator(); it.hasNext();) {
    949             String s = it.next();
    950             if (col.compare(s, last) != 0) {
    951                 if (needEquals)
    952                     tempResult.append(last).append(CldrUtility.LINE_SEPARATOR);
    953                 needEquals = false;
    954                 last = s;
    955             } else {
    956                 needEquals = true;
    957             }
    958             tempResult.append(TransliteratorUtilities.toXML.transliterate(s)).append(
    959                 CldrUtility.LINE_SEPARATOR);
    960         }
    961         ResultsPrinter result = new ResultsPrinter();
    962         result.setResult(tempResult.toString());
    963         return result;
    964     }
    965 
    966     static public Set<String> getMatchingXMLFiles(String dir, String localeRegex) {
    967         Matcher m = PatternCache.get(localeRegex).matcher("");
    968         Set<String> s = new TreeSet<String>();
    969         File[] files = new File(dir).listFiles();
    970         for (int i = 0; i < files.length; ++i) {
    971             String name = files[i].getName();
    972             if (!name.endsWith(".xml"))
    973                 continue;
    974             if (name.startsWith("supplementalData"))
    975                 continue;
    976             String locale = name.substring(0, name.length() - 4); // drop .xml
    977             if (!locale.equals("root") && !m.reset(locale).matches())
    978                 continue;
    979             s.add(locale);
    980         }
    981         return s;
    982     }
    983 
    984     /*
    985      * public static boolean isDraft(Node node) { for (; node.getNodeType() !=
    986      * Node.DOCUMENT_NODE; node = node.getParentNode()){ NamedNodeMap attributes
    987      * = node.getAttributes(); if (attributes == null) continue; for (int i = 0;
    988      * i < attributes.getLength(); ++i) { Node attribute = attributes.item(i);
    989      * if (attribute.getNodeName().equals("draft") &&
    990      * attribute.getNodeValue().equals("true")) return true; } } return false; }
    991      */
    992     public static String getXPath(Node node) {
    993         StringBuffer xpathFragment = new StringBuffer();
    994         StringBuffer xpath = new StringBuffer();
    995         for (; node.getNodeType() != Node.DOCUMENT_NODE; node = node
    996             .getParentNode()) {
    997             xpathFragment.setLength(0);
    998             xpathFragment.append('/').append(node.getNodeName());
    999             NamedNodeMap attributes = node.getAttributes();
   1000             if (attributes != null) {
   1001                 for (int i = 0; i < attributes.getLength(); ++i) {
   1002                     Node attribute = attributes.item(i);
   1003                     xpathFragment.append("[@").append(attribute.getNodeName())
   1004                         .append('=').append(attribute.getNodeValue()).append(']');
   1005                 }
   1006             }
   1007             xpath.insert(0, xpathFragment);
   1008         }
   1009         xpath.insert(0, '/');
   1010         return xpath.toString();
   1011     }
   1012 
   1013     public static String replace(String source, String pattern, String replacement) {
   1014         // dumb code for now
   1015         for (int pos = source.indexOf(pattern, 0); pos >= 0; pos = source.indexOf(
   1016             pattern, pos + 1)) {
   1017             source = source.substring(0, pos) + replacement
   1018                 + source.substring(pos + pattern.length());
   1019         }
   1020         return source;
   1021     }
   1022 
   1023     public static interface Apply {
   1024         String apply(String source);
   1025     }
   1026 
   1027     static UnicodeSet apply(UnicodeSet source, Apply apply) {
   1028         UnicodeSet target = new UnicodeSet();
   1029         for (UnicodeSetIterator usi = new UnicodeSetIterator(source); usi.next();) {
   1030             String s = usi.getString();
   1031             target.add(apply.apply(s));
   1032         }
   1033         return target;
   1034     }
   1035 
   1036     static UnicodeSet nfc(UnicodeSet source) {
   1037         return apply(source, new Apply() {
   1038             public String apply(String source) {
   1039                 return Normalizer.compose(source, false);
   1040             }
   1041         });
   1042     }
   1043 
   1044     public static interface CloseCodePoint {
   1045         /**
   1046          * @param cp
   1047          *            code point to get closure for
   1048          * @param toAddTo
   1049          *            Unicode set for the closure
   1050          * @return toAddTo (for chaining)
   1051          */
   1052         UnicodeSet close(int cp, UnicodeSet toAddTo);
   1053     }
   1054 
   1055     public static UnicodeSet createCaseClosure(UnicodeSet source) {
   1056         UnicodeSet target = new UnicodeSet();
   1057         for (UnicodeSetIterator usi = new UnicodeSetIterator(source); usi.next();) {
   1058             String s = usi.getString();
   1059             UnicodeSet temp = createClosure(s, CCCP);
   1060             if (temp == null)
   1061                 target.add(s);
   1062             else
   1063                 target.addAll(temp);
   1064         }
   1065         return target;
   1066     }
   1067 
   1068     public static class UnicodeSetComparator implements Comparator<Object> {
   1069         UnicodeSetIterator ait = new UnicodeSetIterator();
   1070 
   1071         UnicodeSetIterator bit = new UnicodeSetIterator();
   1072 
   1073         public int compare(Object o1, Object o2) {
   1074             if (o1 == o2)
   1075                 return 0;
   1076             if (o1 == null)
   1077                 return -1;
   1078             if (o2 == null)
   1079                 return 1;
   1080             UnicodeSet a = (UnicodeSet) o1;
   1081             UnicodeSet b = (UnicodeSet) o2;
   1082             if (a.size() != b.size()) {
   1083                 return a.size() < b.size() ? -1 : 1;
   1084             }
   1085             ait.reset(a);
   1086             bit.reset(b);
   1087             while (ait.nextRange()) {
   1088                 bit.nextRange();
   1089                 if (ait.codepoint != bit.codepoint) {
   1090                     return ait.codepoint < bit.codepoint ? -1 : 1;
   1091                 }
   1092                 if (ait.codepoint == UnicodeSetIterator.IS_STRING) {
   1093                     int result = ait.string.compareTo(bit.string);
   1094                     if (result != 0)
   1095                         return result;
   1096                 } else if (ait.codepointEnd != bit.codepointEnd) {
   1097                     return ait.codepointEnd < bit.codepointEnd ? -1 : 1;
   1098                 }
   1099             }
   1100             return 0;
   1101         }
   1102     }
   1103 
   1104     public static final CloseCodePoint CCCP = new CloseCodePoint() {
   1105         Locale locale = Locale.ENGLISH;
   1106 
   1107         UnicodeSet NONE = new UnicodeSet();
   1108 
   1109         UnicodeMap<UnicodeSet> map = new UnicodeMap<UnicodeSet>(); // new
   1110 
   1111         // UnicodeSetComparator()
   1112 
   1113         public UnicodeSet close(int cp, UnicodeSet toAddTo) {
   1114             UnicodeSet result = map.getValue(cp);
   1115             if (result == null) {
   1116                 result = new UnicodeSet();
   1117                 result.add(cp);
   1118                 String s = UCharacter.toLowerCase(locale, UTF16.valueOf(cp));
   1119                 result.add(s);
   1120                 s = UCharacter.toUpperCase(locale, UTF16.valueOf(cp));
   1121                 result.add(s);
   1122                 s = UCharacter.toTitleCase(locale, UTF16.valueOf(cp), null);
   1123                 result.add(s);
   1124                 // special hack
   1125                 if (result.contains("SS"))
   1126                     result.add("sS").add("ss");
   1127                 if (result.size() == 1)
   1128                     result = NONE;
   1129                 map.put(cp, result);
   1130             }
   1131             if (result != NONE)
   1132                 toAddTo.addAll(result);
   1133             else
   1134                 toAddTo.add(cp);
   1135             return toAddTo;
   1136         }
   1137     };
   1138 
   1139     public static UnicodeSet createClosure(String source, CloseCodePoint closer) {
   1140         return createClosure(source, 0, closer);
   1141     }
   1142 
   1143     public static UnicodeSet createClosure(String source, int position,
   1144         CloseCodePoint closer) {
   1145         UnicodeSet result = new UnicodeSet();
   1146         // if at end, return empty set
   1147         if (position >= source.length())
   1148             return result;
   1149         int cp = UTF16.charAt(source, position);
   1150         // if last character, return its set
   1151         int endPosition = position + UTF16.getCharCount(cp);
   1152         if (endPosition >= source.length())
   1153             return closer.close(cp, result);
   1154         // otherwise concatenate its set with the remainder
   1155         UnicodeSet remainder = createClosure(source, endPosition, closer);
   1156         return createAppend(closer.close(cp, result), remainder);
   1157     }
   1158 
   1159     /**
   1160      * Produce the result of appending each element of this to each element of
   1161      * other. That is, [a{cd}] + [d{ef}] => [{ad}{aef}{cdd}{cdef}]
   1162      */
   1163     public static UnicodeSet createAppend(UnicodeSet a, UnicodeSet b) {
   1164         UnicodeSet target = new UnicodeSet();
   1165         for (UnicodeSetIterator usi = new UnicodeSetIterator(a); usi.next();) {
   1166             String s = usi.getString();
   1167             for (UnicodeSetIterator usi2 = new UnicodeSetIterator(b); usi2.next();) {
   1168                 String s2 = usi2.getString();
   1169                 target.add(s + s2);
   1170             }
   1171         }
   1172         return target;
   1173     }
   1174 }
   1175