diff --git a/.changes/next-release/bugfix-AWSSDKforJavav2-baa7ce5.json b/.changes/next-release/bugfix-AWSSDKforJavav2-baa7ce5.json new file mode 100644 index 000000000000..62d6e2b99a5e --- /dev/null +++ b/.changes/next-release/bugfix-AWSSDKforJavav2-baa7ce5.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Correctly handle unions with members named \"type\" by renaming the member variable to avoid conflicts with the existing SDK added \"type\" field." +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/AddShapes.java b/codegen/src/main/java/software/amazon/awssdk/codegen/AddShapes.java index 8d4e9cc41c26..5a4e99de42b5 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/AddShapes.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/AddShapes.java @@ -158,7 +158,7 @@ private MemberModel generateMemberModel(String c2jMemberName, Member c2jMemberDe Map allC2jShapes) { String c2jShapeName = c2jMemberDefinition.getShape(); Shape shape = allC2jShapes.get(c2jShapeName); - String variableName = getNamingStrategy().getVariableName(c2jMemberName); + String variableName = getNamingStrategy().getVariableName(c2jMemberName, parentShape); String variableType = getTypeUtils().getJavaDataType(allC2jShapes, c2jShapeName); String variableDeclarationType = getTypeUtils().getJavaDataType(allC2jShapes, c2jShapeName); diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java b/codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java index d620438f0d78..b9fcf41e6e0f 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java @@ -299,6 +299,16 @@ public String getVariableName(String name) { return unCapitalize(name); } + @Override + public String getVariableName(String name, Shape parentShape) { + if (isJavaKeyword(name) || + isDisallowedNameForShape(unCapitalize(name), parentShape)) { + return unCapitalize(name + CONFLICTING_NAME_SUFFIX); + } + + return unCapitalize(name); + } + @Override public String getEnumValueName(String enumValue) { String result = enumValue; diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/naming/NamingStrategy.java b/codegen/src/main/java/software/amazon/awssdk/codegen/naming/NamingStrategy.java index 637920be14de..5b22363970f0 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/naming/NamingStrategy.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/naming/NamingStrategy.java @@ -124,6 +124,13 @@ public interface NamingStrategy { */ String getVariableName(String name); + /** + * @param name Some contextual name to derive variable name from (i.e. member name, java class name, etc). + * @param parentShape The shape containing the member, used to check for shape-specific reserved names. + * @return Appropriate name to use for a Java variable or field. + */ + String getVariableName(String name, Shape parentShape); + /** * @param enumValue Enum value as defined in the service model used to derive the java name. * @return Appropriate name to use for a Java enum value diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithreservedkeywordmemberrequest.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithreservedkeywordmemberrequest.java index 755b2fb79b45..414c895adb5a 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithreservedkeywordmemberrequest.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/operationwithreservedkeywordmemberrequest.java @@ -35,16 +35,26 @@ public final class OperationWithReservedKeywordMemberRequest extends JsonProtoco .traits(LocationTrait.builder().location(MarshallLocation.PAYLOAD).locationName("ReservedKeywordMember").build()) .build(); - private static final List> SDK_FIELDS = Collections - .unmodifiableList(Arrays.asList(RESERVED_KEYWORD_MEMBER_FIELD)); + private static final SdkField UNION_WITH_TYPE_MEMBER_FIELD = SdkField + . builder(MarshallingType.SDK_POJO).memberName("UnionWithTypeMember") + .getter(getter(OperationWithReservedKeywordMemberRequest::unionWithTypeMember)) + .setter(setter(Builder::unionWithTypeMember)).constructor(UnionWithTypeMember::builder) + .traits(LocationTrait.builder().location(MarshallLocation.PAYLOAD).locationName("UnionWithTypeMember").build()) + .build(); + + private static final List> SDK_FIELDS = Collections.unmodifiableList(Arrays.asList(RESERVED_KEYWORD_MEMBER_FIELD, + UNION_WITH_TYPE_MEMBER_FIELD)); private static final Map> SDK_NAME_TO_FIELD = memberNameToFieldInitializer(); private final ContainsReservedKeyword reservedKeywordMember; + private final UnionWithTypeMember unionWithTypeMember; + private OperationWithReservedKeywordMemberRequest(BuilderImpl builder) { super(builder); this.reservedKeywordMember = builder.reservedKeywordMember; + this.unionWithTypeMember = builder.unionWithTypeMember; } /** @@ -56,6 +66,15 @@ public final ContainsReservedKeyword reservedKeywordMember() { return reservedKeywordMember; } + /** + * Returns the value of the UnionWithTypeMember property for this object. + * + * @return The value of the UnionWithTypeMember property for this object. + */ + public final UnionWithTypeMember unionWithTypeMember() { + return unionWithTypeMember; + } + @Override public Builder toBuilder() { return new BuilderImpl(this); @@ -74,6 +93,7 @@ public final int hashCode() { int hashCode = 1; hashCode = 31 * hashCode + super.hashCode(); hashCode = 31 * hashCode + Objects.hashCode(reservedKeywordMember()); + hashCode = 31 * hashCode + Objects.hashCode(unionWithTypeMember()); return hashCode; } @@ -94,7 +114,8 @@ public final boolean equalsBySdkFields(Object obj) { return false; } OperationWithReservedKeywordMemberRequest other = (OperationWithReservedKeywordMemberRequest) obj; - return Objects.equals(reservedKeywordMember(), other.reservedKeywordMember()); + return Objects.equals(reservedKeywordMember(), other.reservedKeywordMember()) + && Objects.equals(unionWithTypeMember(), other.unionWithTypeMember()); } /** @@ -104,13 +125,15 @@ public final boolean equalsBySdkFields(Object obj) { @Override public final String toString() { return ToString.builder("OperationWithReservedKeywordMemberRequest") - .add("ReservedKeywordMember", reservedKeywordMember()).build(); + .add("ReservedKeywordMember", reservedKeywordMember()).add("UnionWithTypeMember", unionWithTypeMember()).build(); } public final Optional getValueForField(String fieldName, Class clazz) { switch (fieldName) { case "ReservedKeywordMember": return Optional.ofNullable(clazz.cast(reservedKeywordMember())); + case "UnionWithTypeMember": + return Optional.ofNullable(clazz.cast(unionWithTypeMember())); default: return Optional.empty(); } @@ -129,6 +152,7 @@ public final Map> sdkFieldNameToField() { private static Map> memberNameToFieldInitializer() { Map> map = new HashMap<>(); map.put("ReservedKeywordMember", RESERVED_KEYWORD_MEMBER_FIELD); + map.put("UnionWithTypeMember", UNION_WITH_TYPE_MEMBER_FIELD); return Collections.unmodifiableMap(map); } @@ -172,6 +196,34 @@ default Builder reservedKeywordMember(Consumer return reservedKeywordMember(ContainsReservedKeyword.builder().applyMutation(reservedKeywordMember).build()); } + /** + * Sets the value of the UnionWithTypeMember property for this object. + * + * @param unionWithTypeMember + * The new value for the UnionWithTypeMember property for this object. + * @return Returns a reference to this object so that method calls can be chained together. + */ + Builder unionWithTypeMember(UnionWithTypeMember unionWithTypeMember); + + /** + * Sets the value of the UnionWithTypeMember property for this object. + * + * This is a convenience method that creates an instance of the {@link UnionWithTypeMember.Builder} avoiding the + * need to create one manually via {@link UnionWithTypeMember#builder()}. + * + *

+ * When the {@link Consumer} completes, {@link UnionWithTypeMember.Builder#build()} is called immediately and + * its result is passed to {@link #unionWithTypeMember(UnionWithTypeMember)}. + * + * @param unionWithTypeMember + * a consumer that will call methods on {@link UnionWithTypeMember.Builder} + * @return Returns a reference to this object so that method calls can be chained together. + * @see #unionWithTypeMember(UnionWithTypeMember) + */ + default Builder unionWithTypeMember(Consumer unionWithTypeMember) { + return unionWithTypeMember(UnionWithTypeMember.builder().applyMutation(unionWithTypeMember).build()); + } + @Override Builder overrideConfiguration(AwsRequestOverrideConfiguration overrideConfiguration); @@ -182,12 +234,15 @@ default Builder reservedKeywordMember(Consumer static final class BuilderImpl extends JsonProtocolTestsRequest.BuilderImpl implements Builder { private ContainsReservedKeyword reservedKeywordMember; + private UnionWithTypeMember unionWithTypeMember; + private BuilderImpl() { } private BuilderImpl(OperationWithReservedKeywordMemberRequest model) { super(model); reservedKeywordMember(model.reservedKeywordMember); + unionWithTypeMember(model.unionWithTypeMember); } public final ContainsReservedKeyword.Builder getReservedKeywordMember() { @@ -204,6 +259,20 @@ public final Builder reservedKeywordMember(ContainsReservedKeyword reservedKeywo return this; } + public final UnionWithTypeMember.Builder getUnionWithTypeMember() { + return unionWithTypeMember != null ? unionWithTypeMember.toBuilder() : null; + } + + public final void setUnionWithTypeMember(UnionWithTypeMember.BuilderImpl unionWithTypeMember) { + this.unionWithTypeMember = unionWithTypeMember != null ? unionWithTypeMember.build() : null; + } + + @Override + public final Builder unionWithTypeMember(UnionWithTypeMember unionWithTypeMember) { + this.unionWithTypeMember = unionWithTypeMember; + return this; + } + @Override public Builder overrideConfiguration(AwsRequestOverrideConfiguration overrideConfiguration) { super.overrideConfiguration(overrideConfiguration); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/service-2.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/service-2.json index d0aa206e8f01..5b2f1c8ebcaf 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/service-2.json +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/service-2.json @@ -221,6 +221,18 @@ "AllTypesUnionStructure":{"shape":"AllTypesUnionStructure"} } }, + "UnionWithTypeMember": { + "type": "structure", + "union": true, + "members": { + "StringMember": { + "shape": "String" + }, + "Type": { + "shape": "String" + } + } + }, "BaseType":{ "type":"structure", "members":{ @@ -278,7 +290,8 @@ "members": { "ReservedKeywordMember": { "shape": "ContainsReservedKeyword" - } + }, + "UnionWithTypeMember": {"shape": "UnionWithTypeMember"} } }, "Double":{"type":"double"}, diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/unionwithtypemember.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/unionwithtypemember.java new file mode 100644 index 000000000000..017d062adb41 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/unionwithtypemember.java @@ -0,0 +1,321 @@ +package software.amazon.awssdk.services.jsonprotocoltests.model; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Function; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.Mutable; +import software.amazon.awssdk.annotations.NotThreadSafe; +import software.amazon.awssdk.core.SdkField; +import software.amazon.awssdk.core.SdkPojo; +import software.amazon.awssdk.core.protocol.MarshallLocation; +import software.amazon.awssdk.core.protocol.MarshallingType; +import software.amazon.awssdk.core.traits.LocationTrait; +import software.amazon.awssdk.core.util.SdkAutoConstructList; +import software.amazon.awssdk.core.util.SdkAutoConstructMap; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + */ +@Generated("software.amazon.awssdk:codegen") +public final class UnionWithTypeMember implements SdkPojo, Serializable, + ToCopyableBuilder { + private static final SdkField STRING_MEMBER_FIELD = SdkField. builder(MarshallingType.STRING) + .memberName("StringMember").getter(getter(UnionWithTypeMember::stringMember)).setter(setter(Builder::stringMember)) + .traits(LocationTrait.builder().location(MarshallLocation.PAYLOAD).locationName("StringMember").build()).build(); + + private static final SdkField TYPE_FIELD = SdkField. builder(MarshallingType.STRING).memberName("Type") + .getter(getter(UnionWithTypeMember::typeValue)).setter(setter(Builder::typeValue)) + .traits(LocationTrait.builder().location(MarshallLocation.PAYLOAD).locationName("Type").build()).build(); + + private static final List> SDK_FIELDS = Collections.unmodifiableList(Arrays.asList(STRING_MEMBER_FIELD, + TYPE_FIELD)); + + private static final Map> SDK_NAME_TO_FIELD = memberNameToFieldInitializer(); + + private static final long serialVersionUID = 1L; + + private final String stringMember; + + private final String typeValue; + + private final Type type; + + private UnionWithTypeMember(BuilderImpl builder) { + this.stringMember = builder.stringMember; + this.typeValue = builder.typeValue; + this.type = builder.type; + } + + /** + * Returns the value of the StringMember property for this object. + * + * @return The value of the StringMember property for this object. + */ + public final String stringMember() { + return stringMember; + } + + /** + * Returns the value of the Type property for this object. + * + * @return The value of the Type property for this object. + */ + public final String typeValue() { + return typeValue; + } + + @Override + public Builder toBuilder() { + return new BuilderImpl(this); + } + + public static Builder builder() { + return new BuilderImpl(); + } + + public static Class serializableBuilderClass() { + return BuilderImpl.class; + } + + @Override + public final int hashCode() { + int hashCode = 1; + hashCode = 31 * hashCode + Objects.hashCode(stringMember()); + hashCode = 31 * hashCode + Objects.hashCode(typeValue()); + return hashCode; + } + + @Override + public final boolean equals(Object obj) { + return equalsBySdkFields(obj); + } + + @Override + public final boolean equalsBySdkFields(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof UnionWithTypeMember)) { + return false; + } + UnionWithTypeMember other = (UnionWithTypeMember) obj; + return Objects.equals(stringMember(), other.stringMember()) && Objects.equals(typeValue(), other.typeValue()); + } + + /** + * Returns a string representation of this object. This is useful for testing and debugging. Sensitive data will be + * redacted from this string using a placeholder value. + */ + @Override + public final String toString() { + return ToString.builder("UnionWithTypeMember").add("StringMember", stringMember()).add("Type", typeValue()).build(); + } + + public final Optional getValueForField(String fieldName, Class clazz) { + switch (fieldName) { + case "StringMember": + return Optional.ofNullable(clazz.cast(stringMember())); + case "Type": + return Optional.ofNullable(clazz.cast(typeValue())); + default: + return Optional.empty(); + } + } + + /** + * Create an instance of this class with {@link #stringMember()} initialized to the given value. + * + * Sets the value of the StringMember property for this object. + * + * @param stringMember + * The new value for the StringMember property for this object. + */ + public static UnionWithTypeMember fromStringMember(String stringMember) { + return builder().stringMember(stringMember).build(); + } + + /** + * Create an instance of this class with {@link #typeValue()} initialized to the given value. + * + * Sets the value of the Type property for this object. + * + * @param typeValue + * The new value for the Type property for this object. + */ + public static UnionWithTypeMember fromTypeValue(String typeValue) { + return builder().typeValue(typeValue).build(); + } + + /** + * Retrieve an enum value representing which member of this object is populated. + * + * When this class is returned in a service response, this will be {@link Type#UNKNOWN_TO_SDK_VERSION} if the + * service returned a member that is only known to a newer SDK version. + * + * When this class is created directly in your code, this will be {@link Type#UNKNOWN_TO_SDK_VERSION} if zero + * members are set, and {@code null} if more than one member is set. + */ + public Type type() { + return type; + } + + @Override + public final List> sdkFields() { + return SDK_FIELDS; + } + + @Override + public final Map> sdkFieldNameToField() { + return SDK_NAME_TO_FIELD; + } + + private static Map> memberNameToFieldInitializer() { + Map> map = new HashMap<>(); + map.put("StringMember", STRING_MEMBER_FIELD); + map.put("Type", TYPE_FIELD); + return Collections.unmodifiableMap(map); + } + + private static Function getter(Function g) { + return obj -> g.apply((UnionWithTypeMember) obj); + } + + private static BiConsumer setter(BiConsumer s) { + return (obj, val) -> s.accept((Builder) obj, val); + } + + @Mutable + @NotThreadSafe + public interface Builder extends SdkPojo, CopyableBuilder { + /** + * Sets the value of the StringMember property for this object. + * + * @param stringMember + * The new value for the StringMember property for this object. + * @return Returns a reference to this object so that method calls can be chained together. + */ + Builder stringMember(String stringMember); + + /** + * Sets the value of the Type property for this object. + * + * @param typeValue + * The new value for the Type property for this object. + * @return Returns a reference to this object so that method calls can be chained together. + */ + Builder typeValue(String typeValue); + } + + static final class BuilderImpl implements Builder { + private String stringMember; + + private String typeValue; + + private Type type = Type.UNKNOWN_TO_SDK_VERSION; + + private Set setTypes = EnumSet.noneOf(Type.class); + + private BuilderImpl() { + } + + private BuilderImpl(UnionWithTypeMember model) { + stringMember(model.stringMember); + typeValue(model.typeValue); + } + + public final String getStringMember() { + return stringMember; + } + + public final void setStringMember(String stringMember) { + Object oldValue = this.stringMember; + this.stringMember = stringMember; + handleUnionValueChange(Type.STRING_MEMBER, oldValue, this.stringMember); + } + + @Override + public final Builder stringMember(String stringMember) { + Object oldValue = this.stringMember; + this.stringMember = stringMember; + handleUnionValueChange(Type.STRING_MEMBER, oldValue, this.stringMember); + return this; + } + + public final String getTypeValue() { + return typeValue; + } + + public final void setTypeValue(String typeValue) { + Object oldValue = this.typeValue; + this.typeValue = typeValue; + handleUnionValueChange(Type.TYPE, oldValue, this.typeValue); + } + + @Override + public final Builder typeValue(String typeValue) { + Object oldValue = this.typeValue; + this.typeValue = typeValue; + handleUnionValueChange(Type.TYPE, oldValue, this.typeValue); + return this; + } + + @Override + public UnionWithTypeMember build() { + return new UnionWithTypeMember(this); + } + + @Override + public List> sdkFields() { + return SDK_FIELDS; + } + + @Override + public Map> sdkFieldNameToField() { + return SDK_NAME_TO_FIELD; + } + + private final void handleUnionValueChange(Type type, Object oldValue, Object newValue) { + if (this.type == type || oldValue == newValue) { + return; + } + if (newValue == null || newValue instanceof SdkAutoConstructList || newValue instanceof SdkAutoConstructMap) { + setTypes.remove(type); + } else if (oldValue == null || oldValue instanceof SdkAutoConstructList || oldValue instanceof SdkAutoConstructMap) { + setTypes.add(type); + } + if (setTypes.size() == 1) { + this.type = setTypes.iterator().next(); + } else if (setTypes.isEmpty()) { + this.type = Type.UNKNOWN_TO_SDK_VERSION; + } else { + this.type = null; + } + } + } + + /** + * @see UnionWithTypeMember#type() + */ + public enum Type { + STRING_MEMBER, + + TYPE, + + UNKNOWN_TO_SDK_VERSION + } +}