/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.ir.transform.expression;
import gw.internal.gosu.ir.nodes.IRMethod;
import gw.internal.gosu.ir.nodes.IRMethodFactory;
import gw.internal.gosu.ir.nodes.IRMethodFromMethodInfo;
import gw.internal.gosu.ir.transform.ExpressionTransformer;
import gw.internal.gosu.ir.transform.TopLevelTransformationContext;
import gw.internal.gosu.parser.ArrayExpansionMethodInfo;
import gw.internal.gosu.parser.expressions.BeanMethodCallExpression;
import gw.internal.gosu.parser.expressions.Identifier;
import gw.internal.gosu.parser.statements.BeanMethodCallStatement;
import gw.internal.gosu.runtime.GosuRuntimeMethods;
import gw.lang.ir.IRElement;
import gw.lang.ir.IRExpression;
import gw.lang.ir.IRSymbol;
import gw.lang.ir.IRType;
import gw.lang.ir.IRTypeConstants;
import gw.lang.ir.expression.IRCompositeExpression;
import gw.lang.parser.ICustomExpressionRuntime;
import gw.lang.parser.IExpression;
import gw.lang.parser.Keyword;
import gw.lang.parser.MemberAccessKind;
import gw.lang.reflect.IMethodInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.ITypeInfoMethodInfo;
import gw.lang.reflect.gs.IGosuClass;
import gw.lang.reflect.java.JavaTypes;
import java.util.ArrayList;
import java.util.List;
/**
*/
public class BeanMethodCallExpressionTransformer extends AbstractExpressionTransformer<BeanMethodCallExpression>
{
public static IRExpression compile( TopLevelTransformationContext cc, BeanMethodCallExpression expr )
{
BeanMethodCallExpressionTransformer compiler = new BeanMethodCallExpressionTransformer( cc, expr );
return compiler.compile();
}
public BeanMethodCallExpressionTransformer( TopLevelTransformationContext cc, BeanMethodCallExpression expr )
{
super( cc, expr );
}
protected IRExpression compile_impl()
{
if( _expr().isExpansion() )
{
return BeanMethodCallExpansionTransformer.compile( _cc(), _expr() );
}
IRExpression result;
IExpression rootExpr = _expr().getRootExpression();
IMethodInfo mi = getMethodInfo();
IRMethodFromMethodInfo irMethod;
if (_expr().getExpressionRuntime() instanceof ICustomExpressionRuntime)
{
return handleCustomExpressionRuntime( (ICustomExpressionRuntime) _expr().getExpressionRuntime(), _expr().getType() );
}
else if( mi instanceof ITypeInfoMethodInfo )
{
ITypeInfoMethodInfo metaMethod = (ITypeInfoMethodInfo)mi;
IMethodInfo backingMethod = metaMethod.getBackingMethodInfo();
irMethod = IRMethodFactory.createIRMethod(backingMethod, _expr().getFunctionType() );
result = callInstanceMethod( rootExpr, irMethod, _expr().getNamedArgOrder() );
}
else if( mi.isStatic() )
{
irMethod = IRMethodFactory.createIRMethod( mi, _expr().getFunctionType() );
result = callStaticMethod( rootExpr, irMethod, _expr().getNamedArgOrder() );
}
else
{
irMethod = IRMethodFactory.createIRMethod( mi, _expr().getFunctionType() );
result = callInstanceMethod( rootExpr, irMethod, _expr().getNamedArgOrder() );
}
return castIfReturnTypeDerivedFromTypeVariable( irMethod, result );
}
private IRExpression callInstanceMethod( IExpression rootExpr, IRMethodFromMethodInfo irMethod, int[] namedArgOrder )
{
if( isArrayExpansionMethod( irMethod.getOriginalMethod() ) )
{
return BeanMethodCallExpansionTransformer.compile( _cc(), _expr() );
}
else
{
IRExpression irRootRaw = pushRootExpression( irMethod, rootExpr );
IRExpression irRoot = irRootRaw;
IRExpression irMethodCall;
IRSymbol rootSymbol = null;
boolean bShouldNullShortCircuitCheck = shouldNullShortCircuit();
if( bShouldNullShortCircuitCheck )
{
rootSymbol = _cc().makeAndIndexTempSymbol( irRoot.getType() );
irRoot = identifier( rootSymbol );
}
if( irMethod.isBytecodeMethod() )
{
List<IRExpression> irArgs = new ArrayList<IRExpression>();
pushArgumentsWithCasting( irMethod, _expr().getArgs(), irArgs );
if( isSuperCall( rootExpr ) )
{
irMethodCall = callSpecialMethod( getDescriptor( _cc().getSuperType() ), irMethod, irRoot, irArgs, namedArgOrder );
}
else
{
irMethodCall = callMethod( irMethod, irRoot, irArgs, namedArgOrder );
assignStructuralTypeOwner( rootExpr, irMethodCall );
}
}
else
{
irMethodCall = callMethodInfo( irMethod, irRoot, namedArgOrder );
}
if( shouldNullShortCircuit() )
{
if( _expr().getType() == JavaTypes.pVOID() )
{
irMethodCall = buildComposite(
buildAssignment( rootSymbol, irRootRaw ),
buildIf( buildNotEquals( identifier( rootSymbol ), nullLiteral() ),
buildMethodCall( irMethodCall ) ) );
}
else
{
irMethodCall = buildComposite(
buildAssignment( rootSymbol, irRootRaw ),
buildNullCheckTernary( irRoot,
shortCircuitValue( irMethodCall.getType() ),
irMethodCall ) );
}
}
return irMethodCall;
}
}
private boolean shouldNullShortCircuit()
{
return _expr().getMemberAccessKind() == MemberAccessKind.NULL_SAFE;
}
private boolean isArrayExpansionMethod( IMethodInfo mi )
{
return mi instanceof ArrayExpansionMethodInfo;
}
private IRExpression callStaticMethod( IExpression rootExpr, IRMethodFromMethodInfo irMethod, int[] namedArgOrder )
{
if( irMethod.isBytecodeMethod() )
{
List<IRExpression> args = new ArrayList<IRExpression>();
pushArgumentsWithCasting( irMethod, _expr().getArgs(), args );
return callMethod( irMethod, null, args, namedArgOrder );
}
else
{
return callMethodInfo( irMethod, pushRootExpression( irMethod, rootExpr ), namedArgOrder );
}
}
private IRExpression castIfReturnTypeDerivedFromTypeVariable( IRMethod mi, IRExpression root )
{
if( !(_expr().getParent() instanceof BeanMethodCallStatement) )
{
IType retType = _expr().getReturnType();
if( retType != JavaTypes.pVOID() && !retType.isPrimitive() )
{
if( !mi.getReturnType().equals( getDescriptor( retType ) ) )
{
return checkCast( retType, root );
}
}
}
return root;
}
private IMethodInfo getMethodInfo()
{
IMethodInfo methodInfo = _expr().getMethodDescriptor();
if (methodInfo == null) {
throw new NullPointerException("Null method descriptor for expression: " + _expr());
}
// If we get back a method from IGosuObject that is merely overridding a method on Object,
// we need to replace it with a call to Object instead. This is necessary to handle super calls correctly,
// like super.hashCode(), that have to be made against a concrete type, and it also avoids doing
// unnecessary INVOKEINTERFACE calls on these methods.
if (methodInfo.getOwnersType().getName().equals("gw.lang.reflect.gs.IGosuObject")) {
if (methodInfo.getDisplayName().equals("equals")) {
methodInfo = JavaTypes.OBJECT().getTypeInfo().getMethod("equals", JavaTypes.OBJECT());
} else if (methodInfo.getDisplayName().equals("hashCode")) {
methodInfo = JavaTypes.OBJECT().getTypeInfo().getMethod("hashCode");
} else if (methodInfo.getDisplayName().equals("toString")) {
methodInfo = JavaTypes.OBJECT().getTypeInfo().getMethod("toString");
}
}
return methodInfo;
}
private boolean isSuperCall( IExpression rootExpr )
{
return rootExpr instanceof Identifier && Keyword.KW_super.equals( ((Identifier)rootExpr).getSymbol().getName() );
}
private IRExpression callMethodInfo( IRMethodFromMethodInfo irMethod, IRExpression irRootExpr, int[] namedArgOrder )
{
IMethodInfo mi = irMethod.getTerminalMethod();
List<IRExpression> explicitArgs = new ArrayList<IRExpression>();
pushArgumentsNoCasting( irMethod, _expr().getArgs(), explicitArgs );
List<IRElement> callElements = handleNamedArgs( explicitArgs, namedArgOrder );
IRExpression irRoot;
if( !irMethod.isStatic() )
{
IRSymbol rootSymbol = _cc().makeAndIndexTempSymbol( irRootExpr.getType() );
callElements.add( buildAssignment( rootSymbol, irRootExpr ) );
callElements.add( nullCheckVar( rootSymbol ) );
irRoot = identifier( rootSymbol );
}
else
{
irRoot = irRootExpr;
}
IRExpression miCall = callStaticMethod( GosuRuntimeMethods.class, "invokeMethodInfo", new Class[]{IType.class, String.class, IType[].class, Object.class, Object[].class},
exprList(
pushType( mi.getOwnersType(), true ),
pushConstant( mi.getDisplayName() ),
pushParamTypes( mi.getParameters() ),
irRoot,
pushArgumentsAsArray( explicitArgs ) ) );
miCall = unboxValueToType( mi.getReturnType(), miCall );
if( callElements.size() > 0 )
{
// Include temp var assignments so named args are evaluated in lexical order before the call
callElements.add( miCall );
miCall = new IRCompositeExpression( callElements );
}
return miCall;
}
private IRExpression pushRootExpression( IRMethod irMethod, IExpression rootExpr )
{
IRExpression root = ExpressionTransformer.compile( rootExpr, _cc() );
//## todo: is this necessary? s/b typed properly now with implicit typeas
if (irMethod != null) {
root = boxValue( irMethod.getOwningIRType(), root );
}
if( irMethod != null && !irMethod.isStatic() )
{
IRType type = irMethod.getTargetRootIRType();
if( !type.isAssignableFrom( root.getType() ) && (!(rootExpr.getType() instanceof IGosuClass) || !((IGosuClass)rootExpr.getType()).isStructure()) )
{
root = buildCast( type, root );
}
}
return root;
}
private IRExpression pushArgumentsAsArray( List<IRExpression> explicitArgs )
{
List<IRExpression> irArgs = new ArrayList<IRExpression>();
if( explicitArgs != null )
{
for( IRExpression arg : explicitArgs )
{
if( arg.getType().isPrimitive() )
{
arg = boxValue( arg.getType(), arg );
}
irArgs.add( arg );
}
}
return buildInitializedArray(IRTypeConstants.OBJECT(), irArgs );
}
}