Skip to content

Commit

Permalink
Add @AsUtf16 annotation to map java.lang.String to unsigned short*
Browse files Browse the repository at this point in the history
…(array of UTF-16 code units)
  • Loading branch information
equeim committed Feb 1, 2021
1 parent 7918521 commit fa54c45
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 31 deletions.
17 changes: 17 additions & 0 deletions src/main/java/org/bytedeco/javacpp/annotation/AsUtf16.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.bytedeco.javacpp.annotation;

import org.bytedeco.javacpp.tools.Generator;

import java.lang.annotation.*;

/**
* Annotation indicating that {@link java.lang.String} should be mapped
* 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 AsUtf16 { }
126 changes: 100 additions & 26 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.AsUtf16;
import org.bytedeco.javacpp.annotation.NoDeallocator;
import org.bytedeco.javacpp.annotation.NoException;
import org.bytedeco.javacpp.annotation.NoOffset;
Expand Down Expand Up @@ -937,23 +938,44 @@ 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) {");
out.println("static JavaCPP_noinline const char* JavaCPP_getStringCharsUTF8(JNIEnv* env, jstring str) {");
out.println(" if (str == NULL) {");
out.println(" return NULL;");
out.println(" }");
Expand All @@ -975,7 +997,7 @@ boolean classes(boolean handleExceptions, boolean defineAdapters, boolean conver
out.println("#endif");
out.println("}");
out.println();
out.println("static JavaCPP_noinline void JavaCPP_releaseStringBytes(JNIEnv* env, jstring str, const char* ptr) {");
out.println("static JavaCPP_noinline void JavaCPP_releaseStringCharsUTF8(JNIEnv* env, jstring str, const char* ptr) {");
out.println("#ifdef MODIFIED_UTF8_STRING");
out.println(" if (str != NULL) {");
out.println(" env->ReleaseStringUTFChars(str, ptr);");
Expand All @@ -985,6 +1007,23 @@ boolean classes(boolean handleExceptions, boolean defineAdapters, boolean conver
out.println("#endif");
out.println("}");
out.println();
out.println("static JavaCPP_noinline const unsigned short* JavaCPP_getStringCharsUTF16(JNIEnv* env, jstring str) {");
out.println(" if (str == NULL) {");
out.println(" return NULL;");
out.println(" }");
out.println(" const jsize length = env->GetStringLength(str);");
out.println(" unsigned short* ptr = new (std::nothrow) unsigned short[length + 1];");
out.println(" if (ptr != NULL) {");
out.println(" env->GetStringRegion(str, 0, length, ptr);");
out.println(" ptr[length] = 0;");
out.println(" }");
out.println(" return ptr;");
out.println("}");
out.println();
out.println("static JavaCPP_noinline void JavaCPP_releaseStringCharsUTF16(JNIEnv*, jstring, const unsigned short* ptr) {");
out.println(" delete[] ptr;");
out.println("}");
out.println();
}
out.println("class JavaCPP_hidden JavaCPP_exception : public std::exception {");
out.println("public:");
Expand Down Expand Up @@ -1012,9 +1051,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 @@ -2029,7 +2068,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 @@ -2099,7 +2138,7 @@ void parametersBefore(MethodInformation methodInfo) {
}
} else if (methodInfo.parameterTypes[j] == String.class) {
passesStrings = true;
out.println("JavaCPP_getStringBytes(env, arg" + j + ");");
out.println("JavaCPP_getStringChars" + stringFunctionSuffix(methodInfo.parameterAnnotations[j]) + "(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 @@ -2268,19 +2307,19 @@ String returnBefore(MethodInformation methodInfo) {
if (FunctionPointer.class.isAssignableFrom(methodInfo.returnType)) {
out.println(" rptr = new (std::nothrow) " + valueTypeName + ";");
if (returnBy instanceof ByPtrPtr) {
String[] cpptypeName = cppTypeName(methodInfo.returnType);
String[] cpptypeName = cppTypeName(methodInfo.returnType, methodInfo.annotations);
returnPrefix = cpptypeName[0] + "* rptrptr" + cpptypeName[1] + " = ";
}
}
} else if (methodInfo.returnType == String.class) {
out.println(" jstring rarg = NULL;");
out.println(" const char* rptr;");
out.println(" " + typeName[0] + " rptr;");
if (returnBy instanceof ByRef) {
returnPrefix = "std::string rstr(";
returnPrefix = "std::basic_string<" + valueTypeName + "> rstr(";
} else if (returnBy instanceof ByPtrPtr) {
returnPrefix = "rptr = NULL; const char** rptrptr = (const char**)";
returnPrefix = "rptr = NULL; " + typeName[0] + "* rptrptr = (" + typeName[0] + "*)";
} else {
returnPrefix += "(const char*)";
returnPrefix += "(" + typeName[0] + ")";
}
} else {
logger.warn("Method \"" + methodInfo.method + "\" has unsupported return type \"" +
Expand Down Expand Up @@ -2691,7 +2730,7 @@ 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);");
out.println(indent + " rarg = JavaCPP_createString" + stringFunctionSuffix(methodInfo.annotations) + "(env, rptr" + (adapterInfo != null ? ", radapter.size);" : ");"));
out.println(indent + "}");
} else if (methodInfo.returnType.isArray() &&
methodInfo.returnType.getComponentType().isPrimitive()) {
Expand Down Expand Up @@ -2783,7 +2822,7 @@ 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 + ");");
out.println(" JavaCPP_releaseStringChars" + stringFunctionSuffix(methodInfo.parameterAnnotations[j]) + "(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 @@ -2966,7 +3005,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 @@ -3001,7 +3040,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 @@ -3088,7 +3127,15 @@ 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 suffix = stringFunctionSuffix(callbackParameterAnnotations[j]);
if (adapterInfo != null) {
final String adapter = "adapter" + j;
out.println(" jstring obj" + j + " = JavaCPP_createString" + suffix + "(env, (" + typeName[0] + ") " + adapter + ", " + adapter + ".size);");
} else {
out.println(" jstring obj" + j + " = JavaCPP_createString" + suffix + "(env, (" + typeName[0] + ") arg" + j + ");");
}

out.println(" args[" + j + "].l = obj" + j + ";");
} else if (callbackParameterTypes[j].isArray() &&
callbackParameterTypes[j].getComponentType().isPrimitive()) {
Expand Down Expand Up @@ -3171,7 +3218,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 @@ -3248,7 +3295,7 @@ 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] + " = JavaCPP_getStringChars" + stringFunctionSuffix(callbackAnnotations) + "(env, rarg);");
if (returnAdapterInfo != null) {
out.println(" jlong rsize = 0;");
out.println(" void* rowner = (void*)rptr;");
Expand All @@ -3274,9 +3321,9 @@ void callback(Class<?> cls, Method callbackMethod, String callbackName, int allo
out.println(" if (exc != NULL) {");
out.println(" jstring str = (jstring)env->CallObjectMethod(exc, JavaCPP_toStringMID);");
out.println(" env->DeleteLocalRef(exc);");
out.println(" const char *msg = JavaCPP_getStringBytes(env, str);");
out.println(" const char *msg = JavaCPP_getStringCharsUTF8(env, str);");
out.println(" JavaCPP_exception e(msg);");
out.println(" JavaCPP_releaseStringBytes(env, str, msg);");
out.println(" JavaCPP_releaseStringCharsUTF8(env, str, msg);");
out.println(" env->DeleteLocalRef(str);");
out.println(" JavaCPP_detach(attached);");
out.println(" throw e;");
Expand Down Expand Up @@ -3930,7 +3977,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 @@ -3958,12 +4005,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 @@ -3986,7 +4033,14 @@ String[] cppTypeName(Class<?> type) {
} else if (type == PointerPointer.class) {
prefix = "void**";
} else if (type == String.class) {
prefix = "const char*";
switch (getStringCharset(annotations)) {
case UTF16:
prefix = "const unsigned short*";
break;
case UTF8:
prefix = "const char*";
break;
}
} else if (type == byte.class) {
prefix = "signed char";
} else if (type == long.class) {
Expand Down Expand Up @@ -4015,6 +4069,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 @@ -4251,4 +4309,20 @@ static String mangle(String name) {
}
return mangledName.toString();
}

private static StringCharset getStringCharset(Annotation[] annotations) {
if (annotations == null) {
return StringCharset.DEFAULT;
}
for (Annotation annotation : annotations) {
if (annotation instanceof AsUtf16) {
return StringCharset.UTF16;
}
}
return StringCharset.DEFAULT;
}

private static String stringFunctionSuffix(Annotation[] annotations) {
return getStringCharset(annotations).toString();
}
}
8 changes: 8 additions & 0 deletions src/main/java/org/bytedeco/javacpp/tools/StringCharset.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.bytedeco.javacpp.tools;

public enum StringCharset {
UTF8,
UTF16;

public static final StringCharset DEFAULT = UTF8;
}
Loading

0 comments on commit fa54c45

Please sign in to comment.