diff --git a/.changes/next-release/bugfix-AWSSDKforJavav2-01696b7.json b/.changes/next-release/bugfix-AWSSDKforJavav2-01696b7.json new file mode 100644 index 000000000000..a69b43c93336 --- /dev/null +++ b/.changes/next-release/bugfix-AWSSDKforJavav2-01696b7.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Add allowDuplicateKeys to ImmutableMap Builder" +} diff --git a/codegen-lite/src/main/java/software/amazon/awssdk/codegen/lite/regions/ServiceMetadataGenerator.java b/codegen-lite/src/main/java/software/amazon/awssdk/codegen/lite/regions/ServiceMetadataGenerator.java index 7a0e5befbaed..2cca47593d09 100644 --- a/codegen-lite/src/main/java/software/amazon/awssdk/codegen/lite/regions/ServiceMetadataGenerator.java +++ b/codegen-lite/src/main/java/software/amazon/awssdk/codegen/lite/regions/ServiceMetadataGenerator.java @@ -257,7 +257,7 @@ private CodeBlock dnsSuffixesByPartition(Partitions partitions) { private CodeBlock hostnamesByRegion(Partitions partitions) { Map services = getServiceData(partitions); - CodeBlock.Builder builder = CodeBlock.builder().add("$T.<$T, $T>builder()", + CodeBlock.Builder builder = CodeBlock.builder().add("$T.<$T, $T>builder().allowDuplicateKeys(true)", ImmutableMap.class, serviceEndpointKeyClass(), String.class); services.forEach((partition, service) -> { diff --git a/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/regions/s3-service-metadata.java b/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/regions/s3-service-metadata.java index 7377911e9490..3e5f143ed8ca 100644 --- a/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/regions/s3-service-metadata.java +++ b/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/regions/s3-service-metadata.java @@ -69,6 +69,7 @@ public final class S3ServiceMetadata implements ServiceMetadata { private static final Map HOSTNAMES_BY_REGION = ImmutableMap . builder() + .allowDuplicateKeys(true) .put(ServiceEndpointKey.builder().region(Region.of("af-south-1")).tags(EndpointTag.of("dualstack")).build(), "s3.dualstack.af-south-1.amazonaws.com") .put(ServiceEndpointKey.builder().region(Region.of("ap-east-1")).tags(EndpointTag.of("dualstack")).build(), diff --git a/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/regions/sts-service-metadata.java b/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/regions/sts-service-metadata.java index 4e582e9c6c9b..0a814ccc1794 100644 --- a/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/regions/sts-service-metadata.java +++ b/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/regions/sts-service-metadata.java @@ -60,6 +60,7 @@ public final class StsServiceMetadata implements ServiceMetadata { private static final Map HOSTNAMES_BY_REGION = ImmutableMap . builder() + .allowDuplicateKeys(true) .put(ServiceEndpointKey.builder().region(Region.of("aws-global")).build(), "sts.amazonaws.com") .put(ServiceEndpointKey.builder().region(Region.of("us-east-1")).tags(EndpointTag.of("fips")).build(), "sts-fips.us-east-1.amazonaws.com") diff --git a/utils/src/main/java/software/amazon/awssdk/utils/ImmutableMap.java b/utils/src/main/java/software/amazon/awssdk/utils/ImmutableMap.java index c0ec7fba423b..f8d72616d146 100644 --- a/utils/src/main/java/software/amazon/awssdk/utils/ImmutableMap.java +++ b/utils/src/main/java/software/amazon/awssdk/utils/ImmutableMap.java @@ -58,6 +58,7 @@ @SdkProtectedApi public final class ImmutableMap implements Map { + private static final Logger log = Logger.loggerFor(ImmutableMap.class); private static final String UNMODIFIABLE_MESSAGE = "This is an immutable map."; private static final String DUPLICATED_KEY_MESSAGE = "Duplicate keys are provided."; @@ -198,8 +199,18 @@ public static ImmutableMap of(K k0, V v0, K k1, V v1, private static void putAndWarnDuplicateKeys(Map map, K key, V value) { + putAndWarnDuplicateKeys(map, key, value, false); + } + + private static void putAndWarnDuplicateKeys(Map map, K key, V value, boolean allowDuplicateKeys) { if (map.containsKey(key)) { - throw new IllegalArgumentException(DUPLICATED_KEY_MESSAGE); + + if (allowDuplicateKeys) { + log.debug(() -> String.format("Duplicate keys are provided for [%s]. The newer value [%s] will be saved, and the " + + "existing value [%s] will be overwritten.", key, value, map.get(key))); + } else { + throw new IllegalArgumentException(DUPLICATED_KEY_MESSAGE); + } } map.put(key, value); } @@ -289,6 +300,7 @@ public String toString() { public static class Builder { private final Map entries; + private boolean allowDuplicateKeys; public Builder() { this.entries = new HashMap<>(); @@ -297,13 +309,29 @@ public Builder() { /** * Add a key-value pair into the built map. The method will throw * IllegalArgumentException immediately when duplicate keys are - * provided. + * provided and allowDuplicateKeys is false (default value). + * + * + * If duplicate keys are provided and allowDuplicateKeys is true, the latest value will overwrite the existing value, and + * the SDK will log a message at WARN level. * * @return Returns a reference to this object so that method calls can * be chained together. */ public Builder put(K key, V value) { - putAndWarnDuplicateKeys(entries, key, value); + putAndWarnDuplicateKeys(entries, key, value, allowDuplicateKeys); + return this; + } + + /** + * Sets whether duplicate keys are allowed. If true, the latest value will overwrite the existing value. If + * false, an error will be thrown when attempting to put an entry with a duplicate key. Default value is false. + * + * @return Returns a reference to this object so that method calls can + * be chained together. + */ + public Builder allowDuplicateKeys(boolean allowDuplicateKeys) { + this.allowDuplicateKeys = allowDuplicateKeys; return this; } diff --git a/utils/src/test/java/software/amazon/awssdk/utils/ImmutableMapTest.java b/utils/src/test/java/software/amazon/awssdk/utils/ImmutableMapTest.java index 9fc71e426fd6..bddd797fc1f5 100644 --- a/utils/src/test/java/software/amazon/awssdk/utils/ImmutableMapTest.java +++ b/utils/src/test/java/software/amazon/awssdk/utils/ImmutableMapTest.java @@ -74,9 +74,9 @@ public void testOfBuilder() { public void testErrorOnDuplicateKeys() { try { Map builtMap = new ImmutableMap.Builder() - .put(1, "one") - .put(1, "two") - .build(); + .put(1, "one") + .put(1, "two") + .build(); fail("IllegalArgumentException expected."); } catch (IllegalArgumentException iae) { // Ignored or expected. @@ -85,6 +85,17 @@ public void testErrorOnDuplicateKeys() { } } + @Test + public void putDuplicateKeys_allowDuplicateKeysTrue_doesNotThrowErrorAndKeepsNewestValue() { + Map builtMap = new ImmutableMap.Builder() + .allowDuplicateKeys(true) + .put(1, "one") + .put(1, "two") + .build(); + + assertEquals("two", builtMap.get(1)); + } + @Test public void testMapOperations() { Map builtMap = new ImmutableMap.Builder()