/*
* Copyright (c) 2011-2014 Fernando Petrola
*
* 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 com.dragome.utils;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.dragome.commons.AbstractProxyRelatedInvocationHandler;
import com.dragome.commons.ContinueReflection;
public class DragomeCallsiteFactory
{
protected static class InvocationHandlerForLambdas extends AbstractProxyRelatedInvocationHandler
{
private final Class<?> class1;
private final String methodName;
private final Object[] parameters;
private Class<?> returnType;
private String invokeName;
private String callType;
protected InvocationHandlerForLambdas(Class<?> class1, String methodName, Object[] parameters, Class<?> returnTypeClass, String invokeName, String callType)
{
this.class1= class1;
this.methodName= methodName;
this.parameters= parameters;
this.returnType= returnTypeClass;
this.invokeName= invokeName;
this.callType= callType;
}
@ContinueReflection
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
setProxy(proxy);
try
{
// if (returnType.getMethod(method.getName(), method.getParameterTypes()) != null)
if (!method.getName().equals(invokeName))
return invokeDefaultMethod(proxy, method, args);
if ("<init>".equals(methodName))
return class1.newInstance();
List<Object> asList= new ArrayList<Object>(Arrays.asList(parameters));
if (args != null)
asList.addAll(Arrays.asList(args));
Method[] methods= class1.getDeclaredMethods();
Object result= null;
for (int i= 0; i < methods.length && result == null; i++)
{
Method foundMethod= methods[i];
if (foundMethod.getName().equals(methodName))
{
foundMethod.setAccessible(true);
boolean isInstanceMethod= parameters.length > 0 && isSameClass();
if ("static".equals(callType))
isInstanceMethod= !Modifier.isStatic(foundMethod.getModifiers());
Object obj= null;
Object[] invocationArgs= asList.toArray();
if (isInstanceMethod)
{
obj= asList.remove(0);
invocationArgs= asList.toArray();
}
result= foundMethod.invoke(obj, invocationArgs);
}
}
return result;
}
catch (Exception e1)
{
throw new RuntimeException(e1);
}
}
private boolean isSameClass()
{
if (!(parameters[0] instanceof Object))
return false;
Class<? extends Object> type= parameters[0].getClass();
if (Proxy.isProxyClass(type))
{
InvocationHandler invocationHandler= Proxy.getInvocationHandler(parameters[0]);
if (invocationHandler instanceof InvocationHandlerForLambdas)
type= ((InvocationHandlerForLambdas) invocationHandler).returnType;
else
return class1.isAssignableFrom(type);
}
return type.equals(class1);
}
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws NoSuchMethodException, Throwable
{
final Constructor<MethodHandles.Lookup> constructor= MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible())
constructor.setAccessible(true);
final InvocationHandler handler= new InvocationHandler()
{
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
final Class<?> declaringClass= method.getDeclaringClass();
return constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}
};
return handler.invoke(proxy, method, args);
}
}
public static Object create(String className, String invokeName, String returnType, String invokeType, String handle2, Object objects, String callType)
{
try
{
Class<?> class1= Class.forName(className.replace("/", "."));
String methodName= handle2.substring(handle2.indexOf(".") + 1, handle2.indexOf("("));
if (!handle2.startsWith(className + "."))
{
class1= Class.forName(handle2.substring(0, handle2.indexOf(".")).replace("/", "."));
methodName= handle2.substring(handle2.indexOf(".") + 1, handle2.indexOf("("));
}
final Object[] parameters= (Object[]) objects;
Class<?> returnTypeClass= Class.forName(returnType);
return Proxy.newProxyInstance(DragomeCallsiteFactory.class.getClassLoader(), new Class<?>[] { returnTypeClass }, new InvocationHandlerForLambdas(class1, methodName, parameters, returnTypeClass, invokeName, callType));
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
}