/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.parser; import gw.config.CommonServices; import gw.internal.gosu.parser.expressions.ConditionalExpression; import gw.lang.parser.GosuParserTypes; import gw.lang.parser.StandardCoercionManager; import gw.lang.reflect.IConstructorType; import gw.lang.reflect.MethodList; import gw.lang.reflect.gs.IGosuProgram; import gw.lang.reflect.java.JavaTypes; import gw.util.GosuExceptionUtil; import gw.util.IFeatureFilter; import gw.lang.reflect.IScriptabilityModifier; import gw.lang.parser.exceptions.ParseException; import gw.lang.parser.exceptions.ParseIssue; import gw.lang.parser.resources.Res; import gw.lang.reflect.IAttributedFeatureInfo; import gw.lang.reflect.IFunctionType; import gw.lang.reflect.IPropertyInfo; import gw.lang.reflect.IRelativeTypeInfo; import gw.lang.reflect.IType; import gw.lang.reflect.ITypeInfo; import java.util.List; /** */ public class BeanAccess { /** * private to enforce singleton access. */ private BeanAccess() { } /** * Returns true if the method or property is hidden or otherwise not scriptable. */ public static boolean isDescriptorHidden( IAttributedFeatureInfo descriptor ) { return descriptor.isHidden() || !descriptor.isScriptable(); } ////////// type lookup stuff /////////// public static boolean isBeanType( IType typeSource ) { return typeSource != GosuParserTypes.STRING_TYPE() && typeSource != GosuParserTypes.BOOLEAN_TYPE() && // typeSource != GosuParserTypes.DATETIME_TYPE() && typeSource != GosuParserTypes.NULL_TYPE() && typeSource != GosuParserTypes.NUMBER_TYPE() && !typeSource.isPrimitive() && !typeSource.isArray() && !(typeSource instanceof IFunctionType) && !(typeSource instanceof IConstructorType) && !(typeSource instanceof MetaType); } public static boolean isNumericType( IType intrType ) { if( intrType == null ) { return false; } return (intrType.isPrimitive() && intrType != JavaTypes.pBOOLEAN() && intrType != JavaTypes.pVOID()) || JavaTypes.BYTE() == intrType || JavaTypes.SHORT() == intrType || JavaTypes.CHARACTER() == intrType || JavaTypes.INTEGER() == intrType || JavaTypes.LONG() == intrType || JavaTypes.FLOAT() == intrType || JavaTypes.DOUBLE() == intrType || JavaTypes.BIG_INTEGER() == intrType || JavaTypes.BIG_DECIMAL() == intrType || isDimension( intrType ); } private static boolean isDimension( IType intrType ) { // if( intrType instanceof IGosuClass && TypeSystem.getExecutionEnvironment().isSingleModuleMode() ) // { // // We want to avoid calling IGosuClass.isAssignableFrom() during runtime as it requires header parsing // return IDimension.class.isAssignableFrom( ((IGosuClass)intrType).getBackingClass() ); // } return JavaTypes.IDIMENSION().isAssignableFrom( intrType ); } public static boolean isBoxedTypeFor( IType primitiveType, IType boxedType ) { if( primitiveType != null && primitiveType.isPrimitive() ) { if( primitiveType == JavaTypes.pBOOLEAN() && boxedType == JavaTypes.BOOLEAN() ) { return true; } if( primitiveType == JavaTypes.pBYTE() && boxedType == JavaTypes.BYTE() ) { return true; } if( primitiveType == JavaTypes.pCHAR() && boxedType == JavaTypes.CHARACTER() ) { return true; } if( primitiveType == JavaTypes.pDOUBLE() && boxedType == JavaTypes.DOUBLE() ) { return true; } if( primitiveType == JavaTypes.pFLOAT() && boxedType == JavaTypes.FLOAT() ) { return true; } if( primitiveType == JavaTypes.pINT() && boxedType == JavaTypes.INTEGER() ) { return true; } if( primitiveType == JavaTypes.pLONG() && boxedType == JavaTypes.LONG() ) { return true; } if( primitiveType == JavaTypes.pSHORT() && boxedType == JavaTypes.SHORT() ) { return true; } } return false; } ///////// Equality stuff -- should this be here //////// /** * Returns true if the values are logically equal, false otherwise. * <p/> * Perform an equality comparison. Heuristics for evaluation follow: * <ul> * <li> If the LHS is a Number, coerce the RHS to a Number value and compare. * <li> If the LHS is a String, coerce the RHS to a String value and String compare. * <li> (Ditto for Boolean and DateTime). * <li> If the LHS is a Bean, * <ul> * <li> If the RHS is a Bean, compare with equals(). * <li> If the RHS is an ObjectLiteral, compare hash codes. * <li> Otherwise, attempt to determine which operand is coercible to the other, coerce, and compare with equals(). * </ul> * <li> Otherwise, compare with equals(). * </ul> */ public static boolean areValuesEqual( IType lhsType, Object lhsValue, IType rhsType, Object rhsValue ) { // // Note we do *not* verify the compatibility of the lhs & rhs types. That // is too much overhead. Instead compare the types only when you need to. // if( lhsValue == null && rhsValue == null ) { return true; } if( rhsValue == null || lhsValue == null ) { return false; } boolean bValue; //## todo: This is insane. THe equality operator should be symmetric; we should not be looking at the lhs type! if( BeanAccess.isNumericType( lhsType ) ) { bValue = ConditionalExpression.compareNumbers( lhsValue, rhsValue, lhsType, rhsType ) == 0 ? Boolean.TRUE : Boolean.FALSE; } else if( lhsType == GosuParserTypes.STRING_TYPE() ) { bValue = CommonServices.getCoercionManager().makeStringFrom( lhsValue ).equals( CommonServices.getCoercionManager().makeStringFrom( rhsValue ) ); } else if( lhsType == GosuParserTypes.BOOLEAN_TYPE() || lhsType == JavaTypes.pBOOLEAN() ) { bValue = CommonServices.getCoercionManager().makeBooleanFrom( lhsValue ).booleanValue() == CommonServices.getCoercionManager().makeBooleanFrom( rhsValue ).booleanValue(); } else if( lhsType == GosuParserTypes.DATETIME_TYPE() ) { bValue = CommonServices.getCoercionManager().makeDateFrom( lhsValue ).equals( CommonServices.getCoercionManager().makeDateFrom( rhsValue ) ); } else if( isBeanType( lhsType ) ) { if( isBeanType( rhsType ) ) { bValue = areBeansEqual( lhsValue, rhsValue ); } else { bValue = areObjectsLogicallyEqual( lhsType, rhsType, lhsValue, rhsValue ); } } else if( lhsType.isArray() && rhsType.isArray() ) { try { bValue = true; // Determine which operand to convert (support symmetry) CommonServices.getCoercionManager().verifyTypesComparable( lhsType, rhsType, true ); int lhsLength = lhsType.getArrayLength( lhsValue ); int rhsLength = rhsType.getArrayLength( rhsValue ); if( lhsLength == rhsLength ) { for( int i = 0; i < lhsLength; i++ ) { bValue &= areValuesEqual( lhsType.getComponentType(), lhsType.getArrayComponent( lhsValue, i ), rhsType.getComponentType(), rhsType.getArrayComponent( rhsValue, i ) ); if( !bValue ) { break; } } } else { bValue = false; } } catch( ParseIssue e ) { bValue = false; } } else { bValue = areObjectsLogicallyEqual( lhsType, rhsType, lhsValue, rhsValue ); } return bValue; } private static boolean areObjectsLogicallyEqual( IType lhsType, IType rhsType, Object lhsValue, Object rhsValue ) { boolean bValue; IType type; try { // Determine which operand to convert (support symmetry) type = CommonServices.getCoercionManager().verifyTypesComparable( lhsType, rhsType, true ); } catch( ParseIssue e ) { throw GosuExceptionUtil.forceThrow( e ); } boolean bConvertLhsType = type != lhsType; if( bConvertLhsType ) { lhsValue = CommonServices.getCoercionManager().convertValue(lhsValue, rhsType); if( lhsValue == StandardCoercionManager.NO_DICE ) { return false; } } else { rhsValue = CommonServices.getCoercionManager().convertValue(rhsValue, lhsType); if( rhsValue == StandardCoercionManager.NO_DICE ) { return false; } } bValue = lhsValue.equals( rhsValue ); return bValue; } /** * Test for equality between two BeanType values. Note this method is not entirely appropriate for * non-BeanType value i.e., it's not as intelligent as EqualityExpression comparing primitive types, * TypeKeys, etc. * <p/> * Msotly this method is for handling conversion of KeyableBean and Key for comparison. * * @param bean1 A value having an IType of BeanType * @param bean2 A value having an IType of BeanType * * @return True if the beans are equal */ public static boolean areBeansEqual( Object bean1, Object bean2 ) { if( bean1 == bean2 ) { return true; } if( bean1 == null || bean2 == null ) { return false; } IType class1 = TypeLoaderAccess.instance().getIntrinsicTypeFromObject( bean1 ); IType class2 = TypeLoaderAccess.instance().getIntrinsicTypeFromObject( bean2 ); if( class1.isAssignableFrom( class2 ) || class2.isAssignableFrom( class1 ) ) { return bean1.equals( bean2 ); } if( CommonServices.getEntityAccess().isDomainInstance( bean1 ) || CommonServices.getEntityAccess().isDomainInstance( bean2 ) ) { return CommonServices.getEntityAccess().areBeansEqual( bean1, bean2 ); } return bean1.equals( bean2 ); } public static List<? extends IPropertyInfo> getProperties( ITypeInfo beanInfo, IType classBean ) { if( beanInfo instanceof IRelativeTypeInfo ) { return ((IRelativeTypeInfo)beanInfo).getProperties( classBean ); } else { return beanInfo.getProperties(); } } public static MethodList getMethods( ITypeInfo beanInfo, IType whosaskin ) { if( beanInfo instanceof IRelativeTypeInfo && whosaskin != null ) { return ((IRelativeTypeInfo)beanInfo).getMethods( whosaskin ); } else { return beanInfo.getMethods(); } } /** * Resolves the property directly, as if the type were requesting it, giving access to all properties */ public static IPropertyInfo getPropertyInfoDirectly( IType classBean, String strProperty ) throws ParseException { return getPropertyInfo( classBean, classBean, strProperty, null, null, null ); } public static IPropertyInfo getPropertyInfo( IType classBean, String strProperty, IFeatureFilter filter, ParserBase parser, IScriptabilityModifier scriptabilityConstraint) throws ParseException { IType whosaskin = classBean; if( parser != null ) { whosaskin = parser.getGosuClass(); // Hack to ensure that we adhere to visibility rules when parsing whosaskin = whosaskin != null || GosuClassTypeInfo.isIncludeAll() ? whosaskin : JavaTypes.OBJECT(); if( GosuClassTypeInfo.isIncludeAll() && whosaskin instanceof IGosuProgram && // Well... downstream we check for null on whosaskin and ignore TypeSystem.isIncludeAll(), that can't happen for CompileTimeExpressionParser !whosaskin.getName().startsWith( IGosuProgram.PACKAGE ) ) { whosaskin = null; } } return getPropertyInfo( classBean, whosaskin, strProperty, filter, parser, scriptabilityConstraint ); } public static IPropertyInfo getPropertyInfo( IType classBean, IType whosAskin, String strProperty, IFeatureFilter filter, ParserBase parser, IScriptabilityModifier scriptabilityConstraint) throws ParseException { if( classBean == null ) { throw new ParseException( parser == null ? null : parser.makeFullParserState(), Res.MSG_BEAN_CLASS_IS_NULL ); } ITypeInfo beanInfo = classBean.getTypeInfo(); if( beanInfo == null ) { throw new ParseException( parser == null ? null : parser.makeFullParserState(), Res.MSG_NO_EXPLICIT_TYPE_INFO_FOUND, classBean.getName() ); } int iArrayBracket = strProperty.indexOf( '[' ); if( iArrayBracket > 0 ) { strProperty = strProperty.substring( 0, iArrayBracket ); } IPropertyInfo property = getProperty( beanInfo, whosAskin, strProperty ); if( property != null ) { if( !isDescriptorHidden( property ) && (filter == null || filter.acceptFeature(classBean, property )) ) { if( !property.isVisible(scriptabilityConstraint) ) { throw new ParseException( parser == null ? null : parser.makeFullParserState(), Res.MSG_PROPERTY_NOT_VISIBLE, property.getName() ); } return property; } } else { IType expComponentType = TypeLord.getExpandableComponentType( classBean ); if( expComponentType != null && expComponentType != classBean ) { // Allow expressions of the form: <array-of-foo>.<property-of-foo>. The // result of evaluating such an expression is of type array-of-property-type. return new ArrayExpansionPropertyInfo( getPropertyInfo( expComponentType, strProperty, filter, parser, scriptabilityConstraint ) ); } } throw new PropertyNotFoundException( strProperty, classBean, parser == null ? null : parser.makeFullParserState() ); } public static IPropertyInfo getProperty( ITypeInfo beanInfo, IType classBean, String strMember ) { IPropertyInfo property; if( beanInfo instanceof IRelativeTypeInfo) { property = ((IRelativeTypeInfo)beanInfo).getProperty( classBean, strMember ); } else { property = beanInfo.getProperty( strMember ); } return property; } }