Merge "Move ICU4C regex usage from libcore/"
diff --git a/android_icu4j/Android.bp b/android_icu4j/Android.bp
index 5da0613..9725b81 100644
--- a/android_icu4j/Android.bp
+++ b/android_icu4j/Android.bp
@@ -29,10 +29,77 @@
visibility: [
"//libcore:__subpackages__",
],
- srcs: ["src/main/java/**/*.java"],
+ srcs: [
+ ":android_icu4j_repackaged_src_files",
+ ":libcore_icu_bridge_src_files",
+ ],
path: "src/main/java",
}
+filegroup {
+ name: "android_icu4j_repackaged_src_files",
+ srcs: ["src/main/java/android/icu/**/*.java"],
+ path: "src/main/java",
+}
+
+// The files contains Android-specific codes to expose intra-core APIs
+// from ICU4J/ICU4C to libcore. The package is com.android.icu.* and should not
+// expose any public APIs.
+filegroup {
+ name: "libcore_icu_bridge_src_files",
+ srcs: ["src/main/java/com/android/icu/**/*.java"],
+ path: "src/main/java",
+}
+
+// Rule generating resource lib for android_icu4j.
+// In the downstream branch master-icu-dev, the resource files are generated.
+java_library {
+ name: "android_icu4j_resources_lib",
+ visibility: [
+ "//libcore",
+ ],
+ java_resource_dirs: ["resources"],
+ sdk_version: "none",
+ system_modules: "none",
+}
+
+// Same as android_icu4j_resources_lib but compiling against core_current sdk
+// in order to avoid using non-public API from core-libart and core-oj
+// because core-icu4j will be in a different i18n APEX module.
+
+java_library {
+ name: "android_icu4j_resources_lib_sdk_core_current",
+ visibility: [
+ "//libcore",
+ ],
+ java_resource_dirs: ["resources"],
+ sdk_version: "core_current",
+}
+
+// core-repackaged-icu4j contains only the repackaged ICU4J that does not
+// use any internal or android specific code. If it ever did then it could depend on
+// art-module-intra-core-api-stubs-system-modules (a superset) instead.
+// It is important that core-icu4j is restricted to only use stable APIs from the ART module
+// since it is in a different APEX module that can be updated independently.
+java_library_static {
+ name: "core-repackaged-icu4j",
+ installable: false,
+ srcs: [":android_icu4j_repackaged_src_files"],
+ // The resource files are generated in the downstream branch master-icu-dev.
+ java_resource_dirs: ["resources"],
+
+ sdk_version: "none",
+ system_modules: "art-module-public-api-stubs-system-modules",
+
+ dxflags: ["--core-library"],
+ errorprone: {
+ javacflags: [
+ "-Xep:MissingOverride:OFF", // Ignore missing @Override.
+ "-Xep:ConstantOverflow:WARN", // Known constant overflow in SplittableRandom
+ ],
+ },
+}
+
// A separated core library that contains ICU4J because ICU4J will be in a different APEX module,
// not in ART module.
java_library {
@@ -45,26 +112,15 @@
installable: true,
hostdex: true,
- srcs: [":android_icu4j_src_files"],
+ srcs: [":libcore_icu_bridge_src_files"],
+ static_libs: ["core-repackaged-icu4j"],
- // The resource files are generated in the downstream branch master-icu-dev.
- java_resource_dirs: ["resources"],
-
- // We use art-module-public-api-stubs-system-modules when compiling core-icu4j as ICU4J does not
- // use any internal or android specific code. If it ever did then it could depend on
- // art-module-intra-core-api-stubs-system-modules (a superset) instead.
// It is important that core-icu4j is restricted to only use stable APIs from the ART module
// since it is in a different APEX module that can be updated independently.
sdk_version: "none",
- system_modules: "art-module-public-api-stubs-system-modules",
+ system_modules: "art-module-intra-core-api-stubs-system-modules",
dxflags: ["--core-library"],
- errorprone: {
- javacflags: [
- "-Xep:MissingOverride:OFF", // Ignore missing @Override.
- "-Xep:ConstantOverflow:WARN", // Known constant overflow in SplittableRandom
- ],
- },
}
//
diff --git a/android_icu4j/api/intra/current-api.txt b/android_icu4j/api/intra/current-api.txt
index 1d0dd77..4911658 100644
--- a/android_icu4j/api/intra/current-api.txt
+++ b/android_icu4j/api/intra/current-api.txt
@@ -181,3 +181,26 @@
}
+package com.android.icu.util.regex {
+
+ public class NativeMatcher {
+ method public static com.android.icu.util.regex.NativeMatcher create(com.android.icu.util.regex.NativePattern);
+ method public boolean find(int, int[]);
+ method public boolean findNext(int[]);
+ method public int getMatchedGroupIndex(String);
+ method public int groupCount();
+ method public boolean hitEnd();
+ method public boolean lookingAt(int[]);
+ method public boolean matches(int[]);
+ method public boolean requireEnd();
+ method public void setInput(String, int, int);
+ method public void useAnchoringBounds(boolean);
+ method public void useTransparentBounds(boolean);
+ }
+
+ public class NativePattern {
+ method public static com.android.icu.util.regex.NativePattern create(String, int);
+ }
+
+}
+
diff --git a/android_icu4j/src/main/java/com/android/icu/util/regex/NativeMatcher.java b/android_icu4j/src/main/java/com/android/icu/util/regex/NativeMatcher.java
new file mode 100644
index 0000000..9165b3e
--- /dev/null
+++ b/android_icu4j/src/main/java/com/android/icu/util/regex/NativeMatcher.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.icu.util.regex;
+
+import libcore.util.NativeAllocationRegistry;
+
+public class NativeMatcher {
+
+ private static final NativeAllocationRegistry REGISTRY = NativeAllocationRegistry
+ .createMalloced(NativeMatcher.class.getClassLoader(), getNativeFinalizer());
+
+ /*
+ * @ReachabilitySensitive ensures that this and NativePattern remain reachable while we're using
+ * nativePattern.address directly.
+ */
+ @dalvik.annotation.optimization.ReachabilitySensitive
+ private final NativePattern nativePattern;
+ @dalvik.annotation.optimization.ReachabilitySensitive
+ private final long address;
+
+ @libcore.api.IntraCoreApi
+ public static NativeMatcher create(NativePattern pattern) {
+ return new NativeMatcher(pattern);
+ }
+
+ private NativeMatcher(NativePattern pattern) {
+ nativePattern = pattern;
+ address = openImpl(pattern.address);
+ REGISTRY.registerNativeAllocation(this, address);
+ }
+
+ @libcore.api.IntraCoreApi
+ public int getMatchedGroupIndex(String groupName) {
+ return getMatchedGroupIndexImpl(nativePattern.address, groupName);
+ }
+
+ @libcore.api.IntraCoreApi
+ public boolean find(int startIndex, int[] offsets) {
+ return findImpl(address, startIndex, offsets);
+ }
+
+ @libcore.api.IntraCoreApi
+ public boolean findNext(int[] offsets) {
+ return findNextImpl(address, offsets);
+ }
+
+ @libcore.api.IntraCoreApi
+ public int groupCount() {
+ return groupCountImpl(address);
+ }
+
+ @libcore.api.IntraCoreApi
+ public boolean hitEnd() {
+ return hitEndImpl(address);
+ }
+
+ @libcore.api.IntraCoreApi
+ public boolean lookingAt(int[] offsets) {
+ return lookingAtImpl(address, offsets);
+ }
+
+ @libcore.api.IntraCoreApi
+ public boolean matches(int[] offsets) {
+ return matchesImpl(address, offsets);
+ }
+
+ @libcore.api.IntraCoreApi
+ public boolean requireEnd() {
+ return requireEndImpl(address);
+ }
+
+ @libcore.api.IntraCoreApi
+ public void setInput(String input, int start, int end) {
+ setInputImpl(address, input, start, end);
+ }
+
+ @libcore.api.IntraCoreApi
+ public void useAnchoringBounds(boolean value) {
+ useAnchoringBoundsImpl(address, value);
+ }
+
+ @libcore.api.IntraCoreApi
+ public void useTransparentBounds(boolean value) {
+ useTransparentBoundsImpl(address, value);
+ }
+
+ private static native long openImpl(long patternAddr);
+ private static native int getMatchedGroupIndexImpl(long patternAddr, String name);
+ private static native boolean findImpl(long addr, int startIndex, int[] offsets);
+ private static native boolean findNextImpl(long addr, int[] offsets);
+ private static native int groupCountImpl(long addr);
+ private static native boolean hitEndImpl(long addr);
+ private static native boolean lookingAtImpl(long addr, int[] offsets);
+ private static native boolean matchesImpl(long addr, int[] offsets);
+ private static native boolean requireEndImpl(long addr);
+ private static native void setInputImpl(long addr, String input, int start, int end);
+ private static native void useAnchoringBoundsImpl(long addr, boolean value);
+ private static native void useTransparentBoundsImpl(long addr, boolean value);
+
+ /**
+ * @return address of a native function of type <code>void f(void* nativePtr)</code>
+ * used to free this kind of native allocation
+ */
+ private static native long getNativeFinalizer();
+
+}
diff --git a/android_icu4j/src/main/java/com/android/icu/util/regex/NativePattern.java b/android_icu4j/src/main/java/com/android/icu/util/regex/NativePattern.java
new file mode 100644
index 0000000..2d0a6ca
--- /dev/null
+++ b/android_icu4j/src/main/java/com/android/icu/util/regex/NativePattern.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.icu.util.regex;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * Provide an entry point to use ICU4C icu::RegexPattern.
+ */
+@libcore.api.IntraCoreApi
+public class NativePattern {
+
+ private static final NativeAllocationRegistry REGISTRY = NativeAllocationRegistry
+ .createMalloced(NativePattern.class.getClassLoader(), getNativeFinalizer());
+
+ @dalvik.annotation.optimization.ReachabilitySensitive
+ final long address;
+
+ @libcore.api.IntraCoreApi
+ public static NativePattern create(String pattern, int flags) {
+ return new NativePattern(pattern, flags);
+ }
+
+ private NativePattern(String pattern, int flags) {
+ address = compileImpl(pattern, flags);
+ REGISTRY.registerNativeAllocation(this, address);
+ }
+
+ /**
+ * @return native address of the native allocation.
+ */
+ private static native long compileImpl(String pattern, int flags);
+
+ /**
+ * @return address of a native function of type <code>void f(void* nativePtr)</code>
+ * used to free this kind of native allocation
+ */
+ private static native long getNativeFinalizer();
+
+}
diff --git a/android_icu4j/src/main/java/com/android/icu/util/regex/TEST_MAPPING b/android_icu4j/src/main/java/com/android/icu/util/regex/TEST_MAPPING
new file mode 100644
index 0000000..a2bd15f
--- /dev/null
+++ b/android_icu4j/src/main/java/com/android/icu/util/regex/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+ "postsubmit": [
+ {
+ "name": "CtsLibcoreTestCases",
+ "options": [
+ {
+ "include-filter": "org.apache.harmony.regex.tests.java.util.regex"
+ },
+ {
+ "include-filter": "libcore.java.util.regex"
+ },
+ {
+ "include-filter": "org.apache.harmony.tests.java.util.regex"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/android_icu4j/src/main/jni/Android.bp b/android_icu4j/src/main/jni/Android.bp
new file mode 100644
index 0000000..15eae8a
--- /dev/null
+++ b/android_icu4j/src/main/jni/Android.bp
@@ -0,0 +1,51 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+package {
+ default_visibility: [
+ "//art/build/apex",
+ ],
+}
+
+cc_library_shared {
+ name: "libicu_jni",
+ host_supported: true,
+ shared_libs: [
+ "libbase",
+ "libicuuc",
+ "libicui18n",
+ "liblog",
+ "libnativehelper",
+ ],
+ srcs: [
+ "*.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ ],
+ cppflags: ["-DU_USING_ICU_NAMESPACE=0"],
+ target: {
+ android: {
+ cflags: [
+ // -DANDROID_LINK_SHARED_ICU4C to enable access to the full ICU4C.
+ // See external/icu/android_icu4c/include/uconfig_local.h
+ // for more information.
+ "-DANDROID_LINK_SHARED_ICU4C",
+ ],
+ },
+ },
+}
diff --git a/android_icu4j/src/main/jni/IcuUtilities.cpp b/android_icu4j/src/main/jni/IcuUtilities.cpp
new file mode 100644
index 0000000..53c7617
--- /dev/null
+++ b/android_icu4j/src/main/jni/IcuUtilities.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <nativehelper/JNIHelp.h>
+
+#include "IcuUtilities.h"
+
+#include "JniConstants.h"
+#include "unicode/strenum.h"
+#include "unicode/ustring.h"
+#include "unicode/uloc.h"
+
+bool maybeThrowIcuException(JNIEnv* env, const char* function, UErrorCode error) {
+ if (U_SUCCESS(error)) {
+ return false;
+ }
+ const char* exceptionClass = "java/lang/RuntimeException";
+ if (error == U_ILLEGAL_ARGUMENT_ERROR) {
+ exceptionClass = "java/lang/IllegalArgumentException";
+ } else if (error == U_INDEX_OUTOFBOUNDS_ERROR || error == U_BUFFER_OVERFLOW_ERROR) {
+ exceptionClass = "java/lang/ArrayIndexOutOfBoundsException";
+ } else if (error == U_UNSUPPORTED_ERROR) {
+ exceptionClass = "java/lang/UnsupportedOperationException";
+ } else if (error == U_FORMAT_INEXACT_ERROR) {
+ exceptionClass = "java/lang/ArithmeticException";
+ }
+ jniThrowExceptionFmt(env, exceptionClass, "%s failed: %s", function, u_errorName(error));
+ return true;
+}
diff --git a/android_icu4j/src/main/jni/IcuUtilities.h b/android_icu4j/src/main/jni/IcuUtilities.h
new file mode 100644
index 0000000..451dc32
--- /dev/null
+++ b/android_icu4j/src/main/jni/IcuUtilities.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ICU_UTILITIES_H_included
+#define ICU_UTILITIES_H_included
+
+#include "jni.h"
+#include "unicode/utypes.h" // For UErrorCode.
+
+bool maybeThrowIcuException(JNIEnv* env, const char* function, UErrorCode error);
+
+#endif // ICU_UTILITIES_H_included
diff --git a/android_icu4j/src/main/jni/JniConstants.cpp b/android_icu4j/src/main/jni/JniConstants.cpp
new file mode 100644
index 0000000..88126e8
--- /dev/null
+++ b/android_icu4j/src/main/jni/JniConstants.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "JniConstants"
+
+#include "JniConstants.h"
+
+#include <atomic>
+#include <mutex>
+#include <stdlib.h>
+
+#include <log/log.h>
+
+namespace {
+
+jclass findClass(JNIEnv* env, const char* name) {
+ jclass result = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass(name)));
+ if (result == NULL) {
+ ALOGE("failed to find class '%s'", name);
+ abort();
+ }
+ return result;
+}
+
+static std::mutex g_constants_mutex;
+
+// Flag indicating whether cached constants are valid
+static bool g_constants_valid = false;
+
+jclass patternSyntaxExceptionClass;
+
+// EnsureJniConstantsInitialized initializes cached constants. It should be
+// called before returning a heap object from the cache to ensure cache is
+// initialized. This pattern is only necessary because if a process finishes one
+// runtime and starts another then JNI_OnLoad may not be called.
+void EnsureJniConstantsInitialized(JNIEnv* env) {
+ if (g_constants_valid) {
+ return;
+ }
+
+ std::lock_guard guard(g_constants_mutex);
+ if (g_constants_valid) {
+ return;
+ }
+
+ patternSyntaxExceptionClass = findClass(env, "java/util/regex/PatternSyntaxException");
+
+ g_constants_valid = true;
+}
+
+} // namespace
+
+void JniConstants::Initialize(JNIEnv* env) {
+ EnsureJniConstantsInitialized(env);
+}
+
+jclass JniConstants::GetPatternSyntaxExceptionClass(JNIEnv* env) {
+ EnsureJniConstantsInitialized(env);
+ return patternSyntaxExceptionClass;
+}
+
+void JniConstants::Invalidate() {
+ std::lock_guard guard(g_constants_mutex);
+ g_constants_valid = false;
+}
\ No newline at end of file
diff --git a/android_icu4j/src/main/jni/JniConstants.h b/android_icu4j/src/main/jni/JniConstants.h
new file mode 100644
index 0000000..18275cb
--- /dev/null
+++ b/android_icu4j/src/main/jni/JniConstants.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <jni.h>
+
+/**
+ * A cache to avoid calling FindClass at runtime.
+ */
+struct JniConstants {
+ // Initialized cached heap objects. This should be called in JNI_OnLoad.
+ static void Initialize(JNIEnv* env);
+
+ // Invalidate cached heap objects. This should be called in JNI_OnUnload.
+ static void Invalidate();
+
+ static jclass GetPatternSyntaxExceptionClass(JNIEnv* env);
+};
\ No newline at end of file
diff --git a/android_icu4j/src/main/jni/Register.cpp b/android_icu4j/src/main/jni/Register.cpp
new file mode 100644
index 0000000..a66d59a
--- /dev/null
+++ b/android_icu4j/src/main/jni/Register.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Register"
+
+#include <stdlib.h>
+#include <nativehelper/ScopedLocalFrame.h>
+#include <log/log.h>
+
+#include "JniConstants.h"
+
+// ART calls this on startup, so we can statically register all our native methods.
+jint JNI_OnLoad(JavaVM* vm, void*) {
+ ALOGV("libicu_jni JNI_OnLoad");
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ ALOGE("JavaVM::GetEnv() failed");
+ abort();
+ }
+
+ ScopedLocalFrame localFrame(env);
+
+#define REGISTER(FN) extern void FN(JNIEnv*); FN(env)
+ REGISTER(register_com_android_icu_util_regex_NativePattern);
+ REGISTER(register_com_android_icu_util_regex_NativeMatcher);
+#undef REGISTER
+
+ JniConstants::Initialize(env);
+ return JNI_VERSION_1_6;
+}
+
+// ART calls this on shutdown, do any global cleanup here.
+// -- Very important if we restart multiple ART runtimes in the same process to reset the state.
+void JNI_OnUnload(JavaVM*, void*) {
+ // Don't use the JavaVM in this method. ART only calls this once all threads are
+ // unregistered.
+ ALOGV("libicu_jni JNI_OnUnload");
+ JniConstants::Invalidate();
+}
diff --git a/android_icu4j/src/main/jni/ScopedJavaUnicodeString.h b/android_icu4j/src/main/jni/ScopedJavaUnicodeString.h
new file mode 100644
index 0000000..a30aa7e
--- /dev/null
+++ b/android_icu4j/src/main/jni/ScopedJavaUnicodeString.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SCOPED_JAVA_UNICODE_STRING_H_included
+#define SCOPED_JAVA_UNICODE_STRING_H_included
+
+#include <nativehelper/JNIHelp.h>
+#include "unicode/unistr.h"
+
+// A smart pointer that provides access to an ICU UnicodeString given a JNI
+// jstring. We give ICU a direct pointer to the characters on the Java heap.
+// It's clever enough to copy-on-write if necessary.
+class ScopedJavaUnicodeString {
+ public:
+ ScopedJavaUnicodeString(JNIEnv* env, jstring s) : mEnv(env), mString(s) {
+ if (s == NULL) {
+ jniThrowNullPointerException(mEnv, NULL);
+ } else {
+ mChars = env->GetStringChars(mString, NULL);
+ const int32_t charCount = env->GetStringLength(mString);
+ mUnicodeString.setTo(false, mChars, charCount);
+ }
+ }
+
+ ~ScopedJavaUnicodeString() {
+ if (mString != NULL) {
+ mEnv->ReleaseStringChars(mString, mChars);
+ }
+ }
+
+ bool valid() const {
+ return (mString != NULL);
+ }
+
+ const icu::UnicodeString& unicodeString() const {
+ return mUnicodeString;
+ }
+
+ icu::UnicodeString& unicodeString() {
+ return mUnicodeString;
+ }
+
+ private:
+ JNIEnv* mEnv;
+ jstring mString;
+ const jchar* mChars;
+ icu::UnicodeString mUnicodeString;
+
+ // Disallow copy and assignment.
+ ScopedJavaUnicodeString(const ScopedJavaUnicodeString&);
+ void operator=(const ScopedJavaUnicodeString&);
+};
+
+#endif // SCOPED_JAVA_UNICODE_STRING_H_included
diff --git a/android_icu4j/src/main/jni/TEST_MAPPING b/android_icu4j/src/main/jni/TEST_MAPPING
new file mode 100644
index 0000000..18d7bf5
--- /dev/null
+++ b/android_icu4j/src/main/jni/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+ {
+ "path": "external/icu/android_icu4j/src/main/java/com/android/icu/util/regex"
+ }
+ ]
+}
diff --git a/android_icu4j/src/main/jni/com_android_icu_util_regex_NativeMatcher.cpp b/android_icu4j/src/main/jni/com_android_icu_util_regex_NativeMatcher.cpp
new file mode 100644
index 0000000..8e5e200
--- /dev/null
+++ b/android_icu4j/src/main/jni/com_android_icu_util_regex_NativeMatcher.cpp
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "NativeMatcher"
+
+#include <memory>
+#include <stdlib.h>
+
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedStringChars.h>
+#include <nativehelper/jni_macros.h>
+
+#include <android-base/logging.h>
+#include "IcuUtilities.h"
+#include "ScopedJavaUnicodeString.h"
+#include "unicode/parseerr.h"
+#include "unicode/regex.h"
+
+// ICU documentation: http://icu-project.org/apiref/icu4c/classRegexMatcher.html
+
+/**
+ * Encapsulates an instance of ICU4C's RegexMatcher class along with a copy of
+ * the input it's currently operating on in the native heap.
+ *
+ * Rationale: We choose to make a copy here because it turns out to be a lot
+ * cheaper when a moving GC and/or string compression is enabled. This is
+ * because env->GetStringChars() always copies in this scenario. This becomes
+ * especially bad when the String in question is long and/or contains a large
+ * number of matches.
+ *
+ * Drawbacks: The native allocation associated with this class is no longer
+ * fixed size, so we're effectively lying to the NativeAllocationRegistry about
+ * the size of the object(s) we're allocating on the native heap. The peak
+ * memory usage doesn't change though, given that GetStringChars would have
+ * made an allocation of precisely the same size.
+ */
+class MatcherState {
+public:
+ MatcherState(icu::RegexMatcher* matcher) :
+ mMatcher(matcher),
+ mUChars(nullptr),
+ mUText(nullptr),
+ mStatus(U_ZERO_ERROR) {
+ }
+
+ bool updateInput(JNIEnv* env, jstring input) {
+ // First, close the UText struct, since we're about to allocate a new one.
+ if (mUText != nullptr) {
+ utext_close(mUText);
+ mUText = nullptr;
+ }
+
+ // Then delete the UChar* associated with the UText struct..
+ mUChars.reset(nullptr);
+
+ // TODO: We should investigate whether we can avoid an additional copy
+ // in the native heap when is_copy == JNI_TRUE. The problem with doing
+ // that is that we might call ReleaseStringChars with a different
+ // JNIEnv* on a different downcall. This is currently safe as
+ // implemented in ART, but is unlikely to be portable and the spec stays
+ // silent on the matter.
+ ScopedStringChars inputChars(env, input);
+ if (inputChars.get() == nullptr) {
+ // There will be an exception pending if we get here.
+ return false;
+ }
+
+ // Make a copy of |input| on the native heap. This copy will be live
+ // until the next call to updateInput or close.
+ mUChars.reset(new (std::nothrow) UChar[inputChars.size()]);
+ if (mUChars.get() == nullptr) {
+ env->ThrowNew(env->FindClass("Ljava/lang/OutOfMemoryError;"), "Out of memory");
+ return false;
+ }
+
+ static_assert(sizeof(UChar) == sizeof(jchar), "sizeof(Uchar) != sizeof(jchar)");
+ memcpy(mUChars.get(), inputChars.get(), inputChars.size() * sizeof(jchar));
+
+ // Reset any errors that might have occurred on previous patches.
+ mStatus = U_ZERO_ERROR;
+ mUText = utext_openUChars(nullptr, mUChars.get(), inputChars.size(), &mStatus);
+ if (mUText == nullptr) {
+ CHECK(maybeThrowIcuException(env, "utext_openUChars", mStatus));
+ return false;
+ }
+
+ // It is an error for ICU to have returned a non-null mUText but to
+ // still have indicated an error.
+ CHECK(U_SUCCESS(mStatus));
+
+ mMatcher->reset(mUText);
+ return true;
+ }
+
+ ~MatcherState() {
+ if (mUText != nullptr) {
+ utext_close(mUText);
+ }
+ }
+
+ icu::RegexMatcher* matcher() {
+ return mMatcher.get();
+ }
+
+ UErrorCode& status() {
+ return mStatus;
+ }
+
+ void updateOffsets(JNIEnv* env, jintArray javaOffsets) {
+ ScopedIntArrayRW offsets(env, javaOffsets);
+ if (offsets.get() == NULL) {
+ return;
+ }
+
+ for (size_t i = 0, groupCount = mMatcher->groupCount(); i <= groupCount; ++i) {
+ offsets[2*i + 0] = mMatcher->start(i, mStatus);
+ offsets[2*i + 1] = mMatcher->end(i, mStatus);
+ }
+ }
+
+private:
+ std::unique_ptr<icu::RegexMatcher> mMatcher;
+ std::unique_ptr<UChar[]> mUChars;
+ UText* mUText;
+ UErrorCode mStatus;
+
+ // Disallow copy and assignment.
+ MatcherState(const MatcherState&);
+ void operator=(const MatcherState&);
+};
+
+static inline MatcherState* toMatcherState(jlong address) {
+ return reinterpret_cast<MatcherState*>(static_cast<uintptr_t>(address));
+}
+
+static void NativeMatcher_free(void* address) {
+ MatcherState* state = reinterpret_cast<MatcherState*>(address);
+ delete state;
+}
+
+static jlong NativeMatcher_getNativeFinalizer(JNIEnv*, jclass) {
+ return reinterpret_cast<jlong>(&NativeMatcher_free);
+}
+
+static jboolean NativeMatcher_findImpl(JNIEnv* env, jclass, jlong addr, jint startIndex, jintArray offsets) {
+ MatcherState* state = toMatcherState(addr);
+ UBool result = state->matcher()->find(startIndex, state->status());
+ if (result) {
+ state->updateOffsets(env, offsets);
+ return JNI_TRUE;
+ } else {
+ return JNI_FALSE;
+ }
+}
+
+static jboolean NativeMatcher_findNextImpl(JNIEnv* env, jclass, jlong addr, jintArray offsets) {
+ MatcherState* state = toMatcherState(addr);
+ UBool result = state->matcher()->find();
+ if (result) {
+ state->updateOffsets(env, offsets);
+ return JNI_TRUE;
+ } else {
+ return JNI_FALSE;
+ }
+}
+
+static jint NativeMatcher_groupCountImpl(JNIEnv*, jclass, jlong addr) {
+ MatcherState* state = toMatcherState(addr);
+ return state->matcher()->groupCount();
+}
+
+static jboolean NativeMatcher_hitEndImpl(JNIEnv*, jclass, jlong addr) {
+ MatcherState* state = toMatcherState(addr);
+ if (state->matcher()->hitEnd() != 0) {
+ return JNI_TRUE;
+ } else {
+ return JNI_FALSE;
+ }
+}
+
+static jboolean NativeMatcher_lookingAtImpl(JNIEnv* env, jclass, jlong addr, jintArray offsets) {
+ MatcherState* state = toMatcherState(addr);
+ UBool result = state->matcher()->lookingAt(state->status());
+ if (result) {
+ state->updateOffsets(env, offsets);
+ return JNI_TRUE;
+ } else {
+ return JNI_FALSE;
+ }
+}
+
+static jboolean NativeMatcher_matchesImpl(JNIEnv* env, jclass, jlong addr, jintArray offsets) {
+ MatcherState* state = toMatcherState(addr);
+ UBool result = state->matcher()->matches(state->status());
+ if (result) {
+ state->updateOffsets(env, offsets);
+ return JNI_TRUE;
+ } else {
+ return JNI_FALSE;
+ }
+}
+
+static jlong NativeMatcher_openImpl(JNIEnv* env, jclass, jlong patternAddr) {
+ icu::RegexPattern* pattern = reinterpret_cast<icu::RegexPattern*>(static_cast<uintptr_t>(patternAddr));
+ UErrorCode status = U_ZERO_ERROR;
+ icu::RegexMatcher* result = pattern->matcher(status);
+ if (maybeThrowIcuException(env, "RegexPattern::matcher", status)) {
+ return 0;
+ }
+
+ return reinterpret_cast<uintptr_t>(new MatcherState(result));
+}
+
+static jboolean NativeMatcher_requireEndImpl(JNIEnv*, jclass, jlong addr) {
+ MatcherState* state = toMatcherState(addr);
+ if (state->matcher()->requireEnd() != 0) {
+ return JNI_TRUE;
+ } else {
+ return JNI_FALSE;
+ }
+}
+
+static void NativeMatcher_setInputImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jint start, jint end) {
+ MatcherState* state = toMatcherState(addr);
+ if (state->updateInput(env, javaText)) {
+ state->matcher()->region(start, end, state->status());
+ }
+}
+
+static void NativeMatcher_useAnchoringBoundsImpl(JNIEnv*, jclass, jlong addr, jboolean value) {
+ MatcherState* state = toMatcherState(addr);
+ state->matcher()->useAnchoringBounds(value);
+}
+
+static void NativeMatcher_useTransparentBoundsImpl(JNIEnv*, jclass, jlong addr, jboolean value) {
+ MatcherState* state = toMatcherState(addr);
+ state->matcher()->useTransparentBounds(value);
+}
+
+static jint NativeMatcher_getMatchedGroupIndexImpl(JNIEnv* env, jclass, jlong patternAddr, jstring javaGroupName) {
+ icu::RegexPattern* pattern = reinterpret_cast<icu::RegexPattern*>(static_cast<uintptr_t>(patternAddr));
+ ScopedJavaUnicodeString groupName(env, javaGroupName);
+ UErrorCode status = U_ZERO_ERROR;
+
+ jint result = pattern->groupNumberFromName(groupName.unicodeString(), status);
+ if (U_SUCCESS(status)) {
+ return result;
+ }
+ if (status == U_REGEX_INVALID_CAPTURE_GROUP_NAME) {
+ return -1;
+ }
+ maybeThrowIcuException(env, "RegexPattern::groupNumberFromName", status);
+ return -1;
+}
+
+
+static JNINativeMethod gMethods[] = {
+ NATIVE_METHOD(NativeMatcher, getMatchedGroupIndexImpl, "(JLjava/lang/String;)I"),
+ NATIVE_METHOD(NativeMatcher, findImpl, "(JI[I)Z"),
+ NATIVE_METHOD(NativeMatcher, findNextImpl, "(J[I)Z"),
+ NATIVE_METHOD(NativeMatcher, getNativeFinalizer, "()J"),
+ NATIVE_METHOD(NativeMatcher, groupCountImpl, "(J)I"),
+ NATIVE_METHOD(NativeMatcher, hitEndImpl, "(J)Z"),
+ NATIVE_METHOD(NativeMatcher, lookingAtImpl, "(J[I)Z"),
+ NATIVE_METHOD(NativeMatcher, matchesImpl, "(J[I)Z"),
+ NATIVE_METHOD(NativeMatcher, openImpl, "(J)J"),
+ NATIVE_METHOD(NativeMatcher, requireEndImpl, "(J)Z"),
+ NATIVE_METHOD(NativeMatcher, setInputImpl, "(JLjava/lang/String;II)V"),
+ NATIVE_METHOD(NativeMatcher, useAnchoringBoundsImpl, "(JZ)V"),
+ NATIVE_METHOD(NativeMatcher, useTransparentBoundsImpl, "(JZ)V"),
+};
+void register_com_android_icu_util_regex_NativeMatcher(JNIEnv* env) {
+ jniRegisterNativeMethods(env, "com/android/icu/util/regex/NativeMatcher", gMethods, NELEM(gMethods));
+}
diff --git a/android_icu4j/src/main/jni/com_android_icu_util_regex_NativePattern.cpp b/android_icu4j/src/main/jni/com_android_icu_util_regex_NativePattern.cpp
new file mode 100644
index 0000000..f93049c
--- /dev/null
+++ b/android_icu4j/src/main/jni/com_android_icu_util_regex_NativePattern.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "NativePattern"
+
+#include <stdlib.h>
+
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/jni_macros.h>
+
+#include "unicode/parseerr.h"
+#include "unicode/regex.h"
+
+#include "JniConstants.h"
+#include "ScopedJavaUnicodeString.h"
+
+// ICU documentation: http://icu-project.org/apiref/icu4c/classRegexPattern.html
+
+static const char* regexDetailMessage(UErrorCode status) {
+ // These human-readable error messages were culled from "utypes.h", and then slightly tuned
+ // to make more sense in context.
+ // If we don't have a special-case, we'll just return the textual name of
+ // the enum value (such as U_REGEX_RULE_SYNTAX), which is better than nothing.
+ switch (status) {
+ case U_REGEX_INTERNAL_ERROR: return "An internal error was detected";
+ case U_REGEX_RULE_SYNTAX: return "Syntax error in regexp pattern";
+ case U_REGEX_INVALID_STATE: return "Matcher in invalid state for requested operation";
+ case U_REGEX_BAD_ESCAPE_SEQUENCE: return "Unrecognized backslash escape sequence in pattern";
+ case U_REGEX_PROPERTY_SYNTAX: return "Incorrect Unicode property";
+ case U_REGEX_UNIMPLEMENTED: return "Use of unimplemented feature";
+ case U_REGEX_MISMATCHED_PAREN: return "Incorrectly nested parentheses in regexp pattern";
+ case U_REGEX_NUMBER_TOO_BIG: return "Decimal number is too large";
+ case U_REGEX_BAD_INTERVAL: return "Error in {min,max} interval";
+ case U_REGEX_MAX_LT_MIN: return "In {min,max}, max is less than min";
+ case U_REGEX_INVALID_BACK_REF: return "Back-reference to a non-existent capture group";
+ case U_REGEX_INVALID_FLAG: return "Invalid value for match mode flags";
+ case U_REGEX_LOOK_BEHIND_LIMIT: return "Look-behind pattern matches must have a bounded maximum length";
+ case U_REGEX_SET_CONTAINS_STRING: return "Regular expressions cannot have UnicodeSets containing strings";
+ case U_REGEX_OCTAL_TOO_BIG: return "Octal character constants must be <= 0377.";
+ case U_REGEX_MISSING_CLOSE_BRACKET: return "Missing closing bracket in character class";
+ case U_REGEX_INVALID_RANGE: return "In a character range [x-y], x is greater than y";
+ case U_REGEX_STACK_OVERFLOW: return "Regular expression backtrack stack overflow";
+ case U_REGEX_TIME_OUT: return "Maximum allowed match time exceeded";
+ case U_REGEX_STOPPED_BY_CALLER: return "Matching operation aborted by user callback function";
+ default:
+ return u_errorName(status);
+ }
+}
+
+static void throwPatternSyntaxException(JNIEnv* env, UErrorCode status, jstring pattern, UParseError error) {
+ static jmethodID method = env->GetMethodID(JniConstants::GetPatternSyntaxExceptionClass(env),
+ "<init>", "(Ljava/lang/String;Ljava/lang/String;I)V");
+ jstring message = env->NewStringUTF(regexDetailMessage(status));
+ jclass exceptionClass = JniConstants::GetPatternSyntaxExceptionClass(env);
+ jobject exception = env->NewObject(exceptionClass, method, message, pattern, error.offset);
+ env->Throw(reinterpret_cast<jthrowable>(exception));
+}
+
+static void NativePattern_free(void* addr) {
+ delete reinterpret_cast<icu::RegexPattern*>(addr);
+}
+
+static jlong NativePattern_getNativeFinalizer(JNIEnv*, jclass) {
+ return reinterpret_cast<jlong>(&NativePattern_free);
+}
+
+static jlong NativePattern_compileImpl(JNIEnv* env, jclass, jstring javaRegex, jint flags) {
+ flags |= UREGEX_ERROR_ON_UNKNOWN_ESCAPES;
+
+ UErrorCode status = U_ZERO_ERROR;
+ UParseError error;
+ error.offset = -1;
+
+ ScopedJavaUnicodeString regex(env, javaRegex);
+ if (!regex.valid()) {
+ return 0;
+ }
+ icu::UnicodeString& regexString(regex.unicodeString());
+ icu::RegexPattern* result = icu::RegexPattern::compile(regexString, flags, error, status);
+ if (!U_SUCCESS(status)) {
+ throwPatternSyntaxException(env, status, javaRegex, error);
+ }
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(result));
+}
+
+
+static JNINativeMethod gMethods[] = {
+ NATIVE_METHOD(NativePattern, compileImpl, "(Ljava/lang/String;I)J"),
+ NATIVE_METHOD(NativePattern, getNativeFinalizer, "()J"),
+};
+
+void register_com_android_icu_util_regex_NativePattern(JNIEnv* env) {
+ jniRegisterNativeMethods(env, "com/android/icu/util/regex/NativePattern", gMethods, NELEM(gMethods));
+}
diff --git a/tools/srcgen/generate_android_icu4j.sh b/tools/srcgen/generate_android_icu4j.sh
index bbef01c..3335e27 100755
--- a/tools/srcgen/generate_android_icu4j.sh
+++ b/tools/srcgen/generate_android_icu4j.sh
@@ -50,8 +50,9 @@
# Clean out previous generated code / resources.
DEST_SRC_DIR=${ANDROID_ICU4J_DIR}/src/main/java
-rm -rf ${DEST_SRC_DIR}
-mkdir -p ${DEST_SRC_DIR}
+DEST_GENERATED_SRC_DIR=${DEST_SRC_DIR}/android/icu
+rm -rf ${DEST_GENERATED_SRC_DIR}
+mkdir -p ${DEST_GENERATED_SRC_DIR}
DEST_RESOURCE_DIR=${ANDROID_ICU4J_DIR}/resources
rm -rf ${DEST_RESOURCE_DIR}