diff --git a/src/IKVM.Java/local/ikvm/runtime/Util.java b/src/IKVM.Java/local/ikvm/runtime/Util.java
index 4654b26052..fb7dc90c12 100644
--- a/src/IKVM.Java/local/ikvm/runtime/Util.java
+++ b/src/IKVM.Java/local/ikvm/runtime/Util.java
@@ -23,8 +23,14 @@
*/
package ikvm.runtime;
+import cli.System.Delegate;
import cli.System.Type;
import cli.System.RuntimeTypeHandle;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
import sun.misc.Unsafe;
public final class Util
@@ -48,8 +54,33 @@ private Util()
public static native Type getRuntimeTypeFromClass(Class classObject);
+ /**
+ * Creates a delegate of the given type that invokes the given method handle. The handle is adapted to the
+ * signature of the delegate's Invoke method, which performs any conversions required; a handle that cannot be
+ * adapted results in a {@link java.lang.invoke.WrongMethodTypeException}.
+ */
+ public static native Delegate getDelegateFromMethodHandle(Type delegateType, MethodHandle methodHandle);
+
+ /**
+ * Creates a delegate of the given type that invokes the given method or constructor. Access is checked as it is
+ * for {@link MethodHandles.Lookup#unreflect}; call {@code setAccessible(true)} on the member first in order to
+ * create a delegate for an otherwise inaccessible one.
+ */
+ public static Delegate getDelegateFromMethod(Type delegateType, Executable executable) throws IllegalAccessException
+ {
+ if (executable == null)
+ throw new NullPointerException("executable");
+
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ MethodHandle methodHandle = executable instanceof Constructor
+ ? lookup.unreflectConstructor((Constructor)executable)
+ : lookup.unreflect((Method)executable);
+
+ return getDelegateFromMethodHandle(delegateType, methodHandle);
+ }
+
public static native Throwable mapException(Throwable x);
-
+
public static native Throwable unmapException(Throwable x);
public static void throwException(cli.System.Exception x)
diff --git a/src/IKVM.Runtime/Java/Externs/ikvm/runtime/Util.cs b/src/IKVM.Runtime/Java/Externs/ikvm/runtime/Util.cs
index 1fbc79ad5c..fa74c7bf27 100644
--- a/src/IKVM.Runtime/Java/Externs/ikvm/runtime/Util.cs
+++ b/src/IKVM.Runtime/Java/Externs/ikvm/runtime/Util.cs
@@ -185,6 +185,46 @@ public static Type getRuntimeTypeFromClass(global::java.lang.Class classObject)
return wrapper.TypeAsBaseType;
}
+ ///
+ /// Creates a delegate of the given type that invokes the given method handle. The handle is adapted to the
+ /// signature of the delegate's Invoke method, which performs any conversions required (boxing, primitive
+ /// widening, ghost wrapping, receiver binding); an incompatible handle results in a
+ /// .
+ ///
+ ///
+ ///
+ ///
+ public static Delegate getDelegateFromMethodHandle(Type delegateType, global::java.lang.invoke.MethodHandle methodHandle)
+ {
+#if FIRST_PASS
+ throw new NotImplementedException();
+#else
+ if (delegateType == null)
+ throw new global::java.lang.NullPointerException("delegateType");
+ if (methodHandle == null)
+ throw new global::java.lang.NullPointerException("methodHandle");
+
+ var invoke = delegateType.BaseType == typeof(MulticastDelegate) ? delegateType.GetMethod("Invoke") : null;
+ if (invoke == null)
+ throw new global::java.lang.IllegalArgumentException(delegateType.FullName + " is not a delegate type.");
+
+ foreach (var parameter in invoke.GetParameters())
+ if (parameter.ParameterType.IsByRef || parameter.ParameterType.IsPointer)
+ throw new global::java.lang.IllegalArgumentException(delegateType.FullName + " has a by-ref or pointer parameter.");
+
+ // adapt the handle to the delegate's own signature; this is what performs the conversions, and is also
+ // what rejects a handle that cannot be called through this delegate
+ var methodType = JVM.Context.MethodHandleUtil.GetDelegateMethodType(delegateType);
+ methodHandle = methodHandle.asType(methodType).asFixedArity();
+
+ // the adapted handle is materialized as its canonical MH/MHV delegate, which by construction has exactly
+ // the signature of the requested delegate; binding its Invoke as the target closes over it
+ var inner = JVM.Context.MethodHandleUtil.GetDelegateForInvokeExact(methodHandle);
+ return Delegate.CreateDelegate(delegateType, inner, inner.GetType().GetMethod("Invoke"), false) ??
+ throw new global::java.lang.IllegalArgumentException("Cannot create a " + delegateType.FullName + " for a method handle of type " + methodType + ".");
+#endif
+ }
+
[HideFromJava]
public static Exception mapException(Exception e)
{
diff --git a/src/IKVM.Runtime/MethodHandleUtil.jniexport.cs b/src/IKVM.Runtime/MethodHandleUtil.jniexport.cs
index 0d4e614310..06bb39af2e 100644
--- a/src/IKVM.Runtime/MethodHandleUtil.jniexport.cs
+++ b/src/IKVM.Runtime/MethodHandleUtil.jniexport.cs
@@ -633,20 +633,32 @@ internal Type GetDelegateTypeForInvokeExact(global::java.lang.invoke.MethodType
return type._invokeExactDelegateType;
}
- internal T GetDelegateForInvokeExact(global::java.lang.invoke.MethodHandle mh)
- where T : class, Delegate
+ ///
+ /// Gets the canonical delegate that invokes the given method handle exactly. The type of the returned delegate
+ /// is the or instantiation that corresponds to the handle's method type, as
+ /// returned by .
+ ///
+ ///
+ ///
+ internal Delegate GetDelegateForInvokeExact(global::java.lang.invoke.MethodHandle mh)
{
var type = mh.type();
if (mh._invokeExactDelegate == null)
{
type._invokeExactDynamicMethod ??= DynamicMethodBuilder.CreateInvokeExact(context, type);
mh._invokeExactDelegate = type._invokeExactDynamicMethod.CreateDelegate(GetDelegateTypeForInvokeExact(type), mh);
- var del = mh._invokeExactDelegate as T;
- if (del != null)
- return del;
}
- throw java.lang.invoke.Invokers.newWrongMethodTypeException(GetDelegateMethodType(typeof(T)), type);
+ return (Delegate)mh._invokeExactDelegate;
+ }
+
+ internal T GetDelegateForInvokeExact(global::java.lang.invoke.MethodHandle mh)
+ where T : class, Delegate
+ {
+ if (GetDelegateForInvokeExact(mh) is T del)
+ return del;
+
+ throw java.lang.invoke.Invokers.newWrongMethodTypeException(GetDelegateMethodType(typeof(T)), mh.type());
}
///
diff --git a/src/IKVM.Tests/Java/ikvm/runtime/UtilDelegateTests.cs b/src/IKVM.Tests/Java/ikvm/runtime/UtilDelegateTests.cs
new file mode 100644
index 0000000000..467ce9fae0
--- /dev/null
+++ b/src/IKVM.Tests/Java/ikvm/runtime/UtilDelegateTests.cs
@@ -0,0 +1,157 @@
+using System;
+
+using FluentAssertions;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IKVM.Tests.Java.ikvm.runtime
+{
+
+ [TestClass]
+ public class UtilDelegateTests
+ {
+
+ static global::java.lang.Class Class(string name) => global::java.lang.Class.forName(name);
+
+ static global::java.lang.reflect.Method Method(string className, string methodName, params string[] parameterTypeNames)
+ {
+ var parameterTypes = new global::java.lang.Class[parameterTypeNames.Length];
+ for (int i = 0; i < parameterTypes.Length; i++)
+ parameterTypes[i] = Class(parameterTypeNames[i]);
+
+ return Class(className).getMethod(methodName, parameterTypes);
+ }
+
+ [TestMethod]
+ public void CanCreateDelegateForInstanceMethod()
+ {
+ var m = Method("java.util.ArrayList", "size");
+ var d = (Func