1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html
3/*
4 *******************************************************************************
5 * Copyright (C) 2003-2016, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 *******************************************************************************
8 */
9
10#include "unicode/utypes.h"
11
12#if !UCONFIG_NO_COLLATION
13
14#include "svccoll.h"
15#include "unicode/coll.h"
16#include "unicode/strenum.h"
17#include "cmemory.h"
18#include "hash.h"
19#include "uassert.h"
20
21#include "cstring.h" // internal api used to compare locale strings
22
23void CollationServiceTest::runIndexedTest(int32_t index, UBool exec, const char* &name, char* /*par */)
24{
25    if (exec) logln("TestSuite CollationServiceTest: ");
26    switch (index) {
27        TESTCASE(0, TestRegister);
28        TESTCASE(1, TestRegisterFactory);
29        TESTCASE(2, TestSeparateTree);
30    default: name = ""; break;
31    }
32}
33
34void CollationServiceTest::TestRegister()
35{
36#if !UCONFIG_NO_SERVICE
37    // register a singleton
38    const Locale& FR = Locale::getFrance();
39    const Locale& US = Locale::getUS();
40    const Locale US_FOO("en", "US", "FOO");
41
42    UErrorCode status = U_ZERO_ERROR;
43
44    Collator* frcol = Collator::createInstance(FR, status);
45    Collator* uscol = Collator::createInstance(US, status);
46    if(U_FAILURE(status)) {
47        errcheckln(status, "Failed to create collators with %s", u_errorName(status));
48        delete frcol;
49        delete uscol;
50        return;
51    }
52
53    { // try override en_US collator
54        Collator *clone = frcol->clone();
55        URegistryKey key = Collator::registerInstance(frcol, US, status);
56        // frcol has been adopted. We must not use it any more, nor rely on its attributes.
57        frcol = NULL;
58
59        Collator* ncol = Collator::createInstance(US_FOO, status);
60        if (*clone != *ncol) {
61            errln("register of french collator for en_US failed on request for en_US_FOO");
62        }
63        delete clone;
64
65        // The requested locale may be the same as the valid locale,
66        // or may not be supported at all. See ticket #10477.
67        Locale loc = ncol->getLocale(ULOC_REQUESTED_LOCALE, status);
68        if (U_SUCCESS(status) && loc != US_FOO && loc != US) {
69            errln(UnicodeString("requested locale for en_US_FOO is not en_US_FOO nor en_US but ") + loc.getName());
70        }
71        status = U_ZERO_ERROR;
72        loc = ncol->getLocale(ULOC_VALID_LOCALE, status);
73        if (loc != US) {
74            errln(UnicodeString("valid locale for en_US_FOO is not en_US but ") + loc.getName());
75        }
76        loc = ncol->getLocale(ULOC_ACTUAL_LOCALE, status);
77        if (loc != US) {
78            errln(UnicodeString("actual locale for en_US_FOO is not en_US but ") + loc.getName());
79        }
80        delete ncol; ncol = NULL;
81
82        if (!Collator::unregister(key, status)) {
83            errln("failed to unregister french collator");
84        }
85
86        ncol = Collator::createInstance(US, status);
87        if (*uscol != *ncol) {
88            errln("collator after unregister does not match original");
89        }
90        delete ncol; ncol = NULL;
91    }
92
93    // recreate frcol
94    frcol = Collator::createInstance(FR, status);
95
96    LocalUCollatorPointer frFR(ucol_open("fr_FR", &status));
97
98    { // try create collator for new locale
99        Locale fu_FU_FOO("fu", "FU", "FOO");
100        Locale fu_FU("fu", "FU", "");
101
102        Collator* fucol = Collator::createInstance(fu_FU, status);
103        Collator *clone = frcol->clone();
104        URegistryKey key = Collator::registerInstance(frcol, fu_FU, status);
105        frcol = NULL;  // frcol has been adopted.
106        Collator* ncol = Collator::createInstance(fu_FU_FOO, status);
107        if (*clone != *ncol) {
108            errln("register of fr collator for fu_FU failed");
109        }
110        delete clone;
111
112        UnicodeString locName = fu_FU.getName();
113        StringEnumeration* localeEnum = Collator::getAvailableLocales();
114        UBool found = FALSE;
115        const UnicodeString* locStr, *ls2;
116        for (locStr = localeEnum->snext(status);
117        !found && locStr != NULL;
118        locStr = localeEnum->snext(status)) {
119            //
120            if (locName == *locStr) {
121                found = TRUE;
122            }
123        }
124
125        StringEnumeration *le2 = NULL;
126        localeEnum->reset(status);
127        int32_t i, count;
128        count = localeEnum->count(status);
129        for(i = 0; i < count; ++i) {
130            if(i == count / 2) {
131                le2 = localeEnum->clone();
132                if(le2 == NULL || count != le2->count(status)) {
133                    errln("ServiceEnumeration.clone() failed");
134                    break;
135                }
136            }
137            if(i >= count / 2) {
138                locStr = localeEnum->snext(status);
139                ls2 = le2->snext(status);
140                if(*locStr != *ls2) {
141                    errln("ServiceEnumeration.clone() failed for item %d", i);
142                }
143            } else {
144                localeEnum->snext(status);
145            }
146        }
147
148        delete localeEnum;
149        delete le2;
150
151        if (!found) {
152            errln("new locale fu_FU not reported as supported locale");
153        }
154
155        UnicodeString displayName;
156        Collator::getDisplayName(fu_FU, displayName);
157        /* The locale display pattern for the locale ja, ko, and zh are different. */
158        const UChar zh_fuFU_Array[] = { 0x0066, 0x0075, 0xff08, 0x0046, 0x0055, 0xff09, 0 };
159        const UnicodeString zh_fuFU(zh_fuFU_Array);
160        const Locale& defaultLocale = Locale::getDefault();
161        if (displayName != "fu (FU)" &&
162           ((defaultLocale == Locale::getKorean() && defaultLocale == Locale::getJapanese()) && displayName == "fu(FU)") &&
163           ((defaultLocale == Locale::getChinese()) && displayName != zh_fuFU)) {
164            errln(UnicodeString("found ") + displayName + " for fu_FU");
165        }
166
167        Collator::getDisplayName(fu_FU, fu_FU, displayName);
168        if (displayName != "fu (FU)" &&
169           ((defaultLocale == Locale::getKorean() && defaultLocale == Locale::getJapanese()) && displayName == "fu(FU)") &&
170           ((defaultLocale == Locale::getChinese()) && displayName != zh_fuFU)) {
171            errln(UnicodeString("found ") + displayName + " for fu_FU");
172        }
173
174        // test ucol_open
175        LocalUCollatorPointer fufu(ucol_open("fu_FU_FOO", &status));
176        if (fufu.isNull()) {
177            errln("could not open fu_FU_FOO with ucol_open");
178        } else {
179            if (*Collator::fromUCollator(fufu.getAlias()) !=
180                    *Collator::fromUCollator(frFR.getAlias())) {
181                errln("collator fufu != collator frFR");
182            }
183        }
184
185        if (!Collator::unregister(key, status)) {
186            errln("failed to unregister french collator");
187        }
188        // !!! note frcoll invalid again, but we're no longer using it
189
190        // other collators should still work ok
191        Locale nloc = ncol->getLocale(ULOC_VALID_LOCALE, status);
192        if (nloc != fu_FU) {
193            errln(UnicodeString("asked for nloc valid locale after close and got") + nloc.getName());
194        }
195        delete ncol; ncol = NULL;
196
197        if (fufu.isValid()) {
198            const char* nlocstr = ucol_getLocaleByType(fufu.getAlias(), ULOC_VALID_LOCALE, &status);
199            if (uprv_strcmp(nlocstr, "fu_FU") != 0) {
200                errln(UnicodeString("asked for uloc valid locale after close and got ") + nlocstr);
201            }
202        }
203
204        ncol = Collator::createInstance(fu_FU, status);
205        if (*fucol != *ncol) {
206            errln("collator after unregister does not match original fu_FU");
207        }
208        delete uscol; uscol = NULL;
209        delete ncol; ncol = NULL;
210        delete fucol; fucol = NULL;
211    }
212#endif
213}
214
215// ------------------
216
217#if !UCONFIG_NO_SERVICE
218struct CollatorInfo {
219  Locale locale;
220  Collator* collator;
221  Hashtable* displayNames; // locale name -> string
222
223  CollatorInfo(const Locale& locale, Collator* collatorToAdopt, Hashtable* displayNamesToAdopt);
224  ~CollatorInfo();
225  UnicodeString& getDisplayName(const Locale& displayLocale, UnicodeString& name) const;
226};
227
228CollatorInfo::CollatorInfo(const Locale& _locale, Collator* _collator, Hashtable* _displayNames)
229  : locale(_locale)
230  , collator(_collator)
231  , displayNames(_displayNames)
232{
233  collator->setLocales(locale, locale, locale);
234}
235
236CollatorInfo::~CollatorInfo() {
237  delete collator;
238  delete displayNames;
239}
240
241UnicodeString&
242CollatorInfo::getDisplayName(const Locale& displayLocale, UnicodeString& name) const {
243  if (displayNames) {
244    UnicodeString* val = (UnicodeString*)displayNames->get(displayLocale.getName());
245    if (val) {
246      name = *val;
247      return name;
248    }
249  }
250
251  return locale.getDisplayName(displayLocale, name);
252}
253
254// ---------------
255
256class TestFactory : public CollatorFactory {
257  CollatorInfo** info;
258  int32_t count;
259  UnicodeString* ids;
260
261  const CollatorInfo* getInfo(const Locale& loc) const {
262    for (CollatorInfo** p = info; *p; ++p) {
263      if (loc == (**p).locale) {
264        return *p;
265      }
266    }
267    return NULL;
268  }
269
270public:
271  TestFactory(CollatorInfo** _info)
272    : info(_info)
273    , count(0)
274    , ids(NULL)
275  {
276    CollatorInfo** p;
277    for (p = info; *p; ++p) {}
278    count = (int32_t)(p - info);
279  }
280
281  ~TestFactory() {
282    for (CollatorInfo** p = info; *p; ++p) {
283      delete *p;
284    }
285    delete[] info;
286    delete[] ids;
287  }
288
289  virtual Collator* createCollator(const Locale& loc) {
290    const CollatorInfo* ci = getInfo(loc);
291    if (ci) {
292      return ci->collator->clone();
293    }
294    return NULL;
295  }
296
297  virtual UnicodeString& getDisplayName(const Locale& objectLocale,
298                                        const Locale& displayLocale,
299                                        UnicodeString& result)
300  {
301    const CollatorInfo* ci = getInfo(objectLocale);
302    if (ci) {
303      ci->getDisplayName(displayLocale, result);
304    } else {
305      result.setToBogus();
306    }
307    return result;
308  }
309
310  const UnicodeString* getSupportedIDs(int32_t& _count, UErrorCode& status) {
311    if (U_SUCCESS(status)) {
312      if (!ids) {
313        ids = new UnicodeString[count];
314        if (!ids) {
315          status = U_MEMORY_ALLOCATION_ERROR;
316          _count = 0;
317          return NULL;
318        }
319
320        for (int i = 0; i < count; ++i) {
321          ids[i] = info[i]->locale.getName();
322        }
323      }
324
325      _count = count;
326      return ids;
327    }
328    return NULL;
329  }
330
331  virtual inline UClassID getDynamicClassID() const {
332    return (UClassID)&gClassID;
333  }
334
335  static UClassID getStaticClassID() {
336    return (UClassID)&gClassID;
337  }
338
339private:
340  static char gClassID;
341};
342
343char TestFactory::gClassID = 0;
344#endif
345
346void CollationServiceTest::TestRegisterFactory(void)
347{
348#if !UCONFIG_NO_SERVICE
349    int32_t n1, n2, n3;
350    Locale fu_FU("fu", "FU", "");
351    Locale fu_FU_FOO("fu", "FU", "FOO");
352
353    UErrorCode status = U_ZERO_ERROR;
354
355    Hashtable* fuFUNames = new Hashtable(FALSE, status);
356    if (!fuFUNames) {
357        errln("memory allocation error");
358        return;
359    }
360    fuFUNames->setValueDeleter(uprv_deleteUObject);
361
362    fuFUNames->put(fu_FU.getName(), new UnicodeString("ze leetle bunny Fu-Fu"), status);
363    fuFUNames->put(fu_FU_FOO.getName(), new UnicodeString("zee leetel bunny Foo-Foo"), status);
364    fuFUNames->put(Locale::getDefault().getName(), new UnicodeString("little bunny Foo Foo"), status);
365
366    Collator* frcol = Collator::createInstance(Locale::getFrance(), status);
367    Collator* gecol = Collator::createInstance(Locale::getGermany(), status);
368    Collator* jpcol = Collator::createInstance(Locale::getJapan(), status);
369    if(U_FAILURE(status)) {
370      errcheckln(status, "Failed to create collators with %s", u_errorName(status));
371      delete frcol;
372      delete gecol;
373      delete jpcol;
374      delete fuFUNames;
375      return;
376    }
377
378    CollatorInfo** info = new CollatorInfo*[4];
379    if (!info) {
380        errln("memory allocation error");
381        return;
382    }
383
384    info[0] = new CollatorInfo(Locale::getUS(), frcol, NULL);
385    info[1] = new CollatorInfo(Locale::getFrance(), gecol, NULL);
386    info[2] = new CollatorInfo(fu_FU, jpcol, fuFUNames);
387    info[3] = NULL;
388
389    TestFactory* factory = new TestFactory(info);
390    if (!factory) {
391        errln("memory allocation error");
392        return;
393    }
394
395    Collator* uscol = Collator::createInstance(Locale::getUS(), status);
396    Collator* fucol = Collator::createInstance(fu_FU, status);
397
398    {
399        n1 = checkAvailable("before registerFactory");
400
401        URegistryKey key = Collator::registerFactory(factory, status);
402
403        n2 = checkAvailable("after registerFactory");
404        assertTrue("count after > count before", n2 > n1);
405
406        Collator* ncol = Collator::createInstance(Locale::getUS(), status);
407        if (*frcol != *ncol) {
408            errln("frcoll for en_US failed");
409        }
410        delete ncol; ncol = NULL;
411
412        ncol = Collator::createInstance(fu_FU_FOO, status);
413        if (*jpcol != *ncol) {
414            errln("jpcol for fu_FU_FOO failed");
415        }
416
417        // The requested locale may be the same as the valid locale,
418        // or may not be supported at all. See ticket #10477.
419        Locale loc = ncol->getLocale(ULOC_REQUESTED_LOCALE, status);
420        if (U_SUCCESS(status) && loc != fu_FU_FOO && loc != fu_FU) {
421            errln(UnicodeString("requested locale for fu_FU_FOO is not fu_FU_FOO nor fu_FU but ") + loc.getName());
422        }
423        status = U_ZERO_ERROR;
424        loc = ncol->getLocale(ULOC_VALID_LOCALE, status);
425        if (loc != fu_FU) {
426            errln(UnicodeString("valid locale for fu_FU_FOO is not fu_FU but ") + loc.getName());
427        }
428        delete ncol; ncol = NULL;
429
430        UnicodeString locName = fu_FU.getName();
431        StringEnumeration* localeEnum = Collator::getAvailableLocales();
432        UBool found = FALSE;
433        const UnicodeString* locStr;
434        for (locStr = localeEnum->snext(status);
435            !found && locStr != NULL;
436            locStr = localeEnum->snext(status))
437        {
438            if (locName == *locStr) {
439                found = TRUE;
440            }
441        }
442        delete localeEnum;
443
444        if (!found) {
445            errln("new locale fu_FU not reported as supported locale");
446        }
447
448        UnicodeString name;
449        Collator::getDisplayName(fu_FU, name);
450        if (name != "little bunny Foo Foo") {
451            errln(UnicodeString("found ") + name + " for fu_FU");
452        }
453
454        Collator::getDisplayName(fu_FU, fu_FU_FOO, name);
455        if (name != "zee leetel bunny Foo-Foo") {
456            errln(UnicodeString("found ") + name + " for fu_FU in fu_FU_FOO");
457        }
458
459        if (!Collator::unregister(key, status)) {
460            errln("failed to unregister factory");
461        }
462        // ja, fr, ge collators no longer valid
463
464        ncol = Collator::createInstance(fu_FU, status);
465        if (*fucol != *ncol) {
466            errln("collator after unregister does not match original fu_FU");
467        }
468        delete ncol;
469
470        n3 = checkAvailable("after unregister");
471        assertTrue("count after unregister == count before register", n3 == n1);
472    }
473
474    delete fucol;
475    delete uscol;
476#endif
477}
478
479/**
480 * Iterate through the given iterator, checking to see that all the strings
481 * in the expected array are present.
482 * @param expected array of strings we expect to see, or NULL
483 * @param expectedCount number of elements of expected, or 0
484 */
485int32_t CollationServiceTest::checkStringEnumeration(const char* msg,
486                                                     StringEnumeration& iter,
487                                                     const char** expected,
488                                                     int32_t expectedCount) {
489    UErrorCode ec = U_ZERO_ERROR;
490    U_ASSERT(expectedCount >= 0 && expectedCount < 31); // [sic] 31 not 32
491    int32_t i = 0, idxAfterReset = 0, n = iter.count(ec);
492    assertSuccess("count", ec);
493    UnicodeString buf, buffAfterReset;
494    int32_t seenMask = 0;
495    for (;; ++i) {
496        const UnicodeString* s = iter.snext(ec);
497        if (!assertSuccess("snext", ec) || s == NULL)
498            break;
499        if (i != 0)
500            buf.append(UNICODE_STRING_SIMPLE(", "));
501        buf.append(*s);
502        // check expected list
503        for (int32_t j=0, bit=1; j<expectedCount; ++j, bit<<=1) {
504            if ((seenMask&bit)==0) {
505                UnicodeString exp(expected[j], (char*)NULL);
506                if (*s == exp) {
507                    seenMask |= bit;
508                    logln((UnicodeString)"Ok: \"" + exp + "\" seen");
509                }
510            }
511        }
512    }
513    // can't get pesky operator+(const US&, foo) to cooperate; use toString
514#if !UCONFIG_NO_FORMATTING
515    logln(UnicodeString() + msg + " = [" + buf + "] (" + toString(i) + ")");
516#else
517    logln(UnicodeString() + msg + " = [" + buf + "] (??? NO_FORMATTING)");
518#endif
519    assertTrue("count verified", i==n);
520    iter.reset(ec);
521    for (;; ++idxAfterReset) {
522        const UChar *s = iter.unext(NULL, ec);
523        if (!assertSuccess("unext", ec) || s == NULL)
524            break;
525        if (idxAfterReset != 0)
526            buffAfterReset.append(UNICODE_STRING_SIMPLE(", "));
527        buffAfterReset.append(s);
528    }
529    assertTrue("idxAfterReset verified", idxAfterReset==n);
530    assertTrue("buffAfterReset verified", buffAfterReset==buf);
531    // did we see all expected strings?
532    if (((1<<expectedCount)-1) != seenMask) {
533        for (int32_t j=0, bit=1; j<expectedCount; ++j, bit<<=1) {
534            if ((seenMask&bit)==0) {
535                errln((UnicodeString)"FAIL: \"" + expected[j] + "\" not seen");
536            }
537        }
538    }
539    return n;
540}
541
542/**
543 * Check the integrity of the results of Collator::getAvailableLocales().
544 * Return the number of items returned.
545 */
546#if !UCONFIG_NO_SERVICE
547int32_t CollationServiceTest::checkAvailable(const char* msg) {
548    StringEnumeration *iter = Collator::getAvailableLocales();
549    if (!assertTrue("getAvailableLocales != NULL", iter!=NULL)) return -1;
550    int32_t n = checkStringEnumeration(msg, *iter, NULL, 0);
551    delete iter;
552    return n;
553}
554#endif
555
556static const char* KW[] = {
557    "collation"
558};
559static const int32_t KW_COUNT = UPRV_LENGTHOF(KW);
560
561static const char* KWVAL[] = {
562    "phonebook",
563    "stroke"
564};
565static const int32_t KWVAL_COUNT = UPRV_LENGTHOF(KWVAL);
566
567void CollationServiceTest::TestSeparateTree() {
568    UErrorCode ec = U_ZERO_ERROR;
569    StringEnumeration *iter = Collator::getKeywords(ec);
570    if (!assertTrue("getKeywords != NULL", iter!=NULL)) return;
571    if (!assertSuccess("getKeywords", ec)) return;
572    checkStringEnumeration("getKeywords", *iter, KW, KW_COUNT);
573    delete iter;
574
575    iter = Collator::getKeywordValues(KW[0], ec);
576    if (!assertTrue("getKeywordValues != NULL", iter!=NULL, FALSE, TRUE)) return;
577    if (!assertSuccess("getKeywordValues", ec)) return;
578    checkStringEnumeration("getKeywordValues", *iter, KWVAL, KWVAL_COUNT);
579    delete iter;
580
581    UBool isAvailable;
582    Locale equiv = Collator::getFunctionalEquivalent("collation",
583                                                     Locale::createFromName("de"),
584                                                     isAvailable, ec);
585    assertSuccess("getFunctionalEquivalent", ec);
586    assertEquals("getFunctionalEquivalent(de)", "root", equiv.getName());
587    assertTrue("getFunctionalEquivalent(de).isAvailable==TRUE",
588               isAvailable == TRUE);
589
590    equiv = Collator::getFunctionalEquivalent("collation",
591                                              Locale::createFromName("de_DE"),
592                                              isAvailable, ec);
593    assertSuccess("getFunctionalEquivalent", ec);
594    assertEquals("getFunctionalEquivalent(de_DE)", "root", equiv.getName());
595    assertTrue("getFunctionalEquivalent(de_DE).isAvailable==FALSE",
596               isAvailable == FALSE);
597
598    equiv = Collator::getFunctionalEquivalent("collation",
599                                                     Locale::createFromName("sv"),
600                                                     isAvailable, ec);
601    assertSuccess("getFunctionalEquivalent", ec);
602    assertEquals("getFunctionalEquivalent(sv)", "sv", equiv.getName());
603    assertTrue("getFunctionalEquivalent(sv).isAvailable==TRUE",
604               isAvailable == TRUE);
605
606    equiv = Collator::getFunctionalEquivalent("collation",
607                                              Locale::createFromName("sv_SE"),
608                                              isAvailable, ec);
609    assertSuccess("getFunctionalEquivalent", ec);
610    assertEquals("getFunctionalEquivalent(sv_SE)", "sv", equiv.getName());
611    assertTrue("getFunctionalEquivalent(sv_SE).isAvailable==FALSE",
612               isAvailable == FALSE);
613}
614
615#endif
616