/*
 * Copyright 2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.spockframework.mock.runtime;

import org.spockframework.mock.*;
import org.spockframework.util.ReflectionUtil;

import java.lang.reflect.*;

public class DefaultMethodInvoker implements IResponseGenerator {
  private final Object target;
  private final Method method;
  private final Object[] arguments;

  public DefaultMethodInvoker(Object target, Method method, Object[] arguments) {
    this.target = target;
    this.method = method;
    this.arguments = arguments;
  }

  @Override
  public Object respond(IMockInvocation invocation) {
    // This implementation uses classes from the java.lang.invoke package in order to invoke a default method.
    // Without exception handling, the implementation is analogous to the following code:
    //      final Field field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
    //      field.setAccessible(true);
    //      final MethodHandles.Lookup implLookup = (MethodHandles.Lookup) field.get(null);
    //      return implLookup.unreflectSpecial(method, method.getDeclaringClass()).bindTo(target).invokeWithArguments((Object)arguments);
    // The java.lang.invoke package is only available since Java 7. In order to preserve the compatibility of spock-core
    // with older versions of Java, we rewrite the above code using reflection.

    Object boundHandle = null;
    Method invokeWithArgumentsMethod = null;
    try {
      // Get the private field Lookup.IMPL_LOOKUP, which is maximally privileged
      Class<?> lookupClass = Class.forName("java.lang.invoke.MethodHandles$Lookup");
      final Field field = lookupClass.getDeclaredField("IMPL_LOOKUP");
      field.setAccessible(true);
      Object implLookup = field.get(null);

      // Get a method handle for the default method
      Method unreflectSpecialMethod = lookupClass.getMethod("unreflectSpecial", Method.class, Class.class);
      Object specialHandle = unreflectSpecialMethod.invoke(implLookup, method, method.getDeclaringClass());

      // Get a bound handle that prepends the target to the original arguments
      Method bindToMethod = specialHandle.getClass().getMethod("bindTo", Object.class);
      boundHandle = bindToMethod.invoke(specialHandle, target);

      // Get the method MethodHandle.invokeWithArguments(Object...)
      invokeWithArgumentsMethod = boundHandle.getClass().getMethod("invokeWithArguments", Object[].class);
    } catch (Exception e) {
      throw new CannotCreateMockException(target.getClass(), "Failed to invoke default method '" + method.getName() + "'", e);
    }

    // Call boundHandle.invokeWithArguments(arguments), sneaky throwing possible exceptions
    Object result = ReflectionUtil.invokeMethod(boundHandle, invokeWithArgumentsMethod, (Object)arguments);
    return result;
  }
}

@@ -714,24 +714,147 @@
 til.
-ReflectionUtil;%0A
+ExceptionUtil;%0Aimport org.spockframework.util.ReflectionUtil;%0A%0Aimport java.lang.invoke.MethodHandle;%0Aimport java.lang.invoke.MethodHandles;
 %0Aimp
@@ -1289,211 +1289,45 @@
     
-// This implementation uses classes from the java.lang.invoke package in order to invoke a default method.%0A    // Without exception handling, the implementation is analogous to the following code:%0A    //
+MethodHandle methodHandle;%0A    try %7B%0A
     
@@ -1409,27 +1409,21 @@
 KUP%22);%0A 
-   //
      
- 
 field.se
@@ -1441,22 +1441,16 @@
 (true);%0A
-    //
       fi
@@ -1535,22 +1535,24 @@
     
-//      return
+  methodHandle =
  imp
@@ -1630,1707 +1630,393 @@
 get)
-.invokeWithArguments((Object)arguments);%0A    // The java.lang.invoke package is only available since Java 7. In order to preserve the compatibility of spock-core%0A    // with older versions of Java, we rewrite the above code using reflection.%0A%0A    Object boundHandle = null;%0A    Method invokeWithArgumentsMethod = null;%0A    try %7B%0A      // Get the private field Lookup.IMPL_LOOKUP, which is maximally privileged%0A      Class%3C?%3E lookupClass = Class.forName(%22java.lang.invoke.MethodHandles$Lookup%22);%0A      final Field field = lookupClass.getDeclaredField(%22IMPL_LOOKUP%22);%0A      field.setAccessible(true);%0A      Object implLookup = field.get(null);%0A%0A      // Get a method handle for the default method%0A      Method unreflectSpecialMethod = lookupClass.getMethod(%22unreflectSpecial%22, Method.class, Class.class);%0A      Object specialHandle = unreflectSpecialMethod.invoke(implLookup, method, method.getDeclaringClass());%0A%0A      // Get a bound handle that prepends the target to the original arguments%0A      Method bindToMethod = specialHandle.getClass().getMethod(%22bindTo%22, Object.class);%0A      boundHandle = bindToMethod.invoke(specialHandle, target);%0A%0A      // Get the method MethodHandle.invokeWithArguments(Object...)%0A      invokeWithArgumentsMethod = boundHandle.getClass().getMethod(%22invokeWithArguments%22, Object%5B%5D.class);%0A    %7D catch (Exception e) %7B%0A      throw new CannotCreateMockException(target.getClass(), %22Failed to invoke default method '%22 + method.getName() + %22'%22, e);%0A    %7D%0A%0A    // Call boundHandle.invokeWithArguments(arguments), sneaky throwing possible exceptions%0A    Object result = ReflectionUtil.invokeMethod(boundHandle, invokeWithArgumentsMethod, (Object)arguments);%0A    return result;
+;%0A    %7D catch (Exception e) %7B%0A      throw new CannotCreateMockException(target.getClass(), %22Failed to invoke default method '%22 + method.getName() + %22'. Adding '--add-opens=java.base/java.lang.invoke=ALL-UNNAMED' might fix this.%22, e);%0A    %7D%0A%0A    try %7B%0A      return methodHandle.invokeWithArguments(arguments);%0A    %7D catch (Throwable e) %7B%0A      return ExceptionUtil.sneakyThrow(e);%0A    %7D
 %0A  %7D

Remove reflection from DefaultMethodInvoker