1/*
2 **********************************************************************
3 * Copyright (c) 2015, International Business Machines
4 * Corporation and others.  All Rights Reserved.
5 **********************************************************************
6 * Author: Alan Liu
7 * Created: January 14 2004
8 * Since: ICU 2.8
9 **********************************************************************
10 */
11package com.ibm.icu.dev.test.util;
12
13import java.lang.reflect.InvocationTargetException;
14import java.lang.reflect.Method;
15import java.util.ArrayList;
16import java.util.Arrays;
17import java.util.Collections;
18import java.util.LinkedHashSet;
19import java.util.List;
20import java.util.Locale;
21import java.util.Set;
22
23import com.ibm.icu.dev.test.TestFmwk;
24import com.ibm.icu.text.Collator;
25import com.ibm.icu.text.DisplayContext;
26import com.ibm.icu.text.DisplayContext.Type;
27import com.ibm.icu.text.LocaleDisplayNames;
28import com.ibm.icu.text.LocaleDisplayNames.UiListItem;
29import com.ibm.icu.util.IllformedLocaleException;
30import com.ibm.icu.util.ULocale;
31
32public class ULocaleCollationTest extends TestFmwk {
33
34    public static void main(String[] args) throws Exception {
35        new ULocaleCollationTest().run(args);
36    }
37
38    public void TestCollator() {
39        checkService("ja_JP_YOKOHAMA", new ServiceFacade() {
40            public Object create(ULocale req) {
41                return Collator.getInstance(req);
42            }
43        }, null, new Registrar() {
44            public Object register(ULocale loc, Object prototype) {
45                return Collator.registerInstance((Collator) prototype, loc);
46            }
47            public boolean unregister(Object key) {
48                return Collator.unregister(key);
49            }
50        });
51    }
52
53
54    /**
55     * Interface used by checkService defining a protocol to create an
56     * object, given a requested locale.
57     */
58    interface ServiceFacade {
59        Object create(ULocale requestedLocale);
60    }
61
62    /**
63     * Interface used by checkService defining a protocol to get a
64     * contained subobject, given its parent object.
65     */
66    interface Subobject {
67        Object get(Object parent);
68    }
69
70    /**
71     * Interface used by checkService defining a protocol to register
72     * and unregister a service object prototype.
73     */
74    interface Registrar {
75        Object register(ULocale loc, Object prototype);
76        boolean unregister(Object key);
77    }
78
79
80
81    /**
82     * Compare two locale IDs.  If they are equal, return 0.  If `string'
83     * starts with `prefix' plus an additional element, that is, string ==
84     * prefix + '_' + x, then return 1.  Otherwise return a value < 0.
85     */
86    static int loccmp(String string, String prefix) {
87        int slen = string.length(),
88                plen = prefix.length();
89        /* 'root' is "less than" everything */
90        if (prefix.equals("root")) {
91            return string.equals("root") ? 0 : 1;
92        }
93        // ON JAVA (only -- not on C -- someone correct me if I'm wrong)
94        // consider "" to be an alternate name for "root".
95        if (plen == 0) {
96            return slen == 0 ? 0 : 1;
97        }
98        if (!string.startsWith(prefix)) return -1; /* mismatch */
99        if (slen == plen) return 0;
100        if (string.charAt(plen) == '_') return 1;
101        return -2; /* false match, e.g. "en_USX" cmp "en_US" */
102    }
103
104    /**
105     * Check the relationship between requested locales, and report problems.
106     * The caller specifies the expected relationships between requested
107     * and valid (expReqValid) and between valid and actual (expValidActual).
108     * Possible values are:
109     * "gt" strictly greater than, e.g., en_US > en
110     * "ge" greater or equal,      e.g., en >= en
111     * "eq" equal,                 e.g., en == en
112     */
113    void checklocs(String label,
114            String req,
115            Locale validLoc,
116            Locale actualLoc,
117            String expReqValid,
118            String expValidActual) {
119        String valid = validLoc.toString();
120        String actual = actualLoc.toString();
121        int reqValid = loccmp(req, valid);
122        int validActual = loccmp(valid, actual);
123        boolean reqOK = (expReqValid.equals("gt") && reqValid > 0) ||
124                (expReqValid.equals("ge") && reqValid >= 0) ||
125                (expReqValid.equals("eq") && reqValid == 0);
126        boolean valOK = (expValidActual.equals("gt") && validActual > 0) ||
127                (expValidActual.equals("ge") && validActual >= 0) ||
128                (expValidActual.equals("eq") && validActual == 0);
129        if (reqOK && valOK) {
130            logln("Ok: " + label + "; req=" + req + ", valid=" + valid +
131                    ", actual=" + actual);
132        } else {
133            errln("FAIL: " + label + "; req=" + req + ", valid=" + valid +
134                    ", actual=" + actual +
135                    (reqOK ? "" : "\n  req !" + expReqValid + " valid") +
136                    (valOK ? "" : "\n  val !" + expValidActual + " actual"));
137        }
138    }
139
140    /**
141     * Use reflection to call getLocale() on the given object to
142     * determine both the valid and the actual locale.  Verify these
143     * for correctness.
144     */
145    void checkObject(String requestedLocale, Object obj,
146            String expReqValid, String expValidActual) {
147        Class[] getLocaleParams = new Class[] { ULocale.Type.class };
148        try {
149            Class cls = obj.getClass();
150            Method getLocale = cls.getMethod("getLocale", getLocaleParams);
151            ULocale valid = (ULocale) getLocale.invoke(obj, new Object[] {
152                    ULocale.VALID_LOCALE });
153            ULocale actual = (ULocale) getLocale.invoke(obj, new Object[] {
154                    ULocale.ACTUAL_LOCALE });
155            checklocs(cls.getName(), requestedLocale,
156                    valid.toLocale(), actual.toLocale(),
157                    expReqValid, expValidActual);
158        }
159
160        // Make the following exceptions _specific_ -- do not
161        // catch(Exception), since that will catch the exception
162        // that errln throws.
163        catch(NoSuchMethodException e1) {
164            // no longer an error, Currency has no getLocale
165            // errln("FAIL: reflection failed: " + e1);
166        } catch(SecurityException e2) {
167            errln("FAIL: reflection failed: " + e2);
168        } catch(IllegalAccessException e3) {
169            errln("FAIL: reflection failed: " + e3);
170        } catch(IllegalArgumentException e4) {
171            errln("FAIL: reflection failed: " + e4);
172        } catch(InvocationTargetException e5) {
173            // no longer an error, Currency has no getLocale
174            // errln("FAIL: reflection failed: " + e5);
175        }
176    }
177
178    /**
179     * Verify the correct getLocale() behavior for the given service.
180     * @param requestedLocale the locale to request.  This MUST BE
181     * FAKE.  In other words, it should be something like
182     * en_US_FAKEVARIANT so this method can verify correct fallback
183     * behavior.
184     * @param svc a factory object that can create the object to be
185     * tested.  This isn't necessary here (one could just pass in the
186     * object) but is required for the overload of this method that
187     * takes a Registrar.
188     */
189    void checkService(String requestedLocale, ServiceFacade svc) {
190        checkService(requestedLocale, svc, null, null);
191    }
192
193    /**
194     * Verify the correct getLocale() behavior for the given service.
195     * @param requestedLocale the locale to request.  This MUST BE
196     * FAKE.  In other words, it should be something like
197     * en_US_FAKEVARIANT so this method can verify correct fallback
198     * behavior.
199     * @param svc a factory object that can create the object to be
200     * tested.
201     * @param sub an object that can be used to retrieve a subobject
202     * which should also be tested.  May be null.
203     * @param reg an object that supplies the registration and
204     * unregistration functionality to be tested.  May be null.
205     */
206    void checkService(String requestedLocale, ServiceFacade svc,
207            Subobject sub, Registrar reg) {
208        ULocale req = new ULocale(requestedLocale);
209        Object obj = svc.create(req);
210        checkObject(requestedLocale, obj, "gt", "ge");
211        if (sub != null) {
212            Object subobj = sub.get(obj);
213            checkObject(requestedLocale, subobj, "gt", "ge");
214        }
215        if (reg != null) {
216            logln("Info: Registering service");
217            Object key = reg.register(req, obj);
218            Object objReg = svc.create(req);
219            checkObject(requestedLocale, objReg, "eq", "eq");
220            if (sub != null) {
221                Object subobj = sub.get(obj);
222                // Assume subobjects don't come from services, so
223                // their metadata should be structured normally.
224                checkObject(requestedLocale, subobj, "gt", "ge");
225            }
226            logln("Info: Unregistering service");
227            if (!reg.unregister(key)) {
228                errln("FAIL: unregister failed");
229            }
230            Object objUnreg = svc.create(req);
231            checkObject(requestedLocale, objUnreg, "gt", "ge");
232        }
233    }
234
235    public void TestNameList() {
236        String[][][] tests = {
237                /* name in French, name in self, minimized, modified */
238                {{"fr-Cyrl-BE", "fr-Cyrl-CA"},
239                    {"Français (cyrillique, Belgique)", "Français (cyrillique, Belgique)", "fr_Cyrl_BE", "fr_Cyrl_BE"},
240                    {"Français (cyrillique, Canada)", "Français (cyrillique, Canada)", "fr_Cyrl_CA", "fr_Cyrl_CA"},
241                },
242                {{"en", "de", "fr", "zh"},
243                    {"Allemand", "Deutsch", "de", "de"},
244                    {"Anglais", "English", "en", "en"},
245                    {"Chinois", "中文", "zh", "zh"},
246                    {"Français", "Français", "fr", "fr"},
247                },
248                // some non-canonical names
249                {{"iw", "iw-US", "no", "no-Cyrl", "in", "in-YU"},
250                    {"Hébreu (États-Unis)", "עברית (ארצות הברית)", "iw_US", "iw_US"},
251                    {"Hébreu (Israël)", "עברית (ישראל)", "iw", "iw_IL"},
252                    {"Indonésien (Indonésie)", "Bahasa Indonesia (Indonesia)", "in", "in_ID"},
253                    {"Indonésien (Serbie)", "Bahasa Indonesia (Serbia)", "in_YU", "in_YU"},
254                    {"Norvégien (cyrillique)", "Norsk (kyrillisk)", "no_Cyrl", "no_Cyrl"},
255                    {"Norvégien (latin)", "Norsk (latinsk)", "no", "no_Latn"},
256                },
257                {{"zh-Hant-TW", "en", "en-gb", "fr", "zh-Hant", "de", "de-CH", "zh-TW"},
258                    {"Allemand (Allemagne)", "Deutsch (Deutschland)", "de", "de_DE"},
259                    {"Allemand (Suisse)", "Deutsch (Schweiz)", "de_CH", "de_CH"},
260                    {"Anglais (États-Unis)", "English (United States)", "en", "en_US"},
261                    {"Anglais (Royaume-Uni)", "English (United Kingdom)", "en_GB", "en_GB"},
262                    {"Chinois (traditionnel)", "中文(繁體)", "zh_Hant", "zh_Hant"},
263                    {"Français", "Français", "fr", "fr"},
264                },
265                {{"zh", "en-gb", "en-CA", "fr-Latn-FR"},
266                    {"Anglais (Canada)", "English (Canada)", "en_CA", "en_CA"},
267                    {"Anglais (Royaume-Uni)", "English (United Kingdom)", "en_GB", "en_GB"},
268                    {"Chinois", "中文", "zh", "zh"},
269                    {"Français", "Français", "fr", "fr"},
270                },
271                {{"en-gb", "fr", "zh-Hant", "zh-SG", "sr", "sr-Latn"},
272                    {"Anglais (Royaume-Uni)", "English (United Kingdom)", "en_GB", "en_GB"},
273                    {"Chinois (simplifié, Singapour)", "中文(简体、新加坡)", "zh_SG", "zh_Hans_SG"},
274                    {"Chinois (traditionnel, Taïwan)", "中文(繁體,台灣)", "zh_Hant", "zh_Hant_TW"},
275                    {"Français", "Français", "fr", "fr"},
276                    {"Serbe (cyrillique)", "Српски (ћирилица)", "sr", "sr_Cyrl"},
277                    {"Serbe (latin)", "Srpski (latinica)", "sr_Latn", "sr_Latn"},
278                },
279                {{"fr-Cyrl", "fr-Arab"},
280                    {"Français (arabe)", "Français (arabe)", "fr_Arab", "fr_Arab"},
281                    {"Français (cyrillique)", "Français (cyrillique)", "fr_Cyrl", "fr_Cyrl"},
282                },
283                {{"fr-Cyrl-BE", "fr-Arab-CA"},
284                    {"Français (arabe, Canada)", "Français (arabe, Canada)", "fr_Arab_CA", "fr_Arab_CA"},
285                    {"Français (cyrillique, Belgique)", "Français (cyrillique, Belgique)", "fr_Cyrl_BE", "fr_Cyrl_BE"},
286                }
287        };
288        ULocale french = ULocale.FRENCH;
289        LocaleDisplayNames names = LocaleDisplayNames.getInstance(french,
290                DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU);
291        for (Type type : DisplayContext.Type.values()) {
292            logln("Contexts: " + names.getContext(type).toString());
293        }
294        Collator collator = Collator.getInstance(french);
295
296        for (String[][] test : tests) {
297            Set<ULocale> list = new LinkedHashSet<ULocale>();
298            List<UiListItem> expected = new ArrayList<UiListItem>();
299            for (String item : test[0]) {
300                list.add(new ULocale(item));
301            }
302            for (int i = 1; i < test.length; ++i) {
303                String[] rawRow = test[i];
304                expected.add(new UiListItem(new ULocale(rawRow[2]), new ULocale(rawRow[3]), rawRow[0], rawRow[1]));
305            }
306            List<UiListItem> newList = names.getUiList(list, false, collator);
307            if (!expected.equals(newList)) {
308                if (expected.size() != newList.size()) {
309                    errln(list.toString() + ": wrong size" + expected + ", " + newList);
310                } else {
311                    errln(list.toString());
312                    for (int i = 0; i < expected.size(); ++i) {
313                        assertEquals(i+"", expected.get(i), newList.get(i));
314                    }
315                }
316            } else {
317                assertEquals(list.toString(), expected, newList);
318            }
319        }
320    }
321
322    public void TestIllformedLocale() {
323        ULocale french = ULocale.FRENCH;
324        Collator collator = Collator.getInstance(french);
325        LocaleDisplayNames names = LocaleDisplayNames.getInstance(french,
326                DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU);
327        for (String malformed : Arrays.asList("en-a", "$", "ü--a", "en--US")) {
328            try {
329                Set<ULocale> supported = Collections.singleton(new ULocale(malformed));
330                names.getUiList(supported, false, collator);
331                assertNull("Failed to detect bogus locale «" + malformed + "»", supported);
332            } catch (IllformedLocaleException e) {
333                logln("Successfully detected ill-formed locale «" + malformed + "»:" + e.getMessage());
334            }
335        }
336    }
337}
338