Skip to content

Commit e0d4f21

Browse files
committed
fix(jdk-codemodel): capture type-level generic parameter declarations in reflection path
1 parent e04596e commit e0d4f21

3 files changed

Lines changed: 76 additions & 1 deletion

File tree

jdk-codemodel/src/main/java/build/codemodel/jdk/JDKCodeModel.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import build.codemodel.objectoriented.descriptor.FieldDescriptor;
6666
import build.codemodel.objectoriented.descriptor.ImplementsTypeDescriptor;
6767
import build.codemodel.objectoriented.descriptor.MethodDescriptor;
68+
import build.codemodel.objectoriented.descriptor.ParameterizedTypeDescriptor;
6869
import build.codemodel.objectoriented.naming.MethodName;
6970
import jakarta.inject.Inject;
7071

@@ -407,7 +408,14 @@ private Optional<JDKTypeDescriptor> getJDKTypeDescriptor(final Class<?> classTyp
407408
// include the Classification
408409
typeDescriptor.addTrait(getClassification(classModifier));
409410

410-
// TODO: include the generic parameter declarations on the type itself!
411+
// include the generic parameter declarations on the type itself
412+
final TypeVariable<?>[] typeParameters = classType.getTypeParameters();
413+
if (typeParameters.length > 0) {
414+
final var typeVars = Arrays.stream(typeParameters)
415+
.map(tp -> (TypeVariableUsage) getTypeUsage(tp))
416+
.toList();
417+
typeDescriptor.addTrait(ParameterizedTypeDescriptor.of(this, typeVars.stream()));
418+
}
411419

412420
// include the ExtendsTypeDescriptor (should a super-class be defined)
413421
final var superType = classType.getAnnotatedSuperclass();

jdk-codemodel/src/test/java/build/codemodel/jdk/JDKCodeModelTests.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import build.codemodel.foundation.usage.GenericTypeUsage;
77
import build.codemodel.foundation.usage.NamedTypeUsage;
88
import build.codemodel.foundation.usage.WildcardTypeUsage;
9+
import build.codemodel.foundation.usage.TypeVariableUsage;
10+
import build.codemodel.jdk.example.BoundedContainer;
911
import build.codemodel.jdk.example.WildcardContainer;
1012
import build.codemodel.hierarchical.descriptor.HierarchicalTypeDescriptor;
1113
import build.codemodel.jdk.descriptor.JDKTypeDescriptor;
@@ -20,6 +22,7 @@
2022
import build.codemodel.objectoriented.descriptor.FieldDescriptor;
2123
import build.codemodel.objectoriented.descriptor.ImplementsTypeDescriptor;
2224
import build.codemodel.objectoriented.descriptor.MethodDescriptor;
25+
import build.codemodel.objectoriented.descriptor.ParameterizedTypeDescriptor;
2326
import org.junit.jupiter.api.Test;
2427

2528
import static org.assertj.core.api.Assertions.assertThat;
@@ -409,4 +412,37 @@ void shouldDiscoverWildcardBoundsViaReflection() {
409412
assertThat(unboundedWildcard.upperBound()).isEmpty();
410413
assertThat(unboundedWildcard.lowerBound()).isEmpty();
411414
}
415+
416+
@Test
417+
void shouldDiscoverTypeParameterDeclarationViaReflection() {
418+
final var codeModel = createCodeModel();
419+
final var descriptor = codeModel.getJDKTypeDescriptor(Container.class).orElseThrow();
420+
421+
assertThat(descriptor.getTrait(ParameterizedTypeDescriptor.class)).isPresent();
422+
423+
final var typeVars = descriptor.getTrait(ParameterizedTypeDescriptor.class)
424+
.orElseThrow()
425+
.typeVariables()
426+
.toList();
427+
assertThat(typeVars).hasSize(1);
428+
assertThat(typeVars.getFirst().typeName().name().toString()).isEqualTo("T");
429+
}
430+
431+
@Test
432+
void shouldDiscoverBoundedTypeParameterDeclarationViaReflection() {
433+
final var codeModel = createCodeModel();
434+
final var descriptor = codeModel.getJDKTypeDescriptor(BoundedContainer.class).orElseThrow();
435+
436+
assertThat(descriptor.getTrait(ParameterizedTypeDescriptor.class)).isPresent();
437+
438+
final var typeVar = descriptor.getTrait(ParameterizedTypeDescriptor.class)
439+
.orElseThrow()
440+
.typeVariables()
441+
.findFirst()
442+
.orElseThrow();
443+
assertThat(typeVar).isInstanceOf(TypeVariableUsage.class);
444+
assertThat(typeVar.typeName().name().toString()).isEqualTo("T");
445+
assertThat(typeVar.upperBound()).isPresent();
446+
assertThat(((NamedTypeUsage) typeVar.upperBound().get()).typeName().toString()).contains("Number");
447+
}
412448
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package build.codemodel.jdk.example;
2+
3+
/*-
4+
* #%L
5+
* JDK Code Model
6+
* %%
7+
* Copyright (C) 2026 Workday, Inc.
8+
* %%
9+
* Licensed under the Apache License, Version 2.0 (the "License");
10+
* you may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
* #L%
21+
*/
22+
23+
/**
24+
* A container with a bounded type parameter, used for reflection-path generic discovery tests.
25+
*
26+
* @param <T> the element type, bounded by {@link Number}
27+
*/
28+
public class BoundedContainer<T extends Number> {
29+
30+
public T value;
31+
}

0 commit comments

Comments
 (0)