diff --git a/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java index 3e941d53cb9..f9b6fbd2ffd 100644 --- a/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 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. @@ -26,8 +26,7 @@ import org.springframework.util.StringUtils; /** * Abstract base class for {@link PropertySource} implementations backed by command line * arguments. The parameterized type {@code T} represents the underlying source of command - * line options. For instance, {@link SimpleCommandLinePropertySource} uses a String - * array. + * line options. * *
That is, options must be prefixed with "{@code --}" and may or may not * specify a value. If a value is specified, the name and value must be separated * without spaces by an equals sign ("="). The value may optionally be - * an empty string. + * an empty string. If an option is present multiple times with different values + * — for example, {@code --foo=bar --foo=baz} — all supplied values + * will be stored for the option. * *
@@ -37,14 +39,14 @@ package org.springframework.core.env; * --foo="" * --foo=bar * --foo="bar then baz" - * --foo=bar,baz,biz+ * --foo=bar,baz,biz + * --foo=bar --foo=baz --foo=biz * *
* -foo * --foo bar - * --foo = bar - * --foo=bar --foo=baz --foo=biz+ * --foo = bar * *
This parser supports the POSIX "end of options" delimiter, meaning that any diff --git a/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLinePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLinePropertySource.java index 64dd05cd4f9..05d944cff31 100644 --- a/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLinePropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLinePropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -23,7 +23,8 @@ import org.jspecify.annotations.Nullable; import org.springframework.util.StringUtils; /** - * {@link CommandLinePropertySource} implementation backed by a simple String array. + * {@link CommandLinePropertySource} implementation backed by an instance of + * {@link CommandLineArgs}. * *
This {@code CommandLinePropertySource} implementation aims to provide the simplest @@ -41,7 +42,9 @@ import org.springframework.util.StringUtils; *
That is, options must be prefixed with "{@code --}" and may or may not * specify a value. If a value is specified, the name and value must be separated * without spaces by an equals sign ("="). The value may optionally be - * an empty string. + * an empty string. If an option is present multiple times with different values + * — for example, {@code --foo=bar --foo=baz} — all supplied values + * will be stored for the option. * *
@@ -50,14 +53,14 @@ import org.springframework.util.StringUtils; * --foo="" * --foo=bar * --foo="bar then baz" - * --foo=bar,baz,biz+ * --foo=bar,baz,biz + * --foo=bar --foo=baz --foo=biz * *
* -foo * --foo bar - * --foo = bar - * --foo=bar --foo=baz --foo=biz+ * --foo = bar * *
The underlying parser supports the POSIX "end of options" delimiter, meaning diff --git a/spring-core/src/test/java/org/springframework/core/env/SimpleCommandLineArgsParserTests.java b/spring-core/src/test/java/org/springframework/core/env/SimpleCommandLineArgsParserTests.java index 180dfa8fbce..e7dad041c18 100644 --- a/spring-core/src/test/java/org/springframework/core/env/SimpleCommandLineArgsParserTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/SimpleCommandLineArgsParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -46,7 +46,7 @@ class SimpleCommandLineArgsParserTests { void withSingleOptionAndNoValue() { CommandLineArgs args = parser.parse("--o1"); assertThat(args.containsOption("o1")).isTrue(); - assertThat(args.getOptionValues("o1")).isEqualTo(Collections.EMPTY_LIST); + assertThat(args.getOptionValues("o1")).isEmpty(); } @Test @@ -56,6 +56,20 @@ class SimpleCommandLineArgsParserTests { assertThat(args.getOptionValues("o1")).containsExactly("v1"); } + @Test + void withRepeatedOptionAndSameValues() { + CommandLineArgs args = parser.parse("--o1=v1", "--o1=v1", "--o1=v1"); + assertThat(args.containsOption("o1")).isTrue(); + assertThat(args.getOptionValues("o1")).containsExactly("v1", "v1", "v1"); + } + + @Test + void withRepeatedOptionAndDifferentValues() { + CommandLineArgs args = parser.parse("--o1=v1", "--o1=v2", "--o1=v3"); + assertThat(args.containsOption("o1")).isTrue(); + assertThat(args.getOptionValues("o1")).containsExactly("v1", "v2", "v3"); + } + @Test void withMixOfOptionsHavingValueAndOptionsHavingNoValue() { CommandLineArgs args = parser.parse("--o1=v1", "--o2"); @@ -95,17 +109,17 @@ class SimpleCommandLineArgsParserTests { } @Test - void assertOptionNamesIsUnmodifiable() { + void optionNamesSetIsUnmodifiable() { CommandLineArgs args = new SimpleCommandLineArgsParser().parse(); - assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> - args.getOptionNames().add("bogus")); + assertThatExceptionOfType(UnsupportedOperationException.class) + .isThrownBy(() -> args.getOptionNames().add("bogus")); } @Test - void assertNonOptionArgsIsUnmodifiable() { + void nonOptionArgsListIsUnmodifiable() { CommandLineArgs args = new SimpleCommandLineArgsParser().parse(); - assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> - args.getNonOptionArgs().add("foo")); + assertThatExceptionOfType(UnsupportedOperationException.class) + .isThrownBy(() -> args.getNonOptionArgs().add("foo")); } @Test diff --git a/spring-core/src/test/java/org/springframework/core/env/SimpleCommandLinePropertySourceTests.java b/spring-core/src/test/java/org/springframework/core/env/SimpleCommandLinePropertySourceTests.java index 86923da26ea..9d6d4979e35 100644 --- a/spring-core/src/test/java/org/springframework/core/env/SimpleCommandLinePropertySourceTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/SimpleCommandLinePropertySourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -61,6 +61,15 @@ class SimpleCommandLinePropertySourceTests { assertThat(ps.getProperty("o3")).isNull(); } + @Test // gh-34282 + void withRepeatedOptionArgs() { + CommandLinePropertySource> ps = new SimpleCommandLinePropertySource("--o1=v1", "--o1=v2", "--o1=v3"); + assertThat(ps.containsProperty("o1")).isTrue(); + assertThat(ps.containsProperty("o2")).isFalse(); + assertThat(ps.getProperty("o1")).isEqualTo("v1,v2,v3"); + assertThat(ps.getProperty("o2")).isNull(); + } + @Test // gh-24464 void withOptionalArg_andArgIsEmpty() { EnumerablePropertySource> ps = new SimpleCommandLinePropertySource("--foo=");