/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.parser;
import gw.config.CommonServices;
import gw.lang.Deprecated;
import gw.lang.GosuShop;
import gw.lang.PublishedName;
import gw.lang.javadoc.IDocRef;
import gw.lang.javadoc.IMethodNode;
import gw.lang.parser.EvaluationException;
import gw.lang.parser.TypeVarToTypeMap;
import gw.lang.reflect.IAnnotationInfo;
import gw.lang.reflect.IFeatureInfo;
import gw.lang.reflect.IMethodInfo;
import gw.lang.reflect.IPresentationInfo;
import gw.lang.reflect.IPropertyAccessor;
import gw.lang.reflect.IRelativeTypeInfo;
import gw.lang.reflect.IScriptabilityModifier;
import gw.lang.reflect.IType;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.java.ClassInfoUtil;
import gw.lang.reflect.java.IJavaAnnotatedElement;
import gw.lang.reflect.java.IJavaClassField;
import gw.lang.reflect.java.IJavaClassInfo;
import gw.lang.reflect.java.IJavaClassMethod;
import gw.lang.reflect.java.IJavaClassType;
import gw.lang.reflect.java.IJavaMethodInfo;
import gw.lang.reflect.java.IJavaPropertyDescriptor;
import gw.lang.reflect.java.IJavaPropertyInfo;
import gw.util.GosuExceptionUtil;
import gw.util.GosuStringUtil;
import gw.util.concurrent.LockingLazyVar;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.List;
/**
*/
public class JavaPropertyInfo extends JavaBaseFeatureInfo implements IJavaPropertyInfo
{
private IJavaPropertyDescriptor _pd;
private IType _propertyType;
private IPropertyAccessor _accessor;
private Boolean _bStatic;
private boolean _bExternal;
private boolean _bReadable;
private IJavaClassMethod _getMethod;
private IJavaClassMethod _setMethod;
private IJavaClassField _publicField;
private IPresentationInfo _presInfo;
private String _name;
private LockingLazyVar<IJavaAnnotatedElement> _annotatedElement = new LockingLazyVar<IJavaAnnotatedElement>() {
@Override
protected IJavaAnnotatedElement init() {
return _pd.getReadMethod() != null ? _pd.getReadMethod() : _pd.getWriteMethod();
}
};
/**
* @param container Typically this will be the containing ITypeInfo
* @param pd The property descriptor (from BeanInfo)
*/
public JavaPropertyInfo( IFeatureInfo container, IJavaPropertyDescriptor pd )
{
super( container );
_pd = pd;
initFlags();
}
public JavaPropertyInfo( IFeatureInfo container, IJavaPropertyDescriptor pd, IType propertyType )
{
super( container );
_pd = pd;
_propertyType = propertyType;
initFlags();
}
public JavaPropertyInfo( IFeatureInfo container, IJavaPropertyDescriptor pd, IType propertyType, IPresentationInfo presInfo )
{
this( container, pd, propertyType );
_presInfo = presInfo;
}
private void initFlags()
{
_name = _pd.getName();
_getMethod = _pd.getReadMethod();
if( _getMethod != null ) {
if (_getMethod instanceof MethodJavaClassMethod) {
((MethodJavaClassMethod)_getMethod).setAccessible(true);
}
IAnnotationInfo property = _getMethod.getAnnotation(PublishedName.class);
if (property != null) {
_name = (String) property.getFieldValue("value");
if (_name.startsWith("get") || _name.startsWith("set")) {
_name = _name.substring(3);
} else if (_name.startsWith("is")) {
_name = _name.substring(2);
}
}
}
_setMethod = _pd.getWriteMethod();
if( _setMethod != null ) {
if (_setMethod instanceof MethodJavaClassMethod) {
((MethodJavaClassMethod)_setMethod).setAccessible(true);
}
IAnnotationInfo property = _setMethod.getAnnotation(PublishedName.class);
if (property != null) {
_name = (String) property.getFieldValue("value");
if (_name.startsWith("get") || _name.startsWith("set")) {
_name = _name.substring(3);
} else if (_name.startsWith("is")) {
_name = _name.substring(2);
}
}
}
// check for underlying public field to fill in "missing" getter/setter
if (_getMethod == null && _setMethod != null) {
findFieldOn(_setMethod.getEnclosingClass(), false);
}
if (_setMethod == null && _getMethod != null) {
findFieldOn(_getMethod.getEnclosingClass(), true);
}
if( isWritable( getOwnersType()) ) {
if (_pd.getWriteMethod() != null) {
IJavaClassInfo declClass = _pd.getWriteMethod().getEnclosingClass();
_bExternal = declClass instanceof ClassJavaClassInfo && CommonServices.getEntityAccess().isExternal(((ClassJavaClassInfo)declClass).getJavaClass());
} else {
IJavaClassInfo declClass = _publicField.getEnclosingClass();
_bExternal = declClass instanceof ClassJavaClassInfo && CommonServices.getEntityAccess().isExternal(((ClassJavaClassInfo)declClass).getJavaClass());
}
}
else if( isReadable() ) {
if (_pd.getReadMethod() != null) {
IJavaClassInfo declClass = _pd.getReadMethod().getEnclosingClass();
_bExternal = declClass instanceof ClassJavaClassInfo && CommonServices.getEntityAccess().isExternal(((ClassJavaClassInfo)declClass).getJavaClass());
} else {
if (_publicField == null) {
_bExternal = false;
} else {
IJavaClassInfo declClass = _publicField.getEnclosingClass();
_bExternal = declClass instanceof ClassJavaClassInfo && CommonServices.getEntityAccess().isExternal(((ClassJavaClassInfo) declClass).getJavaClass());
}
}
}
_bReadable = isReadable();
}
private void findFieldOn(IJavaClassInfo clazz, boolean setter) {
for (IJavaClassField field : clazz.getFields()) {
if (field.getName().equals(_name) && Modifier.isStatic( field.getModifiers() ) == isStatic() ) {
IType lhs, rhs;
if (setter) {
lhs = getFeatureType();
rhs = TypeSystem.get(field.getType());
} else {
rhs = getFeatureType();
lhs = TypeSystem.get(field.getType());
}
if(CommonServices.getCoercionManager().canCoerce(lhs, rhs)) {
_publicField = field;
break;
}
}
}
}
@Override
public IType getGenericIntrinsicType()
{
return getIntrinsicType( true );
}
@Override
public IType getFeatureType()
{
return getIntrinsicType( false );
}
private IType getIntrinsicType( boolean bKeepTypeVars )
{
if( _propertyType != null && !bKeepTypeVars )
{
return _propertyType;
}
IType propType = null;
IType ownersType = getOwnersType();
if( _getMethod != null )
{
IJavaClassType genericType = _getMethod.getGenericReturnType();
if( genericType instanceof IJavaClassInfo )
{
propType = _pd.getPropertyType();
}
else if(genericType != null)
{
TypeVarToTypeMap actualParamByVarName =
TypeLord.mapTypeByVarName( ownersType, _getMethod.getEnclosingClass().getJavaType(), bKeepTypeVars );
// actualParamByVarName = JavaMethodInfo.addEnclosingTypeParams(ownersType, actualParamByVarName);
propType = genericType.getActualType( actualParamByVarName, bKeepTypeVars );
}
else
{
propType = TypeSystem.getErrorType();
}
// _propertyType = TypeLoaderAccess.instance().getIntrinsicType( _pd.getPropertyType() );
}
else if( _setMethod != null )
{
TypeVarToTypeMap actualParamByVarName =
TypeLord.mapTypeByVarName( ownersType, _setMethod.getEnclosingClass().getJavaType(), bKeepTypeVars );
// actualParamByVarName = JavaMethodInfo.addEnclosingTypeParams(ownersType, actualParamByVarName);
propType = _setMethod.getGenericParameterTypes()[0].getActualType( actualParamByVarName, bKeepTypeVars );
}
else
{
propType = _pd.getPropertyType();
}
if( propType.isGenericType() && !propType.isParameterizedType() )
{
propType = TypeLord.getDefaultParameterizedType( propType );
}
IJavaClassInfo declaringClass = getDeclaringClass();
if (declaringClass != null) {
propType = ClassInfoUtil.getPublishedType(propType, declaringClass);
}
if (!bKeepTypeVars){
//Cache the non-generic value.
_propertyType = propType;
}
return propType;
}
private IJavaClassInfo getDeclaringClass() {
if (_getMethod != null) {
return _getMethod.getEnclosingClass();
} else if (_setMethod != null) {
return _setMethod.getEnclosingClass();
} else {
return null;
}
}
@Override
public boolean isReadable()
{
IJavaClassMethod get = _pd.getReadMethod();
return (get != null && !_pd.isHidden()) || _publicField != null;
}
@Override
public boolean isWritable(IType whosAskin) {
/* causes stack overflow loading the parent type info...
IMethodInfo methodInfo = getWriteMethodInfo();
if (methodInfo != null) {
return methodInfo.isVisible(scriptabilityLevel);
}
*/
IJavaClassMethod set = _pd.getWriteMethod();
if ((set != null && !_pd.isHidden()) || (_publicField != null && !Modifier.isFinal(_publicField.getModifiers()))) {
if (getContainer() instanceof IRelativeTypeInfo) {
IRelativeTypeInfo.Accessibility accessibilityForType = ((IRelativeTypeInfo) getContainer()).getAccessibilityForType(whosAskin);
int mods;
if (set != null) {
mods = set.getModifiers();
} else {
mods = _publicField.getModifiers();
}
boolean isAccessible = false;
boolean isInternal = !Modifier.isPrivate(mods) && !Modifier.isPublic(mods) && !Modifier.isProtected(mods);
switch (accessibilityForType) {
case PUBLIC:
if (Modifier.isPublic(mods)) {
isAccessible = true;
}
break;
case PROTECTED:
if (Modifier.isPublic(mods) || Modifier.isProtected(mods)) {
isAccessible = true;
}
break;
case INTERNAL:
if (Modifier.isPublic(mods) || isInternal || Modifier.isProtected(mods)) {
isAccessible = true;
}
break;
case PRIVATE:
if (Modifier.isPublic(mods) || isInternal || Modifier.isProtected(mods) || Modifier.isPrivate(mods)) {
isAccessible = true;
}
break;
}
return isAccessible;
} else {
return true;
}
}
return false;
}
@Override
public boolean isWritable() {
return isWritable(null);
}
@Override
public List<IAnnotationInfo> getDeclaredAnnotations() {
List<IAnnotationInfo> annotations = super.getDeclaredAnnotations();
if (getMethodDocs() != null && getMethodDocs().get() != null && getMethodDocs().get().isDeprecated()) {
annotations.add(GosuShop.getAnnotationInfoFactory().createJavaAnnotation(makeDeprecated(getMethodDocs().get().getDeprecated()), this));
}
return annotations;
}
@Override
public IDocRef<IMethodNode> getMethodDocs() {
IMethodInfo methodInfo = getReadMethodInfo();
if (methodInfo instanceof IJavaMethodInfo) {
return ((IJavaMethodInfo)methodInfo).getMethodDocs();
}
methodInfo = getWriteMethodInfo();
if (methodInfo instanceof IJavaMethodInfo) {
return ((IJavaMethodInfo)methodInfo).getMethodDocs();
}
return null;
}
@Override
public String getReturnDescription()
{
IDocRef<IMethodNode> methodDocs = getMethodDocs();
return (methodDocs == null || methodDocs.get() == null) ? "" : methodDocs.get().getReturnDescription();
}
@Override
public boolean isStatic()
{
if( _bStatic == null )
{
_bStatic = Boolean.FALSE;
IJavaClassMethod getter = _pd.getReadMethod();
if( getter != null && Modifier.isStatic( getter.getModifiers() ) )
{
_bStatic = Boolean.TRUE;
}
}
return _bStatic;
}
@Override
public boolean isPrivate()
{
IJavaClassMethod getter = _pd.getReadMethod();
return getter == null ? super.isPrivate() : Modifier.isPrivate( getter.getModifiers() );
}
@Override
public boolean isInternal()
{
return !isPrivate() && !isPublic() && !isProtected();
}
@Override
public boolean isProtected()
{
IJavaClassMethod getter = _pd.getReadMethod();
if (getter != null) {
return Modifier.isProtected(getter.getModifiers());
}
IJavaClassMethod setter = _pd.getWriteMethod();
if (setter != null) {
return Modifier.isProtected(setter.getModifiers());
}
return super.isProtected();
}
@Override
public boolean isPublic()
{
IJavaClassMethod getter = _pd.getReadMethod();
if (getter != null) {
return Modifier.isPublic(getter.getModifiers());
}
IJavaClassMethod setter = _pd.getWriteMethod();
if (setter != null) {
return Modifier.isPublic(setter.getModifiers());
}
return super.isPublic();
}
@Override
public boolean isAbstract()
{
IJavaClassMethod getter = _pd.getReadMethod();
return getter == null ? super.isAbstract() : Modifier.isAbstract( getter.getModifiers() );
}
@Override
public boolean isFinal()
{
IJavaClassMethod getter = _pd.getReadMethod();
return getter == null ? super.isFinal() : Modifier.isFinal( getter.getModifiers() );
}
@Override
protected IJavaAnnotatedElement getAnnotatedElement()
{
return _annotatedElement.get();
}
@Override
protected boolean isVisibleViaFeatureDescriptor(IScriptabilityModifier constraint) {
return _pd.isVisibleViaFeatureDescriptor(constraint);
}
@Override
protected boolean isHiddenViaFeatureDescriptor() {
return _pd.isHiddenViaFeatureDescriptor();
}
@Override
protected boolean isDefaultEnumFeature()
{
return false;
}
@Override
public boolean isDeprecated()
{
if (super.isDeprecated()) {
return true;
}
//noinspection SimplifiableIfStatement
if (_pd.getReadMethod() != null) {
return _pd.isDeprecated();
}
return false;
}
@Override
public String getDeprecatedReason() {
String deprecated = super.getDeprecatedReason();
if (isDeprecated() && deprecated == null) {
IAnnotationInfo annotation = _pd.getReadMethod() == null ? null : _pd.getReadMethod().getAnnotation(Deprecated.class);
return annotation == null ? "" : (String) annotation.getFieldValue("value");
}
return deprecated;
}
@Override
public IPropertyAccessor getAccessor()
{
if( _accessor == null )
{
_accessor = new PropertyAccessorAdaptor();
}
return _accessor;
}
@Override
public IPresentationInfo getPresentationInfo()
{
return _presInfo == null ? IPresentationInfo.Default.GET : _presInfo;
}
@Override
public String getName()
{
return _name;
}
@Override
public String getDisplayName()
{
return _pd.getDisplayName();
}
@Override
public String getShortDescription()
{
return _pd.getShortDescription();
}
@Override
public String getDescription() {
String description = null;
IMethodInfo method = getReadMethodInfo();
if (method != null) {
description = method.getDescription();
if ( GosuStringUtil.isEmpty(description)) {
description = method.getReturnDescription();
}
}
if ( GosuStringUtil.isEmpty(description)) {
method = getWriteMethodInfo();
if (method != null) {
description = method.getDescription();
}
}
return description;
}
@Override
public IMethodInfo getReadMethodInfo() {
IJavaClassMethod method = getPropertyDescriptor().getReadMethod();
if (method != null) {
return getOwnersType().getTypeInfo().getMethod(method.getName(), getTypesFromClasses(method.getParameterTypes()));
}
return null;
}
public IJavaClassField getPublicField()
{
return _publicField;
}
@Override
public IMethodInfo getWriteMethodInfo() {
IJavaClassMethod method = getPropertyDescriptor().getWriteMethod();
if (method != null) {
return getOwnersType().getTypeInfo().getMethod(method.getName(), getTypesFromClasses(method.getParameterTypes()));
}
return null;
}
private IType[] getTypesFromClasses(IJavaClassInfo[] types) {
IType retValue[] = new IType[types.length];
for (int i = 0; i < types.length; i++) {
IJavaClassInfo type = types[i];
retValue[i] = type.getJavaType();
}
return retValue;
}
@Override
public String toString() {
return getName();
}
@Override
public IJavaPropertyDescriptor getPropertyDescriptor()
{
return _pd;
}
public class PropertyAccessorAdaptor implements IPropertyAccessor
{
@Override
public Object getValue( Object ctx )
{
if( !_bReadable )
{
throw new EvaluationException( "Property, " + getName() + ", is not readable!" );
}
Object[] args = null;
try
{
Object rVal;
if (_getMethod != null) {
rVal = _getMethod.invoke( ctx, args );
} else {
rVal = ((FieldJavaClassField)_publicField).get(ctx);
rVal = CommonServices.getCoercionManager().convertValue(rVal, getFeatureType());
}
if( _bExternal )
{
return CommonServices.getEntityAccess().convertToInternalIfNecessary( rVal, getOwningClass() );
}
else
{
return rVal;
}
}
catch( InvocationTargetException ite )
{
throw GosuExceptionUtil.forceThrow( ite.getCause() );
}
catch( Throwable t )
{
throw GosuExceptionUtil.forceThrow( t );
}
}
@Override
public void setValue( Object ctx, Object value )
{
if( !isWritable( getOwnersType()) )
{
throw new EvaluationException( "Property, " + getName() + ", is not writable!" );
}
try
{
Object[] args = new Object[]{value};
if (_setMethod != null) {
if( _bExternal )
{
args = CommonServices.getEntityAccess().convertToExternalIfNecessary(
args, ((MethodJavaClassMethod)_setMethod).getJavaParameterTypes(), getOwningClass() );
}
_setMethod.invoke( ctx, args );
} else {
value = CommonServices.getCoercionManager().convertValue(value, TypeSystem.get(_publicField.getType()));
((FieldJavaClassField)_publicField).set(ctx, value);
}
}
catch( InvocationTargetException ite )
{
throw GosuExceptionUtil.forceThrow( ite.getCause() );
}
catch( Throwable t )
{
throw GosuExceptionUtil.forceThrow( t );
}
}
private Class getOwningClass()
{
if( isWritable( getOwnersType()) )
{
return ((ClassJavaClassInfo)_pd.getWriteMethod().getEnclosingClass()).getJavaClass();
}
else
{
return ((ClassJavaClassInfo)_pd.getReadMethod().getEnclosingClass()).getJavaClass();
}
}
public IJavaClassMethod getGetterMethod()
{
return _getMethod;
}
public IJavaClassMethod getSetterMethod()
{
return _setMethod;
}
}
}