/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.lang.parser;
import gw.config.BaseService;
import gw.config.CommonServices;
import gw.lang.GosuShop;
import gw.lang.IDimension;
import gw.lang.parser.coercers.BasePrimitiveCoercer;
import gw.lang.parser.coercers.BigDecimalCoercer;
import gw.lang.parser.coercers.BigIntegerCoercer;
import gw.lang.parser.coercers.BlockCoercer;
import gw.lang.parser.coercers.BooleanCoercer;
import gw.lang.parser.coercers.BooleanHighPriorityCoercer;
import gw.lang.parser.coercers.BooleanPHighPriorityCoercer;
import gw.lang.parser.coercers.ByteCoercer;
import gw.lang.parser.coercers.ByteHighPriorityCoercer;
import gw.lang.parser.coercers.BytePHighPriorityCoercer;
import gw.lang.parser.coercers.CharCoercer;
import gw.lang.parser.coercers.CharHighPriorityCoercer;
import gw.lang.parser.coercers.CharPHighPriorityCoercer;
import gw.lang.parser.coercers.DoubleCoercer;
import gw.lang.parser.coercers.DoubleHighPriorityCoercer;
import gw.lang.parser.coercers.DoublePHighPriorityCoercer;
import gw.lang.parser.coercers.FloatCoercer;
import gw.lang.parser.coercers.FloatHighPriorityCoercer;
import gw.lang.parser.coercers.FloatPHighPriorityCoercer;
import gw.lang.parser.coercers.FunctionFromInterfaceCoercer;
import gw.lang.parser.coercers.FunctionToInterfaceCoercer;
import gw.lang.parser.coercers.IMonitorLockCoercer;
import gw.lang.parser.coercers.IdentityCoercer;
import gw.lang.parser.coercers.IntCoercer;
import gw.lang.parser.coercers.IntHighPriorityCoercer;
import gw.lang.parser.coercers.IntPHighPriorityCoercer;
import gw.lang.parser.coercers.LongCoercer;
import gw.lang.parser.coercers.LongHighPriorityCoercer;
import gw.lang.parser.coercers.LongPHighPriorityCoercer;
import gw.lang.parser.coercers.MetaTypeToClassCoercer;
import gw.lang.parser.coercers.NonWarningStringCoercer;
import gw.lang.parser.coercers.RegExpMatchToBooleanCoercer;
import gw.lang.parser.coercers.RuntimeCoercer;
import gw.lang.parser.coercers.ShortCoercer;
import gw.lang.parser.coercers.ShortHighPriorityCoercer;
import gw.lang.parser.coercers.ShortPHighPriorityCoercer;
import gw.lang.parser.coercers.StandardCoercer;
import gw.lang.parser.coercers.StringCoercer;
import gw.lang.parser.coercers.TypeVariableCoercer;
import gw.lang.parser.exceptions.ParseException;
import gw.lang.parser.resources.Res;
import gw.lang.reflect.IBlockType;
import gw.lang.reflect.IErrorType;
import gw.lang.reflect.IFunctionType;
import gw.lang.reflect.IHasJavaClass;
import gw.lang.reflect.IMetaType;
import gw.lang.reflect.IMethodInfo;
import gw.lang.reflect.IParameterInfo;
import gw.lang.reflect.IPlaceholder;
import gw.lang.reflect.IPropertyInfo;
import gw.lang.reflect.IRelativeTypeInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.ITypeInfo;
import gw.lang.reflect.ITypeVariableArrayType;
import gw.lang.reflect.ITypeVariableType;
import gw.lang.reflect.MethodList;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.IGosuArrayClass;
import gw.lang.reflect.gs.IGosuArrayClassInstance;
import gw.lang.reflect.gs.IGosuClass;
import gw.lang.reflect.gs.IGosuEnhancement;
import gw.lang.reflect.gs.IGosuObject;
import gw.lang.reflect.java.GosuTypes;
import gw.lang.reflect.java.IJavaArrayType;
import gw.lang.reflect.java.IJavaType;
import gw.lang.reflect.java.JavaTypes;
import gw.util.GosuExceptionUtil;
import gw.util.Pair;
import gw.util.concurrent.Cache;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class StandardCoercionManager extends BaseService implements ICoercionManager
{
private static final DecimalFormat BIG_DECIMAL_FORMAT = new DecimalFormat();
static {
BIG_DECIMAL_FORMAT.setParseBigDecimal( true );
}
public static final Object NO_DICE = new Object();
// LRUish cache of coercers
public final TypeSystemAwareCache<Pair<IType, IType>, ICoercer> _coercerCache =
TypeSystemAwareCache.make( "Coercer Cache", 1000,
new Cache.MissHandler<Pair<IType, IType>, ICoercer>()
{
public final ICoercer load( Pair<IType, IType> key )
{
ICoercer coercer = findCoercerImpl( key.getFirst(), key.getSecond(), false );
if( coercer == null )
{
return NullSentinalCoercer.instance();
}
else
{
return coercer;
}
}
} )/*.logEveryNSeconds( 10, new SystemOutLogger( SystemOutLogger.LoggingLevel.INFO ) )*/;
public final boolean canCoerce( IType lhsType, IType rhsType )
{
ICoercer iCoercer = findCoercer( lhsType, rhsType, false );
return iCoercer != null;
}
private Object coerce( IType intrType, IType runtimeType, Object value )
{
ICoercer coercer = findCoercer( intrType, runtimeType, true );
if( coercer != null )
{
return coercer.coerceValue( intrType, value );
}
return null;
}
private boolean hasPotentialLossOfPrecisionOrScale( IType lhsType, IType rhsType )
{
if( lhsType == JavaTypes.pBYTE() || lhsType == JavaTypes.BYTE() )
{
return rhsType != JavaTypes.pBYTE() && rhsType != JavaTypes.BYTE();
}
else if( lhsType == JavaTypes.pCHAR() || lhsType == JavaTypes.CHARACTER() )
{
return rhsType != JavaTypes.pCHAR() && rhsType != JavaTypes.CHARACTER();
}
else if( lhsType == JavaTypes.pDOUBLE() || lhsType == JavaTypes.DOUBLE() )
{
return rhsType != JavaTypes.DOUBLE() && !rhsType.isPrimitive() &&
(JavaTypes.BIG_DECIMAL().isAssignableFrom( rhsType ) || JavaTypes.BIG_INTEGER().isAssignableFrom( rhsType ));
}
else if( lhsType == JavaTypes.pFLOAT() || lhsType == JavaTypes.FLOAT() )
{
return rhsType == JavaTypes.pDOUBLE() || rhsType == JavaTypes.DOUBLE() ||
JavaTypes.BIG_DECIMAL().isAssignableFrom( rhsType ) || JavaTypes.BIG_INTEGER().isAssignableFrom( rhsType );
}
else if( lhsType == JavaTypes.pINT() || lhsType == JavaTypes.INTEGER() )
{
return rhsType != JavaTypes.pINT() && rhsType != JavaTypes.INTEGER() &&
rhsType != JavaTypes.pSHORT() && rhsType != JavaTypes.SHORT() &&
rhsType != JavaTypes.pBYTE() && rhsType != JavaTypes.BYTE() &&
rhsType != JavaTypes.pCHAR() && rhsType != JavaTypes.CHARACTER();
}
else if( lhsType == JavaTypes.pLONG() || lhsType == JavaTypes.LONG() )
{
return rhsType != JavaTypes.pLONG() && rhsType != JavaTypes.LONG() &&
rhsType != JavaTypes.pINT() && rhsType != JavaTypes.INTEGER() &&
rhsType != JavaTypes.pSHORT() && rhsType != JavaTypes.SHORT() &&
rhsType != JavaTypes.pBYTE() && rhsType != JavaTypes.BYTE() &&
rhsType != JavaTypes.pCHAR() && rhsType != JavaTypes.CHARACTER();
}
else if( lhsType == JavaTypes.pSHORT() || lhsType == JavaTypes.SHORT() )
{
return rhsType != JavaTypes.pSHORT() && rhsType != JavaTypes.SHORT() &&
rhsType != JavaTypes.pBYTE() && rhsType != JavaTypes.BYTE();
}
else if( JavaTypes.BIG_INTEGER().isAssignableFrom( lhsType ) )
{
return rhsType != JavaTypes.BIG_INTEGER() && hasPotentialLossOfPrecisionOrScale( JavaTypes.LONG(), rhsType );
}
return false;
}
public final ICoercer findCoercer( IType lhsType, IType rhsType, boolean runtime )
{
if( runtime )
{
return findCoercerImpl( lhsType, rhsType, runtime );
}
else
{
@SuppressWarnings({"unchecked"})
ICoercer iCoercer = _coercerCache.get( new Pair( lhsType, rhsType ) );
if( iCoercer == NullSentinalCoercer.instance() )
{
return null;
}
else
{
return iCoercer;
}
}
}
private ICoercer findCoercerImpl( IType lhsType, IType rhsType, boolean runtime )
{
ICoercer coercer = getCoercerInternal( lhsType, rhsType, runtime );
if( coercer != null )
{
return coercer;
}
//Look at interfaces on rhsType for coercions
IType[] interfaces = rhsType.getInterfaces();
for ( IType anInterface1 : interfaces ) {
coercer = findCoercer(lhsType, anInterface1, runtime);
if (coercer != null) {
return coercer;
}
}
//Recurse to the superclass of rhsType for coercions
if( rhsType.getSupertype() == null || isPrimitiveOrBoxed( lhsType ) )
{
return null;
}
else
{
return findCoercer( lhsType, rhsType.getSupertype(), runtime );
}
}
/**
* Returns a coercer from values of rhsType to values of lhsType if one exists.
* I tried to write a reasonable spec in the comments below that indicate exactly
* what should coerce to what.
*
* @param lhsType the type to coerce to
* @param rhsType the type to coerce from
* @param runtime true if the coercion is happening at runtime rather than compile time
* (note: This param should go away as we store the coercions on the parsed elements, rather than calling into the
* coercion manager)
*
* @return a coercer from the lhsType to the rhsType, or null if no such coercer exists or is needed
*/
protected ICoercer getCoercerInternal( IType lhsType, IType rhsType, boolean runtime )
{
//=============================================================================
// Anything can be coerced to a string
//=============================================================================
if( JavaTypes.STRING() == lhsType )
{
if( JavaTypes.pCHAR().equals( rhsType ) || JavaTypes.CHARACTER().equals( rhsType ) )
{
return NonWarningStringCoercer.instance();
}
else
{
return StringCoercer.instance();
}
}
//=============================================================================
// All primitives and boxed types inter-coerce, except null to true primitives
//=============================================================================
if( lhsType.isPrimitive() && rhsType.equals( JavaTypes.pVOID() ) )
{
return null;
}
if( isPrimitiveOrBoxed( lhsType ) && isPrimitiveOrBoxed( rhsType ) )
{
if( TypeSystem.isBoxedTypeFor( lhsType, rhsType ) ||
TypeSystem.isBoxedTypeFor( rhsType, lhsType ) )
{
return getHighPriorityPrimitiveOrBoxedConverter( lhsType );
}
return getPrimitiveOrBoxedConverter( lhsType );
}
//=============================================================================
// Primitives coerce to things their boxed type is assignable to
//=============================================================================
if( rhsType.isPrimitive() )
{
if( lhsType.isAssignableFrom( TypeSystem.getBoxType( rhsType ) ) )
{
return getPrimitiveOrBoxedConverter( rhsType );
}
}
//=============================================================================
// IMonitorLock supports java-style synchronization
//=============================================================================
if( !rhsType.isPrimitive() && GosuTypes.IMONITORLOCK_NAME.equals(lhsType.getName()) )
{
return IMonitorLockCoercer.instance();
}
//=============================================================================
// Class<T> <- Meta<T' instanceof JavaType>
//=============================================================================
if( (JavaTypes.CLASS().equals( lhsType.getGenericType() ) &&
rhsType instanceof IMetaType && ((IMetaType)rhsType).getType() instanceof IHasJavaClass) )
{
if( !lhsType.isParameterizedType() ||
lhsType.getTypeParameters()[0].isAssignableFrom( ((IMetaType)rhsType).getType() ) ||
(((IMetaType)rhsType).getType().isPrimitive() && canCoerce( lhsType.getTypeParameters()[0], ((IMetaType)rhsType).getType() )) )
{
return MetaTypeToClassCoercer.instance();
}
}
//=============================================================================
// Numeric type unification
//=============================================================================
if( TypeSystem.isNumericType( lhsType ) && TypeSystem.isNumericType( rhsType ) )
{
//=============================================================================
// All numeric values can be down-coerced to the primitives and boxed types
//=============================================================================
if( lhsType.isPrimitive() || isBoxed( lhsType ) )
{
return getPrimitiveOrBoxedConverter( lhsType );
}
//=============================================================================
// All numeric values can be coerced to BigDecimal
//=============================================================================
if( lhsType.equals( JavaTypes.BIG_DECIMAL() ))
{
return BigDecimalCoercer.instance();
}
//=============================================================================
// All numeric values can be coerced to BigInteger
//=============================================================================
if( lhsType.equals( JavaTypes.BIG_INTEGER() ))
{
if( hasPotentialLossOfPrecisionOrScale( lhsType, rhsType ) )
{
return BigIntegerCoercer.instance();
}
else
{
return BigIntegerCoercer.instance();
}
}
}
//=============================================================================
// JavaType interface <- compatible block
//=============================================================================
if( rhsType instanceof IFunctionType && lhsType.isInterface() )
{
IFunctionType rhsFunctionType = (IFunctionType)rhsType;
IFunctionType lhsFunctionType = FunctionToInterfaceCoercer.getRepresentativeFunctionType( lhsType );
if( lhsFunctionType != null )
{
if( lhsFunctionType.isAssignableFrom( rhsFunctionType ) )
{
return FunctionToInterfaceCoercer.instance();
}
else
{
if( lhsFunctionType.areParamsCompatible( rhsFunctionType ) )
{
ICoercer coercer = findCoercer( lhsFunctionType.getReturnType(), rhsFunctionType.getReturnType(), runtime );
if( coercer != null )
{
return FunctionToInterfaceCoercer.instance();
}
}
}
}
}
//=============================================================================
// Coerce synthetic block classes to function types
//=============================================================================
if( lhsType instanceof IFunctionType && rhsType instanceof IBlockClass )
{
if( lhsType.isAssignableFrom( ((IBlockClass)rhsType).getBlockType() ) )
{
return IdentityCoercer.instance();
}
}
//=============================================================================
// compatible block <- JavaType interface
//=============================================================================
if( lhsType instanceof IFunctionType && rhsType.isInterface() &&
FunctionFromInterfaceCoercer.areTypesCompatible( (IFunctionType)lhsType, rhsType ) )
{
return FunctionFromInterfaceCoercer.instance();
}
//=============================================================================
// Coerce block types that are coercable in return values and contravariant in arg types
//=============================================================================
if( lhsType instanceof IBlockType && rhsType instanceof IBlockType )
{
IBlockType lBlock = (IBlockType)lhsType;
IBlockType rBlock = (IBlockType)rhsType;
if( lBlock.areParamsCompatible( rBlock ) )
{
IType leftType = lBlock.getReturnType();
IType rightType = rBlock.getReturnType();
if( rightType != JavaTypes.pVOID() )
{
ICoercer iCoercer = findCoercer( leftType, rightType, runtime );
if( iCoercer != null && !coercionRequiresWarningIfImplicit( leftType, rightType ))
{
return BlockCoercer.instance();
}
}
}
}
//=============================================================================
// Coerce gw.util.RegExpMatch to boolean
//=============================================================================
if( JavaTypes.pBOOLEAN().equals(lhsType) && rhsType.getName().equals("gw.util.RegExpMatch") )
{
return RegExpMatchToBooleanCoercer.instance();
}
return null;
}
public boolean isPrimitiveOrBoxed( IType lhsType )
{
return lhsType.isPrimitive() || isBoxed( lhsType );
}
public static boolean isBoxed( IType lhsType )
{
return lhsType == JavaTypes.BOOLEAN()
|| lhsType == JavaTypes.BYTE()
|| lhsType == JavaTypes.CHARACTER()
|| lhsType == JavaTypes.DOUBLE()
|| lhsType == JavaTypes.FLOAT()
|| lhsType == JavaTypes.INTEGER()
|| lhsType == JavaTypes.LONG()
|| lhsType == JavaTypes.SHORT();
}
protected ICoercer getPrimitiveOrBoxedConverter( IType type )
{
if( type == JavaTypes.pBOOLEAN() )
{
return BasePrimitiveCoercer.BooleanPCoercer.get();
}
else if( type == JavaTypes.BOOLEAN() )
{
return BooleanCoercer.instance();
}
else if( type == JavaTypes.pBYTE() )
{
return BasePrimitiveCoercer.BytePCoercer.get();
}
else if( type == JavaTypes.BYTE() )
{
return ByteCoercer.instance();
}
else if( type == JavaTypes.pCHAR() )
{
return BasePrimitiveCoercer.CharPCoercer.get();
}
else if( type == JavaTypes.CHARACTER() )
{
return CharCoercer.instance();
}
else if( type == JavaTypes.pDOUBLE() )
{
return BasePrimitiveCoercer.DoublePCoercer.get();
}
else if( type == JavaTypes.DOUBLE() )
{
return DoubleCoercer.instance();
}
else if( type == JavaTypes.pFLOAT() )
{
return BasePrimitiveCoercer.FloatPCoercer.get();
}
else if( type == JavaTypes.FLOAT() )
{
return FloatCoercer.instance();
}
else if( type == JavaTypes.pINT() )
{
return BasePrimitiveCoercer.IntPCoercer.get();
}
else if( type == JavaTypes.INTEGER() )
{
return IntCoercer.instance();
}
else if( type == JavaTypes.pLONG() )
{
return BasePrimitiveCoercer.LongPCoercer.get();
}
else if( type == JavaTypes.LONG() )
{
return LongCoercer.instance();
}
else if( type == JavaTypes.pSHORT() )
{
return BasePrimitiveCoercer.ShortPCoercer.get();
}
else if( type == JavaTypes.SHORT() )
{
return ShortCoercer.instance();
}
else if( type == JavaTypes.pVOID() )
{
return IdentityCoercer.instance();
}
else
{
return null;
}
}
protected ICoercer getHighPriorityPrimitiveOrBoxedConverter( IType type )
{
if( type == JavaTypes.pBOOLEAN() )
{
return BooleanPHighPriorityCoercer.instance();
}
else if( type == JavaTypes.BOOLEAN() )
{
return BooleanHighPriorityCoercer.instance();
}
else if( type == JavaTypes.pBYTE() )
{
return BytePHighPriorityCoercer.instance();
}
else if( type == JavaTypes.BYTE() )
{
return ByteHighPriorityCoercer.instance();
}
else if( type == JavaTypes.pCHAR() )
{
return CharPHighPriorityCoercer.instance();
}
else if( type == JavaTypes.CHARACTER() )
{
return CharHighPriorityCoercer.instance();
}
else if( type == JavaTypes.pDOUBLE() )
{
return DoublePHighPriorityCoercer.instance();
}
else if( type == JavaTypes.DOUBLE() )
{
return DoubleHighPriorityCoercer.instance();
}
else if( type == JavaTypes.pFLOAT() )
{
return FloatPHighPriorityCoercer.instance();
}
else if( type == JavaTypes.FLOAT() )
{
return FloatHighPriorityCoercer.instance();
}
else if( type == JavaTypes.pINT() )
{
return IntPHighPriorityCoercer.instance();
}
else if( type == JavaTypes.INTEGER() )
{
return IntHighPriorityCoercer.instance();
}
else if( type == JavaTypes.pLONG() )
{
return LongPHighPriorityCoercer.instance();
}
else if( type == JavaTypes.LONG() )
{
return LongHighPriorityCoercer.instance();
}
else if( type == JavaTypes.pSHORT() )
{
return ShortPHighPriorityCoercer.instance();
}
else if( type == JavaTypes.SHORT() )
{
return ShortHighPriorityCoercer.instance();
}
else if( type == JavaTypes.pVOID() )
{
return IdentityCoercer.instance();
}
else
{
return null;
}
}
public IType verifyTypesComparable( IType lhsType, IType rhsType, boolean bBiDirectional ) throws ParseException
{
return verifyTypesComparable( lhsType, rhsType, bBiDirectional, null );
}
public IType verifyTypesComparable( IType lhsType, IType rhsType, boolean bBiDirectional, IFullParserState parserState ) throws ParseException
{
IType lhsT;
IType rhsT;
if( bBiDirectional )
{
// Bi-Directional indicates comparison as opposed to assignability, therefore for comparison
// we need to test comparability between type variables' bounds
lhsT = TypeSystem.getDefaultParameterizedTypeWithTypeVars( lhsType );
rhsT = TypeSystem.getDefaultParameterizedTypeWithTypeVars( rhsType );
}
else
{
lhsT = lhsType;
rhsT = rhsType;
}
//==================================================================================
// Upcasting
//==================================================================================
if( lhsT == rhsT )
{
return lhsType;
}
if( lhsT.equals( rhsT ) )
{
return lhsType;
}
if( lhsT.isAssignableFrom( rhsT ) )
{
return lhsType;
}
//==================================================================================
// null/void confusion (see http://jira/jira/browse/PL-12766)
//==================================================================================
if( JavaTypes.pVOID().equals( rhsT ) && !lhsT.isPrimitive() )
{
return lhsType;
}
if( JavaTypes.pVOID().equals( lhsT ) && !rhsT.isPrimitive() )
{
return rhsType;
}
//==================================================================================
// Error type handling
//==================================================================================
if( lhsT instanceof IErrorType)
{
return lhsType;
}
if( rhsT instanceof IErrorType )
{
return rhsType;
}
//==================================================================================
// IPlaceholderType type handling
//==================================================================================
if( (lhsT instanceof IPlaceholder && ((IPlaceholder)lhsT).isPlaceholder()) ||
(rhsT instanceof IPlaceholder && ((IPlaceholder)rhsT).isPlaceholder()) )
{
return lhsType;
}
//==================================================================================
//Covariant arrays
//==================================================================================
if( lhsT.isArray() && rhsT.isArray() )
{
// Note an array of primitives and an array of non-primitives are never assignable
if( lhsT.getComponentType().isPrimitive() == rhsT.getComponentType().isPrimitive() &&
lhsT.getComponentType().isAssignableFrom( rhsT.getComponentType() ) )
{
return lhsType;
}
}
//==================================================================================
// Downcasting
//==================================================================================
if( bBiDirectional )
{
if( rhsT.isAssignableFrom( lhsT ) )
{
return lhsType;
}
if( lhsT.isArray() && rhsT.isArray() )
{
if( rhsT.getComponentType().isAssignableFrom( lhsT.getComponentType() ) )
{
return lhsType;
}
}
}
//==================================================================================
// Structurally suitable (static duck typing)
//==================================================================================
if( isStructurallyAssignable( lhsT, rhsT ) )
{
return lhsType;
}
//==================================================================================
// Coercion
//==================================================================================
if( canCoerce( lhsT, rhsT ) )
{
return lhsType;
}
if( bBiDirectional )
{
if( canCoerce( rhsT, lhsT ) )
{
return rhsType;
}
}
String strLhs = TypeSystem.getNameWithQualifiedTypeVariables( lhsType );
String strRhs = TypeSystem.getNameWithQualifiedTypeVariables( rhsType );
throw new ParseException( parserState, lhsType, Res.MSG_TYPE_MISMATCH, strLhs, strRhs );
}
public static boolean isStructurallyAssignable( IType toType, IType fromType )
{
if( !(toType instanceof IGosuClass && ((IGosuClass)toType).isStructure()) )
{
return false;
}
return isStructurallyAssignable_Laxed( toType, fromType );
}
public static boolean isStructurallyAssignable_Laxed( IType toType, IType fromType )
{
ITypeInfo fromTypeInfo = fromType.getTypeInfo();
MethodList fromMethods = fromTypeInfo instanceof IRelativeTypeInfo
? ((IRelativeTypeInfo)fromTypeInfo).getMethods( toType )
: fromTypeInfo.getMethods();
ITypeInfo toTypeInfo = toType.getTypeInfo();
MethodList toMethods = toTypeInfo instanceof IRelativeTypeInfo
? ((IRelativeTypeInfo)toTypeInfo).getMethods( fromType )
: toTypeInfo.getMethods();
for( IMethodInfo toMi : toMethods )
{
if( isObjectMethod( toMi ) ) {
continue;
}
if( toMi.getOwnersType() instanceof IGosuEnhancement ) {
continue;
}
IMethodInfo fromMi = fromMethods.findAssignableMethod( toMi );
if( fromMi == null ) {
if( toMi.getDisplayName().startsWith( "@" ) ) {
// Find matching property/field
IPropertyInfo fromPi = fromTypeInfo.getProperty( toMi.getDisplayName().substring( 1 ) );
if( fromPi != null ) {
if( toMi.getParameters().length == 0 ) {
boolean bAssignable = toMi.getReturnType().equals( fromPi.getFeatureType() ) ||
arePrimitiveTypesAssignable( toMi.getReturnType(), fromPi.getFeatureType() );
if( bAssignable ) {
continue;
}
}
else {
boolean bAssignable = fromPi.isWritable( toType ) &&
(fromPi.getFeatureType().equals( toMi.getParameters()[0].getFeatureType() ) ||
arePrimitiveTypesAssignable( fromPi.getFeatureType(), toMi.getParameters()[0].getFeatureType() ));
if( bAssignable ) {
continue;
}
}
}
}
return false;
}
}
return true;
}
public static boolean arePrimitiveTypesAssignable( IType toType, IType fromType ) {
if( toType == null || fromType == null || !toType.isPrimitive() || !fromType.isPrimitive() ) {
return false;
}
if( toType == fromType )
{
return true;
}
if( toType == JavaTypes.pDOUBLE() ) {
return fromType == JavaTypes.pFLOAT() ||
fromType == JavaTypes.pINT() ||
fromType == JavaTypes.pCHAR() ||
fromType == JavaTypes.pSHORT() ||
fromType == JavaTypes.pBYTE();
}
if( toType == JavaTypes.pFLOAT() ) {
return fromType == JavaTypes.pCHAR() ||
fromType == JavaTypes.pSHORT() ||
fromType == JavaTypes.pBYTE();
}
if( toType == JavaTypes.pLONG() ) {
return fromType == JavaTypes.pINT() ||
fromType == JavaTypes.pCHAR() ||
fromType == JavaTypes.pSHORT() ||
fromType == JavaTypes.pBYTE();
}
if( toType == JavaTypes.pINT() ) {
return fromType == JavaTypes.pSHORT() ||
fromType == JavaTypes.pCHAR() ||
fromType == JavaTypes.pBYTE();
}
if( toType == JavaTypes.pSHORT() ) {
return fromType == JavaTypes.pBYTE();
}
return false;
}
public static boolean isObjectMethod( IMethodInfo mi )
{
IGosuClass gosuObjectType = GosuShop.getGosuClassFrom( JavaTypes.IGOSU_OBJECT() );
if( mi.getOwnersType() == gosuObjectType || mi.getDisplayName().equals( "@itype" ) )
{
// A IGosuObject method
return true;
}
IParameterInfo[] params = mi.getParameters();
IType[] paramTypes = new IType[params.length];
for( int i = 0; i < params.length; i++ )
{
paramTypes[i] = params[i].getFeatureType();
}
IRelativeTypeInfo ti = (IRelativeTypeInfo)JavaTypes.OBJECT().getTypeInfo();
IMethodInfo objMethod = ti.getMethod( JavaTypes.OBJECT(), mi.getDisplayName(), paramTypes );
return objMethod != null;
}
public boolean coercionRequiresWarningIfImplicit( IType lhsType, IType rhsType )
{
//==================================================================================
// Upcasting
//==================================================================================
if( lhsType == rhsType )
{
return false;
}
if( lhsType.equals( rhsType ) )
{
return false;
}
if( lhsType.isAssignableFrom( rhsType ) )
{
return false;
}
if ( rhsType.isPrimitive() && lhsType.isAssignableFrom( TypeSystem.getBoxType( rhsType ) ) )
{
return false;
}
//==================================================================================
// null/void confusion (see http://jira/jira/browse/PL-12766)
//==================================================================================
if( JavaTypes.pVOID().equals(rhsType) )
{
return false;
}
//==================================================================================
// Error type handling
//==================================================================================
if( lhsType instanceof IErrorType )
{
return false;
}
if( rhsType instanceof IErrorType )
{
return false;
}
//==================================================================================
// IPlaceholderType type handling
//==================================================================================
if( (lhsType instanceof IPlaceholder && ((IPlaceholder)lhsType).isPlaceholder()) ||
(rhsType instanceof IPlaceholder && ((IPlaceholder)rhsType).isPlaceholder()) )
{
return false;
}
//==================================================================================
//Covariant arrays (java semantics, which are wrong)
//
// (Five years later, let me totally disagree with my former self. Java array semantics are not only right,
// we've decided to adopt them for generics. Worse is better.)
//==================================================================================
if( lhsType.isArray() && rhsType.isArray() )
{
if( lhsType.getComponentType().isAssignableFrom( rhsType.getComponentType() ) )
{
return false;
}
}
//==================================================================================
// Coercion
//==================================================================================
if( TypeSystem.isNumericType( lhsType ) &&
TypeSystem.isNumericType( rhsType ) )
{
return hasPotentialLossOfPrecisionOrScale( lhsType, rhsType );
}
else
{
if( TypeSystem.isBoxedTypeFor( lhsType, rhsType ) ||
TypeSystem.isBoxedTypeFor( rhsType, lhsType ) )
{
return false;
}
else
{
ICoercer iCoercer = findCoercer( lhsType, rhsType, false );
return iCoercer != null && iCoercer.isExplicitCoercion();
}
}
}
/**
* Given a value and a target Class, return a compatible value via the target Class.
*/
public final Object convertValue(Object value, IType intrType)
{
//==================================================================================
// Null handling
//==================================================================================
if( intrType == null )
{
return null;
}
//## todo: This is a horrible hack
//## todo: The only known case where this is necessary is when we have an array of parameterized java type e.g., List<String>[]
intrType = getBoundingTypeOfTypeVariable( intrType );
if( value == null )
{
return intrType.isPrimitive() ? convertNullAsPrimitive( intrType, true ) : null;
}
IType runtimeType = TypeSystem.getFromObject( value );
//==================================================================================
// IPlaceholder type handling
//==================================================================================
if( (intrType instanceof IPlaceholder && ((IPlaceholder)intrType).isPlaceholder()) ||
(runtimeType instanceof IPlaceholder && ((IPlaceholder)runtimeType).isPlaceholder()) )
{
return value;
}
//==================================================================================
// Runtime polymorphism (with java array semantics)
//==================================================================================
if( intrType == runtimeType )
{
return value;
}
if( intrType.equals( runtimeType ) )
{
return value;
}
if( intrType.isAssignableFrom( runtimeType ) )
{
value = extractObjectArray( intrType, value );
return value;
}
if( intrType.isArray() && runtimeType.isArray() )
{
if( intrType.getComponentType().isAssignableFrom( runtimeType.getComponentType() ) )
{
value = extractObjectArray( intrType, value );
return value;
}
else if( intrType instanceof IGosuArrayClass &&
value instanceof IGosuObject[] )
{
return value;
}
}
// Proxy coercion. The proxy class generated for Java classes is not a super type of the Gosu class.
// The following check allows coercion of the Gosu class to the Gosu proxy class needed for the super call.
if( intrType instanceof IJavaType &&
IGosuClass.ProxyUtil.isProxy( intrType ) &&
runtimeType instanceof IGosuClass &&
intrType.getSupertype() != null &&
intrType.getSupertype().isAssignableFrom( runtimeType ) )
{
return value;
}
// Check Java world types
//noinspection deprecation
if( intrType instanceof IJavaType &&
((IJavaType)intrType).getIntrinsicClass().isAssignableFrom( value.getClass() ) )
{
return value;
}
//==================================================================================
// Coercion
//==================================================================================
Object convertedValue = coerce( intrType, runtimeType, value );
if( convertedValue != null )
{
return convertedValue;
}
else
{
//If the null arose from an actual coercion, return it
if( canCoerce( intrType, runtimeType ) )
{
return convertedValue;
}
else
{
//otherwise, return the value itself uncoerced (See comment above)
if( !runtimeType.isArray() )
{
return NO_DICE;
}
return value;
}
}
}
private IType getBoundingTypeOfTypeVariable( IType intrType )
{
int i = 0;
while( intrType instanceof ITypeVariableArrayType )
{
i++;
intrType = intrType.getComponentType();
}
if( intrType instanceof ITypeVariableType )
{
intrType = ((ITypeVariableType)intrType).getBoundingType();
while( i-- > 0 )
{
intrType = intrType.getArrayType();
}
}
return intrType;
}
private Object extractObjectArray( IType intrType, Object value )
{
if( intrType.isArray() &&
intrType instanceof IJavaArrayType &&
value instanceof IGosuArrayClassInstance )
{
value = ((IGosuArrayClassInstance)value).getObjectArray();
}
return value;
}
public Object convertNullAsPrimitive( IType intrType, boolean isForBoxing )
{
if( intrType == null )
{
return null;
}
if( !intrType.isPrimitive() )
{
throw GosuShop.createEvaluationException( intrType.getName() + " is not a primitive type." );
}
if( intrType == JavaTypes.pBYTE() )
{
return (byte) 0;
}
if( intrType == JavaTypes.pCHAR() )
{
return '\0';
}
if( intrType == JavaTypes.pDOUBLE() )
{
return isForBoxing ? IGosuParser.NaN : (double) 0;
}
if( intrType == JavaTypes.pFLOAT() )
{
return isForBoxing ? Float.NaN : (float) 0;
}
if( intrType == JavaTypes.pINT() )
{
return 0;
}
if( intrType == JavaTypes.pLONG() )
{
return (long) 0;
}
if( intrType == JavaTypes.pSHORT() )
{
return (short) 0;
}
if( intrType == JavaTypes.pBOOLEAN() )
{
return Boolean.FALSE;
}
if( intrType == JavaTypes.pVOID() )
{
return null;
}
throw GosuShop.createEvaluationException( "Unexpected primitive type: " + intrType.getName() );
}
public ICoercer resolveCoercerStatically( IType typeToCoerceTo, IType typeToCoerceFrom )
{
if( typeToCoerceTo == null || typeToCoerceFrom == null )
{
return null;
}
else if( typeToCoerceTo == typeToCoerceFrom )
{
return null;
}
else if( typeToCoerceTo.equals( typeToCoerceFrom ) )
{
return null;
}
else if( typeToCoerceTo instanceof IErrorType || typeToCoerceFrom instanceof IErrorType )
{
return null;
}
else if( typeToCoerceTo instanceof ITypeVariableArrayType )
{
return RuntimeCoercer.instance();
}
else if( typeToCoerceTo instanceof ITypeVariableType )
{
return TypeVariableCoercer.instance();
}
else if( typeToCoerceTo.isAssignableFrom( typeToCoerceFrom ) )
{
return null;
}
else
{
ICoercer coercerInternal = findCoercerImpl( typeToCoerceTo, typeToCoerceFrom, false );
if( coercerInternal == null )
{
if( typeToCoerceFrom.isAssignableFrom( typeToCoerceTo ) && !JavaTypes.pVOID().equals(typeToCoerceTo) )
{
if( areJavaClassesAndAreNotAssignable( typeToCoerceTo, typeToCoerceFrom ) )
{
return RuntimeCoercer.instance();
}
return identityOrRuntime( typeToCoerceTo, typeToCoerceFrom );
}
else if( (typeToCoerceFrom.isInterface() || typeToCoerceTo.isInterface()) &&
!typeToCoerceFrom.isPrimitive() && !typeToCoerceTo.isPrimitive() )
{
return identityOrRuntime( typeToCoerceTo, typeToCoerceFrom );
}
}
return coercerInternal;
}
}
private boolean areJavaClassesAndAreNotAssignable( IType typeToCoerceTo, IType typeToCoerceFrom )
{
if( typeToCoerceFrom instanceof IJavaType && typeToCoerceTo instanceof IJavaType )
{
if( !((IJavaType)typeToCoerceFrom).getBackingClassInfo().isAssignableFrom( ((IJavaType)typeToCoerceTo).getBackingClassInfo() ) )
{
return true;
}
}
return false;
}
private ICoercer identityOrRuntime( IType typeToCoerceTo, IType typeToCoerceFrom )
{
if( TypeSystem.isBytecodeType( typeToCoerceFrom ) &&
TypeSystem.isBytecodeType( typeToCoerceTo ) )
{
return IdentityCoercer.instance(); // (perf) class-to-class downcast can use checkcast bytecode
}
return RuntimeCoercer.instance();
}
public Double makeDoubleFrom( Object obj )
{
if( obj == null )
{
return null;
}
if( obj instanceof IDimension)
{
obj = ((IDimension)obj).toNumber();
}
if( obj instanceof Double )
{
return (Double)obj;
}
double d;
if( obj instanceof Number )
{
d = ((Number)obj).doubleValue();
}
else if( obj instanceof Boolean )
{
return (Boolean) obj ? IGosuParser.ONE : IGosuParser.ZERO;
}
else if( obj instanceof Date )
{
return (double)((Date)obj).getTime();
}
else if( obj instanceof Character )
{
return (double) ((Character) obj).charValue();
}
else if( CommonServices.getCoercionManager().canCoerce( JavaTypes.NUMBER(),
TypeSystem.getFromObject( obj ) ) )
{
Number num = (Number)CommonServices.getCoercionManager().convertValue( obj, JavaTypes.NUMBER() );
return num.doubleValue();
}
else
{
String strValue = obj.toString();
return makeDoubleFrom( parseNumber( strValue ) );
}
if( d >= 0D && d <= 9D )
{
int i = (int)d;
if( ((double)i == d) && (i >= 0 && i <= 9) )
{
return IGosuParser.DOUBLE_DIGITS[i];
}
}
return d;
}
public int makePrimitiveIntegerFrom( Object obj )
{
if( obj == null )
{
return 0;
}
else
{
return makeIntegerFrom( obj );
}
}
public Integer makeIntegerFrom( Object obj )
{
if( obj == null )
{
return null;
}
if( obj instanceof IDimension )
{
obj = ((IDimension)obj).toNumber();
}
if( obj instanceof Integer )
{
return (Integer)obj;
}
if( obj instanceof Number )
{
return ( (Number) obj ).intValue();
}
else if( obj instanceof Boolean )
{
return (Boolean) obj
? IGosuParser.ONE.intValue()
: IGosuParser.ZERO.intValue();
}
else if( obj instanceof Date )
{
return (int) ((Date) obj).getTime();
}
else if( obj instanceof Character )
{
return (int) ((Character) obj).charValue();
}
else if( CommonServices.getCoercionManager().canCoerce( JavaTypes.NUMBER(),
TypeSystem.getFromObject( obj ) ) )
{
Number num = (Number)CommonServices.getCoercionManager().convertValue( obj, JavaTypes.NUMBER() );
return num.intValue();
}
else
{
String strValue = obj.toString();
return makeIntegerFrom( parseNumber( strValue ) );
}
}
public long makePrimitiveLongFrom( Object obj )
{
if( obj == null )
{
return 0;
}
else
{
return makeLongFrom( obj );
}
}
public Long makeLongFrom( Object obj )
{
if( obj == null )
{
return null;
}
if( obj instanceof IDimension )
{
obj = ((IDimension)obj).toNumber();
}
if( obj instanceof Long )
{
return (Long)obj;
}
if( obj instanceof Number )
{
return ((Number)obj).longValue();
}
else if( obj instanceof Boolean )
{
return (Boolean) obj
? IGosuParser.ONE.longValue()
: IGosuParser.ZERO.longValue();
}
else if( obj instanceof Date )
{
return ((Date)obj).getTime();
}
else if( obj instanceof Character )
{
return (long)((Character)obj).charValue();
}
else if( CommonServices.getCoercionManager().canCoerce( JavaTypes.NUMBER(),
TypeSystem.getFromObject( obj ) ) )
{
Number num = (Number)CommonServices.getCoercionManager().convertValue( obj , JavaTypes.NUMBER() );
return num.longValue();
}
else
{
String strValue = obj.toString();
return makeLongFrom( parseNumber( strValue ) );
}
}
public BigDecimal makeBigDecimalFrom( Object obj )
{
if( obj == null )
{
return null;
}
if( obj instanceof IDimension )
{
obj = ((IDimension)obj).toNumber();
}
if( obj instanceof BigDecimal )
{
return (BigDecimal)obj;
}
if( obj instanceof String )
{
try
{
return (BigDecimal)BIG_DECIMAL_FORMAT.parse( obj.toString() );
}
catch( java.text.ParseException e )
{
throw GosuExceptionUtil.convertToRuntimeException( e );
}
}
if( obj instanceof Integer )
{
return BigDecimal.valueOf( (Integer)obj );
}
else if( obj instanceof BigInteger )
{
return new BigDecimal( (BigInteger)obj );
}
else if( obj instanceof Long )
{
return BigDecimal.valueOf( (Long)obj );
}
else if( obj instanceof Short )
{
return BigDecimal.valueOf( (Short)obj );
}
else if( obj instanceof Byte )
{
return BigDecimal.valueOf( (Byte)obj );
}
else if( obj instanceof Character )
{
return BigDecimal.valueOf( (Character)obj );
}
else if (obj instanceof Float)
{
// Convert a float directly to a BigDecimal via the String value; don't
// up-convert it to a double first, since converting a double can be lossy
return new BigDecimal( obj.toString());
}
else if( obj instanceof Number )
{
// Make a double from any type of number that we haven't yet dealt with
Double d = makeDoubleFrom( obj );
return new BigDecimal( d.toString() );
}
else if( obj instanceof Boolean )
{
return (Boolean) obj ? BigDecimal.ONE : BigDecimal.ZERO;
}
else if( obj instanceof Date )
{
return new BigDecimal( ((Date)obj).getTime() );
}
else if( CommonServices.getCoercionManager().canCoerce( JavaTypes.NUMBER(), TypeSystem.getFromObject( obj ) ) )
{
Number num = (Number)CommonServices.getCoercionManager().convertValue( obj, JavaTypes.NUMBER() );
return makeBigDecimalFrom( num );
}
else
{
// As a last resort, convert it to a double and then convert that to a big decimal
Double d = makeDoubleFrom( obj );
return new BigDecimal( d.toString() );
}
}
public BigInteger makeBigIntegerFrom( Object obj )
{
if( obj == null )
{
return null;
}
if( obj instanceof BigInteger )
{
return (BigInteger)obj;
}
if( obj instanceof IDimension )
{
obj = ((IDimension)obj).toNumber();
}
if( obj instanceof String )
{
String strValue = (String)obj;
return makeBigIntegerFrom( parseNumber( strValue ) );
}
BigDecimal d = makeBigDecimalFrom( obj );
return d.toBigInteger();
}
public double makePrimitiveDoubleFrom( Object obj )
{
if( obj == null )
{
return Double.NaN;
}
else
{
return makeDoubleFrom( obj );
}
}
public Float makeFloatFrom( Object obj )
{
if( obj == null )
{
return Float.NaN;
}
if( obj instanceof IDimension )
{
obj = ((IDimension)obj).toNumber();
}
if( obj instanceof Number )
{
return ((Number)obj).floatValue();
}
else if( obj instanceof Boolean )
{
return (Boolean) obj ? 1f : 0f;
}
else if( obj instanceof Date )
{
return (float) ((Date) obj).getTime();
}
else if( obj instanceof Character )
{
return (float) ((Character) obj).charValue();
}
else
{
try
{
return Float.parseFloat( obj.toString() );
}
catch( Throwable t )
{
// Nonparsable floating point numbers have a NaN value (a la JavaScript)
return Float.NaN;
}
}
}
public float makePrimitiveFloatFrom( Object obj )
{
if( obj == null )
{
return Float.NaN;
}
else
{
return makeFloatFrom( obj );
}
}
public String makeStringFrom( Object obj )
{
return obj == null ? null : obj.toString();
}
/**
* @return A Boolean for an arbitrary object.
*/
public boolean makePrimitiveBooleanFrom( Object obj )
{
//noinspection SimplifiableIfStatement
if( obj == null )
{
return false;
}
else
{
return Boolean.TRUE.equals( makeBooleanFrom( obj ) );
}
}
public Boolean makeBooleanFrom( Object obj )
{
if( obj == null )
{
return null;
}
if( obj instanceof IDimension )
{
obj = ((IDimension)obj).toNumber();
}
if( obj instanceof Boolean )
{
return (Boolean)obj;
}
if( obj instanceof String )
{
return Boolean.valueOf( (String)obj );
}
if( obj instanceof Number )
{
return ((Number)obj).doubleValue() == 0D ? Boolean.FALSE : Boolean.TRUE;
}
return Boolean.valueOf( obj.toString() );
}
/**
* Returns a new Date instance representing the object.
*/
public Date makeDateFrom( Object obj )
{
if( obj == null )
{
return null;
}
if( obj instanceof IDimension )
{
obj = ((IDimension)obj).toNumber();
}
if( obj instanceof Date )
{
return (Date)obj;
}
if( obj instanceof Number )
{
return new Date( ((Number)obj).longValue() );
}
if( obj instanceof Calendar)
{
return ((Calendar)obj).getTime();
}
if( !(obj instanceof String) )
{
obj = obj.toString();
}
try
{
return parseDateTime( (String)obj );
}
catch( Exception e )
{
//e.printStackTrace();
}
return null;
}
@Override
public boolean isDateTime( String str ) throws java.text.ParseException
{
return parseDateTime( str ) != null;
}
/**
* Produce a date from a string using standard DateFormat parsing.
*/
public Date parseDateTime( String str ) throws java.text.ParseException
{
if( str == null )
{
return null;
}
return DateFormat.getDateInstance().parse(str);
}
/**
* Convert a string to an array of specified type.
* @param strValue the string to convert
* @param intrType the array component type
* @return the string converted to an array
*/
public static Object makeArrayFromString( String strValue, IType intrType )
{
if( JavaTypes.pCHAR() == intrType )
{
return strValue.toCharArray();
}
if( JavaTypes.CHARACTER() == intrType )
{
Character[] characters = new Character[strValue.length()];
for( int i = 0; i < characters.length; i++ )
{
characters[i] = strValue.charAt(i);
}
return characters;
}
if( JavaTypes.STRING() == intrType )
{
String[] strings = new String[strValue.length()];
for( int i = 0; i < strings.length; i++ )
{
strings[i] = String.valueOf( strValue.charAt( i ) );
}
return strings;
}
throw GosuShop.createEvaluationException( "The type, " + intrType.getName() + ", is not supported as a coercible component type to a String array." );
}
public String formatDate( Date value, String strFormat )
{
DateFormat df = new SimpleDateFormat( strFormat );
return df.format( value );
}
public String formatTime( Date value, String strFormat )
{
DateFormat df = new SimpleDateFormat( strFormat );
return df.format( value );
}
public String formatNumber( Double value, String strFormat )
{
NumberFormat nf = new DecimalFormat( strFormat );
return nf.format( value.doubleValue() );
}
public Number parseNumber( String strValue )
{
try
{
return Double.parseDouble( strValue );
}
catch( Exception e )
{
// Nonparsable floating point numbers have a NaN value (a la JavaScript)
return IGosuParser.NaN;
}
}
private static class NullSentinalCoercer extends StandardCoercer
{
private static final NullSentinalCoercer INSTANCE = new NullSentinalCoercer();
@Override
public Object coerceValue( IType typeToCoerceTo, Object value )
{
throw new IllegalStateException( "This is the null sentinal coercer, and is used only to " +
"represent a miss in the coercer cache. It should never " +
"be returned for actual use" );
}
public static NullSentinalCoercer instance()
{
return INSTANCE;
}
}
}