Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[android] Android "collator" implementation
Browse files Browse the repository at this point in the history
 - Uses java.text.Collator for comparison
 - Uses java.util.Locale for locale loading
 - Uses LanguageTag for BCP 47 parsing
 - Falls back to non-locale-aware nunicode/default comparison for case-sensitive/diacritic-insensitive.
 - Testing these changes depends on running Android render tests
 - "collator" is not yet exposed in the SDK bindings.
  • Loading branch information
ChrisLoer committed Jun 28, 2018
1 parent 2d6304d commit 1164c07
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 3 deletions.
11 changes: 8 additions & 3 deletions platform/android/config.cmake
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
add_definitions(-DMBGL_USE_GLES2=1)
include(cmake/test-files.cmake)
include(cmake/nunicode.cmake)

# Build thin archives.
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> cruT <TARGET> <LINK_FLAGS> <OBJECTS>")
Expand All @@ -21,7 +22,6 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections -Wl,--ve
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections -Wl,--version-script=${CMAKE_SOURCE_DIR}/platform/android/version-script")

mason_use(jni.hpp VERSION 3.0.0 HEADER_ONLY)
mason_use(nunicode VERSION 1.7.1)
mason_use(sqlite VERSION 3.14.2)
mason_use(gtest VERSION 1.8.0)
mason_use(icu VERSION 58.1-min-size)
Expand All @@ -37,13 +37,17 @@ macro(mbgl_platform_core)
PRIVATE platform/android/src/timer.cpp

# Misc
PRIVATE platform/android/src/text/local_glyph_rasterizer_jni.hpp
PRIVATE platform/android/src/text/collator.cpp
PRIVATE platform/android/src/text/collator_jni.hpp
PRIVATE platform/android/src/text/local_glyph_rasterizer.cpp
PRIVATE platform/android/src/text/local_glyph_rasterizer_jni.hpp
PRIVATE platform/android/src/logging_android.cpp
PRIVATE platform/android/src/thread.cpp
PRIVATE platform/default/string_stdlib.cpp
PRIVATE platform/default/bidi.cpp
PRIVATE platform/default/thread_local.cpp
PRIVATE platform/default/unaccent.cpp
PRIVATE platform/default/unaccent.hpp
PRIVATE platform/default/utf.cpp

# Image handling
Expand Down Expand Up @@ -81,7 +85,8 @@ macro(mbgl_platform_core)
PRIVATE platform/android
)

target_add_mason_package(mbgl-core PUBLIC nunicode)
mbgl_nunicode_core()

target_add_mason_package(mbgl-core PUBLIC geojson)
target_add_mason_package(mbgl-core PUBLIC jni.hpp)
target_add_mason_package(mbgl-core PUBLIC rapidjson)
Expand Down
3 changes: 3 additions & 0 deletions platform/android/src/jni.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#include "style/light.hpp"
#include "snapshotter/map_snapshotter.hpp"
#include "snapshotter/map_snapshot.hpp"
#include "text/collator_jni.hpp"
#include "text/local_glyph_rasterizer_jni.hpp"
#include "java/lang.hpp"

Expand Down Expand Up @@ -188,6 +189,8 @@ void registerNatives(JavaVM *vm) {

// text
LocalGlyphRasterizer::registerNative(env);
Locale::registerNative(env);
Collator::registerNative(env);
}

} // namespace android
Expand Down
199 changes: 199 additions & 0 deletions platform/android/src/text/collator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
#include <mbgl/style/expression/collator.hpp>
#include <mbgl/text/language_tag.hpp>
#include <mbgl/util/platform.hpp>

#include <unaccent.hpp>

#include <jni/jni.hpp>

#include "../attach_env.hpp"
#include "collator_jni.hpp"

