From f81623af3815a6366655f733343340da7ac52699 Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Thu, 16 Apr 2026 04:50:18 +0530 Subject: [PATCH 1/3] Fix collection type resolution in DefaultConfiguratorRegistry --- .../impl/DefaultConfiguratorRegistry.java | 21 ++-- .../impl/DefaultConfiguratorRegistryTest.java | 110 ++++++++++++++++++ 2 files changed, 118 insertions(+), 13 deletions(-) create mode 100644 plugin/src/test/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistryTest.java diff --git a/plugin/src/main/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistry.java b/plugin/src/main/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistry.java index 9a9f335bc7..a18123786f 100644 --- a/plugin/src/main/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistry.java +++ b/plugin/src/main/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistry.java @@ -95,20 +95,15 @@ private Configurator internalLookup(Type type) { } } - // TODO: Only try to cast if we can actually get the parameterized type - if (Collection.class.isAssignableFrom(clazz) && type instanceof ParameterizedType) { - ParameterizedType pt = (ParameterizedType) type; - Type actualType = pt.getActualTypeArguments()[0]; - if (actualType instanceof WildcardType) { - actualType = ((WildcardType) actualType).getUpperBounds()[0]; + if (Collection.class.isAssignableFrom(clazz)) { + Type collectionType = Types.getBaseClass(type, Collection.class); + if (collectionType instanceof ParameterizedType pt) { + Type actualType = pt.getActualTypeArguments()[0]; + if (actualType instanceof WildcardType) { + actualType = ((WildcardType) actualType).getUpperBounds()[0]; + } + return internalLookup(actualType); // cache is not reëntrant } - if (actualType instanceof ParameterizedType) { - actualType = ((ParameterizedType) actualType).getRawType(); - } - if (!(actualType instanceof Class)) { - throw new IllegalStateException("Can't handle " + type); - } - return internalLookup(actualType); // cache is not reëntrant } if (Configurable.class.isAssignableFrom(clazz)) { diff --git a/plugin/src/test/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistryTest.java b/plugin/src/test/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistryTest.java new file mode 100644 index 0000000000..7db69fe372 --- /dev/null +++ b/plugin/src/test/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistryTest.java @@ -0,0 +1,110 @@ +package io.jenkins.plugins.casc.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import hudson.tasks.Builder; +import io.jenkins.plugins.casc.Configurator; +import io.jenkins.plugins.casc.impl.configurators.HeteroDescribableConfigurator; +import io.jenkins.plugins.casc.impl.configurators.PrimitiveConfigurator; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +public class DefaultConfiguratorRegistryTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + private DefaultConfiguratorRegistry registry; + + @Before + public void setUp() { + registry = new DefaultConfiguratorRegistry(); + } + + @SuppressWarnings("unused") + public static class DummyTarget { + public List rawList; + public List> nestedList; + public List wildcardList; + public List typeVarList; + public List unknownList; + } + + public static class StringList extends ArrayList {} + + private Type getTypeOf(String fieldName) throws NoSuchFieldException { + return DummyTarget.class.getDeclaredField(fieldName).getGenericType(); + } + + @Test + public void shouldSafelyHandleRawCollections() throws Exception { + Type rawListType = getTypeOf("rawList"); + + Configurator configurator = registry.lookup(rawListType); + + assertNull("Raw collections should safely fall through and return null", configurator); + } + + @Test + public void shouldSafelyExtractNestedGenerics() throws Exception { + Type nestedListType = getTypeOf("nestedList"); + + Configurator configurator = registry.lookup(nestedListType); + + assertNotNull("Configurator should be found for nested generic", configurator); + assertTrue("Should resolve to PrimitiveConfigurator", configurator instanceof PrimitiveConfigurator); + assertEquals("Target should resolve exactly to String.class", String.class, configurator.getTarget()); + } + + @Test + public void shouldSafelyExtractWildcardUpperBounds() throws Exception { + Type wildcardListType = getTypeOf("wildcardList"); + + Configurator configurator = registry.lookup(wildcardListType); + + assertNotNull("Configurator should be found for wildcard", configurator); + assertTrue( + "Should resolve to HeteroDescribableConfigurator", + configurator instanceof HeteroDescribableConfigurator); + assertEquals("Target should resolve exactly to Builder.class", Builder.class, configurator.getTarget()); + } + + @Test + public void shouldSafelyExtractTypeVariables() throws Exception { + Type typeVarListType = getTypeOf("typeVarList"); + + Configurator configurator = registry.lookup(typeVarListType); + + assertNotNull("Configurator should be found for TypeVariable", configurator); + assertTrue( + "Should resolve to HeteroDescribableConfigurator", + configurator instanceof HeteroDescribableConfigurator); + assertEquals("Target should resolve exactly to Builder.class", Builder.class, configurator.getTarget()); + } + + @Test + public void shouldHandleCustomCollectionSubclass() { + Configurator configurator = registry.lookup(StringList.class); + + assertNotNull("Configurator should be found for custom collection subclass", configurator); + assertTrue("Should resolve to PrimitiveConfigurator", configurator instanceof PrimitiveConfigurator); + assertEquals("Target should resolve exactly to String.class", String.class, configurator.getTarget()); + } + + @Test + public void shouldSafelyHandleUnboundedWildcards() throws Exception { + Type unknownListType = getTypeOf("unknownList"); + + Configurator configurator = registry.lookup(unknownListType); + + assertNull("Unbounded wildcards resolve to Object and should safely return null", configurator); + } +} From 0738b5dd6ddf634724b4a2df5ba83787844ccde1 Mon Sep 17 00:00:00 2001 From: somiljain2006 Date: Thu, 16 Apr 2026 04:59:58 +0530 Subject: [PATCH 2/3] Added supresssion --- .../plugins/casc/impl/DefaultConfiguratorRegistryTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugin/src/test/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistryTest.java b/plugin/src/test/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistryTest.java index 7db69fe372..1a0c28adf5 100644 --- a/plugin/src/test/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistryTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistryTest.java @@ -31,7 +31,9 @@ public void setUp() { @SuppressWarnings("unused") public static class DummyTarget { - public List rawList; + @SuppressWarnings("rawtypes") + public List rawList; + public List> nestedList; public List wildcardList; public List typeVarList; From 389e4d68024a5d047cad7a8f4df59e08eb8c1491 Mon Sep 17 00:00:00 2001 From: somil jain <89907422+somiljain2006@users.noreply.github.com> Date: Thu, 16 Apr 2026 14:00:18 +0530 Subject: [PATCH 3/3] Update plugin/src/main/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistry.java Co-authored-by: Tim Jacomb <21194782+timja@users.noreply.github.com> --- .../plugins/casc/impl/DefaultConfiguratorRegistry.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistry.java b/plugin/src/main/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistry.java index a18123786f..f5712f6009 100644 --- a/plugin/src/main/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistry.java +++ b/plugin/src/main/java/io/jenkins/plugins/casc/impl/DefaultConfiguratorRegistry.java @@ -99,8 +99,8 @@ private Configurator internalLookup(Type type) { Type collectionType = Types.getBaseClass(type, Collection.class); if (collectionType instanceof ParameterizedType pt) { Type actualType = pt.getActualTypeArguments()[0]; - if (actualType instanceof WildcardType) { - actualType = ((WildcardType) actualType).getUpperBounds()[0]; + if (actualType instanceof WildcardType wildcardType) { + actualType = wildcardType.getUpperBounds()[0]; } return internalLookup(actualType); // cache is not reëntrant }