Skip to content

Commit c49e233

Browse files
mbelladesebersole
authored andcommitted
#144 Correct recursive type variable reference resolution
1 parent 47b9eb2 commit c49e233

File tree

2 files changed

+113
-26
lines changed

2 files changed

+113
-26
lines changed

hibernate-models/src/main/java/org/hibernate/models/internal/jdk/JdkTrackingTypeSwitcher.java

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
*/
55
package org.hibernate.models.internal.jdk;
66

7+
import org.hibernate.models.internal.TypeVariableReferenceDetailsImpl;
8+
import org.hibernate.models.spi.ModelsContext;
9+
import org.hibernate.models.spi.TypeDetails;
10+
import org.hibernate.models.spi.TypeVariableDetails;
11+
712
import java.lang.reflect.GenericArrayType;
813
import java.lang.reflect.ParameterizedType;
914
import java.lang.reflect.Type;
@@ -14,19 +19,13 @@
1419
import java.util.List;
1520
import java.util.Map;
1621

17-
import org.hibernate.models.internal.TypeVariableReferenceDetailsImpl;
18-
import org.hibernate.models.internal.util.CollectionHelper;
19-
import org.hibernate.models.spi.ModelsContext;
20-
import org.hibernate.models.spi.TypeDetails;
21-
import org.hibernate.models.spi.TypeVariableDetails;
22-
2322
/**
2423
* @author Steve Ebersole
2524
*/
2625
public class JdkTrackingTypeSwitcher implements JdkTypeSwitcher {
2726
private final JdkTrackingTypeSwitch typeSwitch;
2827

29-
private List<String> typeVariableIdentifiers;
28+
private Map<String, TypeVariableResolution> typeVariables;
3029
private Map<String, List<TypeVariableReferenceDetailsImpl>> typeVariableRefXref;
3130

3231
public static TypeDetails standardSwitchType(
@@ -41,8 +40,7 @@ public JdkTrackingTypeSwitcher(ModelsContext modelsContext) {
4140

4241
@Override
4342
public TypeDetails switchType(Type type) {
44-
//noinspection rawtypes
45-
if ( type instanceof Class classType ) {
43+
if ( type instanceof Class<?> classType ) {
4644
return typeSwitch.caseClass( classType );
4745
}
4846

@@ -54,8 +52,7 @@ public TypeDetails switchType(Type type) {
5452
return typeSwitch.caseParameterizedType( parameterizedType );
5553
}
5654

57-
//noinspection rawtypes
58-
if ( type instanceof TypeVariable typeVariable ) {
55+
if ( type instanceof TypeVariable<?> typeVariable ) {
5956
return switchTypeVariable( type, typeVariable );
6057
}
6158

@@ -66,39 +63,60 @@ public TypeDetails switchType(Type type) {
6663
return typeSwitch.defaultCase( type );
6764
}
6865

69-
private TypeDetails switchTypeVariable(Type type, @SuppressWarnings("rawtypes") TypeVariable typeVariable) {
70-
if ( typeVariableIdentifiers == null ) {
71-
typeVariableIdentifiers = new ArrayList<>();
66+
private TypeDetails switchTypeVariable(Type type, TypeVariable<?> typeVariable) {
67+
final TypeVariableResolution resolution = new TypeVariableResolution();
68+
if ( typeVariables == null ) {
69+
typeVariables = new HashMap<>();
70+
typeVariables.put( typeVariable.getName(), resolution );
7271
}
7372
else {
74-
if ( typeVariableIdentifiers.contains( typeVariable.getTypeName() ) ) {
73+
final TypeVariableResolution existingResolution = typeVariables.putIfAbsent(
74+
typeVariable.getTypeName(),
75+
resolution
76+
);
77+
if ( existingResolution != null ) {
78+
final TypeVariableDetails details = existingResolution.getDetails();
79+
if ( details != null ) {
80+
// The type variable has already been switched, so we can return the original details
81+
return details;
82+
}
7583
// this should indicate a "recursive" type var (e.g. `T extends Comparable<T>`)
7684
final TypeVariableReferenceDetailsImpl reference = new TypeVariableReferenceDetailsImpl( type.getTypeName() );
7785
if ( typeVariableRefXref == null ) {
7886
typeVariableRefXref = new HashMap<>();
79-
final List<TypeVariableReferenceDetailsImpl> list = typeVariableRefXref.computeIfAbsent(
80-
type.getTypeName(),
81-
(s) -> new ArrayList<>()
82-
);
83-
list.add( reference );
8487
}
88+
final List<TypeVariableReferenceDetailsImpl> list = typeVariableRefXref.computeIfAbsent(
89+
type.getTypeName(),
90+
(s) -> new ArrayList<>()
91+
);
92+
list.add( reference );
8593
return reference;
8694
}
8795
}
88-
typeVariableIdentifiers.add( typeVariable.getTypeName() );
8996

9097
final TypeVariableDetails switched = typeSwitch.caseTypeVariable( typeVariable );
9198
assert switched != null;
99+
resolution.setDetails( switched );
92100

93101
if ( typeVariableRefXref != null ) {
94-
final List<TypeVariableReferenceDetailsImpl> list = typeVariableRefXref.get( typeVariable.getTypeName() );
95-
if ( CollectionHelper.isNotEmpty( list ) ) {
96-
for ( TypeVariableReferenceDetailsImpl reference : list ) {
97-
reference.setTarget( switched );
98-
}
102+
final List<TypeVariableReferenceDetailsImpl> list = typeVariableRefXref.remove( typeVariable.getTypeName() );
103+
if ( list != null ) {
104+
list.forEach( reference -> reference.setTarget( switched ) );
99105
}
100106
}
101107

102108
return switched;
103109
}
110+
111+
private static class TypeVariableResolution {
112+
private TypeVariableDetails details;
113+
114+
public void setDetails(TypeVariableDetails details) {
115+
this.details = details;
116+
}
117+
118+
public TypeVariableDetails getDetails() {
119+
return details;
120+
}
121+
}
104122
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright: Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.models.testing.tests.generics;
6+
7+
import org.hibernate.models.spi.ClassDetails;
8+
import org.hibernate.models.spi.FieldDetails;
9+
import org.hibernate.models.spi.ModelsContext;
10+
import org.hibernate.models.spi.TypeDetails;
11+
12+
import org.junit.jupiter.api.Test;
13+
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
import static org.hibernate.models.testing.TestHelper.createModelContext;
16+
17+
public class RecursiveMultipleTypeParametersTests {
18+
@Test
19+
void testResolveRelativeTypeWithSelfReferenceFirst() {
20+
final ModelsContext modelsContext = createModelContext( BaseEntityWithSelfReferenceFirst.class );
21+
final ClassDetails classDetails = modelsContext.getClassDetailsRegistry().getClassDetails(
22+
BaseEntityWithSelfReferenceFirst.class.getName()
23+
);
24+
25+
final FieldDetails idField = classDetails.findFieldByName( "id" );
26+
final TypeDetails idType = idField.resolveRelativeType( classDetails );
27+
assertThat( idType.getTypeKind() ).isEqualTo( TypeDetails.Kind.TYPE_VARIABLE );
28+
assertThat( idType.isImplementor( Transitionable.class ) ).isTrue();
29+
}
30+
31+
@Test
32+
void testResolveRelativeTypeWithSelfReferenceAfterType() {
33+
final ModelsContext modelsContext = createModelContext( BaseEntityWithSelfReferenceAfterType.class );
34+
final ClassDetails classDetails = modelsContext.getClassDetailsRegistry().getClassDetails(
35+
BaseEntityWithSelfReferenceAfterType.class.getName()
36+
);
37+
38+
final FieldDetails idField = classDetails.findFieldByName( "id" );
39+
final TypeDetails idType = idField.resolveRelativeType( classDetails );
40+
assertThat( idType.getTypeKind() ).isEqualTo( TypeDetails.Kind.TYPE_VARIABLE );
41+
assertThat( idType.isImplementor( Transitionable.class ) ).isTrue();
42+
}
43+
44+
static class BaseEntityWithSelfReferenceFirst<S extends BaseEntityWithSelfReferenceFirst<S, I>, I extends Transitionable> {
45+
private I id;
46+
47+
private String name;
48+
49+
protected S self() {
50+
return (S) this;
51+
}
52+
}
53+
54+
static class BaseEntityWithSelfReferenceAfterType<I extends Transitionable, S extends BaseEntityWithSelfReferenceAfterType<I, S>> {
55+
private I id;
56+
57+
private String name;
58+
59+
protected S self() {
60+
return (S) this;
61+
}
62+
}
63+
64+
interface Transitionable {
65+
default boolean canTransitionTo(Transitionable other) {
66+
return other != null && !this.equals( other );
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)