namespace mbgl {
namespace android {

void Collator::registerNative(jni::JNIEnv& env) {
javaClass = *jni::Class<Collator>::Find(env).NewGlobalRef(env).release();
}

jni::Class<Collator> Collator::javaClass;

jni::Object<Collator> Collator::getInstance(jni::JNIEnv& env, jni::Object<Locale> locale) {
using Signature = jni::Object<Collator>(jni::Object<Locale>);
auto method = javaClass.GetStaticMethod<Signature>(env, "getInstance");
return javaClass.Call(env, method, locale);
}

void Collator::setStrength(jni::JNIEnv& env, jni::Object<Collator> collator, jni::jint strength) {
using Signature = void(jni::jint);
auto static method = javaClass.GetMethod<Signature>(env, "setStrength");
collator.Call(env, method, strength);
}

jni::jint Collator::compare(jni::JNIEnv& env, jni::Object<Collator> collator, jni::String lhs, jni::String rhs) {
using Signature = jni::jint(jni::String, jni::String);
auto static method = javaClass.GetMethod<Signature>(env, "compare");
return collator.Call(env, method, lhs, rhs);
}

void Locale::registerNative(jni::JNIEnv& env) {
javaClass = *jni::Class<Locale>::Find(env).NewGlobalRef(env).release();
}

/*
We would prefer to use for/toLanguageTag, but they're only available in API level 21+
jni::Object<Locale> Locale::forLanguageTag(jni::JNIEnv& env, jni::String languageTag) {
using Signature = jni::Object<Locale>(jni::String);
auto method = javaClass.GetStaticMethod<Signature>(env, "forLanguageTag");
return javaClass.Call(env, method, languageTag);
}
jni::String Locale::toLanguageTag(jni::JNIEnv& env, jni::Object<Locale> locale) {
using Signature = jni::String();
auto static method = javaClass.GetMethod<Signature>(env, "toLanguageTag");
return locale.Call(env, method);
}
*/


jni::String Locale::getLanguage(jni::JNIEnv& env, jni::Object<Locale> locale) {
using Signature = jni::String();
auto static method = javaClass.GetMethod<Signature>(env, "getLanguage");
return locale.Call(env, method);
}

jni::String Locale::getCountry(jni::JNIEnv& env, jni::Object<Locale> locale) {
using Signature = jni::String();
auto static method = javaClass.GetMethod<Signature>(env, "getCountry");
return locale.Call(env, method);
}

jni::Object<Locale> Locale::getDefault(jni::JNIEnv& env) {
using Signature = jni::Object<Locale>();
auto method = javaClass.GetStaticMethod<Signature>(env, "getDefault");
return javaClass.Call(env, method);
}

jni::Object<Locale> Locale::New(jni::JNIEnv& env, jni::String language) {
static auto constructor = javaClass.GetConstructor<jni::String>(env);
return javaClass.New(env, constructor, language);
}

jni::Object<Locale> Locale::New(jni::JNIEnv& env, jni::String language, jni::String region) {
static auto constructor = javaClass.GetConstructor<jni::String, jni::String>(env);
return javaClass.New(env, constructor, language, region);
}

jni::Class<Locale> Locale::javaClass;

} // namespace android

namespace style {
namespace expression {

class Collator::Impl {
public:
Impl(bool caseSensitive_, bool diacriticSensitive_, optional<std::string> locale_)
: caseSensitive(caseSensitive_)
, diacriticSensitive(diacriticSensitive_)
, env(android::AttachEnv())
{
LanguageTag languageTag = locale_ ? LanguageTag::fromBCP47(*locale_) : LanguageTag();
if (!languageTag.language) {
locale = android::Locale::getDefault(*env).NewGlobalRef(*env);
} else if (!languageTag.region) {
locale = android::Locale::New(*env,
jni::Make<jni::String>(*env, *(languageTag.language)))
.NewGlobalRef(*env);
} else {
locale = android::Locale::New(*env,
jni::Make<jni::String>(*env, *(languageTag.language)),
jni::Make<jni::String>(*env, *(languageTag.region)))
.NewGlobalRef(*env);
}
collator = android::Collator::getInstance(*env, *locale).NewGlobalRef(*env);;
if (!diacriticSensitive && !caseSensitive) {
android::Collator::setStrength(*env, *collator, 0 /*PRIMARY*/);
} else if (diacriticSensitive && !caseSensitive) {
android::Collator::setStrength(*env, *collator, 1 /*SECONDARY*/);
} else if (caseSensitive) {
// If we're case-sensitive and diacritic-sensitive, we use a case-sensitive collator
// and a fallback implementation of diacritic-insensitivity.
android::Collator::setStrength(*env, *collator, 2 /*TERTIARY*/);
}
}

bool operator==(const Impl& other) const {
return caseSensitive == other.caseSensitive &&
diacriticSensitive == other.diacriticSensitive &&
resolvedLocale() == other.resolvedLocale();
}

int compare(const std::string& lhs, const std::string& rhs) const {
bool useUnaccent = !diacriticSensitive && caseSensitive;
// java.text.Collator doesn't support a diacritic-insensitive/case-sensitive collation
// order, so we have to compromise here. We use Android's case-sensitive Collator
// against strings that have been "unaccented" using non-locale-aware nunicode logic.
// Because of the difference in locale-awareness, this means turning on case-sensitivity
// can _potentially_ change compare results for strings that don't actually have any case
// differences.
jni::String jlhs = jni::Make<jni::String>(*env, useUnaccent ?
platform::unaccent(lhs) :
lhs);
jni::String jrhs = jni::Make<jni::String>(*env, useUnaccent ?
platform::unaccent(rhs) :
rhs);

jni::jint result = android::Collator::compare(*env, *collator, jlhs, jrhs);;

jni::DeleteLocalRef(*env, jlhs);
jni::DeleteLocalRef(*env, jrhs);

return result;
}

std::string resolvedLocale() const {
jni::String jLanguage = android::Locale::getLanguage(*env, *locale);
std::string language = jni::Make<std::string>(*env, jLanguage);
jni::DeleteLocalRef(*env, jLanguage);
jni::String jRegion = android::Locale::getCountry(*env, *locale);
std::string region = jni::Make<std::string>(*env, jRegion);
jni::DeleteLocalRef(*env, jRegion);

optional<std::string> resultLanguage;
if (!language.empty()) resultLanguage = language;
optional<std::string> resultRegion;
if (!region.empty()) resultRegion = region;

return LanguageTag(resultLanguage, {}, resultRegion).toBCP47();
}
private:
bool caseSensitive;
bool diacriticSensitive;

android::UniqueEnv env;
jni::UniqueObject<android::Collator> collator;
jni::UniqueObject<android::Locale> locale;
};


Collator::Collator(bool caseSensitive, bool diacriticSensitive, optional<std::string> locale_)
: impl(std::make_shared<Impl>(caseSensitive, diacriticSensitive, std::move(locale_)))
{}

bool Collator::operator==(const Collator& other) const {
return *impl == *(other.impl);
}

int Collator::compare(const std::string& lhs, const std::string& rhs) const {
return impl->compare(lhs, rhs);
}

std::string Collator::resolvedLocale() const {
return impl->resolvedLocale();
}

} // namespace expression
} // namespace style
} // namespace mbgl
57 changes: 57 additions & 0 deletions platform/android/src/text/collator_jni.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#pragma once

