package gw.lang.reflect;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import gw.config.CommonServices;
import gw.lang.parser.CaseInsensitiveCharSequence;
import gw.lang.reflect.gs.IGosuClass;
import gw.lang.reflect.gs.IGosuEnhancement;
import gw.lang.reflect.java.IJavaMethodInfo;
import gw.lang.reflect.java.IJavaType;
import gw.lang.reflect.module.IModule;
import gw.util.GosuExceptionUtil;
/**
* Manages features for types that have inheritence.
*
* Types that can inherit other types (either through direct inheritence or through interfaces) need to expose all
* properties and methods for all types in the hierarchy. This class helps manage this feature by providing a
* caching store for all properties.
*
* This class also provides methods to fetch the public, internal, protected, and private methods of a class.
*
* This class will lazily load features when the features are first accessed. It loads all features at once, methods,
* properties, and constructors.
*
* Copyright 2010 Guidewire Software, Inc.
*/
@SuppressWarnings({"unchecked"})
public class FeatureManager<T extends CharSequence> {
private enum InitState {
NotInitialized,
Initializing,
ERROR, Initialized
}
private IRelativeTypeInfo _typeInfo;
private volatile Map<IModule, InitState> _isinitialized = new HashMap<IModule, InitState>();
private volatile InitState _ctorsInitialized = InitState.NotInitialized;
private Map<IModule, PropertyNameMap<T>[]> _properties = new HashMap<IModule, PropertyNameMap<T>[]>();
// new PropertyNameMap[IRelativeTypeInfo.Accessibility_Size];
private Map<IModule, List<IMethodInfo>[]> _methods = new HashMap<IModule, List<IMethodInfo>[]>();
// new List[IRelativeTypeInfo.Accessibility_Size];
private List<IConstructorInfo>[] _constructors = new List[IRelativeTypeInfo.Accessibility_Size];
private final boolean _caseSensitive;
private final boolean _addObjectMethods;
private String _superPropertyPrefix;
private IType _supertypeToCopyPropertiesFrom;
public FeatureManager(IRelativeTypeInfo typeInfo, boolean caseSensitive) {
this(typeInfo, caseSensitive, false);
}
public FeatureManager(IRelativeTypeInfo typeInfo, boolean caseSensitive, boolean addObjectMethods) {
_typeInfo = typeInfo;
_caseSensitive = caseSensitive;
_addObjectMethods = addObjectMethods;
}
public void clear() {
_isinitialized = new HashMap<IModule, InitState>();
_ctorsInitialized = InitState.NotInitialized;
clearMaps();
}
private void clearMaps() {
for(PropertyNameMap<T>[] properties : _properties.values()) {
for (int i = 0; i < properties.length; i++) {
properties[i] = null;
}
}
for (int i = 0; i < _constructors.length; i++) {
_constructors[i] = null;
}
for(List<IMethodInfo>[] methods : _methods.values()) {
for (int i = 0; i < methods.length; i++) {
methods[i] = null;
}
}
}
private void clearMaps(IModule module) {
PropertyNameMap<T>[] properties = _properties.get(module);
if(properties != null) {
for (int i = 0; i < properties.length; i++) {
properties[i] = null;
}
}
List<IMethodInfo>[] methods = _methods.get(module);
if(methods != null) {
for (int i = 0; i < methods.length; i++) {
methods[i] = null;
}
}
}
private void clearCtors() {
for (int i = 0; i < _constructors.length; i++) {
_constructors[i] = null;
}
}
public static IRelativeTypeInfo.Accessibility getAccessibilityForClass( IType ownersClass, IType whosAskin )
{
if( TypeSystem.isIncludeAll() )
{
return IRelativeTypeInfo.Accessibility.PRIVATE;
}
if( ownersClass == null || whosAskin == null )
{
return IRelativeTypeInfo.Accessibility.PUBLIC;
}
if( getTopLevelTypeName( whosAskin ).equalsIgnoreCase( getTopLevelTypeName( ownersClass ) ) )
{
// Implies private members, which means everything.
return IRelativeTypeInfo.Accessibility.PRIVATE;
}
else if( Modifier.isPrivate( ownersClass.getModifiers() ) )
{
return IRelativeTypeInfo.Accessibility.NONE;
}
else if( isInSameNamespace( ownersClass, whosAskin ) )
{
return IRelativeTypeInfo.Accessibility.INTERNAL;
}
else if( Modifier.isInternal( ownersClass.getModifiers() ) )
{
return IRelativeTypeInfo.Accessibility.NONE;
}
else if( isInEnclosingClassHierarchy( ownersClass, whosAskin ) )
{
return IRelativeTypeInfo.Accessibility.PROTECTED;
}
return IRelativeTypeInfo.Accessibility.PUBLIC;
}
public static boolean isInSameNamespace(IType ownersClass, IType whosAskin) {
String whosAskinNamespace = getTopLevelEnclosingClassNamespace(whosAskin);
return (whosAskinNamespace != null && whosAskinNamespace.equalsIgnoreCase(getTopLevelEnclosingClassNamespace(ownersClass)));
}
private static String getTopLevelEnclosingClassNamespace(IType type) {
IType topLevelClass = type;
while (topLevelClass.getEnclosingType() != null) {
topLevelClass = topLevelClass.getEnclosingType();
}
return topLevelClass.getNamespace();
}
public static boolean isInEnclosingClassHierarchy(IType ownersClass, IType whosAskin) {
return whosAskin != null &&
(isInHierarchy(ownersClass, whosAskin) ||
isInEnhancedTypesHierarchy(ownersClass, whosAskin) ||
isInEnclosingClassHierarchy(ownersClass, whosAskin.getEnclosingType()));
}
protected static boolean isInEnhancedTypesHierarchy(IType ownersClass, IType whosAskin) {
return (whosAskin instanceof IGosuEnhancement &&
((IGosuEnhancement)whosAskin).getEnhancedType() != null &&
ownersClass.isAssignableFrom(((IGosuEnhancement) whosAskin).getEnhancedType()));
}
protected static boolean isInHierarchy(IType ownersClass, IType whosAskin) {
return ownersClass.isAssignableFrom(whosAskin) ||
(ownersClass instanceof IGosuClass && ((IGosuClass) ownersClass).isSubClass( whosAskin ));
}
private static String getTopLevelTypeName( IType type )
{
while( type.getEnclosingType() != null )
{
type = TypeSystem.getPureGenericType( type.getEnclosingType() );
}
return TypeSystem.getPureGenericType( type ).getName();
}
@SuppressWarnings({"unchecked"})
public List<IPropertyInfo> getProperties( IRelativeTypeInfo.Accessibility accessibility, IModule module ) {
maybeInit(module);
PropertyNameMap<T>[] arr = _properties.get( module );
if( arr == null )
{
return Collections.emptyList();
}
PropertyNameMap<T> props = arr[accessibility.ordinal()];
return (List<IPropertyInfo>) (props == null ? Collections.emptyList() : props.values());
}
public IPropertyInfo getProperty( IRelativeTypeInfo.Accessibility accessibility, IModule module, CharSequence propName ) {
maybeInit(module);
PropertyNameMap<T>[] arr = _properties.get( module );
if( arr == null )
{
return null;
}
PropertyNameMap<T> accessMap = arr[accessibility.ordinal()];
return accessMap == null ? null : accessMap.get(convertCharSequenceToCorrectSensitivity(propName));
}
private T convertCharSequenceToCorrectSensitivity(CharSequence propName) {
return (T) (_caseSensitive ? propName.toString() : CaseInsensitiveCharSequence.get(propName));
}
@SuppressWarnings({"unchecked"})
public Collection<T> getPropertyNames(IRelativeTypeInfo.Accessibility accessibility, IModule module) {
maybeInit(module);
PropertyNameMap<T>[] lists = _properties.get( module );
if( lists == null )
{
return Collections.emptyList();
}
return lists[accessibility.ordinal()].keySet();
}
@SuppressWarnings({"unchecked"})
public List<? extends IMethodInfo> getMethods( IRelativeTypeInfo.Accessibility accessibility, IModule module ) {
maybeInit(module);
List<IMethodInfo>[] arr = _methods.get( module );
if( arr == null )
{
return Collections.emptyList();
}
List<IMethodInfo> iMethodInfos = arr[accessibility.ordinal()];
return (List<IMethodInfo>) (iMethodInfos == null ? Collections.emptyList() : iMethodInfos);
}
public IMethodInfo getMethod( IRelativeTypeInfo.Accessibility accessibility, IModule module, CharSequence methodName, IType... params ) {
maybeInit(module);
return ITypeInfo.FIND.method( getMethods( accessibility, module ), methodName, params );
}
@SuppressWarnings({"unchecked"})
public List<? extends IConstructorInfo> getConstructors( IRelativeTypeInfo.Accessibility accessibility ) {
maybeInit();
List<IConstructorInfo> list = _constructors[accessibility.ordinal()];
return (List<IConstructorInfo>) (list == null ? Collections.emptyList() : list);
}
public IConstructorInfo getConstructor( IRelativeTypeInfo.Accessibility accessibility, IType[] params ) {
maybeInit();
return ITypeInfo.FIND.constructor( getConstructors(accessibility), params );
}
@SuppressWarnings({"unchecked"})
protected void maybeInit() {
maybeInit(_typeInfo.getOwnersType().getTypeLoader().getModule());
}
@SuppressWarnings({"ConstantConditions"})
private void maybeInit(IModule module) {
if (_isinitialized.get(module) != InitState.Initialized && _isinitialized.get(module) != InitState.ERROR) {
TypeSystem.lock();
try {
if (_isinitialized.get(module) != InitState.Initialized) {
if (_isinitialized.get(module) == InitState.Initializing) {
throw new IllegalStateException("Properties for " + _typeInfo.getOwnersType() + " are cyclic.");
}
_isinitialized.put(module, InitState.Initializing);
clearMaps(module);
try {
PropertyNameMap<T>[] properties = new PropertyNameMap[IRelativeTypeInfo.Accessibility_Size];
{
PropertyNameMap privateProps = new PropertyNameMap();
for (IType type : _typeInfo.getOwnersType().getInterfaces()) {
mergeProperties(privateProps, convertType(type), false);
}
IType supertype = _typeInfo.getOwnersType().getSupertype();
if ( supertype != null ) {
mergeProperties( privateProps, convertType( supertype ), true );
}
List<IPropertyInfo> declaredProperties = (List<IPropertyInfo>) _typeInfo.getDeclaredProperties();
for (IPropertyInfo property : declaredProperties) {
mergeProperty(privateProps, property, true);
}
addEnhancementProperties(privateProps, _caseSensitive);
privateProps.freeze();
// The size checking madness is to save memory. If the lists/maps are the same then reuse.
properties[IRelativeTypeInfo.Accessibility.PRIVATE.ordinal()] = privateProps;
properties[IRelativeTypeInfo.Accessibility.PROTECTED.ordinal()] = convertToMap(filterFeatures(privateProps.values(), IRelativeTypeInfo.Accessibility.PROTECTED));
if (properties[IRelativeTypeInfo.Accessibility.PROTECTED.ordinal()].size() == privateProps.size()) {
properties[IRelativeTypeInfo.Accessibility.PROTECTED.ordinal()] = privateProps;
}
properties[IRelativeTypeInfo.Accessibility.INTERNAL.ordinal()] = convertToMap(filterFeatures(privateProps.values(), IRelativeTypeInfo.Accessibility.INTERNAL));
if (properties[IRelativeTypeInfo.Accessibility.INTERNAL.ordinal()].size() == properties[IRelativeTypeInfo.Accessibility.PROTECTED.ordinal()].size()) {
properties[IRelativeTypeInfo.Accessibility.INTERNAL.ordinal()] = properties[IRelativeTypeInfo.Accessibility.PROTECTED.ordinal()];
}
properties[IRelativeTypeInfo.Accessibility.PUBLIC.ordinal()] = convertToMap(filterFeatures(privateProps.values(), IRelativeTypeInfo.Accessibility.PUBLIC));
if (properties[IRelativeTypeInfo.Accessibility.PUBLIC.ordinal()].size() == properties[IRelativeTypeInfo.Accessibility.INTERNAL.ordinal()].size()) {
properties[IRelativeTypeInfo.Accessibility.PUBLIC.ordinal()] = properties[IRelativeTypeInfo.Accessibility.INTERNAL.ordinal()];
}
properties[IRelativeTypeInfo.Accessibility.NONE.ordinal()] = new PropertyNameMap();
}
_properties.put(module, properties);
List<IMethodInfo>[] methods = new List[IRelativeTypeInfo.Accessibility_Size];
{
List<IMethodInfo> privateMethods = new ArrayList<IMethodInfo>();
if( _addObjectMethods ) {
mergeMethods( privateMethods, convertType( IJavaType.OBJECT ), false );
}
for (IType type : _typeInfo.getOwnersType().getInterfaces()) {
mergeMethods(privateMethods, convertType(type), false);
}
if ( _typeInfo.getOwnersType().getSupertype() != null) {
mergeMethods(privateMethods, convertType( _typeInfo.getOwnersType().getSupertype()), true);
}
List<? extends IMethodInfo> declaredMethods = _typeInfo.getDeclaredMethods();
for (IMethodInfo methodInfo : declaredMethods) {
mergeMethod(privateMethods, methodInfo, true);
}
addEnhancementMethods(privateMethods);
((ArrayList) privateMethods).trimToSize();
privateMethods = Collections.unmodifiableList(privateMethods);
// The size checking madness is to save memory. If the lists/maps are the same then reuse.
methods[IRelativeTypeInfo.Accessibility.PRIVATE.ordinal()] = Collections.unmodifiableList(privateMethods);
methods[IRelativeTypeInfo.Accessibility.PROTECTED.ordinal()] = Collections.unmodifiableList((List<IMethodInfo>) filterFeatures(privateMethods, IRelativeTypeInfo.Accessibility.PROTECTED));
if (methods[IRelativeTypeInfo.Accessibility.PROTECTED.ordinal()].size() == privateMethods.size()) {
methods[IRelativeTypeInfo.Accessibility.PROTECTED.ordinal()] = privateMethods;
}
methods[IRelativeTypeInfo.Accessibility.INTERNAL.ordinal()] = Collections.unmodifiableList((List<IMethodInfo>) filterFeatures(privateMethods, IRelativeTypeInfo.Accessibility.INTERNAL));
if (methods[IRelativeTypeInfo.Accessibility.INTERNAL.ordinal()].size() == methods[IRelativeTypeInfo.Accessibility.PROTECTED.ordinal()].size()) {
methods[IRelativeTypeInfo.Accessibility.INTERNAL.ordinal()] = methods[IRelativeTypeInfo.Accessibility.PROTECTED.ordinal()];
}
methods[IRelativeTypeInfo.Accessibility.PUBLIC.ordinal()] = Collections.unmodifiableList((List<IMethodInfo>) filterFeatures(privateMethods, IRelativeTypeInfo.Accessibility.PUBLIC));
if (methods[IRelativeTypeInfo.Accessibility.PUBLIC.ordinal()].size() == methods[IRelativeTypeInfo.Accessibility.INTERNAL.ordinal()].size()) {
methods[IRelativeTypeInfo.Accessibility.PUBLIC.ordinal()] = methods[IRelativeTypeInfo.Accessibility.INTERNAL.ordinal()];
}
methods[IRelativeTypeInfo.Accessibility.NONE.ordinal()] = Collections.emptyList();
}
_methods.put(module, methods);
if(_ctorsInitialized != InitState.Initialized && _ctorsInitialized != InitState.ERROR) {
clearCtors();
try {
List<IConstructorInfo>[] constructors = new List[IRelativeTypeInfo.Accessibility_Size];
{
List<IConstructorInfo> privateMethods = new ArrayList<IConstructorInfo>( _typeInfo.getDeclaredConstructors());
((ArrayList) privateMethods).trimToSize();
privateMethods = Collections.unmodifiableList(privateMethods);
constructors[IRelativeTypeInfo.Accessibility.PRIVATE.ordinal()] = Collections.unmodifiableList(privateMethods);
constructors[IRelativeTypeInfo.Accessibility.PROTECTED.ordinal()] = Collections.unmodifiableList((List<IConstructorInfo>) filterFeatures(privateMethods, IRelativeTypeInfo.Accessibility.PROTECTED));
if (constructors[IRelativeTypeInfo.Accessibility.PROTECTED.ordinal()].size() == privateMethods.size()) {
constructors[IRelativeTypeInfo.Accessibility.PROTECTED.ordinal()] = privateMethods;
}
constructors[IRelativeTypeInfo.Accessibility.INTERNAL.ordinal()] = Collections.unmodifiableList((List<IConstructorInfo>) filterFeatures(privateMethods, IRelativeTypeInfo.Accessibility.INTERNAL));
if (constructors[IRelativeTypeInfo.Accessibility.INTERNAL.ordinal()].size() == constructors[IRelativeTypeInfo.Accessibility.PROTECTED.ordinal()].size()) {
constructors[IRelativeTypeInfo.Accessibility.INTERNAL.ordinal()] = constructors[IRelativeTypeInfo.Accessibility.PROTECTED.ordinal()];
}
constructors[IRelativeTypeInfo.Accessibility.PUBLIC.ordinal()] = Collections.unmodifiableList((List<IConstructorInfo>) filterFeatures(privateMethods, IRelativeTypeInfo.Accessibility.PUBLIC));
if (constructors[IRelativeTypeInfo.Accessibility.PUBLIC.ordinal()].size() == constructors[IRelativeTypeInfo.Accessibility.INTERNAL.ordinal()].size()) {
constructors[IRelativeTypeInfo.Accessibility.PUBLIC.ordinal()] = constructors[IRelativeTypeInfo.Accessibility.INTERNAL.ordinal()];
}
constructors[IRelativeTypeInfo.Accessibility.NONE.ordinal()] = Collections.emptyList();
}
_constructors = constructors;
_ctorsInitialized = InitState.Initialized;
} finally {
if(_ctorsInitialized != InitState.Initialized) {
_ctorsInitialized = InitState.ERROR;
}
}
}
_isinitialized.put(module, InitState.Initialized);
} finally {
if (_isinitialized.get(module) != InitState.Initialized) {
_isinitialized.put(module, InitState.ERROR);
}
}
}
} catch ( Exception ex ) {
ex.printStackTrace(); // exception is swallowed by source diff handler? print it again here
throw GosuExceptionUtil.forceThrow( ex, _typeInfo.getOwnersType().getName() );
} finally {
TypeSystem.unlock();
}
}
}
protected IType convertType(IType type) {
return type;
}
protected void addEnhancementMethods(List<IMethodInfo> privateMethods) {
CommonServices.getEntityAccess().addEnhancementMethods( _typeInfo.getOwnersType(), privateMethods );
}
protected void addEnhancementProperties(PropertyNameMap<T> privateProps, boolean caseSensitive) {
CommonServices.getEntityAccess().addEnhancementProperties( _typeInfo.getOwnersType(), privateProps, caseSensitive );
}
public void setSuperPropertyPrefix( String superPropertyPrefix ) {
_superPropertyPrefix = superPropertyPrefix;
}
public void setSupertypeToCopyPropertiesFrom( IType supertypeToCopyPropertiesFrom ) {
_supertypeToCopyPropertiesFrom = supertypeToCopyPropertiesFrom;
}
private PropertyNameMap<T> convertToMap(List<IPropertyInfo> features) {
PropertyNameMap<T> ret = new PropertyNameMap();
for (IPropertyInfo feature : features) {
ret.put(convertCharSequenceToCorrectSensitivity(feature.getName()), feature);
}
ret.freeze();
return ret;
}
private List filterFeatures(List props, IRelativeTypeInfo.Accessibility accessibility) {
ArrayList<IFeatureInfo> ret = new ArrayList<IFeatureInfo>();
for (Object o : props) {
IAttributedFeatureInfo property = (IAttributedFeatureInfo) o;
if (isFeatureAccessible(property, accessibility)) {
ret.add(property);
}
}
ret.trimToSize();
return ret;
}
public static boolean isFeatureAccessible(IAttributedFeatureInfo property, IRelativeTypeInfo.Accessibility accessibility) {
boolean isAccessible = false;
switch (accessibility) {
case NONE:
break;
case PUBLIC:
if (property.isPublic()) {
isAccessible = true;
}
break;
case PROTECTED:
if (property.isPublic() || property.isProtected()) {
isAccessible = true;
}
break;
case INTERNAL:
if (property.isPublic() || property.isInternal() || property.isProtected()) {
isAccessible = true;
}
break;
case PRIVATE:
if (property.isPublic() || property.isInternal() || property.isProtected() || property.isPrivate()) {
isAccessible = true;
}
break;
}
return isAccessible;
}
protected void mergeProperties(PropertyNameMap<T> props, IType type, boolean replace) {
if( type != null )
{
List<? extends IPropertyInfo> propertyInfos;
if (type.getTypeInfo() instanceof IRelativeTypeInfo) {
propertyInfos = ((IRelativeTypeInfo) type.getTypeInfo()).getProperties( _typeInfo.getOwnersType());
} else {
propertyInfos = type.getTypeInfo().getProperties();
}
for (IPropertyInfo propertyInfo : propertyInfos) {
IType ownersType = propertyInfo.getOwnersType();
if ( _supertypeToCopyPropertiesFrom == null || ownersType.isAssignableFrom( _supertypeToCopyPropertiesFrom ) || ownersType instanceof IGosuEnhancement ) {
mergeProperty( props, propertyInfo, replace );
}
}
}
}
protected void mergeProperty(PropertyNameMap<T> props, IPropertyInfo propertyInfo, boolean replace) {
boolean prependPrefix = _superPropertyPrefix != null && ! propertyInfo.getOwnersType().equals( _typeInfo.getOwnersType() );
T cs = convertCharSequenceToCorrectSensitivity( prependPrefix ? ( _superPropertyPrefix + propertyInfo.getName() ) : propertyInfo.getName() );
if (replace || !props.containsKey(cs)) {
if ( prependPrefix ) {
props.put( cs, new PropertyInfoBuilder().like( propertyInfo ).withName( cs.toString() ).build( propertyInfo.getContainer() ) );
}
else {
props.put(cs, propertyInfo);
}
}
}
protected void mergeMethods(List<IMethodInfo> methods, IType type, boolean replace) {
List<? extends IMethodInfo> methodInfos;
if (type != null) {
if (type.getTypeInfo() instanceof IRelativeTypeInfo) {
methodInfos = ((IRelativeTypeInfo) type.getTypeInfo()).getMethods( _typeInfo.getOwnersType());
} else {
methodInfos = type.getTypeInfo().getMethods();
}
for (IMethodInfo methodInfo : methodInfos) {
mergeMethod(methods, methodInfo, replace);
}
}
}
protected void mergeMethod(List<IMethodInfo> methods, IMethodInfo thisMethodInfo, boolean replace) {
IParameterInfo[] thisMethodParameters;
if (thisMethodInfo instanceof IJavaMethodInfo) {
thisMethodParameters = ((IJavaMethodInfo)thisMethodInfo).getGenericParameters();
} else {
thisMethodParameters = thisMethodInfo.getParameters();
}
boolean add = true;
int replacementIndex = -1;
for (int i = 0; i < methods.size(); i++) {
IMethodInfo superMethodInfo = methods.get(i);
replacementIndex++;
if (superMethodInfo.getDisplayName().equalsIgnoreCase(thisMethodInfo.getDisplayName())) {
IParameterInfo[] superMethodParameters;
if (superMethodInfo instanceof IJavaMethodInfo ) {
superMethodParameters = ((IJavaMethodInfo) superMethodInfo).getGenericParameters();
} else {
superMethodParameters = superMethodInfo.getParameters();
}
if (argsEqual(superMethodParameters, thisMethodParameters)) {
if (replace) {
methods.set(replacementIndex, thisMethodInfo);
}
add = false;
break;
}
}
}
if (add) {
methods.add(thisMethodInfo);
}
}
protected boolean areMethodParamsEqual(IParameterInfo thisMethodParam, IParameterInfo superMethodParam) {
IType thisMethodParamType = thisMethodParam.getFeatureType();
IType superMethodParamType = superMethodParam.getFeatureType();
if(thisMethodParamType instanceof ITypeVariableType && superMethodParamType instanceof ITypeVariableType) {
return ((ITypeVariableType)thisMethodParamType).getBoundingType().equals(((ITypeVariableType)superMethodParamType).getBoundingType());
}
return thisMethodParamType.equals(superMethodParamType);
}
private boolean argsEqual(IParameterInfo[] parameters, IParameterInfo[] parameters1) {
if (parameters.length == parameters1.length) {
for (int i = 0; i < parameters.length; i++) {
IParameterInfo parameter = parameters[i];
if ( !areMethodParamsEqual(parameter, parameters1[i]) ) {
return false;
}
}
return true;
}
return false;
}
}