/*
* 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);
}
}
}