Skip to content

Commit b6053ae

Browse files
somiljain2006timja
andauthored
Fix collection type resolution in DefaultConfiguratorRegistry (#2833)
Co-authored-by: Tim Jacomb <[email protected]>
1 parent ac12440 commit b6053ae

2 files changed

Lines changed: 120 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 wildcardType) {
103+
actualType = wildcardType.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: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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+
@SuppressWarnings("rawtypes")
35+
public List rawList;
36+
37+
public List<List<String>> nestedList;
38+
public List<? extends Builder> wildcardList;
39+
public List<T> typeVarList;
40+
public List<?> unknownList;
41+
}
42+
43+
public static class StringList extends ArrayList<String> {}
44+
45+
private Type getTypeOf(String fieldName) throws NoSuchFieldException {
46+
return DummyTarget.class.getDeclaredField(fieldName).getGenericType();
47+
}
48+
49+
@Test
50+
public void shouldSafelyHandleRawCollections() throws Exception {
51+
Type rawListType = getTypeOf("rawList");
52+
53+
Configurator<?> configurator = registry.lookup(rawListType);
54+
55+
assertNull("Raw collections should safely fall through and return null", configurator);
56+
}
57+
58+
@Test
59+
public void shouldSafelyExtractNestedGenerics() throws Exception {
60+
Type nestedListType = getTypeOf("nestedList");
61+
62+
Configurator<?> configurator = registry.lookup(nestedListType);
63+
64+
assertNotNull("Configurator should be found for nested generic", configurator);
65+
assertTrue("Should resolve to PrimitiveConfigurator", configurator instanceof PrimitiveConfigurator);
66+
assertEquals("Target should resolve exactly to String.class", String.class, configurator.getTarget());
67+
}
68+
69+
@Test
70+
public void shouldSafelyExtractWildcardUpperBounds() throws Exception {
71+
Type wildcardListType = getTypeOf("wildcardList");
72+
73+
Configurator<?> configurator = registry.lookup(wildcardListType);
74+
75+
assertNotNull("Configurator should be found for wildcard", configurator);
76+
assertTrue(
77+
"Should resolve to HeteroDescribableConfigurator",
78+
configurator instanceof HeteroDescribableConfigurator);
79+
assertEquals("Target should resolve exactly to Builder.class", Builder.class, configurator.getTarget());
80+
}
81+
82+
@Test
83+
public void shouldSafelyExtractTypeVariables() throws Exception {
84+
Type typeVarListType = getTypeOf("typeVarList");
85+
86+
Configurator<?> configurator = registry.lookup(typeVarListType);
87+
88+
assertNotNull("Configurator should be found for TypeVariable", configurator);
89+
assertTrue(
90+
"Should resolve to HeteroDescribableConfigurator",
91+
configurator instanceof HeteroDescribableConfigurator);
92+
assertEquals("Target should resolve exactly to Builder.class", Builder.class, configurator.getTarget());
93+
}
94+
95+
@Test
96+
public void shouldHandleCustomCollectionSubclass() {
97+
Configurator<?> configurator = registry.lookup(StringList.class);
98+
99+
assertNotNull("Configurator should be found for custom collection subclass", configurator);
100+
assertTrue("Should resolve to PrimitiveConfigurator", configurator instanceof PrimitiveConfigurator);
101+
assertEquals("Target should resolve exactly to String.class", String.class, configurator.getTarget());
102+
}
103+
104+
@Test
105+
public void shouldSafelyHandleUnboundedWildcards() throws Exception {
106+
Type unknownListType = getTypeOf("unknownList");
107+
108+
Configurator<?> configurator = registry.lookup(unknownListType);
109+
110+
assertNull("Unbounded wildcards resolve to Object and should safely return null", configurator);
111+
}
112+
}

0 commit comments

Comments
 (0)