Merge "Add JNI wrappers for ImmutableIndex"
diff --git a/luni/src/main/java/libcore/icu/AlphabeticIndex.java b/luni/src/main/java/libcore/icu/AlphabeticIndex.java
index 534e14a..efd45e9 100644
--- a/luni/src/main/java/libcore/icu/AlphabeticIndex.java
+++ b/luni/src/main/java/libcore/icu/AlphabeticIndex.java
@@ -22,6 +22,56 @@
  * Exposes icu4c's AlphabeticIndex.
  */
 public final class AlphabeticIndex {
+
+  /**
+   * Exposes icu4c's ImmutableIndex (new to icu 51). This exposes a read-only,
+   * thread safe snapshot view of an AlphabeticIndex at the moment it was
+   * created, and allows for random access to buckets by index.
+   */
+  public static final class ImmutableIndex {
+    private long peer;
+
+    private ImmutableIndex(long peer) {
+      this.peer = peer;
+    }
+
+    @Override protected synchronized void finalize() throws Throwable {
+      try {
+        destroy(peer);
+        peer = 0;
+      } finally {
+        super.finalize();
+      }
+    }
+
+    /**
+     * Returns the number of the label buckets in this index.
+     */
+    public int getBucketCount() {
+      return getBucketCount(peer);
+    }
+
+    /**
+     * Returns the index of the bucket in which 's' should appear.
+     * Function is synchronized because underlying routine walks an iterator
+     * whose state is maintained inside the index object.
+     */
+    public int getBucketIndex(String s) {
+      return getBucketIndex(peer, s);
+    }
+
+    /**
+     * Returns the label for the bucket at the given index (as returned by getBucketIndex).
+     */
+    public String getBucketLabel(int index) {
+      return getBucketLabel(peer, index);
+    }
+
+    private static native int getBucketCount(long peer);
+    private static native int getBucketIndex(long peer, String s);
+    private static native String getBucketLabel(long peer, int index);
+  }
+
   private long peer;
 
   /**
@@ -48,16 +98,18 @@
    * it remains that of the locale that was originally specified
    * when creating this index.
    */
-  public synchronized void addLabels(Locale locale) {
+  public synchronized AlphabeticIndex addLabels(Locale locale) {
     addLabels(peer, locale.toString());
+    return this;
   }
 
   /**
    * Adds the index characters in the range between the specified start and
    * end code points, inclusive.
    */
-  public synchronized void addLabelRange(int codePointStart, int codePointEnd) {
+  public synchronized AlphabeticIndex addLabelRange(int codePointStart, int codePointEnd) {
     addLabelRange(peer, codePointStart, codePointEnd);
+    return this;
   }
 
   /**
@@ -79,10 +131,17 @@
   /**
    * Returns the label for the bucket at the given index (as returned by getBucketIndex).
    */
-  public String getBucketLabel(int index) {
+  public synchronized String getBucketLabel(int index) {
     return getBucketLabel(peer, index);
   }
 
+  /**
+   * Returns an ImmutableIndex created from this AlphabeticIndex.
+   */
+  public synchronized ImmutableIndex getImmutableIndex() {
+    return new ImmutableIndex(buildImmutableIndex(peer));
+  }
+
   private static native long create(String locale);
   private static native void destroy(long peer);
   private static native void addLabels(long peer, String locale);
@@ -90,4 +149,5 @@
   private static native int getBucketCount(long peer);
   private static native int getBucketIndex(long peer, String s);
   private static native String getBucketLabel(long peer, int index);
+  private static native long buildImmutableIndex(long peer);
 }
diff --git a/luni/src/main/native/libcore_icu_AlphabeticIndex.cpp b/luni/src/main/native/libcore_icu_AlphabeticIndex.cpp
index bda80c0..b815853 100644
--- a/luni/src/main/native/libcore_icu_AlphabeticIndex.cpp
+++ b/luni/src/main/native/libcore_icu_AlphabeticIndex.cpp
@@ -113,6 +113,56 @@
   return env->NewString(label.getBuffer(), label.length());
 }
 
+static jlong AlphabeticIndex_buildImmutableIndex(JNIEnv* env, jclass, jlong peer) {
+  AlphabeticIndex* ai = fromPeer(peer);
+  UErrorCode status = U_ZERO_ERROR;
+  AlphabeticIndex::ImmutableIndex* ii = ai->buildImmutableIndex(status);
+  if (maybeThrowIcuException(env, "AlphabeticIndex::buildImmutableIndex", status)) {
+    return 0;
+  }
+  return reinterpret_cast<uintptr_t>(ii);
+}
+
+static AlphabeticIndex::ImmutableIndex* immutableIndexFromPeer(jlong peer) {
+  return reinterpret_cast<AlphabeticIndex::ImmutableIndex*>(static_cast<uintptr_t>(peer));
+}
+
+static jint ImmutableIndex_getBucketCount(JNIEnv*, jclass, jlong peer) {
+  AlphabeticIndex::ImmutableIndex* ii = immutableIndexFromPeer(peer);
+  return ii->getBucketCount();
+}
+
+static jint ImmutableIndex_getBucketIndex(JNIEnv* env, jclass, jlong peer, jstring javaString) {
+  AlphabeticIndex::ImmutableIndex* ii = immutableIndexFromPeer(peer);
+  ScopedJavaUnicodeString string(env, javaString);
+  if (!string.valid()) {
+    return -1;
+  }
+  UErrorCode status = U_ZERO_ERROR;
+  jint result = ii->getBucketIndex(string.unicodeString(), status);
+  if (maybeThrowIcuException(env, "AlphabeticIndex::ImmutableIndex::getBucketIndex", status)) {
+    return -1;
+  }
+  return result;
+}
+
+static jstring ImmutableIndex_getBucketLabel(JNIEnv* env, jclass, jlong peer, jint index) {
+  AlphabeticIndex::ImmutableIndex* ii = immutableIndexFromPeer(peer);
+  const AlphabeticIndex::Bucket* bucket = ii->getBucket(index);
+  if (bucket == NULL) {
+    jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Invalid index: %d", index);
+    return NULL;
+  }
+
+  // Return "" for the underflow/inflow/overflow buckets.
+  if (bucket->getLabelType() != U_ALPHAINDEX_NORMAL) {
+    return env->NewStringUTF("");
+  }
+
+  const UnicodeString& label(bucket->getLabel());
+  return env->NewString(label.getBuffer(), label.length());
+}
+
 static JNINativeMethod gMethods[] = {
   NATIVE_METHOD(AlphabeticIndex, create, "(Ljava/lang/String;)J"),
   NATIVE_METHOD(AlphabeticIndex, destroy, "(J)V"),
@@ -121,7 +171,14 @@
   NATIVE_METHOD(AlphabeticIndex, getBucketCount, "(J)I"),
   NATIVE_METHOD(AlphabeticIndex, getBucketIndex, "(JLjava/lang/String;)I"),
   NATIVE_METHOD(AlphabeticIndex, getBucketLabel, "(JI)Ljava/lang/String;"),
+  NATIVE_METHOD(AlphabeticIndex, buildImmutableIndex, "(J)J"),
+};
+static JNINativeMethod gImmutableIndexMethods[] = {
+  NATIVE_METHOD(ImmutableIndex, getBucketCount, "(J)I"),
+  NATIVE_METHOD(ImmutableIndex, getBucketIndex, "(JLjava/lang/String;)I"),
+  NATIVE_METHOD(ImmutableIndex, getBucketLabel, "(JI)Ljava/lang/String;"),
 };
 void register_libcore_icu_AlphabeticIndex(JNIEnv* env) {
   jniRegisterNativeMethods(env, "libcore/icu/AlphabeticIndex", gMethods, NELEM(gMethods));
+  jniRegisterNativeMethods(env, "libcore/icu/AlphabeticIndex$ImmutableIndex", gImmutableIndexMethods, NELEM(gImmutableIndexMethods));
 }
diff --git a/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java b/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java
index e4b90ea..ff0a34c 100644
--- a/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java
+++ b/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java
@@ -19,22 +19,26 @@
 import java.util.Locale;
 
 public class AlphabeticIndexTest extends junit.framework.TestCase {
-  private static void assertHasLabel(AlphabeticIndex ai, String string, String expectedLabel) {
-    ai.addLabels(Locale.US);
-    int index = ai.getBucketIndex(string);
-    String label = ai.getBucketLabel(index);
+  private static ImmutableIndex createIndex(Locale locale) {
+    return new AlphabeticIndex(locale).addLabels(Locale.US)
+        .getImmutableIndex();
+  }
+
+  private static void assertHasLabel(ImmutableIndex ii, String string, String expectedLabel) {
+    int index = ii.getBucketIndex(string);
+    String label = ii.getBucketLabel(index);
     assertEquals(expectedLabel, label);
   }
 
   public void test_en() throws Exception {
     // English [A-Z]
-    AlphabeticIndex en = new AlphabeticIndex(Locale.ENGLISH);
+    ImmutableIndex en = createIndex(Locale.ENGLISH);
     assertHasLabel(en, "Allen", "A");
     assertHasLabel(en, "allen", "A");
   }
 
   public void test_ja() throws Exception {
-    AlphabeticIndex ja = new AlphabeticIndex(Locale.JAPANESE);
+    ImmutableIndex ja = createIndex(Locale.JAPANESE);
 
     // Japanese
     //   sorts hiragana/katakana, Kanji/Chinese, English, other
@@ -61,7 +65,7 @@
   public void test_ko() throws Exception {
     // Korean (sorts Korean, then English)
     // …, ᄀ, ᄂ, ᄃ, ᄅ, ᄆ, ᄇ, ᄉ, ᄋ, ᄌ, ᄎ, ᄏ, ᄐ, ᄑ, ᄒ, …
-    AlphabeticIndex ko = new AlphabeticIndex(Locale.KOREAN);
+    ImmutableIndex ko = createIndex(Locale.KOREAN);
     assertHasLabel(ko, "\u1100", "\u1100");
     assertHasLabel(ko, "\u3131", "\u1100");
     assertHasLabel(ko, "\u1101", "\u1100");
@@ -71,7 +75,7 @@
   public void test_cs() throws Exception {
     // Czech
     // …, [A-C], Č,[D-H], CH, [I-R], Ř, S, Š, [T-Z], Ž, …
-    AlphabeticIndex cs = new AlphabeticIndex(new Locale("cs"));
+    ImmutableIndex cs = createIndex(new Locale("cs"));
     assertHasLabel(cs, "Cena", "C");
     assertHasLabel(cs, "Čáp", "\u010c");
     assertHasLabel(cs, "Ruda", "R");
@@ -85,14 +89,14 @@
 
   public void test_fr() throws Exception {
     // French: [A-Z] (no accented chars)
-    AlphabeticIndex fr = new AlphabeticIndex(Locale.FRENCH);
+    ImmutableIndex fr = createIndex(Locale.FRENCH);
     assertHasLabel(fr, "Øfer", "O");
     assertHasLabel(fr, "Œster", "O");
   }
 
   public void test_da() throws Exception {
     // Danish: [A-Z], Æ, Ø, Å
-    AlphabeticIndex da = new AlphabeticIndex(new Locale("da"));
+    ImmutableIndex da = createIndex(new Locale("da"));
     assertHasLabel(da, "Ænes", "\u00c6");
     assertHasLabel(da, "Øfer", "\u00d8");
     assertHasLabel(da, "Œster", "\u00d8");
@@ -101,14 +105,14 @@
 
   public void test_de() throws Exception {
     // German: [A-Z] (no ß or umlauted characters in standard alphabet)
-    AlphabeticIndex de = new AlphabeticIndex(Locale.GERMAN);
+    ImmutableIndex de = createIndex(Locale.GERMAN);
     assertHasLabel(de, "ßind", "S");
   }
 
   public void test_th() throws Exception {
     // Thai (sorts English then Thai)
     // …, ก, ข, ฃ, ค, ฅ, ฆ, ง, จ, ฉ, ช, ซ, ฌ, ญ, ฎ, ฏ, ฐ, ฑ, ฒ, ณ, ด, ต, ถ, ท, ธ, น, บ, ป, ผ, ฝ, พ, ฟ, ภ, ม, ย, ร, ฤ, ล, ฦ, ว, ศ, ษ, ส, ห, ฬ, อ, ฮ, …,
-    AlphabeticIndex th = new AlphabeticIndex(new Locale("th"));
+    ImmutableIndex th = createIndex(new Locale("th"));
     assertHasLabel(th, "\u0e2d\u0e07\u0e04\u0e4c\u0e40\u0e25\u0e47\u0e01", "\u0e2d");
     assertHasLabel(th, "\u0e2a\u0e34\u0e07\u0e2b\u0e40\u0e2a\u0e19\u0e35", "\u0e2a");
   }
@@ -116,21 +120,21 @@
   public void test_ar() throws Exception {
     // Arabic (sorts English then Arabic)
     // …, ا, ب, ت, ث, ج, ح, خ, د, ذ, ر, ز, س, ش, ص, ض, ط, ظ, ع, غ, ف, ق, ك, ل, م, ن, ه, و, ي, …
-    AlphabeticIndex ar = new AlphabeticIndex(new Locale("ar"));
+    ImmutableIndex ar = createIndex(new Locale("ar"));
     assertHasLabel(ar, "\u0646\u0648\u0631", /* Noor */ "\u0646");
   }
 
   public void test_he() throws Exception {
     // Hebrew (sorts English then Hebrew)
     // …, א, ב, ג, ד, ה, ו, ז, ח, ט, י, כ, ל, מ, נ, ס, ע, פ, צ, ק, ר, ש, ת, …
-    AlphabeticIndex he = new AlphabeticIndex(new Locale("he"));
+    ImmutableIndex he = createIndex(new Locale("he"));
     assertHasLabel(he, "\u05e4\u05e8\u05d9\u05d3\u05de\u05df", "\u05e4");
   }
 
   public void test_zh_CN() throws Exception {
     // Simplified Chinese (default collator Pinyin): [A-Z]
     // Shen/Chen (simplified): should be, usually, 'S' for name collator and 'C' for apps/other
-    AlphabeticIndex zh_CN = new AlphabeticIndex(new Locale("zh", "CN"));
+    ImmutableIndex zh_CN = createIndex(new Locale("zh", "CN"));
 
     // Jia/Gu: should be, usually, 'J' for name collator and 'G' for apps/other
     assertHasLabel(zh_CN, "\u8d3e", "J");
@@ -145,7 +149,7 @@
     // Traditional Chinese
     // …, 一, 丁, 丈, 不, 且, 丞, 串, 並, 亭, 乘, 乾, 傀, 亂, 僎, 僵, 儐, 償, 叢, 儳, 嚴, 儷, 儻, 囌, 囑, 廳, …
     // Shen/Chen
-    AlphabeticIndex zh_TW = new AlphabeticIndex(new Locale("zh", "TW"));
+    ImmutableIndex zh_TW = createIndex(new Locale("zh", "TW"));
     assertHasLabel(zh_TW, "\u6c88", "\u4e32");
     assertHasLabel(zh_TW, "\u700b", "\u53e2");
     // Jia/Gu
@@ -170,24 +174,24 @@
   }
 
   public void test_getBucketIndex_NPE() throws Exception {
-    AlphabeticIndex ai = new AlphabeticIndex(Locale.US);
+    ImmutableIndex ii = createIndex(Locale.US);
     try {
-      ai.getBucketIndex(null);
+      ii.getBucketIndex(null);
       fail();
     } catch (NullPointerException expected) {
     }
   }
 
   public void test_getBucketLabel_invalid() throws Exception {
-    AlphabeticIndex ai = new AlphabeticIndex(Locale.US);
+    ImmutableIndex ii = createIndex(Locale.US);
     try {
-      ai.getBucketLabel(-1);
+      ii.getBucketLabel(-1);
       fail();
     } catch (IllegalArgumentException expected) {
     }
 
     try {
-      ai.getBucketLabel(123456);
+      ii.getBucketLabel(123456);
       fail();
     } catch (IllegalArgumentException expected) {
     }