/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.coercer;
import gw.internal.gosu.ir.builders.SimpleCompiler;
import gw.internal.gosu.parser.TypeLord;
import gw.lang.GosuShop;
import gw.lang.function.IBlock;
import gw.lang.ir.IRClass;
import gw.lang.ir.IRTypeConstants;
import gw.lang.ir.builder.IRClassBuilder;
import gw.lang.ir.builder.IRExpressionBuilder;
import gw.lang.ir.builder.IRMethodBuilder;
import gw.lang.parser.ICoercer;
import gw.lang.reflect.IHasJavaClass;
import gw.lang.reflect.IMethodInfo;
import gw.lang.reflect.IParameterInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.IGosuEnhancement;
import gw.lang.reflect.gs.IGosuObject;
import gw.lang.reflect.java.JavaTypes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static gw.lang.ir.builder.IRBuilderMethods.*;
public class FunctionToInterfaceClassGenerator {
private static Map<Class, Class> _classesToBlockCoercers = new HashMap<Class, Class>();
public static synchronized void clearCachedClasses() {
_classesToBlockCoercers.clear();
}
public static synchronized Class getBlockToInterfaceConversionClass( IType typeToCoerceTo ) {
Class coercionClass = _classesToBlockCoercers.get( ((IHasJavaClass)typeToCoerceTo).getBackingClass() );
if (coercionClass == null) {
coercionClass = generateBlockToInterfaceConversionClass( typeToCoerceTo );
_classesToBlockCoercers.put( ((IHasJavaClass)typeToCoerceTo).getBackingClass(), coercionClass );
}
return coercionClass;
}
private static Class generateBlockToInterfaceConversionClass( IType typeToCoerceTo ) {
typeToCoerceTo = TypeLord.getPureGenericType( typeToCoerceTo );
IRClassBuilder classBuilder = initializeClass( typeToCoerceTo );
addFields( classBuilder );
addConstructor( classBuilder );
addInterfaceMethod( classBuilder, typeToCoerceTo );
addToStringMethod( classBuilder );
IRClass irClass = classBuilder.build();
byte[] bytes = SimpleCompiler.INSTANCE.compile(irClass, false);
return TypeSystem.getGosuClassLoader().defineClass( irClass.getName(), bytes );
}
private static IRClassBuilder initializeClass( IType typeToCoerceTo ) {
IRClassBuilder classBuilder = new IRClassBuilder( "__proxy.generated.blocktointerface.ProxyFor" + typeToCoerceTo.getRelativeName(), Object.class );
classBuilder.withInterface( typeToCoerceTo );
return classBuilder;
}
private static void addFields( IRClassBuilder classBuilder ) {
classBuilder.createField().withName( "_block" ).withType( IBlock.class )._private().build();
classBuilder.createField().withName( "_coercer" ).withType( ICoercer.class )._private().build();
classBuilder.createField().withName( "_returnType" ).withType( IType.class )._private().build();
}
private static void addConstructor( IRClassBuilder classBuilder ) {
IRMethodBuilder method = classBuilder.createConstructor();
method._public().parameters("block", IBlock.class, "coercer", ICoercer.class, "returnType", IType.class).body(
set("_block", var("block")),
set("_coercer", var("coercer")),
set("_returnType", var("returnType")),
_superInit(),
_return()
);
}
private static void addInterfaceMethod( IRClassBuilder classBuilder, IType typeToCoerceTo ) {
IMethodInfo proxiedMethod = getSingleMethod( typeToCoerceTo );
IRMethodBuilder method = classBuilder.createMethod();
method.name( proxiedMethod.getDisplayName() )
._public()
.copyParameters( proxiedMethod )
.returns( GosuShop.getIRTypeResolver().getDescriptor( proxiedMethod.getReturnType() ) );
List<IRExpressionBuilder> arrayContents = new ArrayList<IRExpressionBuilder>();
IParameterInfo[] parameters = proxiedMethod.getParameters();
for (int i = 0; i < parameters.length; i++) {
arrayContents.add(var("arg" + i));
}
if (proxiedMethod.getReturnType() == JavaTypes.pVOID()) {
method.body(
field("_block").call("invokeWithArgs", newArray(Object.class, arrayContents)),
_return()
);
} else {
method.body(
assign("value", IRTypeConstants.OBJECT(), field("_block").call("invokeWithArgs", newArray(Object.class, arrayContents))),
_if(field("_coercer").isNotNull()).then(
assign("value", field("_coercer").call("coerceValue", field("_returnType"), var("value")))
),
_return(var("value"))
);
}
}
private static void addToStringMethod( IRClassBuilder classBuilder ) {
IRMethodBuilder method = classBuilder.createMethod();
method.name( "toString" )
._public()
.returns( IRTypeConstants.STRING() );
method.body(
assign( "value", IRTypeConstants.OBJECT(), field( "_block" ).call( "toString" ) ),
_return( var( "value" ) )
);
}
private static IMethodInfo getSingleMethod( IType interfaceType )
{
if( interfaceType.isInterface() )
{
List<IMethodInfo> list = new ArrayList<IMethodInfo>( interfaceType.getTypeInfo().getMethods() );
//extract all object methods since they are guaranteed to be implemented
for( Iterator<? extends IMethodInfo> it = list.iterator(); it.hasNext(); )
{
IMethodInfo methodInfo = it.next();
IParameterInfo[] parameterInfos = methodInfo.getParameters();
IType[] paramTypes = new IType[parameterInfos.length];
for( int i = 0; i < parameterInfos.length; i++ )
{
paramTypes[i] = parameterInfos[i].getFeatureType();
}
if( JavaTypes.OBJECT().getTypeInfo().getMethod( methodInfo.getDisplayName(), paramTypes ) != null ||
methodInfo.getOwnersType() instanceof IGosuEnhancement)
{
it.remove();
}
else if( methodInfo.getOwnersType().getName().contains( IGosuObject.class.getName() ) )
{
it.remove();
}
}
if( list.size() == 1 )
{
return list.get( 0 );
}
}
return null;
}
}