Skip to content

Commit e2a4cc1

Browse files
authored
Added support for modeling union types. (#3124)
* Added support for modeling union types. Service models marked as a union type have additional helper methods to take advantage of the knowledge that just one member is expected to be set. More information: #3119 * Addressed comments: two values set is now type() == null instead of type() == UNKNOWN_TO_SDK_VERSION.
1 parent 064d697 commit e2a4cc1

File tree

20 files changed

+4848
-81
lines changed

20 files changed

+4848
-81
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "AWS SDK for Java v2",
3+
"contributor": "",
4+
"type": "feature",
5+
"description": "Allow services to model structures with mutually exclusive fields (union types). Such structures have additional static constructors and the ability to query for which field is populated. Services which support this feature at launch: accessanalyzer, appconfig, appconfigdata, appmesh, connect, emrcontainers, evidently, grafana, groundstation, healthlake, inspector2, iottwinmaker, migrationhubstrategy, nimble, panorama, proton, rdsdata, redshiftdata, s3control, snowdevicemanagement, ssmincidents, transcribe, wisdom."
6+
}

codegen/src/main/java/software/amazon/awssdk/codegen/AddShapes.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ protected final ShapeModel generateShapeModel(String javaClassName, String shape
8484
shapeModel.withIsEventStream(shape.isEventstream());
8585
shapeModel.withIsEvent(shape.isEvent());
8686
shapeModel.withXmlNamespace(shape.getXmlNamespace());
87+
shapeModel.withIsUnion(shape.isUnion());
8788

8889
boolean hasHeaderMember = false;
8990
boolean hasStatusCodeMember = false;
@@ -188,6 +189,8 @@ private MemberModel generateMemberModel(String c2jMemberName, Member c2jMemberDe
188189
memberModel.setEventHeader(c2jMemberDefinition.isEventheader());
189190
memberModel.setEndpointDiscoveryId(c2jMemberDefinition.isEndpointdiscoveryid());
190191
memberModel.setXmlAttribute(c2jMemberDefinition.isXmlAttribute());
192+
memberModel.setUnionEnumTypeName(namingStrategy.getUnionEnumTypeName(memberModel));
193+
191194

192195
// Pass the xmlNameSpace from the member reference
193196
if (c2jMemberDefinition.getXmlNamespace() != null) {

codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/MemberModel.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public class MemberModel extends DocumentationModel {
8484

8585
private String beanStyleSetterName;
8686

87+
private String unionEnumTypeName;
88+
8789
private boolean isJsonValue;
8890

8991
private String timestampFormat;
@@ -481,7 +483,7 @@ public String getDeprecatedSetterDocumentation() {
481483
public String getDefaultConsumerFluentSetterDocumentation() {
482484
return (StringUtils.isNotBlank(documentation) ? documentation : defaultSetter().replace("%s", name) + "\n")
483485
+ LF
484-
+ "This is a convenience that creates an instance of the {@link "
486+
+ "This is a convenience method that creates an instance of the {@link "
485487
+ variable.getSimpleType()
486488
+ ".Builder} avoiding the need to create one manually via {@link "
487489
+ variable.getSimpleType()
@@ -509,6 +511,13 @@ public String getDefaultConsumerFluentSetterDocumentation() {
509511
+ ")";
510512
}
511513

514+
public String getUnionConstructorDocumentation() {
515+
return "Create an instance of this class with {@link #" + this.getFluentGetterMethodName() +
516+
"()} initialized to the given value." +
517+
LF + LF +
518+
getSetterDocumentation();
519+
}
520+
512521
private String getParamDoc() {
513522
return LF
514523
+ "@param "
@@ -726,4 +735,12 @@ public String getDeprecatedBeanStyleSetterMethodName() {
726735
public void setDeprecatedBeanStyleSetterMethodName(String deprecatedBeanStyleSetterMethodName) {
727736
this.deprecatedBeanStyleSetterMethodName = deprecatedBeanStyleSetterMethodName;
728737
}
738+
739+
public String getUnionEnumTypeName() {
740+
return unionEnumTypeName;
741+
}
742+
743+
public void setUnionEnumTypeName(String unionEnumTypeName) {
744+
this.unionEnumTypeName = unionEnumTypeName;
745+
}
729746
}

codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/ShapeModel.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
package software.amazon.awssdk.codegen.model.intermediate;
1717

18+
import static software.amazon.awssdk.codegen.internal.Constant.LF;
1819
import static software.amazon.awssdk.codegen.internal.Constant.REQUEST_CLASS_SUFFIX;
1920
import static software.amazon.awssdk.codegen.internal.Constant.RESPONSE_CLASS_SUFFIX;
2021
import static software.amazon.awssdk.codegen.internal.DocumentationUtils.removeFromEnd;
@@ -71,6 +72,8 @@ public class ShapeModel extends DocumentationModel implements HasDeprecation {
7172

7273
private boolean document;
7374

75+
private boolean union;
76+
7477
public ShapeModel() {
7578
}
7679

@@ -526,6 +529,16 @@ public String getDocumentationShapeName() {
526529
}
527530
}
528531

532+
public String getUnionTypeGetterDocumentation() {
533+
return "Retrieve an enum value representing which member of this object is populated. "
534+
+ LF + LF
535+
+ "When this class is returned in a service response, this will be {@link Type#UNKNOWN_TO_SDK_VERSION} if the "
536+
+ "service returned a member that is only known to a newer SDK version."
537+
+ LF + LF
538+
+ "When this class is created directly in your code, this will be {@link Type#UNKNOWN_TO_SDK_VERSION} if zero "
539+
+ "members are set, and {@code null} if more than one member is set.";
540+
}
541+
529542
@Override
530543
public String toString() {
531544
return shapeName;
@@ -617,4 +630,12 @@ public ShapeModel withIsDocument(boolean document) {
617630
this.document = document;
618631
return this;
619632
}
633+
634+
public boolean isUnion() {
635+
return union;
636+
}
637+
638+
public void withIsUnion(boolean union) {
639+
this.union = union;
640+
}
620641
}

codegen/src/main/java/software/amazon/awssdk/codegen/model/service/Shape.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ public class Shape {
7474

7575
private boolean document;
7676

77+
private boolean union;
78+
7779
public boolean isFault() {
7880
return fault;
7981
}
@@ -318,4 +320,11 @@ public void setDocument(boolean document) {
318320
this.document = document;
319321
}
320322

323+
public boolean isUnion() {
324+
return union;
325+
}
326+
327+
public void setUnion(boolean union) {
328+
this.union = union;
329+
}
321330
}

codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,11 @@ public String getSdkFieldFieldName(MemberModel memberModel) {
363363
return screamCase(memberModel.getName()) + "_FIELD";
364364
}
365365

366+
@Override
367+
public String getUnionEnumTypeName(MemberModel memberModel) {
368+
return screamCase(memberModel.getName());
369+
}
370+
366371
private String rewriteInvalidMemberName(String memberName, Shape parentShape) {
367372
if (isJavaKeyword(memberName) || isDisallowedNameForShape(memberName, parentShape)) {
368373
return Utils.unCapitalize(memberName + CONFLICTING_NAME_SUFFIX);
@@ -372,6 +377,11 @@ private String rewriteInvalidMemberName(String memberName, Shape parentShape) {
372377
}
373378

374379
private boolean isDisallowedNameForShape(String name, Shape parentShape) {
380+
// Reserve the name "type" for union shapes.
381+
if (parentShape.isUnion() && name.equals("type")) {
382+
return true;
383+
}
384+
375385
if (parentShape.isException()) {
376386
return RESERVED_EXCEPTION_METHOD_NAMES.contains(name);
377387
} else {

codegen/src/main/java/software/amazon/awssdk/codegen/naming/NamingStrategy.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,14 @@ public interface NamingStrategy {
147147
*/
148148
String getSdkFieldFieldName(MemberModel memberModel);
149149

150+
/**
151+
* Returns the name of the provided member as if it will be included in an enum (as in, when the parent shape is a union
152+
* and we need to create an enum with each member name in it).
153+
*
154+
* @param memberModel Member to generate the union enum type name for.
155+
*/
156+
String getUnionEnumTypeName(MemberModel memberModel);
157+
150158
/**
151159
* Names a method that would check for existence of the member in the response.
152160
*

codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/AbstractMemberSetters.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,26 @@ private MethodSpec.Builder setterDeclaration(String methodName, ParameterSpec pa
201201
}
202202

203203
private CodeBlock copySetterBody(String copyAssignment, String regularAssignment, String copyMethodName) {
204+
CodeBlock.Builder body = CodeBlock.builder();
205+
206+
if (shapeModel.isUnion()) {
207+
body.addStatement("Object oldValue = this.$N", fieldName());
208+
}
209+
204210
Optional<ClassName> copierClass = serviceModelCopiers.copierClassFor(memberModel);
205211

206-
return copierClass.map(className -> CodeBlock.builder().addStatement(copyAssignment,
207-
fieldName(),
208-
className,
209-
copyMethodName)
210-
.build())
211-
.orElseGet(() -> CodeBlock.builder().addStatement(regularAssignment, fieldName()).build());
212+
if (copierClass.isPresent()) {
213+
body.addStatement(copyAssignment, fieldName(), copierClass.get(), copyMethodName);
214+
} else {
215+
body.addStatement(regularAssignment, fieldName());
216+
}
217+
218+
if (shapeModel.isUnion()) {
219+
body.addStatement("handleUnionValueChange(Type.$N, oldValue, this.$N)",
220+
memberModel.getUnionEnumTypeName(),
221+
fieldName());
222+
}
223+
224+
return body.build();
212225
}
213226
}

0 commit comments

Comments
 (0)