diff --git a/src/main/java/org/hibernate/models/internal/ArrayTypeDetailsImpl.java b/src/main/java/org/hibernate/models/internal/ArrayTypeDetailsImpl.java index d38e3ba..3ba9be1 100644 --- a/src/main/java/org/hibernate/models/internal/ArrayTypeDetailsImpl.java +++ b/src/main/java/org/hibernate/models/internal/ArrayTypeDetailsImpl.java @@ -13,6 +13,7 @@ import org.hibernate.models.spi.ArrayTypeDetails; import org.hibernate.models.spi.ClassDetails; import org.hibernate.models.spi.TypeDetails; +import org.hibernate.models.spi.TypeVariableDetails; /** * @author Steve Ebersole @@ -84,9 +85,9 @@ public boolean isImplementor(Class checkType) { } @Override - public TypeDetails resolveTypeVariable(String identifier) { + public TypeDetails resolveTypeVariable(TypeVariableDetails typeVariable) { if ( constituentType.getTypeKind() == Kind.PARAMETERIZED_TYPE ) { - return constituentType.asParameterizedType().resolveTypeVariable( identifier ); + return constituentType.asParameterizedType().resolveTypeVariable( typeVariable ); } return null; } diff --git a/src/main/java/org/hibernate/models/internal/ClassTypeDetailsImpl.java b/src/main/java/org/hibernate/models/internal/ClassTypeDetailsImpl.java index 3664766..121a771 100644 --- a/src/main/java/org/hibernate/models/internal/ClassTypeDetailsImpl.java +++ b/src/main/java/org/hibernate/models/internal/ClassTypeDetailsImpl.java @@ -12,6 +12,7 @@ import org.hibernate.models.spi.ClassDetails; import org.hibernate.models.spi.ClassTypeDetails; import org.hibernate.models.spi.TypeDetails; +import org.hibernate.models.spi.TypeVariableDetails; /** * @author Steve Ebersole @@ -42,8 +43,8 @@ public Kind getTypeKind() { } @Override - public TypeDetails resolveTypeVariable(String identifier) { - return getClassDetails().resolveTypeVariable( identifier ); + public TypeDetails resolveTypeVariable(TypeVariableDetails typeVariable) { + return getClassDetails().resolveTypeVariable( typeVariable ); } @Override diff --git a/src/main/java/org/hibernate/models/internal/CollectionElementSwitch.java b/src/main/java/org/hibernate/models/internal/CollectionElementSwitch.java index fc7e4e6..42b0622 100644 --- a/src/main/java/org/hibernate/models/internal/CollectionElementSwitch.java +++ b/src/main/java/org/hibernate/models/internal/CollectionElementSwitch.java @@ -86,7 +86,7 @@ public TypeDetails caseWildcardType(WildcardTypeDetails wildcardType) { @Override public TypeDetails caseTypeVariable(TypeVariableDetails typeVariable) { if ( typeVariable.isImplementor( Collection.class ) ) { - return memberTypeDetails.resolveTypeVariable( typeVariable.getIdentifier() ); + return memberTypeDetails.resolveTypeVariable( typeVariable ); } return null; } @@ -94,7 +94,7 @@ public TypeDetails caseTypeVariable(TypeVariableDetails typeVariable) { @Override public TypeDetails caseTypeVariableReference(TypeVariableReferenceDetails typeVariableReference) { if ( typeVariableReference.isImplementor( Collection.class ) ) { - return memberTypeDetails.resolveTypeVariable( typeVariableReference.getIdentifier() ); + return memberTypeDetails.resolveTypeVariable( typeVariableReference.getTarget() ); } return null; } diff --git a/src/main/java/org/hibernate/models/internal/MapKeySwitch.java b/src/main/java/org/hibernate/models/internal/MapKeySwitch.java index 18cdd1d..d00a3bd 100644 --- a/src/main/java/org/hibernate/models/internal/MapKeySwitch.java +++ b/src/main/java/org/hibernate/models/internal/MapKeySwitch.java @@ -86,7 +86,7 @@ public TypeDetails caseWildcardType(WildcardTypeDetails wildcardType) { @Override public TypeDetails caseTypeVariable(TypeVariableDetails typeVariable) { if ( typeVariable.isImplementor( Map.class ) ) { - return memberTypeDetails.resolveTypeVariable( typeVariable.getIdentifier() ); + return memberTypeDetails.resolveTypeVariable( typeVariable ); } return null; } @@ -94,7 +94,7 @@ public TypeDetails caseTypeVariable(TypeVariableDetails typeVariable) { @Override public TypeDetails caseTypeVariableReference(TypeVariableReferenceDetails typeVariableReference) { if ( typeVariableReference.isImplementor( Map.class ) ) { - return memberTypeDetails.resolveTypeVariable( typeVariableReference.getIdentifier() ); + return memberTypeDetails.resolveTypeVariable( typeVariableReference.getTarget() ); } return null; } diff --git a/src/main/java/org/hibernate/models/internal/MapValueSwitch.java b/src/main/java/org/hibernate/models/internal/MapValueSwitch.java index 4681d76..e3c8151 100644 --- a/src/main/java/org/hibernate/models/internal/MapValueSwitch.java +++ b/src/main/java/org/hibernate/models/internal/MapValueSwitch.java @@ -86,7 +86,7 @@ public TypeDetails caseWildcardType(WildcardTypeDetails wildcardType) { @Override public TypeDetails caseTypeVariable(TypeVariableDetails typeVariable) { if ( typeVariable.isImplementor( Map.class ) ) { - return memberTypeDetails.resolveTypeVariable( typeVariable.getIdentifier() ); + return memberTypeDetails.resolveTypeVariable( typeVariable ); } return null; } @@ -94,7 +94,7 @@ public TypeDetails caseTypeVariable(TypeVariableDetails typeVariable) { @Override public TypeDetails caseTypeVariableReference(TypeVariableReferenceDetails typeVariableReference) { if ( typeVariableReference.isImplementor( Map.class ) ) { - return memberTypeDetails.resolveTypeVariable( typeVariableReference.getIdentifier() ); + return memberTypeDetails.resolveTypeVariable( typeVariableReference.getTarget() ); } return null; } diff --git a/src/main/java/org/hibernate/models/internal/ParameterizedTypeDetailsImpl.java b/src/main/java/org/hibernate/models/internal/ParameterizedTypeDetailsImpl.java index c416fa6..b722dc4 100644 --- a/src/main/java/org/hibernate/models/internal/ParameterizedTypeDetailsImpl.java +++ b/src/main/java/org/hibernate/models/internal/ParameterizedTypeDetailsImpl.java @@ -15,6 +15,8 @@ import org.hibernate.models.spi.TypeVariableDetails; import org.hibernate.models.spi.TypeVariableScope; +import static org.hibernate.models.spi.TypeDetailsHelper.resolveTypeVariableFromParameterizedType; + /** * @author Steve Ebersole */ @@ -45,16 +47,7 @@ public TypeVariableScope getOwner() { } @Override - public TypeDetails resolveTypeVariable(String identifier) { - final List typeParameters = genericClassDetails.getTypeParameters(); - assert typeParameters.size() == arguments.size(); - - for ( int i = 0; i < typeParameters.size(); i++ ) { - if ( typeParameters.get( i ).getIdentifier().equals( identifier ) ) { - return arguments.get( i ); - } - } - - return null; + public TypeDetails resolveTypeVariable(TypeVariableDetails typeVariable) { + return resolveTypeVariableFromParameterizedType( this, typeVariable ); } } diff --git a/src/main/java/org/hibernate/models/internal/PrimitiveTypeDetailsImpl.java b/src/main/java/org/hibernate/models/internal/PrimitiveTypeDetailsImpl.java index 3c49961..3ac6091 100644 --- a/src/main/java/org/hibernate/models/internal/PrimitiveTypeDetailsImpl.java +++ b/src/main/java/org/hibernate/models/internal/PrimitiveTypeDetailsImpl.java @@ -10,6 +10,7 @@ import org.hibernate.models.spi.ClassDetails; import org.hibernate.models.spi.PrimitiveTypeDetails; import org.hibernate.models.spi.TypeDetails; +import org.hibernate.models.spi.TypeVariableDetails; /** * @author Steve Ebersole @@ -31,7 +32,7 @@ public PrimitiveTypeDetails asPrimitiveType() { } @Override - public TypeDetails resolveTypeVariable(String identifier) { + public TypeDetails resolveTypeVariable(TypeVariableDetails typeVariable) { return this; } diff --git a/src/main/java/org/hibernate/models/internal/TypeVariableDetailsImpl.java b/src/main/java/org/hibernate/models/internal/TypeVariableDetailsImpl.java index 6c1b231..937f18f 100644 --- a/src/main/java/org/hibernate/models/internal/TypeVariableDetailsImpl.java +++ b/src/main/java/org/hibernate/models/internal/TypeVariableDetailsImpl.java @@ -9,6 +9,7 @@ import java.util.List; +import org.hibernate.models.spi.ClassDetails; import org.hibernate.models.spi.TypeDetails; import org.hibernate.models.spi.TypeVariableDetails; @@ -17,12 +18,14 @@ */ public class TypeVariableDetailsImpl implements TypeVariableDetails { private final String identifier; + private final ClassDetails declaringType; private final List bounds; private final String name; - public TypeVariableDetailsImpl(String identifier, List bounds) { + public TypeVariableDetailsImpl(String identifier, ClassDetails declaringType, List bounds) { this.identifier = identifier; + this.declaringType = declaringType; this.bounds = bounds; this.name = calculateName( bounds ); @@ -39,6 +42,10 @@ private String calculateName(List bounds) { return identifier; } + @Override public ClassDetails getDeclaringType() { + return declaringType; + } + @Override public List getBounds() { return bounds; } @@ -57,8 +64,8 @@ public boolean isImplementor(Class checkType) { } @Override - public TypeDetails resolveTypeVariable(String identifier) { - return this.identifier.equals( identifier ) ? this : null; + public TypeDetails resolveTypeVariable(TypeVariableDetails typeVariable) { + return identifier.equals( typeVariable.getIdentifier() ) ? this : null; } @Override diff --git a/src/main/java/org/hibernate/models/internal/TypeVariableReferenceDetailsImpl.java b/src/main/java/org/hibernate/models/internal/TypeVariableReferenceDetailsImpl.java index 24aa6e2..2883f29 100644 --- a/src/main/java/org/hibernate/models/internal/TypeVariableReferenceDetailsImpl.java +++ b/src/main/java/org/hibernate/models/internal/TypeVariableReferenceDetailsImpl.java @@ -61,7 +61,7 @@ public boolean isImplementor(Class checkType) { } @Override - public TypeDetails resolveTypeVariable(String identifier) { - return this.identifier.equals( identifier ) ? target : null; + public TypeDetails resolveTypeVariable(TypeVariableDetails typeVariable) { + return this.identifier.equals( typeVariable.getIdentifier() ) ? target : null; } } diff --git a/src/main/java/org/hibernate/models/internal/VoidTypeDetailsImpl.java b/src/main/java/org/hibernate/models/internal/VoidTypeDetailsImpl.java index 47bab8d..1674541 100644 --- a/src/main/java/org/hibernate/models/internal/VoidTypeDetailsImpl.java +++ b/src/main/java/org/hibernate/models/internal/VoidTypeDetailsImpl.java @@ -11,6 +11,7 @@ import org.hibernate.models.spi.ClassDetails; import org.hibernate.models.spi.TypeDetails; +import org.hibernate.models.spi.TypeVariableDetails; import org.hibernate.models.spi.VoidTypeDetails; /** @@ -41,7 +42,7 @@ public VoidTypeDetails asVoidType() { } @Override - public TypeDetails resolveTypeVariable(String identifier) { + public TypeDetails resolveTypeVariable(TypeVariableDetails typeVariable) { return this; } diff --git a/src/main/java/org/hibernate/models/internal/WildcardTypeDetailsImpl.java b/src/main/java/org/hibernate/models/internal/WildcardTypeDetailsImpl.java index 2d82e10..15ac475 100644 --- a/src/main/java/org/hibernate/models/internal/WildcardTypeDetailsImpl.java +++ b/src/main/java/org/hibernate/models/internal/WildcardTypeDetailsImpl.java @@ -9,6 +9,7 @@ import org.hibernate.models.spi.ClassTypeDetails; import org.hibernate.models.spi.TypeDetails; +import org.hibernate.models.spi.TypeVariableDetails; import org.hibernate.models.spi.WildcardTypeDetails; /** @@ -74,7 +75,7 @@ public boolean isImplementor(Class checkType) { } @Override - public TypeDetails resolveTypeVariable(String identifier) { + public TypeDetails resolveTypeVariable(TypeVariableDetails typeVariable) { return null; } } diff --git a/src/main/java/org/hibernate/models/internal/jandex/JandexBuilders.java b/src/main/java/org/hibernate/models/internal/jandex/JandexBuilders.java index 079342e..630a61d 100644 --- a/src/main/java/org/hibernate/models/internal/jandex/JandexBuilders.java +++ b/src/main/java/org/hibernate/models/internal/jandex/JandexBuilders.java @@ -19,8 +19,7 @@ import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; -import static org.hibernate.models.internal.jandex.JandexTypeSwitchStandard.TYPE_SWITCH_STANDARD; -import static org.hibernate.models.internal.jandex.JandexTypeSwitcher.switchType; +import static org.hibernate.models.internal.jandex.JandexTypeSwitchStandard.switchType; /** * Jandex based ClassDetailsBuilder @@ -137,7 +136,7 @@ public static JandexMethodDetails buildMethodDetails( return new JandexMethodDetails( method, MethodDetails.MethodKind.GETTER, - switchType( returnType, TYPE_SWITCH_STANDARD, buildingContext ), + switchType( returnType, declaringType, buildingContext ), declaringType, buildingContext ); @@ -148,7 +147,7 @@ else if ( isBoolean( returnType ) && ( methodName.startsWith( "is" ) return new JandexMethodDetails( method, MethodDetails.MethodKind.GETTER, - switchType( returnType, TYPE_SWITCH_STANDARD, buildingContext ), + switchType( returnType, declaringType, buildingContext ), declaringType, buildingContext ); @@ -162,7 +161,7 @@ else if ( isBoolean( returnType ) && ( methodName.startsWith( "is" ) return new JandexMethodDetails( method, MethodDetails.MethodKind.SETTER, - switchType( method.parameterType( 0 ), TYPE_SWITCH_STANDARD, buildingContext ), + switchType( method.parameterType( 0 ), declaringType, buildingContext ), declaringType, buildingContext ); diff --git a/src/main/java/org/hibernate/models/internal/jandex/JandexClassDetails.java b/src/main/java/org/hibernate/models/internal/jandex/JandexClassDetails.java index f50b421..c4d2aa2 100644 --- a/src/main/java/org/hibernate/models/internal/jandex/JandexClassDetails.java +++ b/src/main/java/org/hibernate/models/internal/jandex/JandexClassDetails.java @@ -30,7 +30,7 @@ import static java.util.Collections.emptyList; import static org.hibernate.models.internal.ModelsClassLogging.MODELS_CLASS_LOGGER; -import static org.hibernate.models.internal.jandex.JandexTypeSwitchStandard.TYPE_SWITCH_STANDARD; +import static org.hibernate.models.internal.jandex.JandexTypeSwitchStandard.switchType; import static org.hibernate.models.internal.util.CollectionHelper.arrayList; import static org.hibernate.models.internal.util.CollectionHelper.isEmpty; @@ -41,9 +41,9 @@ public class JandexClassDetails extends AbstractAnnotationTarget implements Clas private final ClassInfo classInfo; private final ClassDetails superClass; - private final TypeDetails genericSuperType; + private TypeDetails genericSuperType; private final List implementedInterfaces; - private final List typeParameters; + private List typeParameters; private List fields; private List methods; @@ -54,9 +54,7 @@ public JandexClassDetails(ClassInfo classInfo, SourceModelBuildingContext buildi this.classInfo = classInfo; this.superClass = determineSuperType( classInfo, buildingContext ); - this.genericSuperType = determineGenericSuperType( classInfo, buildingContext ); this.implementedInterfaces = determineInterfaces( classInfo, buildingContext ); - this.typeParameters = determineTypeParameters( classInfo, buildingContext ); } private static ClassDetails determineSuperType( @@ -76,7 +74,7 @@ private TypeDetails determineGenericSuperType(ClassInfo classInfo, SourceModelBu return null; } - return JandexTypeSwitcher.switchType( classInfo.superClassType(), TYPE_SWITCH_STANDARD, buildingContext ); + return switchType( classInfo.superClassType(), buildingContext ); } private static List determineInterfaces( @@ -89,9 +87,8 @@ private static List determineInterfaces( final List result = arrayList( interfaceTypes.size() ); for ( Type interfaceType : interfaceTypes ) { - final TypeDetails switchedType = JandexTypeSwitcher.switchType( + final TypeDetails switchedType = switchType( interfaceType, - TYPE_SWITCH_STANDARD, buildingContext ); result.add( switchedType ); @@ -107,7 +104,7 @@ private List determineTypeParameters(ClassInfo classInfo, S final ArrayList result = arrayList( jandexTypeVariables.size() ); for ( TypeVariable jandexTypeVariable : jandexTypeVariables ) { - result.add( (TypeVariableDetails) JandexTypeSwitcher.switchType( jandexTypeVariable, TYPE_SWITCH_STANDARD, buildingContext ) ); + result.add( (TypeVariableDetails) switchType( jandexTypeVariable, this, buildingContext ) ); } return result; } @@ -149,6 +146,9 @@ public ClassDetails getSuperClass() { @Override public TypeDetails getGenericSuperType() { + if ( genericSuperType == null && classInfo.superClassType() != null ) { + genericSuperType = determineGenericSuperType( classInfo, getBuildingContext() ); + } return genericSuperType; } @@ -159,6 +159,9 @@ public List getImplementedInterfaces() { @Override public List getTypeParameters() { + if ( typeParameters == null ) { + this.typeParameters = determineTypeParameters( classInfo, getBuildingContext() ); + } return typeParameters; } diff --git a/src/main/java/org/hibernate/models/internal/jandex/JandexFieldDetails.java b/src/main/java/org/hibernate/models/internal/jandex/JandexFieldDetails.java index bc9790c..02b7abc 100644 --- a/src/main/java/org/hibernate/models/internal/jandex/JandexFieldDetails.java +++ b/src/main/java/org/hibernate/models/internal/jandex/JandexFieldDetails.java @@ -11,9 +11,9 @@ import java.util.Collection; import java.util.Map; -import org.hibernate.models.spi.MutableMemberDetails; import org.hibernate.models.spi.ClassDetails; import org.hibernate.models.spi.FieldDetails; +import org.hibernate.models.spi.MutableMemberDetails; import org.hibernate.models.spi.SourceModelBuildingContext; import org.hibernate.models.spi.TypeDetails; @@ -21,8 +21,7 @@ import org.jboss.jandex.FieldInfo; import org.jboss.jandex.Type; -import static org.hibernate.models.internal.jandex.JandexTypeSwitchStandard.TYPE_SWITCH_STANDARD; -import static org.hibernate.models.internal.jandex.JandexTypeSwitcher.switchType; +import static org.hibernate.models.internal.jandex.JandexTypeSwitchStandard.switchType; /** * @author Steve Ebersole @@ -42,7 +41,7 @@ public JandexFieldDetails( super( buildingContext ); this.fieldInfo = fieldInfo; this.declaringType = declaringType; - this.type = switchType( fieldInfo.type(), TYPE_SWITCH_STANDARD, buildingContext ); + this.type = switchType( fieldInfo.type(), declaringType, buildingContext ); this.isArray = fieldInfo.type().kind() == Type.Kind.ARRAY; this.isPlural = isArray || type.isImplementor( Collection.class ) || type.isImplementor( Map.class ); diff --git a/src/main/java/org/hibernate/models/internal/jandex/JandexRecordComponentDetails.java b/src/main/java/org/hibernate/models/internal/jandex/JandexRecordComponentDetails.java index b04a566..609a753 100644 --- a/src/main/java/org/hibernate/models/internal/jandex/JandexRecordComponentDetails.java +++ b/src/main/java/org/hibernate/models/internal/jandex/JandexRecordComponentDetails.java @@ -20,8 +20,7 @@ import org.jboss.jandex.RecordComponentInfo; import org.jboss.jandex.Type; -import static org.hibernate.models.internal.jandex.JandexTypeSwitchStandard.TYPE_SWITCH_STANDARD; -import static org.hibernate.models.internal.jandex.JandexTypeSwitcher.switchType; +import static org.hibernate.models.internal.jandex.JandexTypeSwitchStandard.switchType; /** * @author Steve Ebersole @@ -41,7 +40,7 @@ public JandexRecordComponentDetails( super( buildingContext ); this.recordComponentInfo = recordComponentInfo; this.declaringType = declaringType; - this.type = switchType( recordComponentInfo.type(), TYPE_SWITCH_STANDARD, buildingContext ); + this.type = switchType( recordComponentInfo.type(), declaringType, buildingContext ); this.isArray = recordComponentInfo.type().kind() == Type.Kind.ARRAY; this.isPlural = isArray || type.isImplementor( Collection.class ) || type.isImplementor( Map.class ); diff --git a/src/main/java/org/hibernate/models/internal/jandex/JandexTypeSwitchStandard.java b/src/main/java/org/hibernate/models/internal/jandex/JandexTypeSwitchStandard.java index 338faf0..c197655 100644 --- a/src/main/java/org/hibernate/models/internal/jandex/JandexTypeSwitchStandard.java +++ b/src/main/java/org/hibernate/models/internal/jandex/JandexTypeSwitchStandard.java @@ -36,7 +36,6 @@ import org.jboss.jandex.VoidType; import org.jboss.jandex.WildcardType; -import static org.hibernate.models.internal.jandex.JandexTypeSwitcher.switchType; import static org.hibernate.models.internal.util.CollectionHelper.arrayList; /** @@ -45,7 +44,21 @@ * @author Steve Ebersole */ public class JandexTypeSwitchStandard implements JandexTypeSwitch { - public static final JandexTypeSwitchStandard TYPE_SWITCH_STANDARD = new JandexTypeSwitchStandard(); + public static TypeDetails switchType(Type type, SourceModelBuildingContext buildingContext) { + assert type.kind() != Type.Kind.TYPE_VARIABLE; + return switchType( type, null, buildingContext ); + } + + public static TypeDetails switchType(Type type, ClassDetails declaringType, SourceModelBuildingContext buildingContext) { + final JandexTypeSwitchStandard genericVariableSwitch = new JandexTypeSwitchStandard( declaringType ); + return JandexTypeSwitcher.switchType( type, genericVariableSwitch, buildingContext ); + } + + private final ClassDetails declaringType; + + public JandexTypeSwitchStandard(ClassDetails declaringType) { + this.declaringType = declaringType; + } @Override public TypeDetails caseClass(ClassType classType, SourceModelBuildingContext buildingContext) { @@ -81,7 +94,7 @@ public TypeDetails caseParameterizedType( .resolveClassDetails( parameterizedType.name().toString() ); return new ParameterizedTypeDetailsImpl( classDetails, - extractTypeParameters( parameterizedType, this, buildingContext ), + resolveTypes( parameterizedType.arguments(), this, buildingContext ), null ); } @@ -91,7 +104,7 @@ public TypeDetails caseWildcardType(WildcardType wildcardType, SourceModelBuildi try { final Type bound = (Type) BOUND_METHOD.invoke( wildcardType ); final boolean isExtends = (boolean) IS_EXTENDS_METHOD.invoke( wildcardType ); - return new WildcardTypeDetailsImpl( switchType( bound, TYPE_SWITCH_STANDARD, buildingContext ), isExtends ); + return new WildcardTypeDetailsImpl( JandexTypeSwitcher.switchType( bound, this, buildingContext ), isExtends ); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException( e ); @@ -102,6 +115,7 @@ public TypeDetails caseWildcardType(WildcardType wildcardType, SourceModelBuildi public TypeDetails caseTypeVariable(TypeVariable typeVariable, SourceModelBuildingContext buildingContext) { return new TypeVariableDetailsImpl( typeVariable.identifier(), + declaringType, resolveTypes( typeVariable.bounds(), this, buildingContext ) ); } @@ -115,7 +129,7 @@ public TypeDetails caseTypeVariableReference( @Override public TypeDetails caseArrayType(ArrayType arrayType, SourceModelBuildingContext buildingContext) { - final TypeDetails componentTypeDetails = switchType( arrayType.componentType(), this, buildingContext ); + final TypeDetails componentTypeDetails = JandexTypeSwitcher.switchType( arrayType.componentType(), this, buildingContext ); return TypeDetailsHelper.arrayOf( componentTypeDetails, buildingContext ); } @@ -124,23 +138,6 @@ public TypeDetails defaultCase(Type type, SourceModelBuildingContext buildingCon throw new UnsupportedOperationException( "Unexpected Type kind - " + type ); } - private static List extractTypeParameters( - ParameterizedType parameterizedType, - JandexTypeSwitch typeSwitch, - SourceModelBuildingContext buildingContext) { - final List typeArguments = parameterizedType.arguments(); - if ( CollectionHelper.isEmpty( typeArguments ) ) { - return Collections.emptyList(); - } - - final ArrayList result = arrayList( typeArguments.size() ); - for ( Type typeArgument : typeArguments ) { - final TypeDetails switchedType = switchType( typeArgument, typeSwitch, buildingContext ); - result.add( switchedType ); - } - return result; - } - public static List resolveTypes( List types, JandexTypeSwitch typeSwitch, @@ -151,7 +148,7 @@ public static List resolveTypes( final ArrayList result = arrayList( types.size() ); for ( Type actualTypeArgument : types ) { - final TypeDetails switchedType = switchType( actualTypeArgument, typeSwitch, buildingContext ); + final TypeDetails switchedType = JandexTypeSwitcher.switchType( actualTypeArgument, typeSwitch, buildingContext ); result.add( switchedType ); } return result; diff --git a/src/main/java/org/hibernate/models/internal/jdk/JdkTrackingTypeSwitch.java b/src/main/java/org/hibernate/models/internal/jdk/JdkTrackingTypeSwitch.java index 2abe4a4..9075c72 100644 --- a/src/main/java/org/hibernate/models/internal/jdk/JdkTrackingTypeSwitch.java +++ b/src/main/java/org/hibernate/models/internal/jdk/JdkTrackingTypeSwitch.java @@ -8,6 +8,8 @@ package org.hibernate.models.internal.jdk; import java.lang.reflect.GenericArrayType; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; @@ -96,8 +98,19 @@ public WildcardTypeDetails caseWildcardType(WildcardType wildcardType) { } public TypeVariableDetails caseTypeVariable(TypeVariable typeVariable) { + final GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration(); + final ClassDetails declaringClass; + if ( genericDeclaration instanceof Class genericClass ) { + declaringClass = buildingContext.getClassDetailsRegistry().getClassDetails( genericClass.getName() ); + } + else { + declaringClass = buildingContext.getClassDetailsRegistry() + .getClassDetails( ( (Method) genericDeclaration ).getDeclaringClass().getName() ); + } + return new TypeVariableDetailsImpl( typeVariable.getTypeName(), + declaringClass, resolveTypes( typeVariable.getBounds() ) ); } diff --git a/src/main/java/org/hibernate/models/spi/ClassDetails.java b/src/main/java/org/hibernate/models/spi/ClassDetails.java index 392e3a5..eece588 100644 --- a/src/main/java/org/hibernate/models/spi/ClassDetails.java +++ b/src/main/java/org/hibernate/models/spi/ClassDetails.java @@ -12,13 +12,14 @@ import org.hibernate.models.internal.SimpleClassDetails; import org.hibernate.models.internal.util.IndexedConsumer; +import static org.hibernate.models.spi.TypeDetailsHelper.resolveTypeVariableFromParameterizedType; + /** * Abstraction for what Hibernate understands about a "class", generally before it has access to * the actual {@link Class} reference, if there is a {@code Class} at all (dynamic models). * - * @see ClassDetailsRegistry - * * @author Steve Ebersole + * @see ClassDetailsRegistry */ public interface ClassDetails extends AnnotationTarget, TypeVariableScope { /** @@ -122,57 +123,49 @@ default void forSelfAndEachSuper(ClassDetailsConsumer consumer) { forEachSuper( consumer ); } + /** + * Returns {@code true} is the provided classDetails is a + * superclass of this class, {@code false} otherwise + */ + default boolean isSuperclass(ClassDetails classDetails) { + ClassDetails check = getSuperClass(); + while ( check != null && check != OBJECT_CLASS_DETAILS ) { + if ( classDetails == check ) { + return true; + } + check = check.getSuperClass(); + } + return false; + } + @Override - default TypeDetails resolveTypeVariable(String identifier) { - final TypeVariableDetails local = TypeDetailsHelper.findTypeVariableDetails( - identifier, - getTypeParameters() - ); - if ( local != null && local.isResolved() ) { - return local; + default TypeDetails resolveTypeVariable(TypeVariableDetails typeVariable) { + if ( this == typeVariable.getDeclaringType() ) { + return TypeDetailsHelper.findTypeVariableDetails( + typeVariable.getIdentifier(), + getTypeParameters() + ); } - if ( getGenericSuperType() != null ) { - if ( getGenericSuperType().getTypeKind() == TypeDetails.Kind.CLASS ) { - final TypeVariableDetails genericSuperVar = TypeDetailsHelper.findTypeVariableDetails( - identifier, - getGenericSuperType().asClassType().getClassDetails().getTypeParameters() + if ( isSuperclass( typeVariable.getDeclaringType() ) ) { + final TypeDetails genericSuperType = getGenericSuperType(); + if ( genericSuperType != null && genericSuperType.getTypeKind() == TypeDetails.Kind.PARAMETERIZED_TYPE ) { + final TypeDetails resolvedType = resolveTypeVariableFromParameterizedType( + genericSuperType.asParameterizedType(), + typeVariable ); - if ( genericSuperVar != null ) { - return genericSuperVar; + if ( resolvedType != null ) { + return resolvedType; } } - else { - // assume parameterized - final List typeParameters = getSuperClass().getTypeParameters(); - final List typeArguments = getGenericSuperType().asParameterizedType().getArguments(); - assert typeParameters.size() == typeArguments.size(); - - for ( int i = 0; i < typeParameters.size(); i++ ) { - final TypeVariableDetails typeVariableDetails = typeParameters.get( i ); - if ( typeVariableDetails.getIdentifier().equals( identifier ) ) { - // we found the parameter, use the matching argument - return typeArguments.get( i ); - } - } - final TypeVariableDetails genericSuperVar = TypeDetailsHelper.findTypeVariableDetails2( - identifier, - typeArguments - ); - if ( genericSuperVar != null ) { - return genericSuperVar; - } - } - } - if ( getSuperClass() != null ) { - final TypeDetails typeDetails = getSuperClass().resolveTypeVariable( identifier ); - if ( typeDetails != ClassBasedTypeDetails.OBJECT_TYPE_DETAILS ) { - return typeDetails; + final ClassDetails superClass = getSuperClass(); + if ( superClass != null ) { + return superClass.resolveTypeVariable( typeVariable ); } } - return local != null ? local : ClassBasedTypeDetails.OBJECT_TYPE_DETAILS; + return ClassBasedTypeDetails.OBJECT_TYPE_DETAILS; } @Override diff --git a/src/main/java/org/hibernate/models/spi/MemberDetails.java b/src/main/java/org/hibernate/models/spi/MemberDetails.java index 8d191a9..4352f25 100644 --- a/src/main/java/org/hibernate/models/spi/MemberDetails.java +++ b/src/main/java/org/hibernate/models/spi/MemberDetails.java @@ -241,6 +241,17 @@ default TypeDetails resolveRelativeType(TypeVariableScope container) { return getType().determineRelativeType( container ); } + /** + * Same as {@link #resolveRelativeType(TypeVariableScope)}, but for the + * {@linkplain #getAssociatedType() associated type}. + * + * @see #getAssociatedType() + * @see #resolveRelativeType(TypeVariableScope) + */ + default TypeDetails resolveRelativeAssociatedType(TypeVariableScope container) { + return getAssociatedType().determineRelativeType( container ); + } + /** * Determine the concrete class of the member relative to the given {@code container} type. *

diff --git a/src/main/java/org/hibernate/models/spi/TypeDetailsHelper.java b/src/main/java/org/hibernate/models/spi/TypeDetailsHelper.java index ae0ea0c..3823c72 100644 --- a/src/main/java/org/hibernate/models/spi/TypeDetailsHelper.java +++ b/src/main/java/org/hibernate/models/spi/TypeDetailsHelper.java @@ -16,6 +16,7 @@ import org.hibernate.models.internal.util.CollectionHelper; import static org.hibernate.models.internal.util.CollectionHelper.arrayList; +import static org.hibernate.models.spi.ClassBasedTypeDetails.OBJECT_TYPE_DETAILS; /** * Helper utilities for dealing with {@linkplain TypeDetails} @@ -32,7 +33,7 @@ public class TypeDetailsHelper { * class {@code Item} { * T id; * } - * class Hat extends {@code Item} { * ... * } * @@ -65,7 +66,7 @@ public static TypeDetails resolveRelativeType(TypeDetails type, TypeVariableScop } case TYPE_VARIABLE -> { final TypeVariableDetails typeVariable = type.asTypeVariable(); - return container.resolveTypeVariable( typeVariable.getIdentifier() ); + return container.resolveTypeVariable( typeVariable ); } case TYPE_VARIABLE_REFERENCE -> { throw new UnsupportedOperationException( "TypeVariableReferenceDetails not supported for concrete type resolution" ); @@ -75,7 +76,7 @@ public static TypeDetails resolveRelativeType(TypeDetails type, TypeVariableScop if ( wildcardType.getBound() != null ) { return wildcardType.getBound(); } - return ClassBasedTypeDetails.OBJECT_TYPE_DETAILS; + return OBJECT_TYPE_DETAILS; } default -> { throw new UnsupportedOperationException( "Unknown TypeDetails kind - " + type.getTypeKind() ); @@ -95,33 +96,21 @@ public static TypeVariableDetails findTypeVariableDetails(String identifier, Lis return null; } - public static TypeVariableDetails findTypeVariableDetails2(String identifier, List typeParameters) { - if ( CollectionHelper.isNotEmpty( typeParameters ) ) { - for ( TypeDetails typeParameter : typeParameters ) { - if ( typeParameter instanceof TypeVariableDetails typeVariableDetails ) { - if ( typeVariableDetails.getIdentifier().equals( identifier ) ) { - return typeVariableDetails; - } - } - } - } - - return null; - } - /** * Very much the same as {@linkplain #resolveRelativeType(TypeDetails, TypeVariableScope)}, except that * here we resolve the relative type to the corresponding {@link ClassBasedTypeDetails} which * gives easy access to the type's {@linkplain ClassBasedTypeDetails#getClassDetails() ClassDetails} */ - public static ClassBasedTypeDetails resolveRelativeClassType(TypeDetails memberType, TypeVariableScope containerType) { + public static ClassBasedTypeDetails resolveRelativeClassType( + TypeDetails memberType, + TypeVariableScope containerType) { switch ( memberType.getTypeKind() ) { case CLASS, PRIMITIVE, VOID, ARRAY -> { return (ClassBasedTypeDetails) memberType; } case TYPE_VARIABLE -> { final TypeVariableDetails typeVariable = memberType.asTypeVariable(); - final TypeDetails typeDetails = containerType.resolveTypeVariable( typeVariable.getIdentifier() ); + final TypeDetails typeDetails = containerType.resolveTypeVariable( typeVariable ); if ( typeDetails.getTypeKind() == TypeDetails.Kind.CLASS ) { return typeDetails.asClassType(); } @@ -131,7 +120,7 @@ else if ( typeDetails.getTypeKind() == TypeDetails.Kind.TYPE_VARIABLE ) { // and assume the bound is a class return resolvedTypeVariable.getBounds().get( 0 ).asClassType(); } - return ClassBasedTypeDetails.OBJECT_TYPE_DETAILS; + return OBJECT_TYPE_DETAILS; } else { // assume parameterized @@ -140,7 +129,7 @@ else if ( typeDetails.getTypeKind() == TypeDetails.Kind.TYPE_VARIABLE ) { // and assume the bound is a class return parameterizedType.getArguments().get( 0 ).asClassType(); } - return ClassBasedTypeDetails.OBJECT_TYPE_DETAILS; + return OBJECT_TYPE_DETAILS; } } case TYPE_VARIABLE_REFERENCE -> { @@ -184,14 +173,55 @@ public static ClassDetails resolveRawClass(TypeDetails typeDetails) { return ClassDetails.OBJECT_CLASS_DETAILS; } case TYPE_VARIABLE_REFERENCE -> { - final String identifier = typeDetails.asTypeVariableReference().getIdentifier(); - final TypeDetails identifiedTypeDetails = typeDetails.resolveTypeVariable( identifier ); + final TypeVariableReferenceDetails typeVariableReference = typeDetails.asTypeVariableReference(); + final TypeDetails identifiedTypeDetails = typeDetails.resolveTypeVariable( typeVariableReference.getTarget() ); return identifiedTypeDetails.determineRawClass(); } } return ClassDetails.OBJECT_CLASS_DETAILS; } + /** + * Resolve a {@linkplain TypeVariableDetails type variable}'s type relative to the + * provided {@linkplain ParameterizedTypeDetails parameterized type}. + * + * @param parameterizedType the parameterized type used to resolve the type variable's relative type + * @param typeVariable the type variable to resolve + * + * @return the type variable's relative type, or {@code null} if not resolved + */ + public static TypeDetails resolveTypeVariableFromParameterizedType( + ParameterizedTypeDetails parameterizedType, + TypeVariableDetails typeVariable) { + final ClassDetails classDetails = parameterizedType.getRawClassDetails(); + final List typeParameters = classDetails.getTypeParameters(); + final List typeArguments = parameterizedType.getArguments(); + assert typeParameters.size() == typeArguments.size(); + + for ( int i = 0; i < typeParameters.size(); i++ ) { + final TypeVariableDetails typeParameter = typeParameters.get( i ); + if ( typeParameter.getIdentifier().equals( typeVariable.getIdentifier() ) ) { + if ( classDetails != typeVariable.getDeclaringType() ) { + final TypeDetails genericSuper = classDetails.getGenericSuperType(); + if ( genericSuper != null && genericSuper.getTypeKind() == TypeDetails.Kind.PARAMETERIZED_TYPE ) { + // Recursively check if the type variable is resolved in supertypes + final TypeDetails superResolvedType = classDetails.resolveTypeVariable( typeVariable ); + if ( superResolvedType.getTypeKind() != TypeDetails.Kind.TYPE_VARIABLE + && superResolvedType != OBJECT_TYPE_DETAILS ) { + return superResolvedType; + } + } + } + // Either we found the exact parameter definition, or the local generic supertype + // redefines a type variable with the same identifier, and we should ignore it. + // Return the matching generic type argument + return typeArguments.get( i ); + } + } + + return null; + } + /** * Make an array type of the given component type */ diff --git a/src/main/java/org/hibernate/models/spi/TypeVariableDetails.java b/src/main/java/org/hibernate/models/spi/TypeVariableDetails.java index e68d1b2..39bb5bd 100644 --- a/src/main/java/org/hibernate/models/spi/TypeVariableDetails.java +++ b/src/main/java/org/hibernate/models/spi/TypeVariableDetails.java @@ -30,6 +30,11 @@ public interface TypeVariableDetails extends TypeDetails { String getIdentifier(); + /** + * The {@linkplain ClassDetails class} where this type variable is defined. + */ + ClassDetails getDeclaringType(); + List getBounds(); @Override diff --git a/src/main/java/org/hibernate/models/spi/TypeVariableScope.java b/src/main/java/org/hibernate/models/spi/TypeVariableScope.java index c13f01b..18cbdc9 100644 --- a/src/main/java/org/hibernate/models/spi/TypeVariableScope.java +++ b/src/main/java/org/hibernate/models/spi/TypeVariableScope.java @@ -14,22 +14,30 @@ */ public interface TypeVariableScope { /** - * Find the identified type variable for this type. + * Resolve the type of the provided type variable relative to this scope. *

- * Foe example, given + * For example, given *

 	 * class {@code Thing} {
 	 *     I id;
 	 * }
 	 * 
- * A call to this method with {@code "I"} will return the {@code I extends Number} - * type variable. + * A call to this method on the {@code Thing} scope with the type variable representing + * {@code I} will return the {@code I extends Number} type variable definition itself. + *

+ * If this scope defines a corresponding type argument, the concrete type is returned. + * For example, given + *

+	 * class {@code Stuff extends Thing} {
+	 * }
+	 * 
+ * This method will yield the {@code Integer} type details. * - * @param identifier The type variable identifier + * @param typeVariable The type variable to resolve * - * @return The type variable, or {@code null} none could be found + * @return The type variable's resolved type, or {@code null} none could be found */ - TypeDetails resolveTypeVariable(String identifier); + TypeDetails resolveTypeVariable(TypeVariableDetails typeVariable); /** * Determine the raw {@linkplain ClassDetails class} for the given type. Never returns {@code null}, opting diff --git a/src/test/java/org/hibernate/models/generics/NestedInheritanceTest.java b/src/test/java/org/hibernate/models/generics/NestedInheritanceTest.java new file mode 100644 index 0000000..8545765 --- /dev/null +++ b/src/test/java/org/hibernate/models/generics/NestedInheritanceTest.java @@ -0,0 +1,108 @@ +package org.hibernate.models.generics; + +import org.hibernate.models.SourceModelTestHelper; +import org.hibernate.models.internal.SourceModelBuildingContextImpl; +import org.hibernate.models.spi.ClassDetails; +import org.hibernate.models.spi.ClassTypeDetails; +import org.hibernate.models.spi.FieldDetails; +import org.hibernate.models.spi.TypeDetails; +import org.hibernate.models.spi.TypeVariableDetails; + +import org.junit.jupiter.api.Test; + +import org.jboss.jandex.Index; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +public class NestedInheritanceTest { + @Test + void testNestedGenericHierarchyWithJandex() { + final Index index = SourceModelTestHelper.buildJandexIndex( + BaseClass.class, + IntermediateOne.class, + IntermediateTwo.class, + IntermediateThree.class, + LeafClass.class + ); + testNestedGenericHierarchy( index ); + } + + @Test + void testNestedGenericHierarchyWithoutJandex() { + testNestedGenericHierarchy( null ); + } + + void testNestedGenericHierarchy(Index index) { + final SourceModelBuildingContextImpl buildingContext = SourceModelTestHelper.createBuildingContext( + index, + BaseClass.class, + IntermediateOne.class, + IntermediateTwo.class, + IntermediateThree.class, + LeafClass.class + ); + + final ClassDetails baseClassDetails = buildingContext.getClassDetailsRegistry().getClassDetails( BaseClass.class.getName() ); + final ClassDetails intermediateClassDetails = buildingContext.getClassDetailsRegistry().getClassDetails( IntermediateOne.class.getName() ); + final ClassDetails leafClassDetails = buildingContext.getClassDetailsRegistry().getClassDetails( LeafClass.class.getName() ); + + final FieldDetails base = baseClassDetails.findFieldByName( "base" ); + final TypeDetails baseFieldType = base.getType(); + assertThat( baseFieldType.getTypeKind() ).isEqualTo( TypeDetails.Kind.TYPE_VARIABLE ); + assertThat( baseFieldType.isResolved() ).isFalse(); + + { + final TypeDetails resolvedRelativeType = base.resolveRelativeType( baseClassDetails ); + assertThat( resolvedRelativeType.isResolved() ).isFalse(); + assertThat( resolvedRelativeType ).isInstanceOf( TypeVariableDetails.class ); + } + + final FieldDetails one = intermediateClassDetails.findFieldByName( "one" ); + final TypeDetails oneFieldType = one.getType(); + assertThat( oneFieldType.getTypeKind() ).isEqualTo( TypeDetails.Kind.TYPE_VARIABLE ); + assertThat( oneFieldType.isResolved() ).isFalse(); + + { + final TypeDetails concreteType = base.resolveRelativeType( intermediateClassDetails ); + assertThat( concreteType ).isInstanceOf( ClassTypeDetails.class ); + final ClassDetails concreteClassDetails = ( (ClassTypeDetails) concreteType ).getClassDetails(); + assertThat( concreteClassDetails.toJavaClass() ).isEqualTo( Integer.class ); + + final TypeDetails intermediateConcreteType = one.resolveRelativeType( intermediateClassDetails ); + assertThat( intermediateConcreteType.isResolved() ).isFalse(); + assertThat( intermediateConcreteType ).isInstanceOf( TypeVariableDetails.class ); + } + + { + final TypeDetails baseConcreteType = base.resolveRelativeType( leafClassDetails ); + assertThat( baseConcreteType ).isInstanceOf( ClassTypeDetails.class ); + final ClassDetails concreteClassDetails = ( (ClassTypeDetails) baseConcreteType ).getClassDetails(); + assertThat( concreteClassDetails.toJavaClass() ).isEqualTo( Integer.class ); + + final TypeDetails intermediateConcreteType = one.resolveRelativeType( leafClassDetails ); + assertThat( intermediateConcreteType ).isInstanceOf( ClassTypeDetails.class ); + final ClassDetails intermediateConcreteClassDetails = ( (ClassTypeDetails) intermediateConcreteType ).getClassDetails(); + assertThat( intermediateConcreteClassDetails.toJavaClass() ).isEqualTo( String.class ); + } + } + + static class BaseClass { + T base; + } + + static class IntermediateOne extends BaseClass { + T one; + } + + static class IntermediateTwo extends IntermediateOne { + } + + static class IntermediateThree extends IntermediateTwo { + } + + static class LeafClass extends IntermediateThree { + } +} diff --git a/src/test/java/org/hibernate/models/generics/NestedRecursiveInheritanceTest.java b/src/test/java/org/hibernate/models/generics/NestedRecursiveInheritanceTest.java new file mode 100644 index 0000000..a63414c --- /dev/null +++ b/src/test/java/org/hibernate/models/generics/NestedRecursiveInheritanceTest.java @@ -0,0 +1,125 @@ +package org.hibernate.models.generics; + +import java.util.Map; + +import org.hibernate.models.SourceModelTestHelper; +import org.hibernate.models.internal.SourceModelBuildingContextImpl; +import org.hibernate.models.spi.ClassDetails; +import org.hibernate.models.spi.ClassTypeDetails; +import org.hibernate.models.spi.FieldDetails; +import org.hibernate.models.spi.TypeDetails; + +import org.junit.jupiter.api.Test; + +import org.jboss.jandex.Index; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +public class NestedRecursiveInheritanceTest { + @Test + void testNestedGenericHierarchyWithJandex() { + final Index index = SourceModelTestHelper.buildJandexIndex( + Child.class, + Parent.class, + ChildHierarchy1.class, + ParentHierarchy1.class, + ChildHierarchy2.class, + ParentHierarchy2.class, + ChildHierarchy22.class, + ParentHierarchy22.class + ); + testNestedGenericHierarchy( index ); + } + + @Test + void testNestedGenericHierarchyWithoutJandex() { + testNestedGenericHierarchy( null ); + } + + void testNestedGenericHierarchy(Index index) { + final SourceModelBuildingContextImpl buildingContext = SourceModelTestHelper.createBuildingContext( + index, + Child.class, + Parent.class, + ChildHierarchy1.class, + ParentHierarchy1.class, + ChildHierarchy2.class, + ParentHierarchy2.class, + ChildHierarchy22.class, + ParentHierarchy22.class + ); + + { + final ClassDetails child = buildingContext.getClassDetailsRegistry() + .getClassDetails( Child.class.getName() ); + final ClassDetails child2 = buildingContext.getClassDetailsRegistry() + .getClassDetails( ChildHierarchy2.class.getName() ); + final ClassDetails child22 = buildingContext.getClassDetailsRegistry() + .getClassDetails( ChildHierarchy22.class.getName() ); + + final FieldDetails parentField = child.findFieldByName( "parent" ); + final TypeDetails parentFieldType = parentField.getType(); + assertThat( parentFieldType.getTypeKind() ).isEqualTo( TypeDetails.Kind.TYPE_VARIABLE ); + assertThat( parentFieldType.determineRawClass().toJavaClass() ).isEqualTo( Parent.class ); + + final TypeDetails child2Parent = parentField.resolveRelativeType( child2 ); + assertThat( child2Parent.getTypeKind() ).isEqualTo( TypeDetails.Kind.TYPE_VARIABLE ); + assertThat( child2Parent.determineRawClass().toJavaClass() ).isEqualTo( ParentHierarchy2.class ); + + final TypeDetails child22Parent = parentField.resolveRelativeType( child22 ); + assertThat( child22Parent ).isInstanceOf( ClassTypeDetails.class ); + assertThat( child22Parent.determineRawClass().toJavaClass() ).isEqualTo( ParentHierarchy22.class ); + } + + { + final ClassDetails parent = buildingContext.getClassDetailsRegistry() + .getClassDetails( Parent.class.getName() ); + final ClassDetails parent2 = buildingContext.getClassDetailsRegistry() + .getClassDetails( ParentHierarchy2.class.getName() ); + final ClassDetails parent22 = buildingContext.getClassDetailsRegistry() + .getClassDetails( ParentHierarchy22.class.getName() ); + + final FieldDetails childrenField = parent.findFieldByName( "children" ); + final TypeDetails childrenFieldType = childrenField.getAssociatedType(); + assertThat( childrenFieldType.getTypeKind() ).isEqualTo( TypeDetails.Kind.TYPE_VARIABLE ); + assertThat( childrenFieldType.determineRawClass().toJavaClass() ).isEqualTo( Child.class ); + + final TypeDetails parent2Children = childrenField.resolveRelativeAssociatedType( parent2 ); + assertThat( parent2Children.getTypeKind() ).isEqualTo( TypeDetails.Kind.TYPE_VARIABLE ); + assertThat( parent2Children.determineRawClass().toJavaClass() ).isEqualTo( ChildHierarchy2.class ); + + final TypeDetails parent22Children = childrenField.resolveRelativeAssociatedType( parent22 ); + assertThat( parent22Children ).isInstanceOf( ClassTypeDetails.class ); + assertThat( parent22Children.determineRawClass().toJavaClass() ).isEqualTo( ChildHierarchy22.class ); + } + } + + static abstract class Child

{ + P parent; + } + + static abstract class Parent { + Map children; + } + + static class ParentHierarchy1 extends Parent { + } + + static class ChildHierarchy1 extends Child { + } + + static class ChildHierarchy2

extends Child

{ + } + + static class ParentHierarchy2 extends Parent { + } + + static class ChildHierarchy22 extends ChildHierarchy2 { + } + + static class ParentHierarchy22 extends ParentHierarchy2 { + } +}