From 61a88bc1b7b0ad870d5c5080cfffe42314e8b62c Mon Sep 17 00:00:00 2001 From: Andrey Litvitski Date: Sun, 8 Feb 2026 23:03:08 +0300 Subject: [PATCH] Fix defaultValue handling for primitive Option parameters Fixes a case where defaultValue was ignored for primitive option types. Closes: gh-1314 Signed-off-by: Andrey Litvitski --- .../adapter/MethodInvokerCommandAdapter.java | 21 +++--- ...nvokerCommandAdapterDefaultValueTests.java | 72 +++++++++++++++++++ 2 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/core/command/adapter/MethodInvokerCommandAdapterDefaultValueTests.java diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/command/adapter/MethodInvokerCommandAdapter.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/adapter/MethodInvokerCommandAdapter.java index b6ba6f9b6..70e9988e6 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/core/command/adapter/MethodInvokerCommandAdapter.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/core/command/adapter/MethodInvokerCommandAdapter.java @@ -45,6 +45,7 @@ * * @author Mahmoud Ben Hassine * @author David Pilar + * @author Andrey Litvitski * @since 4.0.0 */ public class MethodInvokerCommandAdapter extends AbstractCommand { @@ -157,23 +158,21 @@ private List prepareArguments(CommandContext commandContext) { } else { Class parameterType = parameterTypes[i]; - // check if the option type is primitive or not - if (!parameterType.isPrimitive()) { - // try to convert default value if present - String defaultValue = optionAnnotation.defaultValue(); - if (!defaultValue.isEmpty()) { - Object value = this.conversionService.convert(defaultValue, parameterType); - args.add(value); + String defaultValue = optionAnnotation.defaultValue(); + if (!defaultValue.isEmpty()) { + Object value = this.conversionService.convert(defaultValue, parameterType); + args.add(value); + } + else { + if (parameterType.isPrimitive()) { + // for primitive types, add default value of the primitive + args.add(Utils.getDefaultValueForPrimitiveType(parameterType)); } else { // for non-primitive types, add null args.add(null); } } - else { - // for primitive types, add default value of the primitive - args.add(Utils.getDefaultValueForPrimitiveType(parameterType)); - } } } continue; diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/command/adapter/MethodInvokerCommandAdapterDefaultValueTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/command/adapter/MethodInvokerCommandAdapterDefaultValueTests.java new file mode 100644 index 000000000..ce49625d0 --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/command/adapter/MethodInvokerCommandAdapterDefaultValueTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.core.command.adapter; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Method; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.shell.core.command.CommandContext; +import org.springframework.shell.core.command.ExitStatus; +import org.springframework.shell.core.command.annotation.Option; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Andrey Litvitski + */ +class MethodInvokerCommandAdapterDefaultValueTests { + + private static class Target { + + int seen; + + public void run(@Option(longName = "retries", defaultValue = "3") int retries) { + this.seen = retries; + } + + } + + @Test + void optionDefaultValueIsUsedForPrimitiveWhenOptionMissing() throws Exception { + Target target = new Target(); + Method method = Target.class.getDeclaredMethod("run", int.class); + + DefaultConversionService conversionService = new DefaultConversionService(); + Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + + CommandContext ctx = Mockito.mock(CommandContext.class); + Mockito.when(ctx.getOptionByLongName("retries")).thenReturn(null); + + StringWriter out = new StringWriter(); + Mockito.when(ctx.outputWriter()).thenReturn(new PrintWriter(out)); + + MethodInvokerCommandAdapter adapter = new MethodInvokerCommandAdapter("name", "desc", "group", "help", false, + method, target, conversionService, validator); + + ExitStatus status = adapter.doExecute(ctx); + + assertThat(status).isEqualTo(ExitStatus.OK); + assertThat(target.seen).isEqualTo(3); + } + +} \ No newline at end of file