Skip to content

Commit 9f380eb

Browse files
committed
[Java.Interop] Normalize JniType.Name on JNI convention.
java.lang.Class.getName() is...weird, at least on Desktop Java: it is NOT the JNI value; `java.lang.Object.class.getName()` returns "java.lang.Object", i.e. "Java" convention. Nested types are some bizarre combination of Java & JNI: `java.lang.Thread.State.class.getName()` returns "java.lang.Thread$State", i.e. `$` is used to separate nested types, as is done in JNI. This is why JniType.Name already replaces s#\.#/#g, resulting in JNI convention ('java/lang/Object', 'java/lang/Thread$State'). ...except for builtin types: `int.class.getName()` returns "int", *not* "I", EVEN THOUGH `int[].class.getName()` is "[I". Ditto other arrays; `Object[].class.getName()` is "[Ljava.lang.Object;", because why the hell not? In short, Class.getName() is *really weird* about which convention it's using, using either Java, JNI, or bastardized mixing of the two. Which makes it difficult to sanely use the result of JniType.Name in any other context, e.g. to generate JNI method signatures, because you never know what the value is. Fix that: JniType.Name shall henceforth return a value in JNI convention, including for bulitin types: add a typename mapping from the Java builtin values (e.g. "int") to their corresponding JNI values (e.g. "I"), so that JniType.Name for an `int` is "I". A problem, testing-wise: `new JniType("I")` and `new JniType("int")` fail, because JNIEnv::FindClass() refuses to do anything with builtins. (JNI bug?) Thus, in order to *test* that JniType.Name for an `int` is "I", we first need to convince the JVM to give us a Class instance for `int`. To do *that*, we use JNI to invoke Java Reflection to lookup java.lang.Object.hashCode(), which returns an int. This allows us to get Java's `int.class`, allowing us to call `int.class.getName()` and ensure that it is sanely mapped to "I", not "int".
1 parent 210558e commit 9f380eb

File tree

2 files changed

+37
-1
lines changed

2 files changed

+37
-1
lines changed

src/Java.Interop/Java.Interop/JniType.cs

+18-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ namespace Java.Interop {
1111

1212
public sealed class JniType : IDisposable {
1313

14+
readonly static KeyValuePair<string, string>[] BuiltinMappings = new KeyValuePair<string, string>[] {
15+
new KeyValuePair<string, string>("byte", "B"),
16+
new KeyValuePair<string, string>("boolean", "Z"),
17+
new KeyValuePair<string, string>("char", "C"),
18+
new KeyValuePair<string, string>("double", "D"),
19+
new KeyValuePair<string, string>("float", "F"),
20+
new KeyValuePair<string, string>("int", "I"),
21+
new KeyValuePair<string, string>("long", "J"),
22+
new KeyValuePair<string, string>("short", "S"),
23+
new KeyValuePair<string, string>("void", "V"),
24+
};
25+
1426
public static unsafe JniType DefineClass (string name, JniReferenceSafeHandle loader, byte[] classFileData)
1527
{
1628
fixed (byte* buf = classFileData) {
@@ -46,8 +58,13 @@ public string Name {
4658
AssertValid ();
4759

4860
var s = JniEnvironment.Current.Class_getName.CallVirtualObjectMethod (SafeHandle);
49-
return JniEnvironment.Strings.ToString (s, JniHandleOwnership.Transfer)
61+
var n = JniEnvironment.Strings.ToString (s, JniHandleOwnership.Transfer)
5062
.Replace ('.', '/');
63+
for (int i = 0; i < BuiltinMappings.Length; ++i) {
64+
if (n == BuiltinMappings [i].Key)
65+
return BuiltinMappings [i].Value;
66+
}
67+
return n;
5168
}
5269
}
5370

src/Java.Interop/Tests/Java.Interop/JniTypeTest.cs

+19
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,25 @@ public void GetStaticFieldID ()
119119
}
120120
}
121121

122+
[Test]
123+
public void Name ()
124+
{
125+
using (var Object_class = new JniType ("java/lang/Object"))
126+
using (var Class_class = new JniType ("java/lang/Class"))
127+
using (var Class_getMethod = Class_class.GetInstanceMethod ("getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"))
128+
using (var Method_class = new JniType ("java/lang/reflect/Method"))
129+
using (var Method_getReturnType = Method_class.GetInstanceMethod ("getReturnType", "()Ljava/lang/Class;"))
130+
using (var hashCode_str = JniEnvironment.Strings.NewString ("hashCode"))
131+
using (var Object_hashCode = Class_getMethod.CallVirtualObjectMethod (Object_class.SafeHandle, new JValue (hashCode_str)))
132+
using (var Object_hashCode_rt = Method_getReturnType.CallVirtualObjectMethod (Object_hashCode))
133+
{
134+
Assert.AreEqual ("java/lang/Object", Object_class.Name);
135+
136+
using (var t = new JniType (Object_hashCode_rt, JniHandleOwnership.DoNotTransfer))
137+
Assert.AreEqual ("I", t.Name);
138+
}
139+
}
140+
122141
[Test]
123142
public void RegisterWithVM ()
124143
{

0 commit comments

Comments
 (0)