ICU-21123 Support FormattedNumber::getGender() for "short" and "narrow" formatting too

See #1617
diff --git a/icu4c/source/i18n/number_longnames.cpp b/icu4c/source/i18n/number_longnames.cpp
index be62a13..7e5e3ca 100644
--- a/icu4c/source/i18n/number_longnames.cpp
+++ b/icu4c/source/i18n/number_longnames.cpp
@@ -228,6 +228,10 @@
 // case. It loads all plural forms, because selection between plural forms is
 // dependent upon the value being formatted.
 //
+// See data/unit/de.txt and data/unit/fr.txt for examples - take a look at
+// units/compound/power2: German has case, French has differences for gender,
+// but no case.
+//
 // TODO(icu-units#138): Conceptually similar to PluralTableSink, however the
 // tree structures are different. After homogenizing the structures, we may be
 // able to unify the two classes.
@@ -336,6 +340,11 @@
     UnicodeString *outArray;
 };
 
+// Fetches localised formatting patterns for the given subKey. See documentation
+// for InflectedPluralSink for details.
+//
+// Data is loaded for the appropriate unit width, with missing data filled in
+// from unitsShort.
 void getInflectedMeasureData(StringPiece subKey,
                              const Locale &locale,
                              const UNumberUnitWidth &width,
@@ -429,28 +438,40 @@
     LocalUResourceBundlePointer unitsBundle(ures_open(U_ICUDATA_UNIT, locale.getName(), &status));
     if (U_FAILURE(status)) { return; }
 
+    CharString subKey;
+    subKey.append("/", status);
+    subKey.append(unit.getType(), status);
+    subKey.append("/", status);
+
     // Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ...
     // TODO(ICU-20400): Get duration-*-person data properly with aliases.
-    StringPiece subtypeForResource;
     int32_t subtypeLen = static_cast<int32_t>(uprv_strlen(unit.getSubtype()));
     if (subtypeLen > 7 && uprv_strcmp(unit.getSubtype() + subtypeLen - 7, "-person") == 0) {
-        subtypeForResource = {unit.getSubtype(), subtypeLen - 7};
+        subKey.append({unit.getSubtype(), subtypeLen - 7}, status);
     } else {
-        subtypeForResource = unit.getSubtype();
+        subKey.append({unit.getSubtype(), subtypeLen}, status);
+    }
+
+    if (width != UNUM_UNIT_WIDTH_FULL_NAME) {
+        UErrorCode localStatus = status;
+        CharString genderKey;
+        genderKey.append("units", localStatus);
+        genderKey.append(subKey, localStatus);
+        genderKey.append("/gender", localStatus);
+        StackUResourceBundle fillIn;
+        ures_getByKeyWithFallback(unitsBundle.getAlias(), genderKey.data(), fillIn.getAlias(),
+                                  &localStatus);
+        outArray[GENDER_INDEX] = ures_getUnicodeString(fillIn.getAlias(), &localStatus);
     }
 
     CharString key;
     key.append("units", status);
-    // TODO(icu-units#140): support gender for other unit widths.
     if (width == UNUM_UNIT_WIDTH_NARROW) {
         key.append("Narrow", status);
     } else if (width == UNUM_UNIT_WIDTH_SHORT) {
         key.append("Short", status);
     }
-    key.append("/", status);
-    key.append(unit.getType(), status);
-    key.append("/", status);
-    key.append(subtypeForResource, status);
+    key.append(subKey, status);
 
     // Grab desired case first, if available. Then grab no-case data to fill in
     // the gaps.
@@ -486,10 +507,8 @@
     // TODO(ICU-13353): The fallback to short does not work in ICU4C.
     // Manually fall back to short (this is done automatically in Java).
     key.clear();
-    key.append("unitsShort/", status);
-    key.append(unit.getType(), status);
-    key.append("/", status);
-    key.append(subtypeForResource, status);
+    key.append("unitsShort", status);
+    key.append(subKey, status);
     ures_getAllItemsWithFallback(unitsBundle.getAlias(), key.data(), sink, status);
 }
 
diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp
index 40124c2..2540a1a 100644
--- a/icu4c/source/test/intltest/numbertest_api.cpp
+++ b/icu4c/source/test/intltest/numbertest_api.cpp
@@ -2287,15 +2287,14 @@
     LocalizedNumberFormatter formatter;
     FormattedNumber fn;
     for (const TestCase &t : cases) {
-        // TODO(icu-units#140): make this work for more than just UNUM_UNIT_WIDTH_FULL_NAME
-        // formatter = NumberFormatter::with()
-        //                 .unit(MeasureUnit::forIdentifier(t.unitIdentifier, status))
-        //                 .locale(Locale(t.locale));
-        // fn = formatter.formatDouble(1.1, status);
-        // assertEquals(UnicodeString("Testing gender with default width, unit: ") + t.unitIdentifier +
-        //                  ", locale: " + t.locale,
-        //              t.expectedGender, fn.getGender(status));
-        // status.assertSuccess();
+        formatter = NumberFormatter::with()
+                        .unit(MeasureUnit::forIdentifier(t.unitIdentifier, status))
+                        .locale(Locale(t.locale));
+        fn = formatter.formatDouble(1.1, status);
+        assertEquals(UnicodeString("Testing gender with default width, unit: ") + t.unitIdentifier +
+                         ", locale: " + t.locale,
+                     t.expectedGender, fn.getGender(status));
+        status.assertSuccess();
 
         formatter = NumberFormatter::with()
                         .unit(MeasureUnit::forIdentifier(t.unitIdentifier, status))
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
index 608fa01..eeb959d 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
@@ -141,10 +141,8 @@
         key.append("/gender");
 
         try {
-            ICUResourceBundle stackBundle =
-                (ICUResourceBundle)unitsBundle.getWithFallback(key.toString());
-            return stackBundle.getString();
-        } catch (Exception e) {
+            return unitsBundle.getWithFallback(key.toString()).getString();
+        } catch (MissingResourceException e) {
             // TODO(icu-units#28): "$unitRes/gender" does not exist. Do we want to
             // check whether the parent "$unitRes" exists? Then we could return
             // U_MISSING_RESOURCE_ERROR for incorrect usage (e.g. builtinUnit not
@@ -161,6 +159,10 @@
     // case. It loads all plural forms, because selection between plural forms is
     // dependent upon the value being formatted.
     //
+    // See data/unit/de.txt and data/unit/fr.txt for examples - take a look at
+    // units/compound/power2: German has case, French has differences for
+    // gender, but no case.
+    //
     // TODO(icu-units#138): Conceptually similar to PluralTableSink, however the
     // tree structures are different. After homogenizing the structures, we may be
     // able to unify the two classes.
@@ -259,6 +261,11 @@
         String[] outArray;
     }
 
+    // Fetches localised formatting patterns for the given subKey. See
+    // documentation for InflectedPluralSink for details.
+    //
+    // Data is loaded for the appropriate unit width, with missing data filled
+    // in from unitsShort.
     static void getInflectedMeasureData(String subKey,
                                         ULocale locale,
                                         UnitWidth width,
@@ -284,7 +291,7 @@
             if (width == UnitWidth.SHORT) {
                 return;
             }
-        } catch (Exception e) {
+        } catch (MissingResourceException e) {
             // Continue: fall back to short
         }
 
@@ -338,25 +345,40 @@
         ICUResourceBundle resource;
         resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME,
                 locale);
+
+        StringBuilder subKey = new StringBuilder();
+        subKey.append("/");
+        subKey.append(unit.getType());
+        subKey.append("/");
+
+        // Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ...
+        // TODO(ICU-20400): Get duration-*-person data properly with aliases.
+        if (unit.getSubtype() != null && unit.getSubtype().endsWith("-person")) {
+            subKey.append(unit.getSubtype(), 0, unit.getSubtype().length() - 7);
+        } else {
+            subKey.append(unit.getSubtype());
+        }
+
+        if (width != UnitWidth.FULL_NAME) {
+            StringBuilder genderKey = new StringBuilder();
+            genderKey.append("units");
+            genderKey.append(subKey);
+            genderKey.append("/gender");
+            try {
+                outArray[GENDER_INDEX] = resource.getWithFallback(genderKey.toString()).getString();
+            } catch (MissingResourceException e) {
+                // continue
+            }
+        }
+
         StringBuilder key = new StringBuilder();
         key.append("units");
-        // TODO(icu-units#140): support gender for other unit widths.
         if (width == UnitWidth.NARROW) {
             key.append("Narrow");
         } else if (width == UnitWidth.SHORT) {
             key.append("Short");
         }
-        key.append("/");
-        key.append(unit.getType());
-        key.append("/");
-
-        // Map duration-year-person, duration-week-person, etc. to duration-year, duration-week, ...
-        // TODO(ICU-20400): Get duration-*-person data properly with aliases.
-        if (unit.getSubtype() != null && unit.getSubtype().endsWith("-person")) {
-            key.append(unit.getSubtype(), 0, unit.getSubtype().length() - 7);
-        } else {
-            key.append(unit.getSubtype());
-        }
+        key.append(subKey);
 
         // Grab desired case first, if available. Then grab nominative case to fill
         // in the gaps.
@@ -427,7 +449,7 @@
         key.append(compoundKey);
         try {
             return resource.getStringWithFallback(key.toString());
-        } catch (Exception e) {
+        } catch (MissingResourceException e) {
             if (width == UnitWidth.SHORT) {
                 return "";
             }
@@ -440,7 +462,7 @@
         key.append(compoundKey);
         try {
             return resource.getStringWithFallback(key.toString());
-        } catch (Exception e) {
+        } catch (MissingResourceException e) {
             return "";
         }
     }
@@ -502,7 +524,7 @@
                 } else {
                     this.value1 = value;
                 }
-            } catch (Exception e) {
+            } catch (MissingResourceException e) {
                 // Fall back to uninflected.
             }
         }
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
index cd56e27..a50c282 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
@@ -2278,14 +2278,13 @@
         LocalizedNumberFormatter formatter;
         FormattedNumber fn;
         for (TestCase t : cases) {
-            // // TODO(icu-units#140): make this work for more than just UnitWidth.FULL_NAME
-            // formatter = NumberFormatter.with()
-            //                 .unit(MeasureUnit.forIdentifier(t.unitIdentifier))
-            //                 .locale(new ULocale(t.locale));
-            // fn = formatter.format(1.1);
-            // assertEquals("Testing gender with default width, unit: " + t.unitIdentifier +
-            //                  ", locale: " + t.locale,
-            //              t.expectedGender, fn.getGender());
+            formatter = NumberFormatter.with()
+                            .unit(MeasureUnit.forIdentifier(t.unitIdentifier))
+                            .locale(new ULocale(t.locale));
+            fn = formatter.format(1.1);
+            assertEquals("Testing gender with default width, unit: " + t.unitIdentifier +
+                             ", locale: " + t.locale,
+                         t.expectedGender, fn.getGender());
 
             formatter = NumberFormatter.with()
                             .unit(MeasureUnit.forIdentifier(t.unitIdentifier))