ICU-20698 UHashtable allow integer value zero
diff --git a/icu4c/source/common/localematcher.cpp b/icu4c/source/common/localematcher.cpp
index 5795cbf..132aee2 100644
--- a/icu4c/source/common/localematcher.cpp
+++ b/icu4c/source/common/localematcher.cpp
@@ -345,9 +345,8 @@
 int32_t LocaleMatcher::putIfAbsent(const LSR &lsr, int32_t i, int32_t suppLength,
                                    UErrorCode &errorCode) {
     if (U_FAILURE(errorCode)) { return suppLength; }
-    int32_t index = uhash_geti(supportedLsrToIndex, &lsr);
-    if (index == 0) {
-        uhash_puti(supportedLsrToIndex, const_cast<LSR *>(&lsr), i + 1, &errorCode);
+    if (!uhash_containsKey(supportedLsrToIndex, &lsr)) {
+        uhash_putiAllowZero(supportedLsrToIndex, const_cast<LSR *>(&lsr), i, &errorCode);
         if (U_SUCCESS(errorCode)) {
             supportedLSRs[suppLength] = &lsr;
             supportedIndexes[suppLength++] = i;
@@ -685,12 +684,11 @@
     int32_t bestSupportedLsrIndex = -1;
     for (int32_t bestShiftedDistance = LocaleDistance::shiftDistance(thresholdDistance);;) {
         // Quick check for exact maximized LSR.
-        // Returns suppIndex+1 where 0 means not found.
         if (supportedLsrToIndex != nullptr) {
             desiredLSR.setHashCode();
-            int32_t index = uhash_geti(supportedLsrToIndex, &desiredLSR);
-            if (index != 0) {
-                int32_t suppIndex = index - 1;
+            UBool found = false;
+            int32_t suppIndex = uhash_getiAndFound(supportedLsrToIndex, &desiredLSR, &found);
+            if (found) {
                 if (remainingIter != nullptr) {
                     remainingIter->rememberCurrent(desiredIndex, errorCode);
                 }
diff --git a/icu4c/source/common/uhash.cpp b/icu4c/source/common/uhash.cpp
index 86311ce..67c7c363 100644
--- a/icu4c/source/common/uhash.cpp
+++ b/icu4c/source/common/uhash.cpp
@@ -133,8 +133,10 @@
  * or a pointer.  If a hint bit is zero, then the associated
  * token is assumed to be an integer.
  */
+#define HINT_BOTH_INTEGERS (0)
 #define HINT_KEY_POINTER   (1)
 #define HINT_VALUE_POINTER (2)
+#define HINT_ALLOW_ZERO    (4)
 
 /********************************************************************
  * PRIVATE Implementation
@@ -479,8 +481,9 @@
         goto err;
     }
     U_ASSERT(hash != NULL);
-    /* Cannot always check pointer here or iSeries sees NULL every time. */
-    if ((hint & HINT_VALUE_POINTER) && value.pointer == NULL) {
+    if ((hint & HINT_VALUE_POINTER) ?
+            value.pointer == NULL :
+            value.integer == 0 && (hint & HINT_ALLOW_ZERO) == 0) {
         /* Disallow storage of NULL values, since NULL is returned by
          * get() to indicate an absent key.  Storing NULL == removing.
          */
@@ -687,6 +690,28 @@
     return _uhash_find(hash, keyholder, hash->keyHasher(keyholder))->value.integer;
 }
 
+U_CAPI int32_t U_EXPORT2
+uhash_getiAndFound(const UHashtable *hash,
+                   const void *key,
+                   UBool *found) {
+    UHashTok keyholder;
+    keyholder.pointer = (void *)key;
+    const UHashElement *e = _uhash_find(hash, keyholder, hash->keyHasher(keyholder));
+    *found = !IS_EMPTY_OR_DELETED(e->hashcode);
+    return e->value.integer;
+}
+
+U_CAPI int32_t U_EXPORT2
+uhash_igetiAndFound(const UHashtable *hash,
+                    int32_t key,
+                    UBool *found) {
+    UHashTok keyholder;
+    keyholder.integer = key;
+    const UHashElement *e = _uhash_find(hash, keyholder, hash->keyHasher(keyholder));
+    *found = !IS_EMPTY_OR_DELETED(e->hashcode);
+    return e->value.integer;
+}
+
 U_CAPI void* U_EXPORT2
 uhash_put(UHashtable *hash,
           void* key,
@@ -736,7 +761,34 @@
     keyholder.integer = key;
     valueholder.integer = value;
     return _uhash_put(hash, keyholder, valueholder,
-                      0, /* neither is a ptr */
+                      HINT_BOTH_INTEGERS,
+                      status).integer;
+}
+
+U_CAPI int32_t U_EXPORT2
+uhash_putiAllowZero(UHashtable *hash,
+                    void *key,
+                    int32_t value,
+                    UErrorCode *status) {
+    UHashTok keyholder, valueholder;
+    keyholder.pointer = key;
+    valueholder.integer = value;
+    return _uhash_put(hash, keyholder, valueholder,
+                      HINT_KEY_POINTER | HINT_ALLOW_ZERO,
+                      status).integer;
+}
+
+
+U_CAPI int32_t U_EXPORT2
+uhash_iputiAllowZero(UHashtable *hash,
+                     int32_t key,
+                     int32_t value,
+                     UErrorCode *status) {
+    UHashTok keyholder, valueholder;
+    keyholder.integer = key;
+    valueholder.integer = value;
+    return _uhash_put(hash, keyholder, valueholder,
+                      HINT_BOTH_INTEGERS | HINT_ALLOW_ZERO,
                       status).integer;
 }
 
@@ -785,6 +837,29 @@
     U_ASSERT(hash->count == 0);
 }
 
+U_CAPI UBool U_EXPORT2
+uhash_containsKey(const UHashtable *hash, const void *key) {
+    UHashTok keyholder;
+    keyholder.pointer = (void *)key;
+    const UHashElement *e = _uhash_find(hash, keyholder, hash->keyHasher(keyholder));
+    return !IS_EMPTY_OR_DELETED(e->hashcode);
+}
+
+/**
+ * Returns true if the UHashtable contains an item with this integer key.
+ *
+ * @param hash The target UHashtable.
+ * @param key An integer key stored in a hashtable
+ * @return true if the key is found.
+ */
+U_CAPI UBool U_EXPORT2
+uhash_icontainsKey(const UHashtable *hash, int32_t key) {
+    UHashTok keyholder;
+    keyholder.integer = key;
+    const UHashElement *e = _uhash_find(hash, keyholder, hash->keyHasher(keyholder));
+    return !IS_EMPTY_OR_DELETED(e->hashcode);
+}
+
 U_CAPI const UHashElement* U_EXPORT2
 uhash_find(const UHashtable *hash, const void* key) {
     UHashTok keyholder;
diff --git a/icu4c/source/common/uhash.h b/icu4c/source/common/uhash.h
index b59d271..3b3f8c5 100644
--- a/icu4c/source/common/uhash.h
+++ b/icu4c/source/common/uhash.h
@@ -54,6 +54,13 @@
  * uhash_remove() on that key.  This keeps uhash_get(), uhash_count(),
  * and uhash_nextElement() consistent with one another.
  *
+ * Keys and values can be integers.
+ * Functions that work with an integer key have an "i" prefix.
+ * Functions that work with an integer value have an "i" suffix.
+ * As with putting a NULL value pointer, putting a zero value integer removes the item.
+ * Except, there are pairs of functions that allow setting zero values
+ * and fetching (value, found) pairs.
+ *
  * To see everything in a hashtable, use uhash_nextElement() to
  * iterate through its contents.  Each call to this function returns a
  * UHashElement pointer.  A hash element contains a key, value, and
@@ -406,6 +413,44 @@
            UErrorCode *status);
 
 /**
+ * Put a (key=pointer, value=integer) item in a UHashtable.  If the
+ * keyDeleter is non-NULL, then the hashtable owns 'key' after this
+ * call.  valueDeleter must be NULL.
+ * Storing a 0 value is possible; call uhash_igetiAndFound() to retrieve values including zero.
+ *
+ * @param hash The target UHashtable.
+ * @param key The key to store.
+ * @param value The integer value to store.
+ * @param status A pointer to an UErrorCode to receive any errors.
+ * @return The previous value, or 0 if none.
+ * @see uhash_getiAndFound
+ */
+U_CAPI int32_t U_EXPORT2
+uhash_putiAllowZero(UHashtable *hash,
+                    void *key,
+                    int32_t value,
+                    UErrorCode *status);
+
+/**
+ * Put a (key=integer, value=integer) item in a UHashtable.  If the
+ * keyDeleter is non-NULL, then the hashtable owns 'key' after this
+ * call.  valueDeleter must be NULL.
+ * Storing a 0 value is possible; call uhash_igetiAndFound() to retrieve values including zero.
+ *
+ * @param hash The target UHashtable.
+ * @param key The key to store.
+ * @param value The integer value to store.
+ * @param status A pointer to an UErrorCode to receive any errors.
+ * @return The previous value, or 0 if none.
+ * @see uhash_igetiAndFound
+ */
+U_CAPI int32_t U_EXPORT2
+uhash_iputiAllowZero(UHashtable *hash,
+                     int32_t key,
+                     int32_t value,
+                     UErrorCode *status);
+
+/**
  * Retrieve a pointer value from a UHashtable using a pointer key,
  * as previously stored by uhash_put().
  * @param hash The target UHashtable.
@@ -449,6 +494,34 @@
            int32_t key);
 
 /**
+ * Retrieves an integer value from a UHashtable using a pointer key,
+ * as previously stored by uhash_putiAllowZero() or uhash_puti().
+ *
+ * @param hash The target UHashtable.
+ * @param key A pointer key stored in a hashtable
+ * @param found A pointer to a boolean which will be set for whether the key was found.
+ * @return The requested item, or 0 if not found.
+ */
+U_CAPI int32_t U_EXPORT2
+uhash_getiAndFound(const UHashtable *hash,
+                   const void *key,
+                   UBool *found);
+
+/**
+ * Retrieves an integer value from a UHashtable using an integer key,
+ * as previously stored by uhash_iputiAllowZero() or uhash_iputi().
+ *
+ * @param hash The target UHashtable.
+ * @param key An integer key stored in a hashtable
+ * @param found A pointer to a boolean which will be set for whether the key was found.
+ * @return The requested item, or 0 if not found.
+ */
+U_CAPI int32_t U_EXPORT2
+uhash_igetiAndFound(const UHashtable *hash,
+                    int32_t key,
+                    UBool *found);
+
+/**
  * Remove an item from a UHashtable stored by uhash_put().
  * @param hash The target UHashtable.
  * @param key A key stored in a hashtable
@@ -496,6 +569,26 @@
 uhash_removeAll(UHashtable *hash);
 
 /**
+ * Returns true if the UHashtable contains an item with this pointer key.
+ *
+ * @param hash The target UHashtable.
+ * @param key A pointer key stored in a hashtable
+ * @return true if the key is found.
+ */
+U_CAPI UBool U_EXPORT2
+uhash_containsKey(const UHashtable *hash, const void *key);
+
+/**
+ * Returns true if the UHashtable contains an item with this integer key.
+ *
+ * @param hash The target UHashtable.
+ * @param key An integer key stored in a hashtable
+ * @return true if the key is found.
+ */
+U_CAPI UBool U_EXPORT2
+uhash_icontainsKey(const UHashtable *hash, int32_t key);
+
+/**
  * Locate an element of a UHashtable.  The caller must not modify the
  * returned object.  The primary use of this function is to obtain the
  * stored key when it may not be identical to the search key.  For
diff --git a/icu4c/source/common/unicode/localematcher.h b/icu4c/source/common/unicode/localematcher.h
index 63a68b0..63d20f2 100644
--- a/icu4c/source/common/unicode/localematcher.h
+++ b/icu4c/source/common/unicode/localematcher.h
@@ -704,7 +704,7 @@
     LSR *lsrs;
     int32_t supportedLocalesLength;
     // These are in preference order: 1. Default locale 2. paradigm locales 3. others.
-    UHashtable *supportedLsrToIndex;  // Map<LSR, Integer> stores index+1 because 0 is "not found"
+    UHashtable *supportedLsrToIndex;  // Map<LSR, Integer>
     // Array versions of the supportedLsrToIndex keys and values.
     // The distance lookup loops over the supportedLSRs and returns the index of the best match.
     const LSR **supportedLSRs;
diff --git a/icu4c/source/test/cintltst/chashtst.c b/icu4c/source/test/cintltst/chashtst.c
index 5d1a4a8..405d56d 100644
--- a/icu4c/source/test/cintltst/chashtst.c
+++ b/icu4c/source/test/cintltst/chashtst.c
@@ -11,6 +11,7 @@
 *******************************************************************************
 */
 
+#include <stdbool.h>
 #include "cintltst.h"
 #include "uhash.h"
 #include "unicode/ctest.h"
@@ -22,6 +23,7 @@
  *********************************************************************/
 
 static void TestBasic(void);
+static void TestAllowZero(void);
 static void TestOtherAPI(void);
 static void hashIChars(void);
 
@@ -87,6 +89,7 @@
 void addHashtableTest(TestNode** root) {
    
     addTest(root, &TestBasic,   "tsutil/chashtst/TestBasic");
+    addTest(root, &TestAllowZero, "tsutil/chashtst/TestAllowZero");
     addTest(root, &TestOtherAPI, "tsutil/chashtst/TestOtherAPI");
     addTest(root, &hashIChars, "tsutil/chashtst/hashIChars");
     
@@ -133,6 +136,9 @@
     _get(hash, omega, 48);
     _get(hash, two, 200);
 
+    // puti(key, value==0) removes the key's element.
+    _put(hash, two, 0, 200);
+
     if(_compareChars((void*)one, (void*)three) == TRUE ||
         _compareChars((void*)one, (void*)one2) != TRUE ||
         _compareChars((void*)one, (void*)one) != TRUE ||
@@ -145,9 +151,56 @@
         _compareIChars((void*)one, NULL) == TRUE  )  {
         log_err("FAIL: compareIChars failed\n");
     }
-     
-    uhash_close(hash);
 
+    uhash_close(hash);
+}
+
+static void TestAllowZero() {
+    UErrorCode status = U_ZERO_ERROR;
+    UHashtable *hash = uhash_open(hashChars, isEqualChars, NULL,  &status);
+    if (U_FAILURE(status)) {
+        log_err("FAIL: uhash_open failed with %s and returned 0x%08x\n",
+                u_errorName(status), hash);
+        return;
+    }
+    if (hash == NULL) {
+        log_err("FAIL: uhash_open returned NULL\n");
+        return;
+    }
+    log_verbose("Ok: uhash_open returned 0x%08X\n", hash);
+
+    int32_t oldValue = uhash_putiAllowZero(hash, (char *)"one", 1, &status);
+    UBool found = false;
+    if (U_FAILURE(status) || oldValue != 0 || !uhash_containsKey(hash, "one") ||
+            uhash_geti(hash, "one") != 1 ||
+            uhash_getiAndFound(hash, "one", &found) != 1 || !found) {
+        log_err("FAIL: uhash_putiAllowZero(one, 1)");
+    }
+    oldValue = uhash_putiAllowZero(hash, (char *)"zero", 0, &status);
+    found = false;
+    if (U_FAILURE(status) || oldValue != 0 || !uhash_containsKey(hash, "zero") ||
+            uhash_geti(hash, "zero") != 0 ||
+            uhash_getiAndFound(hash, "zero", &found) != 0 || !found) {
+        log_err("FAIL: uhash_putiAllowZero(zero, 0)");
+    }
+    // Overwrite "one" to 0.
+    oldValue = uhash_putiAllowZero(hash, (char *)"one", 0, &status);
+    found = false;
+    if (U_FAILURE(status) || oldValue != 1 || !uhash_containsKey(hash, "one") ||
+            uhash_geti(hash, "one") != 0 ||
+            uhash_getiAndFound(hash, "one", &found) != 0 || !found) {
+        log_err("FAIL: uhash_putiAllowZero(one, 0)");
+    }
+    // Remove "zero" using puti(zero, 0).
+    oldValue = uhash_puti(hash, (char *)"zero", 0, &status);
+    found = true;
+    if (U_FAILURE(status) || oldValue != 0 || uhash_containsKey(hash, "zero") ||
+            uhash_geti(hash, "zero") != 0 ||
+            uhash_getiAndFound(hash, "zero", &found) != 0 || found) {
+        log_err("FAIL: uhash_puti(zero, 0)");
+    }
+
+    uhash_close(hash);
 }
 
 static void TestOtherAPI(void){
@@ -343,30 +396,46 @@
     int32_t oldValue =
         uhash_puti(hash, (void*) key, value, &status);
     if (U_FAILURE(status)) {
-        log_err("FAIL: uhash_put(%s) failed with %s and returned %ld\n",
+        log_err("FAIL: uhash_puti(%s) failed with %s and returned %ld\n",
                 key, u_errorName(status), oldValue);
     } else if (oldValue != expectedOldValue) {
-        log_err("FAIL: uhash_put(%s) returned old value %ld; expected %ld\n",
+        log_err("FAIL: uhash_puti(%s) returned old value %ld; expected %ld\n",
                 key, oldValue, expectedOldValue);
     } else {
-        log_verbose("Ok: uhash_put(%s, %d) returned old value %ld\n",
+        log_verbose("Ok: uhash_puti(%s, %d) returned old value %ld\n",
                     key, value, oldValue);
     }
+    int32_t newValue = uhash_geti(hash, key);
+    if (newValue != value) {
+        log_err("FAIL: uhash_puti(%s) failed to set the intended value %ld: "
+                "uhash_geti() returns %ld\n",
+                key, value, newValue);
+    }
+    UBool contained = uhash_containsKey(hash, key);
+    if (value == 0) {
+        if (contained) {
+            log_err("FAIL: uhash_puti(%s, zero) failed to remove the key item: "
+                    "uhash_containsKey() returns true\n",
+                    key);
+        }
+    } else {
+        if (!contained) {
+            log_err("FAIL: uhash_puti(%s, not zero) appears to have removed the key item: "
+                    "uhash_containsKey() returns false\n",
+                    key);
+        }
+    }
 }
 
 static void _get(UHashtable* hash,
           const char* key,
           int32_t expectedValue) {
-    UErrorCode status = U_ZERO_ERROR;
     int32_t value = uhash_geti(hash, key);
-    if (U_FAILURE(status)) {
-        log_err("FAIL: uhash_get(%s) failed with %s and returned %ld\n",
-                key, u_errorName(status), value);
-    } else if (value != expectedValue) {
-        log_err("FAIL: uhash_get(%s) returned %ld; expected %ld\n",
+    if (value != expectedValue) {
+        log_err("FAIL: uhash_geti(%s) returned %ld; expected %ld\n",
                 key, value, expectedValue);
     } else {
-        log_verbose("Ok: uhash_get(%s) returned value %ld\n",
+        log_verbose("Ok: uhash_geti(%s) returned value %ld\n",
                     key, value);
     }
 }
@@ -376,11 +445,15 @@
              int32_t expectedValue) {
     int32_t value = uhash_removei(hash, key);
     if (value != expectedValue) {
-        log_err("FAIL: uhash_remove(%s) returned %ld; expected %ld\n",
+        log_err("FAIL: uhash_removei(%s) returned %ld; expected %ld\n",
                 key, value, expectedValue);
     } else {
-        log_verbose("Ok: uhash_remove(%s) returned old value %ld\n",
+        log_verbose("Ok: uhash_removei(%s) returned old value %ld\n",
                     key, value);
     }
+    if (uhash_containsKey(hash, key)) {
+        log_err("FAIL: uhash_removei(%s) failed to remove the key item: "
+                "uhash_containsKey() returns false\n",
+                key);
+    }
 }
-
diff --git a/icu4c/source/tools/toolutil/xmlparser.cpp b/icu4c/source/tools/toolutil/xmlparser.cpp
index 60cd49c..482d6f6 100644
--- a/icu4c/source/tools/toolutil/xmlparser.cpp
+++ b/icu4c/source/tools/toolutil/xmlparser.cpp
@@ -658,7 +658,7 @@
         return (const UnicodeString *)he->key.pointer;
     } else {
         // add this new name and return its hashed key pointer
-        fNames.puti(s, 0, errorCode);
+        fNames.puti(s, 1, errorCode);
         he=fNames.find(s);
         return (const UnicodeString *)he->key.pointer;
     }