diff --git a/src/main/java/org/bytedeco/javacpp/annotation/Utf16String.java b/src/main/java/org/bytedeco/javacpp/annotation/Utf16String.java new file mode 100644 index 000000000..7424f95b6 --- /dev/null +++ b/src/main/java/org/bytedeco/javacpp/annotation/Utf16String.java @@ -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 { } \ No newline at end of file diff --git a/src/main/java/org/bytedeco/javacpp/tools/Generator.java b/src/main/java/org/bytedeco/javacpp/tools/Generator.java index 572dcf3be..6160c0ed9 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/Generator.java +++ b/src/main/java/org/bytedeco/javacpp/tools/Generator.java @@ -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; @@ -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 "); + 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::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::length(ptr));"); + out.println("}"); + out.println(); } if (convertStrings) { out.println("static JavaCPP_noinline const char* JavaCPP_getStringBytes(JNIEnv* env, jstring str) {"); @@ -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:"); @@ -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, \"\", \"(Ljava/lang/String;)V\");"); out.println(" if (mid == NULL) {"); @@ -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); @@ -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 + ";"); @@ -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 \"" + @@ -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()) { @@ -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++) { @@ -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); @@ -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]); @@ -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()) { @@ -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); @@ -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;"); @@ -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")) { @@ -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*"; @@ -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) { @@ -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) { @@ -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; + } } diff --git a/src/test/java/org/bytedeco/javacpp/AdapterTest.java b/src/test/java/org/bytedeco/javacpp/AdapterTest.java index be8a88186..c5eae9b18 100644 --- a/src/test/java/org/bytedeco/javacpp/AdapterTest.java +++ b/src/test/java/org/bytedeco/javacpp/AdapterTest.java @@ -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; @@ -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); @@ -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); @@ -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(); } diff --git a/src/test/resources/org/bytedeco/javacpp/AdapterTest.h b/src/test/resources/org/bytedeco/javacpp/AdapterTest.h index ea677fc35..8bf5fb5b2 100644 --- a/src/test/resources/org/bytedeco/javacpp/AdapterTest.h +++ b/src/test/resources/org/bytedeco/javacpp/AdapterTest.h @@ -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) {