/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.parser;
import gw.lang.reflect.BaseFeatureInfo;
import gw.lang.reflect.FeatureManager;
import gw.lang.reflect.IAnnotationInfo;
import gw.lang.reflect.IConstructorInfo;
import gw.lang.reflect.IEventInfo;
import gw.lang.reflect.IFeatureInfo;
import gw.lang.reflect.IMetaType;
import gw.lang.reflect.IMethodInfo;
import gw.lang.reflect.IParameterInfo;
import gw.lang.reflect.IPropertyAccessor;
import gw.lang.reflect.IPropertyInfo;
import gw.lang.reflect.IRelativeTypeInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.ITypeDeprecated;
import gw.lang.reflect.ITypeInfo;
import gw.lang.reflect.ITypeInfoMethodInfo;
import gw.lang.reflect.ITypeInfoPropertyInfo;
import gw.lang.reflect.MetaMethodInfoDelegate;
import gw.lang.reflect.MetaPropertyInfoDelegate;
import gw.lang.reflect.MethodInfoDelegate;
import gw.lang.reflect.MethodList;
import gw.lang.reflect.PropertyInfoBase;
import gw.lang.reflect.PropertyInfoDelegate;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.java.JavaTypes;
import gw.lang.reflect.module.IModule;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
/**
*/
public class MetaTypeTypeInfo extends BaseFeatureInfo implements IRelativeTypeInfo
{
private FeatureManager _fm;
private Map<IModule, List<IMethodInfo>> _declaredMethods;
private Map<IModule, List<IPropertyInfo>> _declaredProperties;
/**
*/
public MetaTypeTypeInfo( MetaType intrType )
{
super( intrType );
_declaredMethods = new ConcurrentHashMap<IModule, List<IMethodInfo>>();
_declaredProperties = new ConcurrentHashMap<IModule, List<IPropertyInfo>>();
_fm = new FeatureManager( this, true );
}
/**
*/
public String getName()
{
return getOwnersType().getType().getTypeInfo().getName() + " Type";
}
public IMetaType getOwnersType()
{
return (IMetaType)super.getOwnersType();
}
/**
*/
public boolean isStatic()
{
return true;
}
public List<? extends IPropertyInfo> getProperties()
{
return getProperties( null );
}
@Override
public IPropertyInfo getProperty( CharSequence propName )
{
return getProperty( null, propName );
}
@Override
public IPropertyInfo getProperty( IType whosAskin, CharSequence propName )
{
return _fm.getProperty( getAccessibilityForType(whosAskin), propName );
}
/**
*/
public MethodList getMethods()
{
return getMethods( (IType)null );
}
public IMethodInfo getMethod( CharSequence methodName, IType... params )
{
return FIND.method( getMethods(), methodName, params );
}
/**
*/
public List<IConstructorInfo> getConstructors()
{
return Collections.emptyList();
}
public IConstructorInfo getConstructor( IType... params )
{
return null;
}
public IMethodInfo getCallableMethod( CharSequence strMethod, IType... params )
{
return FIND.callableMethod( getMethods(), strMethod, params );
}
public IConstructorInfo getCallableConstructor( IType... params )
{
return FIND.callableConstructor( getConstructors(), params );
}
/**
*/
public List<IEventInfo> getEvents()
{
return Collections.emptyList();
}
/**
*/
public IEventInfo getEvent( CharSequence strEvent )
{
return null;
}
private void addForNameMethod( MethodList methods )
{
if( FIND.method( methods, "forName", JavaTypes.STRING() ) == null )
{
IType iIntrinsicType = TypeSystem.getByFullName( LocalClassForNameHack.class.getName(), TypeSystem.getGlobalModule() );
StaticMethodInfoDelegate forName =
new StaticMethodInfoDelegate( this, iIntrinsicType.getTypeInfo().getMethod( "forName", JavaTypes.STRING() ) )
{
public boolean isDeprecated()
{
IMetaType ownerType = (IMetaType)getOwnersType();
return super.isDeprecated() ||
(ownerType.isLiteral() &&
!(ownerType.getType() instanceof IMetaType));
}
@Override
public String getDeprecatedReason()
{
return "Access this method from the Type property e.g., SomeType.Type." + getName();
}
};
methods.add( forName );
}
}
private Map<CharSequence, IPropertyInfo> mergeProperties( ITypeInfo typeTypeInfo )
{
Map<CharSequence, IPropertyInfo> propertiesByName = new HashMap<CharSequence, IPropertyInfo>();
if( !getOwnersType().isLiteral() ||
getOwnersType().getType() instanceof IMetaType )
{
loadMetaTypeProperties( propertiesByName );
}
else
{
// Include typeinfo from IType for backward compatibility
IType intrinsicTypeClass = JavaTypes.getGosuType( ITypeDeprecated.class );
ITypeInfo intrinsicTypeClassInfo = intrinsicTypeClass.getTypeInfo();
List<? extends IPropertyInfo> properties = intrinsicTypeClassInfo.getProperties();
if( properties != null )
{
for( IPropertyInfo property : properties )
{
IPropertyInfo pi = new DeprecatedStaticPropertyInfoDelegate( this, property );
propertiesByName.put( pi.getName(), pi );
}
}
if( typeTypeInfo instanceof IRelativeTypeInfo )
{
properties = ((IRelativeTypeInfo)typeTypeInfo).getProperties( typeTypeInfo.getOwnersType() );
}
else
{
properties = typeTypeInfo.getProperties();
}
if( properties != null )
{
for( IPropertyInfo property : properties )
{
if( property.isStatic() )
{
IPropertyInfo pi = new PropertyInfoDelegate( this, property );
propertiesByName.put( pi.getName(), pi );
}
}
}
addTypeProperty( propertiesByName );
}
return propertiesByName;
}
private void addTypeProperty( Map<CharSequence, IPropertyInfo> propertiesByName )
{
IPropertyInfo pi = new TypeProperty();
// Only create the "Type" property if this meta-type does not have an existing static property named "Type".
IPropertyInfo existing = propertiesByName.get( pi.getName() );
if( existing == null || !existing.getName().equals( pi.getName() ) ) {
propertiesByName.put( pi.getName(), pi );
}
}
private List<? extends IMethodInfo> getMethods(ITypeInfo typeInfo) {
if (typeInfo instanceof IRelativeTypeInfo) {
return ((IRelativeTypeInfo)typeInfo).getMethods(typeInfo.getOwnersType());
} else {
return typeInfo.getMethods();
}
}
private MethodList mergeMethods( ITypeInfo typeTypeInfo )
{
Set<IMethodInfo> tempMethods = new HashSet<IMethodInfo>();
if( !getOwnersType().isLiteral() ||
getOwnersType().getType() instanceof IMetaType )
{
loadMetaTypeMethods( tempMethods );
}
else
{
// Include typeinfo from IType for backward compatibility
IType intrinsicTypeClass = JavaTypes.getGosuType( ITypeDeprecated.class );
ITypeInfo intrinsicTypeClassInfo = intrinsicTypeClass.getTypeInfo();
List<? extends IMethodInfo> methods = getMethods( intrinsicTypeClassInfo );
for( IMethodInfo method : methods )
{
StaticMethodInfoDelegate mi = new DeprecatedStaticMethodInfoDelegate( this, method );
tempMethods.add( mi );
}
methods = getMethods( typeTypeInfo );
for( IMethodInfo method : methods )
{
if( method.isStatic() )
{
IMethodInfo mi = new MethodInfoDelegate( this, method );
tempMethods.add( mi );
}
}
}
MethodList result = new MethodList();
result.addAll(tempMethods);
return result;
}
public List<IAnnotationInfo> getDeclaredAnnotations()
{
return Collections.emptyList();
}
public Accessibility getAccessibilityForType( IType whosAskin )
{
IType ownersClass = getOwnersType().getType();
if( ownersClass == null || whosAskin == null ) {
return Accessibility.PUBLIC;
}
if (canAccessPrivateMembers(ownersClass, whosAskin)) {
return IRelativeTypeInfo.Accessibility.PRIVATE;
}
return FeatureManager.getAccessibilityForClass( ownersClass, whosAskin );
}
/**
* A private feature is accessible from its declaring class and any inner class
* defined in its declaring class.
*/
private boolean canAccessPrivateMembers( IType ownersClass, IType whosAskin )
{
return getOwnersType() == whosAskin ||
getTopLevelTypeName( whosAskin ).equals( getTopLevelTypeName( ownersClass ) );
}
private String getTopLevelTypeName( IType type )
{
while( type.getEnclosingType() != null )
{
type = type.getEnclosingType();
}
return type.getName();
}
public List<? extends IPropertyInfo> getProperties( IType whosaskin )
{
return _fm.getProperties( getAccessibilityForType( whosaskin ) );
}
@Override
public MethodList getMethods(IType whosAskin)
{
return _fm.getMethods( getAccessibilityForType( whosAskin ) );
}
public IMethodInfo getMethod(IType whosaskin, CharSequence methodName, IType... params) {
MethodList methods = getMethods( whosaskin );
return FIND.method( methods, methodName, params );
}
public List<? extends IConstructorInfo> getConstructors(IType whosaskin) {
return Collections.emptyList();
}
public IConstructorInfo getConstructor(IType whosAskin, IType[] params) {
return null;
}
public List<? extends IPropertyInfo> getDeclaredProperties()
{
IModule module = TypeSystem.getCurrentModule();
List<IPropertyInfo> declaredProperties = _declaredProperties.get( module );
if( declaredProperties == null )
{
TypeSystem.lock();
try
{
declaredProperties = _declaredProperties.get( module );
if( declaredProperties == null )
{
ITypeInfo typeTypeInfo = getOwnersType().getType().getTypeInfo();
Map<CharSequence, IPropertyInfo> propertiesByName = mergeProperties( typeTypeInfo );
declaredProperties = new ArrayList<IPropertyInfo>( propertiesByName.values() );
_declaredProperties.put( module, declaredProperties );
}
}
finally
{
TypeSystem.unlock();
}
}
return declaredProperties;
}
public List<? extends IMethodInfo> getDeclaredMethods()
{
IModule module = TypeSystem.getCurrentModule();
List<IMethodInfo> declaredMethods = _declaredMethods.get( module );
if( declaredMethods == null )
{
TypeSystem.lock();
try
{
declaredMethods = _declaredMethods.get( module );
if( declaredMethods == null )
{
ITypeInfo typeTypeInfo = getOwnersType().getType().getTypeInfo();
MethodList methods = mergeMethods( typeTypeInfo );
addForNameMethod( methods );
declaredMethods = methods;
_declaredMethods.put( module, declaredMethods );
}
}
finally
{
TypeSystem.unlock();
}
}
return declaredMethods;
}
public List<? extends IConstructorInfo> getDeclaredConstructors()
{
return Collections.emptyList();
}
private void loadMetaTypeProperties( Map<CharSequence, IPropertyInfo> properties )
{
IType type = getOwnersType().getType();
if( type != null )
{
Set<IType> allTypes = getTypeInterfaces( getTypeOfType(type), makeTreeSet());
for( IType t : allTypes )
{
for( IPropertyInfo pi : t.getTypeInfo().getProperties() )
{
IPropertyInfo existing = properties.get(pi.getName());
if (existing == null || existing.getFeatureType().isAssignableFrom(pi.getFeatureType())) {
properties.put( pi.getName(), new MetaPropertyInfoDelegate( this, pi ) );
}
}
}
}
}
private TreeSet<IType> makeTreeSet() {
return new TreeSet<IType>(new Comparator<IType>() {
@Override
public int compare(IType o1, IType o2) {
return o1.getName().compareTo(o2.getName());
}
});
}
private IType getTypeOfType(IType type) {
return TypeSystem.get( type.getClass(), TypeSystem.getGlobalModule() );
}
private void loadMetaTypeMethods( Set<IMethodInfo> methods )
{
IType type = getOwnersType().getType();
if( type != null )
{
Set<IType> allTypes = getTypeInterfaces( getTypeOfType(type), makeTreeSet() );
for( IType t : allTypes )
{
for( IMethodInfo pi : t.getTypeInfo().getMethods() ) {
if (shouldAddMethod(methods, pi)) {
methods.add( new MetaMethodInfoDelegate( this, pi ) );
}
}
}
}
}
private boolean shouldAddMethod(Set<IMethodInfo> methodInfos, IMethodInfo candidate) {
for (Iterator<IMethodInfo> iterator = methodInfos.iterator(); iterator.hasNext(); ) {
MetaMethodInfoDelegate current = (MetaMethodInfoDelegate) iterator.next();
if (current.getBackingMethodInfo().equals(candidate)) {
return false;
}
if (current.getDisplayName().equals(candidate.getDisplayName()) && areParameterTypesEqual(current, candidate)) {
boolean isCovariantOverride = current.getReturnType().isAssignableFrom(candidate.getReturnType());
if (isCovariantOverride) {
iterator.remove();
}
return isCovariantOverride;
}
}
return true;
}
private boolean areParameterTypesEqual(IMethodInfo methodInfo, IMethodInfo candidate) {
IParameterInfo[] params1 = methodInfo.getParameters();
IParameterInfo[] params2 = candidate.getParameters();
if (params1.length == params2.length) {
for (int i = 0, l = params1.length; i < l; i++) {
IParameterInfo p1 = params1[i];
IParameterInfo p2 = params2[i];
if (!p1.getFeatureType().equals(p2.getFeatureType())) {
return false;
}
}
return true;
}
return false;
}
private Set<IType> getTypeInterfaces( IType type, Set<IType> set )
{
if( getOwnersType().getType() == MetaType.DEFAULT_TYPE.get() )
{
return Collections.singleton( (IType)JavaTypes.ITYPE() );
}
for( IType iface : type.getInterfaces() )
{
if( !iface.getNamespace().startsWith( "gw.internal" ) )
{
set.add( iface );
}
else
{
getTypeInterfaces(iface, set);
}
}
return set;
}
private static class DeprecatedStaticPropertyInfoDelegate extends PropertyInfoDelegate implements ITypeInfoPropertyInfo
{
public DeprecatedStaticPropertyInfoDelegate( IFeatureInfo container, IPropertyInfo source )
{
super( container, source );
}
@Override
public boolean isStatic()
{
return true;
}
@Override
public boolean isDeprecated()
{
return true;
}
@Override
public IPropertyInfo getBackingPropertyInfo()
{
return getDelegatePI();
}
@Override
public String getDeprecatedReason()
{
return "Access this property from the Type property e.g., SomeType.Type." + getName();
}
}
private static class StaticMethodInfoDelegate extends MethodInfoDelegate
{
public StaticMethodInfoDelegate( IFeatureInfo container, IMethodInfo source )
{
super( container, source );
}
@Override
public boolean isStatic()
{
return true;
}
}
private static class DeprecatedStaticMethodInfoDelegate extends StaticMethodInfoDelegate implements ITypeInfoMethodInfo
{
public DeprecatedStaticMethodInfoDelegate( IFeatureInfo container, IMethodInfo source )
{
super( container, source );
}
@Override
public boolean isDeprecated()
{
return true;
}
@Override
public IMethodInfo getBackingMethodInfo()
{
return getSource();
}
@Override
public String getDeprecatedReason()
{
return "Access this method from the Type property e.g., SomeType.Type." + getName();
}
}
public class TypeProperty extends PropertyInfoBase
{
private IPropertyAccessor _accessor;
public TypeProperty()
{
super( MetaTypeTypeInfo.this );
}
public boolean isStatic()
{
return true;
}
public boolean isReadable()
{
return true;
}
public boolean isWritable( IType whosAskin )
{
return false;
}
public IPropertyAccessor getAccessor()
{
return _accessor != null
? _accessor
: (_accessor =
new IPropertyAccessor()
{
public Object getValue( Object ctx )
{
return MetaTypeTypeInfo.this.getOwnersType().getType();
}
public void setValue( Object ctx, Object value )
{
throw new IllegalStateException();
}
});
}
public List<IAnnotationInfo> getDeclaredAnnotations()
{
return Collections.emptyList();
}
public String getName()
{
return "Type";
}
public IType getFeatureType()
{
return MetaType.get( MetaTypeTypeInfo.this.getOwnersType().getType() );
}
}
}