Skip to content

Commit

Permalink
Add @Utf16String annotation to map java.lang.String to `unsigned shor…
Browse files Browse the repository at this point in the history
…t*` (array of UTF-16 code units)
  • Loading branch information
equeim committed Dec 14, 2020
1 parent 139c4d5 commit 414d598
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 28 deletions.
16 changes: 16 additions & 0 deletions src/main/java/org/bytedeco/javacpp/annotation/Utf16String.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.bytedeco.javacpp.annotation;

import org.bytedeco.javacpp.tools.Generator;

import java.lang.annotation.*;

/**
* Used to map {@code java.lang.String} to array of UTF-16 code units
* ({@code unsigned short*}) instead of UTF-8 encoded byte array ({@code const char*})
*
* @see Generator
*/

@Documented @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.PARAMETER})
public @interface Utf16String { }
133 changes: 112 additions & 21 deletions src/main/java/org/bytedeco/javacpp/tools/Generator.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import org.bytedeco.javacpp.annotation.MemberSetter;
import org.bytedeco.javacpp.annotation.Name;
import org.bytedeco.javacpp.annotation.Namespace;
import org.bytedeco.javacpp.annotation.Utf16String;
import org.bytedeco.javacpp.annotation.NoDeallocator;
import org.bytedeco.javacpp.annotation.NoException;
import org.bytedeco.javacpp.annotation.NoOffset;
Expand Down Expand Up @@ -937,20 +938,41 @@ boolean classes(boolean handleExceptions, boolean defineAdapters, boolean conver
out.println("}");
out.println();
if (handleExceptions || convertStrings) {
out.println("static JavaCPP_noinline jstring JavaCPP_createString(JNIEnv* env, const char* ptr) {");
out.println("#include <string>");
out.println("static JavaCPP_noinline jstring JavaCPP_createStringUTF8(JNIEnv* env, const char* ptr, size_t length) {");
out.println(" if (ptr == NULL) {");
out.println(" return NULL;");
out.println(" }");
out.println("#ifdef MODIFIED_UTF8_STRING");
out.println(" return env->NewStringUTF(ptr);");
out.println("#else");
out.println(" size_t length = strlen(ptr);");
out.println(" jbyteArray bytes = env->NewByteArray(length < INT_MAX ? length : INT_MAX);");
out.println(" env->SetByteArrayRegion(bytes, 0, length < INT_MAX ? length : INT_MAX, (signed char*)ptr);");
out.println(" return (jstring)env->NewObject(JavaCPP_getClass(env, " + jclasses.index(String.class) + "), JavaCPP_stringMID, bytes);");
out.println("#endif");
out.println("}");
out.println();
out.println("static JavaCPP_noinline jstring JavaCPP_createStringUTF8(JNIEnv* env, const char* ptr) {");
out.println(" if (ptr == NULL) {");
out.println(" return NULL;");
out.println(" }");
out.println(" return JavaCPP_createStringUTF8(env, ptr, std::char_traits<char>::length(ptr));");
out.println("}");
out.println();
out.println("static JavaCPP_noinline jstring JavaCPP_createStringUTF16(JNIEnv* env, const unsigned short* ptr, size_t length) {");
out.println(" if (ptr == NULL) {");
out.println(" return NULL;");
out.println(" }");
out.println(" return env->NewString(ptr, length);");
out.println("}");
out.println();
out.println("static JavaCPP_noinline jstring JavaCPP_createStringUTF16(JNIEnv* env, const unsigned short* ptr) {");
out.println(" if (ptr == NULL) {");
out.println(" return NULL;");
out.println(" }");
out.println(" return JavaCPP_createStringUTF16(env, ptr, std::char_traits<unsigned short>::length(ptr));");
out.println("}");
out.println();
}
if (convertStrings) {
out.println("static JavaCPP_noinline const char* JavaCPP_getStringBytes(JNIEnv* env, jstring str) {");
Expand Down Expand Up @@ -985,6 +1007,19 @@ boolean classes(boolean handleExceptions, boolean defineAdapters, boolean conver
out.println("#endif");
out.println("}");
out.println();
out.println("static JavaCPP_noinline const unsigned short* JavaCPP_getStringChars(JNIEnv* env, jstring str) {");
out.println(" if (str == NULL) {");
out.println(" return NULL;");
out.println(" }");
out.println(" return env->GetStringChars(str, NULL);");
out.println("}");
out.println();
out.println("static JavaCPP_noinline void JavaCPP_releaseStringChars(JNIEnv* env, jstring str, const unsigned short* ptr) {");
out.println(" if (str != NULL) {");
out.println(" env->ReleaseStringChars(str, ptr);");
out.println(" }");
out.println("}");
out.println();
}
out.println("class JavaCPP_hidden JavaCPP_exception : public std::exception {");
out.println("public:");
Expand Down Expand Up @@ -1012,9 +1047,9 @@ boolean classes(boolean handleExceptions, boolean defineAdapters, boolean conver
out.println(" try {");
out.println(" throw;");
out.println(" } catch (GENERIC_EXCEPTION_CLASS& e) {");
out.println(" str = JavaCPP_createString(env, e.GENERIC_EXCEPTION_TOSTRING);");
out.println(" str = JavaCPP_createStringUTF8(env, e.GENERIC_EXCEPTION_TOSTRING);");
out.println(" } catch (...) {");
out.println(" str = JavaCPP_createString(env, \"Unknown exception.\");");
out.println(" str = JavaCPP_createStringUTF8(env, \"Unknown exception.\");");
out.println(" }");
out.println(" jmethodID mid = JavaCPP_getMethodID(env, i, \"<init>\", \"(Ljava/lang/String;)V\");");
out.println(" if (mid == NULL) {");
Expand Down Expand Up @@ -1978,7 +2013,7 @@ void parametersBefore(MethodInformation methodInfo) {
Annotation passBy = by(methodInfo, j);
String cast = cast(methodInfo, j);
String[] typeName = methodInfo.parameterRaw[j] ? new String[] { "" }
: cppTypeName(methodInfo.parameterTypes[j]);
: cppTypeName(methodInfo.parameterTypes[j], methodInfo.parameterAnnotations[j]);
AdapterInformation adapterInfo = methodInfo.parameterRaw[j] ? null
: adapterInformation(false, methodInfo, j);

Expand Down Expand Up @@ -2048,7 +2083,11 @@ void parametersBefore(MethodInformation methodInfo) {
}
} else if (methodInfo.parameterTypes[j] == String.class) {
passesStrings = true;
out.println("JavaCPP_getStringBytes(env, arg" + j + ");");
if (isUtf16String(methodInfo.parameterAnnotations[j])) {
out.println("JavaCPP_getStringChars(env, arg" + j + ");");
} else {
out.println("JavaCPP_getStringBytes(env, arg" + j + ");");
}
if (adapterInfo != null || prevAdapterInfo != null) {
out.println(" jlong size" + j + " = 0;");
out.println(" void* owner" + j + " = (void*)ptr" + j + ";");
Expand Down Expand Up @@ -2223,13 +2262,16 @@ String returnBefore(MethodInformation methodInfo) {
}
} else if (methodInfo.returnType == String.class) {
out.println(" jstring rarg = NULL;");
out.println(" const char* rptr;");
final boolean utf16 = isUtf16String(methodInfo.annotations);
final String ptrType = utf16 ? "const unsigned short*" : "const char*";
out.println(" " + ptrType + " rptr;");
if (returnBy instanceof ByRef) {
returnPrefix = "std::string rstr(";
final String charType = utf16 ? "unsigned short" : "char";
returnPrefix = "std::basic_string<" + charType + "> rstr(";
} else if (returnBy instanceof ByPtrPtr) {
returnPrefix = "rptr = NULL; const char** rptrptr = (const char**)";
returnPrefix = "rptr = NULL; " + ptrType + "* rptrptr = (" + ptrType + "*)";
} else {
returnPrefix += "(const char*)";
returnPrefix += "(" + ptrType + ")";
}
} else {
logger.warn("Method \"" + methodInfo.method + "\" has unsupported return type \"" +
Expand Down Expand Up @@ -2640,7 +2682,10 @@ void returnAfter(MethodInformation methodInfo) {
} else if (methodInfo.returnType == String.class) {
passesStrings = true;
out.println(indent + "if (rptr != NULL) {");
out.println(indent + " rarg = JavaCPP_createString(env, rptr);");
final String createString = isUtf16String(methodInfo.annotations)
? "JavaCPP_createStringUTF16"
: "JavaCPP_createStringUTF8";
out.println(indent + " rarg = " + createString + "(env, rptr" + (adapterInfo != null ? ", radapter.size);" : ");"));
out.println(indent + "}");
} else if (methodInfo.returnType.isArray() &&
methodInfo.returnType.getComponentType().isPrimitive()) {
Expand Down Expand Up @@ -2732,7 +2777,11 @@ void parametersAfter(MethodInformation methodInfo) {
", JavaCPP_addressFID, ptr_to_jlong(ptr" + j + "));");
}
} else if (methodInfo.parameterTypes[j] == String.class) {
out.println(" JavaCPP_releaseStringBytes(env, arg" + j + ", ptr" + j + ");");
if (isUtf16String(methodInfo.parameterAnnotations[j])) {
out.println(" JavaCPP_releaseStringChars(env, arg" + j + ", ptr" + j + ");");
} else {
out.println(" JavaCPP_releaseStringBytes(env, arg" + j + ", ptr" + j + ");");
}
} else if (methodInfo.parameterTypes[j].isArray() &&
methodInfo.parameterTypes[j].getComponentType().isPrimitive()) {
for (int k = 0; adapterInfo != null && k < adapterInfo.argc; k++) {
Expand Down Expand Up @@ -2915,7 +2964,7 @@ void callback(Class<?> cls, Method callbackMethod, String callbackName, int allo
}
String callbackReturnCast = cast(callbackReturnType, callbackAnnotations);
Annotation returnBy = by(callbackAnnotations);
String[] returnTypeName = cppTypeName(callbackReturnType);
String[] returnTypeName = cppTypeName(callbackReturnType, callbackAnnotations);
String returnValueTypeName = valueTypeName(returnTypeName);
AdapterInformation returnAdapterInfo = adapterInformation(false, returnValueTypeName, callbackAnnotations);
boolean throwsExceptions = !noException(cls, callbackMethod);
Expand Down Expand Up @@ -2950,7 +2999,7 @@ void callback(Class<?> cls, Method callbackMethod, String callbackName, int allo
out.println(" }");
}
} else {
String[] typeName = cppTypeName(callbackParameterTypes[j]);
String[] typeName = cppTypeName(callbackParameterTypes[j], callbackParameterAnnotations[j]);
String valueTypeName = valueTypeName(typeName);
AdapterInformation adapterInfo = adapterInformation(false, valueTypeName, callbackParameterAnnotations[j]);

Expand Down Expand Up @@ -3037,7 +3086,23 @@ void callback(Class<?> cls, Method callbackMethod, String callbackName, int allo
out.println(" args[" + j + "].l = obj" + j + ";");
} else if (callbackParameterTypes[j] == String.class) {
passesStrings = true;
out.println(" jstring obj" + j + " = JavaCPP_createString(env, (const char*)" + (adapterInfo != null ? "adapter" : "arg") + j + ");");

final String createString;
final String ptrType;
if (isUtf16String(callbackParameterAnnotations[j])) {
createString = "JavaCPP_createStringUTF16";
ptrType = "const unsigned short*";
} else {
createString = "JavaCPP_createStringUTF8";
ptrType = "const char*";
}
if (adapterInfo != null) {
final String adapter = "adapter" + j;
out.println(" jstring obj" + j + " = " + createString + "(env, (" + ptrType + ") " + adapter + ", " + adapter + ".size);");
} else {
out.println(" jstring obj" + j + " = " + createString + "(env, (" + ptrType + ") arg" + j + ");");
}

out.println(" args[" + j + "].l = obj" + j + ";");
} else if (callbackParameterTypes[j].isArray() &&
callbackParameterTypes[j].getComponentType().isPrimitive()) {
Expand Down Expand Up @@ -3120,7 +3185,7 @@ void callback(Class<?> cls, Method callbackMethod, String callbackName, int allo

for (int j = 0; j < callbackParameterTypes.length; j++) {
if (Pointer.class.isAssignableFrom(callbackParameterTypes[j])) {
String[] typeName = cppTypeName(callbackParameterTypes[j]);
String[] typeName = cppTypeName(callbackParameterTypes[j], callbackParameterAnnotations[j]);
Annotation passBy = by(callbackParameterAnnotations[j]);
String cast = cast(callbackParameterTypes[j], callbackParameterAnnotations[j]);
String valueTypeName = valueTypeName(typeName);
Expand Down Expand Up @@ -3197,7 +3262,10 @@ void callback(Class<?> cls, Method callbackMethod, String callbackName, int allo
}
} else if (callbackReturnType == String.class) {
passesStrings = true;
out.println(" " + returnTypeName[0] + " rptr" + returnTypeName[1] + " = JavaCPP_getStringBytes(env, rarg);");
out.println(" " + returnTypeName[0] + " rptr" + returnTypeName[1] +
(isUtf16String(callbackAnnotations)
? " = JavaCPP_getStringChars(env, rarg);"
: " = JavaCPP_getStringBytes(env, rarg);"));
if (returnAdapterInfo != null) {
out.println(" jlong rsize = 0;");
out.println(" void* rowner = (void*)rptr;");
Expand Down Expand Up @@ -3879,7 +3947,7 @@ String[] cppCastTypeName(Class<?> type, Annotation ... annotations) {
// prioritize @Cast
continue;
}
typeName = cppTypeName(type);
typeName = cppTypeName(type, annotations);
if (typeName[0].contains("(*")) {
// function pointer
if (b.length > 0 && b[0] && !typeName[0].endsWith(" const")) {
Expand Down Expand Up @@ -3907,12 +3975,12 @@ String[] cppCastTypeName(Class<?> type, Annotation ... annotations) {
logger.warn("Without \"Adapter\", \"Cast\" and \"Const\" annotations are mutually exclusive.");
}
if (typeName == null) {
typeName = cppTypeName(type);
typeName = cppTypeName(type, annotations);
}
return typeName;
}

String[] cppTypeName(Class<?> type) {
String[] cppTypeName(Class<?> type, Annotation[] annotations) {
String prefix = "", suffix = "";
if (type == Buffer.class || type == Pointer.class) {
prefix = "void*";
Expand All @@ -3935,7 +4003,11 @@ String[] cppTypeName(Class<?> type) {
} else if (type == PointerPointer.class) {
prefix = "void**";
} else if (type == String.class) {
prefix = "const char*";
if (isUtf16String(annotations)) {
prefix = "const unsigned short*";
} else {
prefix = "const char*";
}
} else if (type == byte.class) {
prefix = "signed char";
} else if (type == long.class) {
Expand Down Expand Up @@ -3964,6 +4036,10 @@ String[] cppTypeName(Class<?> type) {
return new String[] { prefix, suffix };
}

String[] cppTypeName(Class<?> type) {
return cppTypeName(type, null);
}

String[] cppFunctionTypeName(Method... functionMethods) {
Method functionMethod = null;
if (functionMethods != null) {
Expand Down Expand Up @@ -4200,4 +4276,19 @@ static String mangle(String name) {
}
return mangledName.toString();
}

static boolean isUtf16String(Annotation[] annotations) {
if (annotations == null) {
return false;
}
for (Annotation annotation : annotations) {
if (annotation instanceof Utf16String) {
return true;
}
if (annotation.annotationType().isAnnotationPresent(Utf16String.class)) {
return true;
}
}
return false;
}
}
17 changes: 13 additions & 4 deletions src/test/java/org/bytedeco/javacpp/AdapterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.bytedeco.javacpp.annotation.StdVector;
import org.bytedeco.javacpp.annotation.StdWString;
import org.bytedeco.javacpp.annotation.UniquePtr;
import org.bytedeco.javacpp.annotation.Utf16String;
import org.bytedeco.javacpp.tools.Builder;
import org.junit.BeforeClass;
import org.junit.Test;
Expand All @@ -51,12 +52,14 @@ public class AdapterTest {
static native @StdString BytePointer testStdString(@StdString BytePointer str);

static native @StdWString CharPointer testStdWString(@StdWString CharPointer str);
static native @StdWString @Utf16String String testStdWString(@StdWString @Utf16String String str);
static native @StdWString IntPointer testStdWString(@StdWString IntPointer str);

static native String testCharString(String str);
static native @Cast("char*") BytePointer testCharString(@Cast("char*") BytePointer str);

static native CharPointer testShortString(CharPointer str);
static native @Utf16String String testShortString(@Utf16String String str);

static native IntPointer testIntString(IntPointer str);

Expand Down Expand Up @@ -145,6 +148,8 @@ static class MovedData extends Pointer {
CharPointer textCharPtr2 = testStdWString(textCharPtr1);
assertEquals(textStr1, textCharPtr1.getString());
assertEquals(textStr1, textCharPtr2.getString());
String textStr3 = testStdWString(textStr1);
assertEquals(textStr1, textStr3);
} else {
// UTF-32
IntPointer textIntPtr1 = new IntPointer(textStr1);
Expand Down Expand Up @@ -175,11 +180,15 @@ static class MovedData extends Pointer {
@Test public void testShortString() {
System.out.println("ShortString");

String textStr = "This is a normal ASCII string.";
CharPointer textPtr1 = new CharPointer(textStr);
String textStr1 = "This is a normal ASCII string.";
CharPointer textPtr1 = new CharPointer(textStr1);
CharPointer textPtr2 = testShortString(textPtr1);
assertEquals(textStr, textPtr1.getString());
assertEquals(textStr, textPtr2.getString());
assertEquals(textStr1, textPtr1.getString());
assertEquals(textStr1, textPtr2.getString());

String textStr2 = testShortString(textStr1);
assertEquals(textStr1, textStr2);

System.gc();
}

Expand Down
13 changes: 10 additions & 3 deletions src/test/resources/org/bytedeco/javacpp/AdapterTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,16 @@ std::wstring testStdWString(std::wstring str) {
return str;
}

char *testCharString(const char *str) {
return strdup(str);
// memory leak...
const char *testCharString(const char *str) {
return str;
}

char *testCharString(char *str) {
return str;
}

const unsigned short *testShortString(const unsigned short *str) {
return str;
}

unsigned short *testShortString(unsigned short *str) {
Expand Down

0 comments on commit 414d598

Please sign in to comment.