/*
 * 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.jdbi.v3.sqlobject;

import static java.util.Collections.synchronizedMap;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.WeakHashMap;

import org.jdbi.v3.core.extension.HandleSupplier;

class DefaultMethodHandler implements Handler {
    private static final Map<Class<?>, MethodHandles.Lookup> privateLookups = synchronizedMap(new WeakHashMap<>());

    private static MethodHandles.Lookup lookupFor(Class<?> clazz) {
        return privateLookups.computeIfAbsent(clazz, type -> {
            try {
                // TERRIBLE, HORRIBLE, NO GOOD, VERY BAD HACK
                // Courtesy of:
                // https://rmannibucau.wordpress.com/2014/03/27/java-8-default-interface-methods-and-jdk-dynamic-proxies/

                // We can use MethodHandles to look up and invoke the super method, but since this class is not an
                // implementation of method.getDeclaringClass(), MethodHandles.Lookup will throw an exception since
                // this class doesn't have access to the super method, according to Java's access rules. This horrible,
                // awful workaround allows us to directly invoke MethodHandles.Lookup's private constructor, bypassing
                // the usual access checks.

                // We should get rid of this workaround as soon as a viable alternative exists.

                final Constructor<MethodHandles.Lookup> constructor =
                        MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
                if (!constructor.isAccessible()) {
                    constructor.setAccessible(true);
                }
                return constructor.newInstance(type, MethodHandles.Lookup.PRIVATE);
            } catch (ReflectiveOperationException e) {
                throw new RuntimeException(e);
            }
        });
    }

    private final MethodHandle methodHandle;

    DefaultMethodHandler(Method method) {
        try {
            Class<?> declaringClass = method.getDeclaringClass();

            methodHandle = lookupFor(declaringClass).unreflectSpecial(method, declaringClass);
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Object invoke(Object target, Object[] args, HandleSupplier handle) {
        try {
            return methodHandle.bindTo(target).invokeWithArguments(args);
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable throwable) {
            throw new RuntimeException(throwable);
        }
    }
}

@@ -752,16 +752,68 @@
 ructor;%0A
+import java.lang.reflect.InvocationTargetException;%0A
 import j
@@ -997,278 +997,1258 @@
     
-private static final Map%3CClass%3C?%3E, MethodHandles.Lookup%3E privateLookups = synchronizedMap(new WeakHashMap%3C%3E());%0A%0A    private static MethodHandles.Lookup lookupFor(Class%3C?%3E clazz) %7B%0A        return privateLookups.computeIfAbsent(clazz, type -%3E %7B%0A            try %7B%0A        
+// MethodHandles.privateLookupIn(Class, Lookup) was added in JDK 9.%0A    // JDK 9 allows us to unreflectSpecial() on an interface default method, where JDK 8 did not.%0A    private static final Method PRIVATE_LOOKUP_IN = privateLookupIn();%0A%0A    private static Method privateLookupIn() %7B%0A        try %7B%0A            return MethodHandles.class.getMethod(%22privateLookupIn%22, Class.class, MethodHandles.Lookup.class);%0A        %7D catch (NoSuchMethodException e) %7B%0A            // Method was added in JDK 9%0A            return null;%0A        %7D%0A    %7D%0A%0A    private static final Map%3CClass%3C?%3E, MethodHandles.Lookup%3E privateLookups = synchronizedMap(new WeakHashMap%3C%3E());%0A%0A    private static MethodHandles.Lookup lookupFor(Class%3C?%3E clazz) %7B%0A        if (PRIVATE_LOOKUP_IN != null) %7B%0A            try %7B%0A                return (MethodHandles.Lookup) PRIVATE_LOOKUP_IN.invoke(null, clazz, MethodHandles.lookup());%0A            %7D catch (IllegalAccessException %7C InvocationTargetException e) %7B%0A                String message = String.format(%0A                        %22Error invoking MethodHandles.privateLookupIn(%25s.class, MethodHandles.lookup()) in JDK 9+ runtime%22,%0A                        clazz);%0A                throw new RuntimeException(message, e);%0A            %7D%0A        %7D%0A%0A
     
@@ -2297,24 +2297,16 @@
 AD HACK%0A
-        
         
@@ -2321,24 +2321,16 @@
 esy of:%0A
-        
         
@@ -2440,32 +2440,24 @@
 s/%0A%0A        
-        
 // We can us
@@ -2543,24 +2543,16 @@
  not an%0A
-        
         
@@ -2651,24 +2651,16 @@
 n since%0A
-        
         
@@ -2771,24 +2771,16 @@
         
-        
 // awful
@@ -2874,24 +2874,16 @@
 passing%0A
-        
         
@@ -2923,41 +2923,12 @@
     
-        
 // 
-We should get rid of t
+T
 his 
@@ -2942,47 +2942,192 @@
 und 
-as soon as a viable alternative exists.
+is only used in JDK 8.x runtimes. JDK 9+ runtimes use MethodHandles.privateLookupIn()%0A        // above.%0A        return privateLookups.computeIfAbsent(clazz, type -%3E %7B%0A            try %7B
 %0A%0A  

Use MethodHandles.privateLookupIn() for default method invocation in Java 9 and later