Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import io.jenkins.plugins.casc.ConfigurationContext;
import io.jenkins.plugins.casc.RootElementConfigurator;
import io.jenkins.plugins.casc.model.Mapping;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -64,17 +66,63 @@ private List<String> resolvePossibleNames(Descriptor descriptor) {
return Optional.ofNullable(descriptor.getClass().getAnnotation(Symbol.class))
.map(s -> Arrays.asList(s.value()))
.orElseGet(() -> {
/* TODO: extract Descriptor parameter type such that DescriptorImpl extends Descriptor<XX> returns XX.
* Then, if `baseClass == fooXX` we get natural name `foo`.
*/
return singletonList(fromPascalCaseToCamelCase(
descriptor.getKlass().toJavaClass().getSimpleName()));
String fallbackName = fromPascalCaseToCamelCase(
unwrapAnonymous(descriptor.getKlass().toJavaClass()).getSimpleName());

Class<?> typeParam = extractDescriptorTypeParameter(descriptor.getClass());
String derivedName = null;

if (typeParam != null) {
Class<?> targetClass = unwrapAnonymous(typeParam);
derivedName = fromPascalCaseToCamelCase(targetClass.getSimpleName());
}

if (derivedName != null && !derivedName.equals(fallbackName)) {
return Arrays.asList(fallbackName, derivedName);
}

return singletonList(fallbackName);
});
}

private Class<?> extractDescriptorTypeParameter(Class<?> clazz) {
while (clazz != null && clazz != Object.class) {
Type genericSuperclass = clazz.getGenericSuperclass();

if (genericSuperclass instanceof ParameterizedType pt
&& pt.getRawType() instanceof Class<?> rawClass
&& Descriptor.class.isAssignableFrom(rawClass)) {

Type[] args = pt.getActualTypeArguments();

if (args.length > 0) {
Type typeArg = args[0];

if (typeArg instanceof Class<?> clazzArg) {
return clazzArg;
}

if (typeArg instanceof ParameterizedType nestedPt
&& nestedPt.getRawType() instanceof Class<?> nestedClass) {
return nestedClass;
}
}
}
clazz = clazz.getSuperclass();
}
return null;
}

private static String fromPascalCaseToCamelCase(String s) {
StringBuilder sb = new StringBuilder(s);
sb.setCharAt(0, Character.toLowerCase(s.charAt(0)));
return sb.toString();
}

private Class<?> unwrapAnonymous(Class<?> clazz) {
while (clazz.isAnonymousClass()) {
clazz = clazz.getSuperclass();
}
return clazz;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package io.jenkins.plugins.casc.impl.configurators;

import static org.junit.Assert.assertEquals;

import hudson.model.Describable;
import hudson.model.Descriptor;
import java.util.Arrays;
import java.util.Collections;
import org.jenkinsci.Symbol;
import org.junit.Test;

public class DescriptorConfiguratorTest {

public static class DummyTask implements Describable<DummyTask> {
@Override
public Descriptor<DummyTask> getDescriptor() {
throw new UnsupportedOperationException("Not required for name extraction tests");
}
}

public static class ParameterizedTask<T> implements Describable<ParameterizedTask<T>> {
@Override
public Descriptor<ParameterizedTask<T>> getDescriptor() {
throw new UnsupportedOperationException();
}
}

public static class DummyTaskDescriptor extends Descriptor<DummyTask> {
public DummyTaskDescriptor() {
super(DummyTask.class);
}
}

@Symbol({"primary", "alias"})
public static class MultiSymbolDescriptor extends Descriptor<DummyTask> {
public MultiSymbolDescriptor() {
super(DummyTask.class);
}
}

public abstract static class SimulatedBuildStepDescriptor<T extends Describable<T>> extends Descriptor<T> {
protected SimulatedBuildStepDescriptor(Class<? extends T> clazz) {
super(clazz);
}
}

public static class DeepTaskDescriptor extends SimulatedBuildStepDescriptor<DummyTask> {
public DeepTaskDescriptor() {
super(DummyTask.class);
}
}

@SuppressWarnings({"rawtypes", "unchecked"})
public static class ParameterizedDescriptor extends Descriptor<ParameterizedTask<String>> {
public ParameterizedDescriptor() {
super((Class) ParameterizedTask.class);
}
}

@SuppressWarnings("rawtypes")
public static class RawDescriptor extends Descriptor {
@SuppressWarnings("unchecked")
public RawDescriptor(Class clazz) {
super(clazz);
}
}

@Test
public void testMultipleSymbols() {
DescriptorConfigurator configurator = new DescriptorConfigurator(new MultiSymbolDescriptor());

assertEquals("Should use the first symbol as primary name", "primary", configurator.getName());
assertEquals(
"Should return all symbols as aliases", Arrays.asList("primary", "alias"), configurator.getNames());
}

@Test
public void testNameResolvedFromGenericExtraction() {
DescriptorConfigurator configurator = new DescriptorConfigurator(new DummyTaskDescriptor());

assertEquals("Should extract 'DummyTask' and convert to camelCase", "dummyTask", configurator.getName());
assertEquals(Collections.singletonList("dummyTask"), configurator.getNames());
}

@Test
public void testNameResolvedFromParameterizedType() {
DescriptorConfigurator configurator = new DescriptorConfigurator(new ParameterizedDescriptor());

assertEquals(
"Should unwrap ParameterizedType to its raw class name", "parameterizedTask", configurator.getName());
}

@Test
public void testNameResolvedFromDeepInheritance() {
DescriptorConfigurator configurator = new DescriptorConfigurator(new DeepTaskDescriptor());

assertEquals(
"Should bypass type erasure and extract from superclass hierarchy",
"dummyTask",
configurator.getName());
}

@Test
@SuppressWarnings("rawtypes")
public void testFallbackWithAnonymousTargetClass() {

Class<?> anonymousTarget = new DummyTask() {}.getClass();
Descriptor rawDescriptor = new RawDescriptor(anonymousTarget);
DescriptorConfigurator configurator = new DescriptorConfigurator(rawDescriptor);

assertEquals(
"Should unwrap anonymous target class via the fallback logic", "dummyTask", configurator.getName());
}
}
Loading