Skip to content

Commit f81623a

Browse files
committed
Fix collection type resolution in DefaultConfiguratorRegistry
1 parent ac12440 commit f81623a

2 files changed

Lines changed: 118 additions & 13 deletions

File tree

plugin/src/main/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistry.java

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -95,20 +95,15 @@ private Configurator internalLookup(Type type) {
9595
}
9696
}
9797

98-
// TODO: Only try to cast if we can actually get the parameterized type
99-
if (Collection.class.isAssignableFrom(clazz) && type instanceof ParameterizedType) {
100-
ParameterizedType pt = (ParameterizedType) type;
101-
Type actualType = pt.getActualTypeArguments()[0];
102-
if (actualType instanceof WildcardType) {
103-
actualType = ((WildcardType) actualType).getUpperBounds()[0];
98+
if (Collection.class.isAssignableFrom(clazz)) {
99+
Type collectionType = Types.getBaseClass(type, Collection.class);
100+
if (collectionType instanceof ParameterizedType pt) {
101+
Type actualType = pt.getActualTypeArguments()[0];
102+
if (actualType instanceof WildcardType) {
103+
actualType = ((WildcardType) actualType).getUpperBounds()[0];
104+
}
105+
return internalLookup(actualType); // cache is not reëntrant
104106
}
105-
if (actualType instanceof ParameterizedType) {
106-
actualType = ((ParameterizedType) actualType).getRawType();
107-
}
108-
if (!(actualType instanceof Class)) {
109-
throw new IllegalStateException("Can't handle " + type);
110-
}
111-
return internalLookup(actualType); // cache is not reëntrant
112107
}
113108

114109
if (Configurable.class.isAssignableFrom(clazz)) {
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package io.jenkins.plugins.casc.impl;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertNotNull;
5+
import static org.junit.Assert.assertNull;
6+
import static org.junit.Assert.assertTrue;
7+
8+
import hudson.tasks.Builder;
9+
import io.jenkins.plugins.casc.Configurator;
10+
import io.jenkins.plugins.casc.impl.configurators.HeteroDescribableConfigurator;
11+
import io.jenkins.plugins.casc.impl.configurators.PrimitiveConfigurator;
12+
import java.lang.reflect.Type;
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
import org.junit.Before;
16+
import org.junit.Rule;
17+
import org.junit.Test;
18+
import org.jvnet.hudson.test.JenkinsRule;
19+
20+
public class DefaultConfiguratorRegistryTest {
21+
22+
@Rule
23+
public JenkinsRule j = new JenkinsRule();
24+
25+
private DefaultConfiguratorRegistry registry;
26+
27+
@Before
28+
public void setUp() {
29+
registry = new DefaultConfiguratorRegistry();
30+
}
31+
32+
@SuppressWarnings("unused")
33+
public static class DummyTarget<T extends Builder> {
34+
public List<?> rawList;
35+
public List<List<String>> nestedList;
36+
public List<? extends Builder> wildcardList;
37+
public List<T> typeVarList;
38+
public List<?> unknownList;
39+
}
40+
41+
public static class StringList extends ArrayList<String> {}
42+
43+
private Type getTypeOf(String fieldName) throws NoSuchFieldException {
44+
return DummyTarget.class.getDeclaredField(fieldName).getGenericType();
45+
}
46+
47+
@Test
48+
public void shouldSafelyHandleRawCollections() throws Exception {
49+
Type rawListType = getTypeOf("rawList");
50+
51+
Configurator<?> configurator = registry.lookup(rawListType);
52+
53+
assertNull("Raw collections should safely fall through and return null", configurator);
54+
}
55+
56+
@Test
57+
public void shouldSafelyExtractNestedGenerics() throws Exception {
58+
Type nestedListType = getTypeOf("nestedList");
59+
60+
Configurator<?> configurator = registry.lookup(nestedListType);
61+
62+
assertNotNull("Configurator should be found for nested generic", configurator);
63+
assertTrue("Should resolve to PrimitiveConfigurator", configurator instanceof PrimitiveConfigurator);
64+
assertEquals("Target should resolve exactly to String.class", String.class, configurator.getTarget());
65+
}
66+
67+
@Test
68+
public void shouldSafelyExtractWildcardUpperBounds() throws Exception {
69+
Type wildcardListType = getTypeOf("wildcardList");
70+
71+
Configurator<?> configurator = registry.lookup(wildcardListType);
72+
73+
assertNotNull("Configurator should be found for wildcard", configurator);
74+
assertTrue(
75+
"Should resolve to HeteroDescribableConfigurator",
76+
configurator instanceof HeteroDescribableConfigurator);
77+
assertEquals("Target should resolve exactly to Builder.class", Builder.class, configurator.getTarget());
78+
}
79+
80+
@Test
81+
public void shouldSafelyExtractTypeVariables() throws Exception {
82+
Type typeVarListType = getTypeOf("typeVarList");
83+
84+
Configurator<?> configurator = registry.lookup(typeVarListType);
85+
86+
assertNotNull("Configurator should be found for TypeVariable", configurator);
87+
assertTrue(
88+
"Should resolve to HeteroDescribableConfigurator",
89+
configurator instanceof HeteroDescribableConfigurator);
90+
assertEquals("Target should resolve exactly to Builder.class", Builder.class, configurator.getTarget());
91+
}
92+
93+
@Test
94+
public void shouldHandleCustomCollectionSubclass() {
95+
Configurator<?> configurator = registry.lookup(StringList.class);
96+
97+
assertNotNull("Configurator should be found for custom collection subclass", configurator);
98+
assertTrue("Should resolve to PrimitiveConfigurator", configurator instanceof PrimitiveConfigurator);
99+
assertEquals("Target should resolve exactly to String.class", String.class, configurator.getTarget());
100+
}
101+
102+
@Test
103+
public void shouldSafelyHandleUnboundedWildcards() throws Exception {
104+
Type unknownListType = getTypeOf("unknownList");
105+
106+
Configurator<?> configurator = registry.lookup(unknownListType);
107+
108+
assertNull("Unbounded wildcards resolve to Object and should safely return null", configurator);
109+
}
110+
}

0 commit comments

Comments
 (0)