From 886ca7f2db574a6fbcce7e2c77becad8eb95e11b Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Sat, 18 Jan 2025 17:03:04 +0100 Subject: [PATCH] Polish contribution and SimpleCommandLineArgs-related code Closes gh-34282 --- .../core/env/CommandLinePropertySource.java | 14 ++++----- .../core/env/SimpleCommandLineArgsParser.java | 7 +++-- .../env/SimpleCommandLinePropertySource.java | 10 ++++--- .../env/SimpleCommandLineArgsParserTests.java | 30 ++++++++++++++----- .../SimpleCommandLinePropertySourceTests.java | 11 ++++++- 5 files changed, 49 insertions(+), 23 deletions(-) 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 a49d3e93d8..20bcc1a95c 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. @@ -25,8 +25,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. * *

Purpose and General Usage

* @@ -259,10 +258,11 @@ public abstract class CommandLinePropertySource extends EnumerablePropertySou * This implementation first checks to see if the name specified is the special * {@linkplain #setNonOptionArgsPropertyName(String) "non-option arguments" property}, * and if so delegates to the abstract {@link #getNonOptionArgs()} method. If so - * and the collection of non-option arguments is empty, this method returns {@code - * null}. If not empty, it returns a comma-separated String of all non-option - * arguments. Otherwise, delegates to and returns the result of the abstract {@link - * #getOptionValues(String)} method. + * and the collection of non-option arguments is empty, this method returns + * {@code null}. If not empty, it returns a comma-separated String of all non-option + * arguments. Otherwise, this method delegates to and returns a comma-separated String + * of the results of the abstract {@link #getOptionValues(String)} method or + * {@code null} if there are no such option values. */ @Override @Nullable diff --git a/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java index 98b80011f5..fbb999987e 100644 --- a/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java +++ b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.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. @@ -28,8 +28,9 @@ package org.springframework.core.env; *

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. if the option is present and has multiple values (e. g. "--foo=bar --foo=baz"), -* the values are parsed as a collection. + * 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. * *

Valid examples of option arguments

*
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 17121c2973..1c4591f3df 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.
@@ -22,7 +22,8 @@ import org.springframework.lang.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}.
  *
  * 

Purpose

*

This {@code CommandLinePropertySource} implementation aims to provide the simplest @@ -40,8 +41,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. if the option is present and has multiple values (e. g. "--foo=bar --foo=baz"), - * the values are parsed as a collection. + * 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. * *

Valid examples of option arguments

*
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 180dfa8fbc..e7dad041c1 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 86923da26e..9d6d4979e3 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=");