/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.lang.reflect;
import gw.config.CommonServices;
import gw.lang.GosuShop;
import gw.lang.parser.ICoercer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public interface ITypeInfo extends IAnnotatedFeatureInfo
{
public static final String TYPEINFO_EXT = "TypeInfo";
/**
* @return An <b>unmodifiable random access</b> list of <code>IPropertyInfo</code>
* instances. The list is sorted ascending by name. Returns an empty list if
* there are no properties.
*/
public List<? extends IPropertyInfo> getProperties();
/**
* Get a property mapped to the specified name.
*
* @param propName The property name.
*
* @return An IPropertyInfo corresponding to the property name.
*/
public IPropertyInfo getProperty( CharSequence propName );
/**
* @return An <b>unmodifiable random access</b> list of <code>IMethodInfo</code>
* instances. The list is sorted ascending by name. Returns an empty list if
* there are no methods.
*/
public MethodList getMethods();
/**
* Returns a IMethodInfo matching the specified name and parameter types or
* null if no match is found.
* <p/>
* Note <code>params</code> must <i>exactly</i> match those of the target
* method in number, order, and type. If null, <code>params</code> is treated
* as an empty array.
*
* @param methodName The name of the method to find.
* @param params Represents the <i>exact</i> number, order, and type of parameters
* in the method. A null value here is treated as an empty array.
*
* @return A IMethodInfo matching the name and parameter types.
*/
public IMethodInfo getMethod( CharSequence methodName, IType... params );
/**
* Returns a IMethodInfo matching the specified name and has parameter types that
* produce the best match.
* <p/>
* If there is a tie with method names then this will throw an illegal argument exception.
*
* @param method The name of the method to find.
* @param params Represents the <i>exact</i> number, order, and type of parameters
* in the method. A null value here is treated as an empty array.
*
* @return A IMethodInfo matching the name and parameter types.
*/
public IMethodInfo getCallableMethod( CharSequence method, IType... params );
/**
* @return An <b>unmodifiable random access</b> list of <code>IConstructorInfo</code>
* instances. The list is sorted ascending by name. Returns an empty list if
* there are no constructors.
*/
public List<? extends IConstructorInfo> getConstructors();
/**
* Returns a IConstructorInfo that has parameter types that produce the best match.
* <p/>
* If there is a tie with method names then this will throw an illegal argument exception.
*
* @param params Represents the <i>exact</i> number, order, and type of parameters
* in the constructor. A null value here is treated as an empty array.
*
* @return A IConstructorInfo matching the parameter types.
*/
public IConstructorInfo getConstructor( IType... params );
/**
* Returns a IConstructorInfo matching the specified parameter types or null
* if no match is found.
*
* @param params Represents the <i>exact</i> number, order, and type of parameters
* in the constructor. A null value here is treated as an empty array.
*
* @return A IConstructorInfo matching the parameter types.
*/
public IConstructorInfo getCallableConstructor( IType... params );
/**
* @return An <b>unmodifiable random access</b> list of <code>IEventInfo</code>
* instances. The list is sorted ascending by name. Returns an empty list if
* ther are no events.
*/
public List<? extends IEventInfo> getEvents();
/**
* Get an event mapped to the specified name.
*
* @param event The event name.
*
* @return An IEventInfo corresponding to the event name.
*/
public IEventInfo getEvent( CharSequence event );
/**
* A general purpose class for finding methods and constructors.
*/
public static class FIND
{
private static final IType[] EMPTY_TYPES = IType.EMPTY_ARRAY;
public static IMethodInfo method( MethodList methods, CharSequence method, IType... params )
{
params = params == null ? EMPTY_TYPES : params;
for( int i = 0; i < methods.size; i++ )
{
IMethodInfo methodInfo = (IMethodInfo) methods.data[i];
if( methodInfo.getDisplayName().equals( method.toString() ) ) {
IParameterInfo[] paramInfos = methodInfo.getParameters();
if (areParamsEqual( paramInfos, params )) {
return methodInfo;
}
}
}
return null;
}
public static IConstructorInfo constructor( List<? extends IConstructorInfo> constructors, IType... params )
{
params = params == null ? EMPTY_TYPES : params;
for( IConstructorInfo constructorInfo : constructors )
{
IParameterInfo[] paramInfos = constructorInfo.getParameters();
if( areParamsEqual( paramInfos, params ) )
{
return constructorInfo;
}
}
return null;
}
/**
* If there is a tie this method will throw an IllegalArgumentException. This method is not strict, which means that
* clients calling this method may get back a method where the arguments must be coerced to the expected parameter tyeps.
* If you wish strict behavior call {@link #callableMethodStrict(MethodList, CharSequence, IType[])}
*/
public static IMethodInfo callableMethod( MethodList methods, CharSequence method, IType... params )
{
return callableMethodImpl( methods, method, false, params );
}
/**
* If there is a tie this method will throw an IllegalArgumentException. This version is strict, which means that
* clients calling this method do not need to do any coercion of arguments in order to invoke the IMethodInfo.
*/
public static IMethodInfo callableMethodStrict( MethodList methods, CharSequence method, IType... params )
{
return callableMethodImpl( methods, method, true, params );
}
/**
* If there is a tie this method will throw an IllegalArgumentException.
*/
private static IMethodInfo callableMethodImpl( MethodList methods, CharSequence method, boolean strict, IType... params )
{
Map<IFunctionType, IMethodInfo> mis = new HashMap<IFunctionType, IMethodInfo>();
params = params == null ? EMPTY_TYPES : params;
for( int i = 0; i < methods.size; i++ )
{
IMethodInfo methodInfo = (IMethodInfo) methods.data[i];
if( methodInfo.getDisplayName().equals( method.toString() ) &&
methodInfo.getParameters().length == params.length )
{
mis.put( new FunctionType( methodInfo ), methodInfo );
}
}
List<MethodScore> list = scoreMethods( new ArrayList<IInvocableType>( mis.keySet() ), Arrays.asList( params ), Collections.EMPTY_LIST );
if( list.size() == 0 )
{
return null;
}
else if( list.size() > 1 && list.get( 0 ).getScore() == list.get( 1 ).getScore() )
{
throw new IllegalArgumentException( "Ambiguous methods: There is more than one method named " + method + " that accepts args " + Arrays.asList( params ) );
}
IInvocableType rawFunctionType = list.get( 0 ).getRawFunctionType();
if( strict && !areParamsCompatible( rawFunctionType.getParameterTypes(), params ) )
{
return null;
}
return mis.get( rawFunctionType );
}
private static boolean areParamsCompatible( IType[] actualParamTypes, IType[] userParamTypes )
{
for( int i = 0; i < actualParamTypes.length; i++ )
{
IType actualParamType = actualParamTypes[i];
IType userParamType = userParamTypes[i];
if( !actualParamType.isAssignableFrom( userParamType ) )
{
return false;
}
}
return true;
}
/**
* If there is a tie this method will throw an IllegalArgumentException. This method is not strict, which means that
* clients calling this method may get back a constructor where the arguments must be coerced to the expected parameter tyeps.
* If you wish strict behavior call {@link #callableConstructorStrict(java.util.List, IType[])}
*/
public static IConstructorInfo callableConstructor( List<? extends IConstructorInfo> constructors, IType... params )
{
return callableConstructorImpl( constructors, false, params );
}
/**
* If there is a tie this method will throw an IllegalArgumentException. This version is strict, which means that
* clients calling this method do not need to do any coercion of arguments in order to invoke the IConstructorInfo.
*/
public static IConstructorInfo callableConstructorStrict( List<? extends IConstructorInfo> constructors, IType... params )
{
return callableConstructorImpl( constructors, true, params );
}
private static IConstructorInfo callableConstructorImpl( List<? extends IConstructorInfo> constructors, boolean strict, IType... params )
{
Map<IConstructorType, IConstructorInfo> cis = new HashMap<IConstructorType, IConstructorInfo>();
params = params == null ? EMPTY_TYPES : params;
for( IConstructorInfo constructorInfo : constructors )
{
if( params.length == constructorInfo.getParameters().length )
{
cis.put( GosuShop.getConstructorInfoFactory().makeConstructorType( constructorInfo ), constructorInfo );
}
}
List<MethodScore> list = scoreMethods( new ArrayList<IInvocableType>( cis.keySet() ), Arrays.asList( params ), Collections.EMPTY_LIST );
if( list.size() == 0 )
{
return null;
}
else if( list.size() > 1 && list.get( 0 ).getScore() == list.get( 1 ).getScore() )
{
throw new IllegalArgumentException( "Ambiguous constructors: There is more than one constructor that accepts args " + Arrays.asList( params ) );
}
IInvocableType rawFunctionType = list.get( 0 ).getRawFunctionType();
if( strict && !areParamsCompatible( rawFunctionType.getParameterTypes(), params ) )
{
return null;
}
return cis.get( rawFunctionType );
}
public static boolean areParamsEqual( IParameterInfo srcArgs[],
IType testArgs[] )
{
if( srcArgs.length == testArgs.length )
{
for( int j = 0; j < srcArgs.length; j++ )
{
IType methodParamType = srcArgs[j].getFeatureType();
IType testParamType = testArgs[j];
// If one of the types is a paramerized type and the other is the generic version, down cast the param type.
while( methodParamType.isArray() && testParamType.isArray() )
{
methodParamType = methodParamType.getComponentType();
testParamType = testParamType.getComponentType();
}
if( methodParamType.isParameterizedType() && !testParamType.isParameterizedType() )
{
methodParamType = TypeSystem.getPureGenericType( methodParamType );
}
else if( testParamType.isParameterizedType() && !methodParamType.isParameterizedType() )
{
testParamType = TypeSystem.getPureGenericType( testParamType );
}
else if( typeVarsAreFromDifferentMethods( methodParamType, testParamType ) )
{
methodParamType = getConcreteBoundingType( methodParamType );
testParamType = getConcreteBoundingType( testParamType );
}
if( methodParamType.isParameterizedType() )
{
if( !TypeSystem.getPureGenericType( methodParamType ).equals( TypeSystem.getPureGenericType( testParamType ) ) )
{
return false;
}
IType[] methodTypeParameters = methodParamType.getTypeParameters();
IType[] testTypeParameters = testParamType.getTypeParameters();
for( int i = 0; i < methodTypeParameters.length; i++ )
{
if( !getConcreteBoundingType( methodTypeParameters[i] ).isAssignableFrom( testTypeParameters[i] ) )
{
return false;
}
}
}
else if( !methodParamType.equals( testParamType ) )
{
if( methodParamType instanceof IPlaceholder && ((IPlaceholder)methodParamType).isPlaceholder() ||
testParamType instanceof IPlaceholder && ((IPlaceholder)testParamType).isPlaceholder() )
{
//## hack: This is a total hack for snapshot bullshit
return true;
}
return false;
}
}
return true;
}
return false;
}
private static boolean typeVarsAreFromDifferentMethods( IType methodParamType, IType testParamType )
{
if( methodParamType instanceof ITypeVariableArrayType && testParamType instanceof ITypeVariableArrayType )
{
return typeVarsAreFromDifferentMethods( methodParamType.getComponentType(), testParamType.getComponentType() );
}
else
{
return testParamType instanceof ITypeVariableType &&
testParamType.getEnclosingType() instanceof FunctionType &&
methodParamType instanceof ITypeVariableType &&
methodParamType.getEnclosingType() instanceof FunctionType &&
testParamType.getEnclosingType() != methodParamType.getEnclosingType();
}
}
private static IType getConcreteBoundingType( IType type )
{
if( type instanceof ITypeVariableType )
{
return getConcreteBoundingType( ((ITypeVariableType)type).getBoundingType() );
}
else if( type instanceof ITypeVariableArrayType )
{
return getConcreteBoundingType( type.getComponentType() );
}
return type;
}
//## todo: Rewrite this. Maybe use this technique: http://stackoverflow.com/questions/14315437/get-best-matching-overload-from-set-of-overloads
//## todo: Note it'll be easier to manage if the best match is designed to have the _lowest_ score. Again the link above provides a decent strategy.
public static List<MethodScore> scoreMethods( List<? extends IInvocableType> listFunctionTypes, List<IType> argTypes, List<IType> inferringTypes )
{
ArrayList<MethodScore> scores = new ArrayList<MethodScore>();
// if there is only one method, don't bother scoring
if( listFunctionTypes.size() == 1 )
{
MethodScore score = new MethodScore();
IInvocableType functionType = listFunctionTypes.get( 0 );
score.setRawFunctionType( functionType );
score.setValid( true );
scores.add( score );
}
else
{
// if there are multiple methods, create scores for them
for( IInvocableType functionType : listFunctionTypes )
{
MethodScore score = new MethodScore();
score.setValid( true );
score.setRawFunctionType( functionType );
scores.add( score );
}
int assignabilityAmt = (argTypes.size() * (ICoercer.MAX_PRIORITY + 1)) + 1; // the amount to increment assignability matches by
for( int i = 0; i < argTypes.size(); i++ )
{
IType exprType = argTypes.get( i );
// keep track of all methods that are assignable at this parameter position
Map<IType, List<MethodScore>> bestMatches = new HashMap<IType, List<MethodScore>>();
for( MethodScore score : scores )
{
IType[] parameterTypes = score.getRawFunctionType().getParameterTypes();
if( argTypes.size() == parameterTypes.length )
{
IType parameterType = TypeSystem.boundTypes( parameterTypes[i], inferringTypes );
if( parameterType.isAssignableFrom( exprType ) )
{
score.incScore( assignabilityAmt );
// remove any existing assignable methods that are supertypes of this
for( Iterator<Map.Entry<IType, List<MethodScore>>> it = bestMatches.entrySet().iterator(); it.hasNext(); )
{
Map.Entry<IType, List<MethodScore>> entry = it.next();
if( !parameterType.equals( entry.getKey() ) )
{
if( parameterType.isAssignableFrom( entry.getKey() ) )
{
// an existing method score is better than this one, so null it out and break
score = null;
break;
}
else if( entry.getKey().isAssignableFrom( parameterType ) )
{
it.remove();
}
}
}
if( score != null )
{
List<MethodScore> bestMatchList = bestMatches.get( parameterType );
if( bestMatchList == null )
{
bestMatchList = new ArrayList<MethodScore>();
bestMatches.put( parameterType, bestMatchList );
}
bestMatchList.add( score );
}
}
else
{
ICoercer iCoercer = CommonServices.getCoercionManager().findCoercer( parameterType, exprType, false );
if( iCoercer != null )
{
score.incScore( iCoercer.getPriority( parameterType, exprType ) + 1 );
}
else
{
// if the argument is neither coercable nor assignable, reset the method score to a very low value
score.setScore( Integer.MIN_VALUE );
}
}
}
else
{
score.setScore( Long.MIN_VALUE );
}
}
// if there were any best matches, increment their scores
if( !bestMatches.isEmpty() )
{
for( List<MethodScore> methodScores : bestMatches.values() )
{
for( MethodScore score : methodScores )
{
score.incScore( assignabilityAmt );
}
}
}
}
Collections.sort( scores );
}
return scores;
}
}
}