1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18#define LOG_TAG "Bidi"
19
20#include "IcuUtilities.h"
21#include "JNIHelp.h"
22#include "JniConstants.h"
23#include "JniException.h"
24#include "ScopedPrimitiveArray.h"
25#include "UniquePtr.h"
26#include "unicode/ubidi.h"
27
28#include <stdlib.h>
29#include <string.h>
30
31struct BiDiData {
32    BiDiData(UBiDi* biDi) : mBiDi(biDi), mEmbeddingLevels(NULL) {
33    }
34
35    ~BiDiData() {
36        ubidi_close(mBiDi);
37    }
38
39    UBiDiLevel* embeddingLevels() {
40        return reinterpret_cast<UBiDiLevel*>(&mEmbeddingLevels[0]);
41    }
42
43    void setEmbeddingLevels(jbyte* newEmbeddingLevels) {
44        mEmbeddingLevels.reset(newEmbeddingLevels);
45    }
46
47    UBiDi* uBiDi() {
48        return mBiDi;
49    }
50
51private:
52    UBiDi* mBiDi;
53    UniquePtr<jbyte[]> mEmbeddingLevels;
54
55    // Disallow copy and assignment.
56    BiDiData(const BiDiData&);
57    void operator=(const BiDiData&);
58};
59
60static BiDiData* biDiData(jlong ptr) {
61    return reinterpret_cast<BiDiData*>(static_cast<uintptr_t>(ptr));
62}
63
64static UBiDi* uBiDi(jlong ptr) {
65    return reinterpret_cast<BiDiData*>(static_cast<uintptr_t>(ptr))->uBiDi();
66}
67
68static jlong Bidi_ubidi_open(JNIEnv*, jclass) {
69    return reinterpret_cast<uintptr_t>(new BiDiData(ubidi_open()));
70}
71
72static void Bidi_ubidi_close(JNIEnv*, jclass, jlong ptr) {
73    delete biDiData(ptr);
74}
75
76static void Bidi_ubidi_setPara(JNIEnv* env, jclass, jlong ptr, jcharArray text, jint length, jint paraLevel, jbyteArray newEmbeddingLevels) {
77    BiDiData* data = biDiData(ptr);
78    // Copy the new embedding levels from the Java heap to the native heap.
79    if (newEmbeddingLevels != NULL) {
80        jbyte* dst;
81        data->setEmbeddingLevels(dst = new jbyte[length]);
82        env->GetByteArrayRegion(newEmbeddingLevels, 0, length, dst);
83    } else {
84        data->setEmbeddingLevels(NULL);
85    }
86    ScopedCharArrayRO chars(env, text);
87    if (chars.get() == NULL) {
88        return;
89    }
90    UErrorCode err = U_ZERO_ERROR;
91    ubidi_setPara(data->uBiDi(), chars.get(), length, paraLevel, data->embeddingLevels(), &err);
92    maybeThrowIcuException(env, "ubidi_setPara", err);
93}
94
95static jlong Bidi_ubidi_setLine(JNIEnv* env, jclass, jlong ptr, jint start, jint limit) {
96    UErrorCode status = U_ZERO_ERROR;
97    UBiDi* sized = ubidi_openSized(limit - start, 0, &status);
98    if (maybeThrowIcuException(env, "ubidi_openSized", status)) {
99        return 0;
100    }
101    UniquePtr<BiDiData> lineData(new BiDiData(sized));
102    ubidi_setLine(uBiDi(ptr), start, limit, lineData->uBiDi(), &status);
103    maybeThrowIcuException(env, "ubidi_setLine", status);
104    return reinterpret_cast<uintptr_t>(lineData.release());
105}
106
107static jint Bidi_ubidi_getDirection(JNIEnv*, jclass, jlong ptr) {
108    return ubidi_getDirection(uBiDi(ptr));
109}
110
111static jint Bidi_ubidi_getLength(JNIEnv*, jclass, jlong ptr) {
112    return ubidi_getLength(uBiDi(ptr));
113}
114
115static jbyte Bidi_ubidi_getParaLevel(JNIEnv*, jclass, jlong ptr) {
116    return ubidi_getParaLevel(uBiDi(ptr));
117}
118
119static jbyteArray Bidi_ubidi_getLevels(JNIEnv* env, jclass, jlong ptr) {
120    UErrorCode status = U_ZERO_ERROR;
121    const UBiDiLevel* levels = ubidi_getLevels(uBiDi(ptr), &status);
122    if (maybeThrowIcuException(env, "ubidi_getLevels", status)) {
123        return NULL;
124    }
125    int len = ubidi_getLength(uBiDi(ptr));
126    jbyteArray result = env->NewByteArray(len);
127    env->SetByteArrayRegion(result, 0, len, reinterpret_cast<const jbyte*>(levels));
128    return result;
129}
130
131static jint Bidi_ubidi_countRuns(JNIEnv* env, jclass, jlong ptr) {
132    UErrorCode status = U_ZERO_ERROR;
133    int count = ubidi_countRuns(uBiDi(ptr), &status);
134    maybeThrowIcuException(env, "ubidi_countRuns", status);
135    return count;
136}
137
138/**
139 * TODO: if we care about performance, we might just want to use an int[] instead of a Run[].
140 */
141static jobjectArray Bidi_ubidi_getRuns(JNIEnv* env, jclass, jlong ptr) {
142    UBiDi* ubidi = uBiDi(ptr);
143    UErrorCode status = U_ZERO_ERROR;
144    int runCount = ubidi_countRuns(ubidi, &status);
145    if (maybeThrowIcuException(env, "ubidi_countRuns", status)) {
146        return NULL;
147    }
148    static jmethodID bidiRunConstructor =
149            env->GetMethodID(JniConstants::bidiRunClass, "<init>", "(III)V");
150    jobjectArray runs = env->NewObjectArray(runCount, JniConstants::bidiRunClass, NULL);
151    UBiDiLevel level = 0;
152    int start = 0;
153    int limit = 0;
154    for (int i = 0; i < runCount; ++i) {
155        ubidi_getLogicalRun(ubidi, start, &limit, &level);
156        jobject run = env->NewObject(JniConstants::bidiRunClass, bidiRunConstructor, start, limit, level);
157        env->SetObjectArrayElement(runs, i, run);
158        start = limit;
159    }
160    return runs;
161}
162
163static jintArray Bidi_ubidi_reorderVisual(JNIEnv* env, jclass, jbyteArray javaLevels, jint length) {
164    ScopedByteArrayRO levelBytes(env, javaLevels);
165    if (levelBytes.get() == NULL) {
166        return NULL;
167    }
168
169    const UBiDiLevel* levels = reinterpret_cast<const UBiDiLevel*>(levelBytes.get());
170
171    UniquePtr<int[]> indexMap(new int[length]);
172    ubidi_reorderVisual(levels, length, &indexMap[0]);
173
174    jintArray result = env->NewIntArray(length);
175    env->SetIntArrayRegion(result, 0, length, &indexMap[0]);
176    return result;
177}
178
179static JNINativeMethod gMethods[] = {
180    NATIVE_METHOD(Bidi, ubidi_close, "(J)V"),
181    NATIVE_METHOD(Bidi, ubidi_countRuns, "(J)I"),
182    NATIVE_METHOD(Bidi, ubidi_getDirection, "(J)I"),
183    NATIVE_METHOD(Bidi, ubidi_getLength, "(J)I"),
184    NATIVE_METHOD(Bidi, ubidi_getLevels, "(J)[B"),
185    NATIVE_METHOD(Bidi, ubidi_getParaLevel, "(J)B"),
186    NATIVE_METHOD(Bidi, ubidi_getRuns, "(J)[Ljava/text/Bidi$Run;"),
187    NATIVE_METHOD(Bidi, ubidi_open, "()J"),
188    NATIVE_METHOD(Bidi, ubidi_reorderVisual, "([BI)[I"),
189    NATIVE_METHOD(Bidi, ubidi_setLine, "(JII)J"),
190    NATIVE_METHOD(Bidi, ubidi_setPara, "(J[CII[B)V"),
191};
192void register_java_text_Bidi(JNIEnv* env) {
193    jniRegisterNativeMethods(env, "java/text/Bidi", gMethods, NELEM(gMethods));
194}
195