#include <mbgl/util/image.hpp>

#include <jni/jni.hpp>

/*
android::Collator and android::Locale are
the JNI wrappers of
java/text/Collator and java/util/Locale
mbgl::Collator is the portable interface
Both implementations are in collator.cpp
*/

namespace mbgl {
namespace android {

class Locale {
public:
static constexpr auto Name() { return "java/util/Locale"; };

/* Requires API level 21+ in order to support script/variant
static jni::Object<Locale> forLanguageTag(jni::JNIEnv&, jni::String);
static jni::String toLanguageTag(jni::JNIEnv&, jni::Object<Locale>);
*/
static jni::Object<Locale> getDefault(jni::JNIEnv&);
static jni::String getLanguage(jni::JNIEnv&, jni::Object<Locale>);
static jni::String getCountry(jni::JNIEnv&, jni::Object<Locale>);

static jni::Object<Locale> New(jni::JNIEnv&, jni::String);
static jni::Object<Locale> New(jni::JNIEnv&, jni::String, jni::String);

static jni::Class<Locale> javaClass;

static void registerNative(jni::JNIEnv&);

};

class Collator {
public:
static constexpr auto Name() { return "java/text/Collator"; };

static jni::Object<Collator> getInstance(jni::JNIEnv&, jni::Object<Locale>);

static void setStrength(jni::JNIEnv&, jni::Object<Collator>, jni::jint);

static jni::jint compare(jni::JNIEnv&, jni::Object<Collator>, jni::String, jni::String);

static jni::Class<Collator> javaClass;

static void registerNative(jni::JNIEnv&);

};

} // namespace android
} // namespace mbgl

0 comments on commit 1164c07

Please sign in to comment.