From 9a614752ce3f12436dd2305d9f31e525440e4d82 Mon Sep 17 00:00:00 2001 From: Rich Gillam <62772518+richgillam@users.noreply.github.com> Date: Thu, 8 Jul 2021 17:06:11 -0700 Subject: [PATCH] ICU-21584 Added code to allow regions of type "grouping" to return their children. --- icu4c/source/i18n/region.cpp | 36 ++++++++----- icu4c/source/test/cintltst/uregiontest.c | 54 +++++++++++++++++++ icu4c/source/test/intltest/regiontst.cpp | 50 +++++++++++++++++ icu4c/source/test/intltest/regiontst.h | 1 + .../core/src/com/ibm/icu/util/Region.java | 17 ++++++ .../com/ibm/icu/dev/test/util/RegionTest.java | 40 ++++++++++++++ 6 files changed, 185 insertions(+), 13 deletions(-) diff --git a/icu4c/source/i18n/region.cpp b/icu4c/source/i18n/region.cpp index d8a5ffddc37..92571a70870 100644 --- a/icu4c/source/i18n/region.cpp +++ b/icu4c/source/i18n/region.cpp @@ -166,19 +166,6 @@ void U_CALLCONV Region::loadRegionData(UErrorCode &status) { continents->addElement(continentName,status); } - UResourceBundle *groupingBundle = nullptr; - while ( ures_hasNext(groupingContainment.getAlias()) ) { - groupingBundle = ures_getNextResource(groupingContainment.getAlias(), groupingBundle, &status); - if (U_FAILURE(status)) { - break; - } - UnicodeString *groupingName = new UnicodeString(ures_getKey(groupingBundle), -1, US_INV); - if (groupingName) { - groupings->addElement(groupingName,status); - } - } - ures_close(groupingBundle); - for ( int32_t i = 0 ; i < allRegions->size() ; i++ ) { LocalPointer r(new Region(), status); if ( U_FAILURE(status) ) { @@ -203,6 +190,29 @@ void U_CALLCONV Region::loadRegionData(UErrorCode &status) { uhash_put(newRegionIDMap.getAlias(),idStrAlias,(void *)(r.orphan()),&status); // regionIDMap takes ownership } + UResourceBundle *groupingBundle = nullptr; + while ( ures_hasNext(groupingContainment.getAlias()) ) { + groupingBundle = ures_getNextResource(groupingContainment.getAlias(), groupingBundle, &status); + if (U_FAILURE(status)) { + break; + } + UnicodeString *groupingName = new UnicodeString(ures_getKey(groupingBundle), -1, US_INV); + groupings->addElement(groupingName,status); + Region *grouping = (Region *) uhash_get(newRegionIDMap.getAlias(),groupingName); + if (grouping != NULL) { + for (int32_t i = 0; i < ures_getSize(groupingBundle); i++) { + UnicodeString child = ures_getUnicodeStringByIndex(groupingBundle, i, &status); + if (U_SUCCESS(status)) { + if (grouping->containedRegions == NULL) { + grouping->containedRegions = new UVector(uprv_deleteUObject, uhash_compareUnicodeString, status); + } + grouping->containedRegions->addElement(new UnicodeString(child), status); + } + } + } + } + ures_close(groupingBundle); + // Process the territory aliases while ( ures_hasNext(territoryAlias.getAlias()) ) { LocalUResourceBundlePointer res(ures_getNextResource(territoryAlias.getAlias(),NULL,&status)); diff --git a/icu4c/source/test/cintltst/uregiontest.c b/icu4c/source/test/cintltst/uregiontest.c index 93e2f19b6e7..09fa74fae43 100644 --- a/icu4c/source/test/cintltst/uregiontest.c +++ b/icu4c/source/test/cintltst/uregiontest.c @@ -24,6 +24,7 @@ static void TestKnownRegions(void); static void TestGetContainedRegions(void); +static void TestGroupingChildren(void); static void TestGetContainedRegionsWithType(void); static void TestGetContainingRegion(void); static void TestGetContainingRegionWithType(void); @@ -38,6 +39,7 @@ void addURegionTest(TestNode** root) { TESTCASE(TestKnownRegions); TESTCASE(TestGetContainedRegions); + TESTCASE(TestGroupingChildren); TESTCASE(TestGetContainedRegionsWithType); TESTCASE(TestGetContainingRegion); TESTCASE(TestGetContainingRegionWithType); @@ -414,6 +416,58 @@ static void TestGetContainedRegions() { } } +static void TestGroupingChildren() { + const char* testGroupings[] = { + "003", "021,013,029", + "419", "013,029,005", + "EU", "AT,BE,CY,CZ,DE,DK,EE,ES,FI,FR,GR,HR,HU,IE,IT,LT,LU,LV,MT,NL,PL,PT,SE,SI,SK,BG,RO" + }; + + for (int32_t i = 0; i < UPRV_LENGTHOF(testGroupings); i += 2) { + const char* groupingCode = testGroupings[i]; + const char* expectedChildren = testGroupings[i + 1]; + + UErrorCode err = U_ZERO_ERROR; + const URegion* grouping = uregion_getRegionFromCode(groupingCode, &err); + if (U_SUCCESS(err)) { + UEnumeration* actualChildren = uregion_getContainedRegions(grouping, &err); + if (U_SUCCESS(err)) { + int32_t numActualChildren = uenum_count(actualChildren, &err); + int32_t numExpectedChildren = 0; + const char* expectedChildStart = expectedChildren; + const char* expectedChildEnd = NULL; + const char* actualChild = NULL; + while ((actualChild = uenum_next(actualChildren, NULL, &err)) != NULL && *expectedChildStart != '\0') { + expectedChildEnd = uprv_strchr(expectedChildStart, ','); + if (expectedChildEnd == NULL) { + expectedChildEnd = expectedChildStart + uprv_strlen(expectedChildStart); + } + if (uprv_strlen(actualChild) != (size_t)(expectedChildEnd - expectedChildStart) || uprv_strncmp(actualChild, expectedChildStart, expectedChildEnd - expectedChildStart) != 0) { + log_err("Mismatch in child list for %s at position %d: expected %s, got %s\n", groupingCode, i, expectedChildStart, actualChild); + } + expectedChildStart = (*expectedChildEnd != '\0') ? expectedChildEnd + 1 : expectedChildEnd; + ++numExpectedChildren; + } + if (expectedChildEnd == NULL) { + expectedChildEnd = expectedChildren; + } + while (expectedChildEnd != NULL && *expectedChildEnd != '\0') { + expectedChildEnd = uprv_strchr(expectedChildEnd + 1, ','); + ++numExpectedChildren; + } + if (numExpectedChildren != numActualChildren) { + log_err("Wrong number of children for %s: expected %d, got %d\n", groupingCode, numExpectedChildren, numActualChildren); + } + uenum_close(actualChildren); + } else { + log_err("Couldn't create iterator for children of %s\n", groupingCode); + } + } else { + log_err("Region %s not found\n", groupingCode); + } + } +} + static void TestGetContainedRegionsWithType() { const KnownRegion * rd; for (rd = knownRegions; rd->code != NULL ; rd++ ) { diff --git a/icu4c/source/test/intltest/regiontst.cpp b/icu4c/source/test/intltest/regiontst.cpp index d8a6723bfe3..c35759f08fa 100644 --- a/icu4c/source/test/intltest/regiontst.cpp +++ b/icu4c/source/test/intltest/regiontst.cpp @@ -358,6 +358,7 @@ RegionTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* TESTCASE_AUTO(TestContains); TESTCASE_AUTO(TestAvailableTerritories); TESTCASE_AUTO(TestNoContainedRegions); + TESTCASE_AUTO(TestGroupingChildren); TESTCASE_AUTO_END; } @@ -733,6 +734,55 @@ void RegionTest::TestNoContainedRegions(void) { delete containedRegions; } +void RegionTest::TestGroupingChildren(void) { + const char* testGroupings[] = { + "003", "021,013,029", + "419", "013,029,005", + "EU", "AT,BE,CY,CZ,DE,DK,EE,ES,FI,FR,GR,HR,HU,IE,IT,LT,LU,LV,MT,NL,PL,PT,SE,SI,SK,BG,RO" + }; + + for (int32_t i = 0; i < UPRV_LENGTHOF(testGroupings); i += 2) { + const char* groupingCode = testGroupings[i]; + const char* expectedChildren = testGroupings[i + 1]; + + UErrorCode err = U_ZERO_ERROR; + const Region* grouping = Region::getInstance(groupingCode, err); + if (U_SUCCESS(err)) { + StringEnumeration* actualChildren = grouping->getContainedRegions(err); + if (U_SUCCESS(err)) { + int32_t numActualChildren = actualChildren->count(err); + int32_t numExpectedChildren = 0; + const char* expectedChildStart = expectedChildren; + const char* expectedChildEnd = NULL; + const char* actualChild = NULL; + while ((actualChild = actualChildren->next(NULL, err)) != NULL && *expectedChildStart != '\0') { + expectedChildEnd = uprv_strchr(expectedChildStart, ','); + if (expectedChildEnd == NULL) { + expectedChildEnd = expectedChildStart + uprv_strlen(expectedChildStart); + } + if (uprv_strlen(actualChild) != size_t(expectedChildEnd - expectedChildStart) || uprv_strncmp(actualChild, expectedChildStart, expectedChildEnd - expectedChildStart) != 0) { + errln("Mismatch in child list for %s at position %d: expected %s, got %s\n", groupingCode, numExpectedChildren, expectedChildStart, actualChild); + } + expectedChildStart = (*expectedChildEnd != '\0') ? expectedChildEnd + 1 : expectedChildEnd; + ++numExpectedChildren; + } + while (expectedChildEnd != NULL && *expectedChildEnd != '\0') { + expectedChildEnd = uprv_strchr(expectedChildEnd + 1, ','); + ++numExpectedChildren; + } + if (numExpectedChildren != numActualChildren) { + errln("Wrong number of children for %s: expected %d, got %d\n", groupingCode, numExpectedChildren, numActualChildren); + } + delete actualChildren; + } else { + errln("Couldn't create iterator for children of %s\n", groupingCode); + } + } else { + errln("Region %s not found\n", groupingCode); + } + } +} + #endif /* #if !UCONFIG_NO_FORMATTING */ //eof diff --git a/icu4c/source/test/intltest/regiontst.h b/icu4c/source/test/intltest/regiontst.h index 32e0e01cf64..4058d6ab824 100644 --- a/icu4c/source/test/intltest/regiontst.h +++ b/icu4c/source/test/intltest/regiontst.h @@ -38,6 +38,7 @@ public: void TestContains(void); void TestAvailableTerritories(void); void TestNoContainedRegions(void); + void TestGroupingChildren(void); private: diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/Region.java b/icu4j/main/classes/core/src/com/ibm/icu/util/Region.java index 8ba8aadd49d..af670ae7f7b 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/Region.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/Region.java @@ -348,6 +348,23 @@ public class Region implements Comparable { } } + // Fill in the grouping containment resource as well + for ( int i = 0 ; i < groupingContainment.getSize(); i++ ) { + UResourceBundle mapping = groupingContainment.get(i); + String parent = mapping.getKey(); + Region parentRegion = regionIDMap.get(parent); + for ( int j = 0 ; j < mapping.getSize(); j++ ) { + String child = mapping.getString(j); + Region childRegion = regionIDMap.get(child); + if ( parentRegion != null && childRegion != null ) { + // Add the child region to the set of regions contained by the parent + parentRegion.containedRegions.add(childRegion); + // Do NOT change the parent of the child region, since groupings are + // never the primary parent of a region. + } + } + } + // Create the availableRegions lists for (int i = 0 ; i < RegionType.values().length ; i++) { diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/RegionTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/RegionTest.java index 794fe0be88e..67710cc8a78 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/RegionTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/RegionTest.java @@ -587,4 +587,44 @@ public class RegionTest extends TestFmwk { "Contained in World = " + containedInWorld.toString()); } } + + @Test + public void TestGroupingChildren() { + String[][][] testGroupings = { + { { "003" }, { "013", "021", "029" } }, + { { "419" }, { "005", "013", "029" } }, + { { "EU" }, { "AT", "BE", "BG", "CY", "CZ", "DE", "DK", "EE", "ES", "FI", "FR", + "GR", "HR", "HU", "IE", "IT", "LT", "LU", "LV", "MT", "NL", "PL", + "PT", "RO", "SE", "SI", "SK" } } + }; + + for (String[][] testCase : testGroupings) { + String groupingCode = testCase[0][0]; + String[] expectedChildren = testCase[1]; + + try { + Region grouping = Region.getInstance(groupingCode); + Set actualChildren = grouping.getContainedRegions(); + List actualChildIDs = new java.util.ArrayList(); + for (Region childRegion : actualChildren) { + actualChildIDs.add(childRegion.toString()); + } + actualChildIDs.sort(null); + + for (int i = 0; i < actualChildIDs.size() && i < expectedChildren.length; i++) { + if (!expectedChildren[i].equals(actualChildIDs.get(i))) { + errln("Mismatch in child list for " + groupingCode + " at position " + + i + ": expected " + expectedChildren[i] + ", got " + actualChildIDs.get(i)); + } + } + if (expectedChildren.length != actualChildIDs.size()) { + errln("Wrong number of children for " + groupingCode + ": expected " + + expectedChildren.length + ", got " + actualChildIDs.size()); + } + + } catch (IllegalArgumentException ex) { + errln("Known region " + groupingCode + " was not recognized"); + } + } + } }