1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.codegen.model.intermediate;
17 
18 import static software.amazon.awssdk.codegen.internal.Constant.LF;
19 import static software.amazon.awssdk.codegen.internal.Constant.REQUEST_CLASS_SUFFIX;
20 import static software.amazon.awssdk.codegen.internal.Constant.RESPONSE_CLASS_SUFFIX;
21 import static software.amazon.awssdk.codegen.internal.DocumentationUtils.removeFromEnd;
22 
23 import com.fasterxml.jackson.annotation.JsonIgnore;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.stream.Collectors;
30 import software.amazon.awssdk.codegen.model.intermediate.customization.ShapeCustomizationInfo;
31 import software.amazon.awssdk.codegen.model.service.XmlNamespace;
32 import software.amazon.awssdk.utils.StringUtils;
33 
34 public class ShapeModel extends DocumentationModel implements HasDeprecation {
35 
36     private String c2jName;
37     // shapeName might be later modified by the customization.
38     private String shapeName;
39     // the local variable name inside marshaller/unmarshaller implementation
40     private boolean deprecated;
41     private String deprecatedMessage;
42     private String type;
43     private List<String> required;
44     private boolean hasPayloadMember;
45     private boolean hasHeaderMember;
46     private boolean hasStatusCodeMember;
47     private boolean hasStreamingMember;
48     private boolean hasRequiresLengthMember;
49     private boolean wrapper;
50     private boolean simpleMethod;
51     private String requestSignerClassFqcn;
52     private EndpointDiscovery endpointDiscovery;
53 
54     private List<MemberModel> members;
55     private List<EnumModel> enums;
56 
57     private VariableModel variable;
58 
59     private ShapeMarshaller marshaller;
60     private ShapeUnmarshaller unmarshaller;
61 
62     private String errorCode;
63     private Integer httpStatusCode;
64     private boolean fault;
65 
66     private ShapeCustomizationInfo customization = new ShapeCustomizationInfo();
67 
68     private boolean isEventStream;
69 
70     private boolean isEvent;
71 
72     private XmlNamespace xmlNamespace;
73 
74     private boolean document;
75 
76     private boolean union;
77 
ShapeModel()78     public ShapeModel() {
79     }
80 
ShapeModel(String c2jName)81     public ShapeModel(String c2jName) {
82         this.c2jName = c2jName;
83     }
84 
getShapeName()85     public String getShapeName() {
86         return shapeName;
87     }
88 
setShapeName(String shapeName)89     public void setShapeName(String shapeName) {
90         this.shapeName = shapeName;
91     }
92 
93     @Override
isDeprecated()94     public boolean isDeprecated() {
95         return deprecated;
96     }
97 
setDeprecated(boolean deprecated)98     public void setDeprecated(boolean deprecated) {
99         this.deprecated = deprecated;
100     }
101 
getDeprecatedMessage()102     public String getDeprecatedMessage() {
103         return deprecatedMessage;
104     }
105 
setDeprecatedMessage(String deprecatedMessage)106     public void setDeprecatedMessage(String deprecatedMessage) {
107         this.deprecatedMessage = deprecatedMessage;
108     }
109 
getC2jName()110     public String getC2jName() {
111         return c2jName;
112     }
113 
setC2jName(String c2jName)114     public void setC2jName(String c2jName) {
115         this.c2jName = c2jName;
116     }
117 
getType()118     public String getType() {
119         return type;
120     }
121 
122 
123 
124     @JsonIgnore
setType(ShapeType shapeType)125     public void setType(ShapeType shapeType) {
126         setType(shapeType.getValue());
127     }
128 
setType(String type)129     public void setType(String type) {
130         this.type = type;
131     }
132 
133     @JsonIgnore
getShapeType()134     public ShapeType getShapeType() {
135         return ShapeType.fromValue(type);
136     }
137 
withType(String type)138     public ShapeModel withType(String type) {
139         this.type = type;
140         return this;
141     }
142 
143     // Returns the list of C2j member names that are required for this shape.
getRequired()144     public List<String> getRequired() {
145         return required;
146     }
147 
setRequired(List<String> required)148     public void setRequired(List<String> required) {
149         this.required = required;
150     }
151 
isHasPayloadMember()152     public boolean isHasPayloadMember() {
153         return hasPayloadMember;
154     }
155 
setHasPayloadMember(boolean hasPayloadMember)156     public void setHasPayloadMember(boolean hasPayloadMember) {
157         this.hasPayloadMember = hasPayloadMember;
158     }
159 
withHasPayloadMember(boolean hasPayloadMember)160     public ShapeModel withHasPayloadMember(boolean hasPayloadMember) {
161         setHasPayloadMember(hasPayloadMember);
162         return this;
163     }
164 
165     /**
166      * @return The member explicitly designated as the payload member
167      */
168     @JsonIgnore
getPayloadMember()169     public MemberModel getPayloadMember() {
170         MemberModel payloadMember = null;
171         for (MemberModel member : members) {
172             if (member.getHttp().getIsPayload()) {
173                 if (payloadMember == null) {
174                     payloadMember = member;
175                 } else {
176                     throw new IllegalStateException(
177                             String.format("Only one payload member can be explicitly set on %s. This is likely an error in " +
178                                           "the C2J model", c2jName));
179                 }
180             }
181         }
182         return payloadMember;
183     }
184 
185     /**
186      * @return The list of members whose location is not specified. If no payload member is
187      *         explicitly set then these members will appear in the payload
188      */
189     @JsonIgnore
getUnboundMembers()190     public List<MemberModel> getUnboundMembers() {
191         List<MemberModel> unboundMembers = new ArrayList<>();
192         if (members != null) {
193             for (MemberModel member : members) {
194                 if (member.getHttp().getLocation() == null && !member.getHttp().getIsPayload()) {
195                     if (hasPayloadMember) {
196                         // There is an explicit payload, but this unbound
197                         // member isn't it.
198                         // Note: Somewhat unintuitive, explicit payloads don't
199                         // have an explicit location; they're identified by
200                         // the payload HTTP trait being true.
201                         throw new IllegalStateException(String.format(
202                                 "C2J Shape %s has both an explicit payload member and unbound (no explicit location) member, %s."
203                                 + " This is undefined behavior, verify the correctness of the C2J model.",
204                                 c2jName, member.getName()));
205                     }
206                     unboundMembers.add(member);
207                 }
208             }
209         }
210         return unboundMembers;
211     }
212 
213     /**
214      * @return The list of members whose are not marked with either eventheader or eventpayload trait.
215      */
216     @JsonIgnore
getUnboundEventMembers()217     public List<MemberModel> getUnboundEventMembers() {
218         if (members == null) {
219             return new ArrayList<>();
220         }
221 
222         return members.stream()
223                       .filter(m -> !m.isEventHeader())
224                       .filter(m -> !m.isEventPayload())
225                       .collect(Collectors.toList());
226     }
227 
228     /**
229      * @return True if the shape has an explicit payload member or implicit payload member(s).
230      */
hasPayloadMembers()231     public boolean hasPayloadMembers() {
232         return hasPayloadMember ||
233                getExplicitEventPayloadMember() != null ||
234                hasImplicitPayloadMembers();
235 
236     }
237 
hasImplicitPayloadMembers()238     public boolean hasImplicitPayloadMembers() {
239         return !getUnboundMembers().isEmpty() ||
240                hasImplicitEventPayloadMembers();
241     }
242 
hasImplicitEventPayloadMembers()243     public boolean hasImplicitEventPayloadMembers() {
244         return isEvent() && !getUnboundEventMembers().isEmpty();
245     }
246 
247     /**
248      * Explicit event payload member will have "eventpayload" trait set to true.
249      * There can be at most only one member that can be declared as explicit payload.
250      *
251      * @return the member that has the 'eventpayload' trait set to true. If none found, return null.
252      */
getExplicitEventPayloadMember()253     public MemberModel getExplicitEventPayloadMember() {
254         if (members == null) {
255             return null;
256         }
257 
258         return members.stream()
259                       .filter(MemberModel::isEventPayload)
260                       .findFirst()
261                       .orElse(null);
262     }
263 
264     /**
265      * If all members in shape have eventheader trait, then there is no payload
266      */
hasNoEventPayload()267     public boolean hasNoEventPayload() {
268         return members == null || members.stream().allMatch(MemberModel::isEventHeader);
269     }
270 
isHasStreamingMember()271     public boolean isHasStreamingMember() {
272         return hasStreamingMember;
273     }
274 
setHasStreamingMember(boolean hasStreamingMember)275     public void setHasStreamingMember(boolean hasStreamingMember) {
276         this.hasStreamingMember = hasStreamingMember;
277     }
278 
withHasStreamingMember(boolean hasStreamingMember)279     public ShapeModel withHasStreamingMember(boolean hasStreamingMember) {
280         setHasStreamingMember(hasStreamingMember);
281         return this;
282     }
283 
isHasRequiresLengthMember()284     public boolean isHasRequiresLengthMember() {
285         return hasRequiresLengthMember;
286     }
287 
setHasRequiresLengthMember(boolean hasRequiresLengthMember)288     public void setHasRequiresLengthMember(boolean hasRequiresLengthMember) {
289         this.hasRequiresLengthMember = hasRequiresLengthMember;
290     }
291 
withHasRequiresLengthMember(boolean hasRequiresLengthMember)292     public ShapeModel withHasRequiresLengthMember(boolean hasRequiresLengthMember) {
293         setHasRequiresLengthMember(hasRequiresLengthMember);
294         return this;
295     }
296 
isHasHeaderMember()297     public boolean isHasHeaderMember() {
298         return hasHeaderMember;
299     }
300 
setHasHeaderMember(boolean hasHeaderMember)301     public void setHasHeaderMember(boolean hasHeaderMember) {
302         this.hasHeaderMember = hasHeaderMember;
303     }
304 
withHasHeaderMember(boolean hasHeaderMember)305     public ShapeModel withHasHeaderMember(boolean hasHeaderMember) {
306         setHasHeaderMember(hasHeaderMember);
307         return this;
308     }
309 
isHasStatusCodeMember()310     public boolean isHasStatusCodeMember() {
311         return hasStatusCodeMember;
312     }
313 
setHasStatusCodeMember(boolean hasStatusCodeMember)314     public void setHasStatusCodeMember(boolean hasStatusCodeMember) {
315         this.hasStatusCodeMember = hasStatusCodeMember;
316     }
317 
isWrapper()318     public boolean isWrapper() {
319         return wrapper;
320     }
321 
setWrapper(boolean wrapper)322     public void setWrapper(boolean wrapper) {
323         this.wrapper = wrapper;
324     }
325 
isSimpleMethod()326     public boolean isSimpleMethod() {
327         return simpleMethod;
328     }
329 
setSimpleMethod(boolean simpleMethod)330     public void setSimpleMethod(boolean simpleMethod) {
331         this.simpleMethod = simpleMethod;
332     }
333 
withHasStatusCodeMember(boolean hasStatusCodeMember)334     public ShapeModel withHasStatusCodeMember(boolean hasStatusCodeMember) {
335         setHasStatusCodeMember(hasStatusCodeMember);
336         return this;
337     }
338 
getMemberByVariableName(String memberVariableName)339     public MemberModel getMemberByVariableName(String memberVariableName) {
340         for (MemberModel memberModel : members) {
341             if (memberModel.getVariable().getVariableName().equals(memberVariableName)) {
342                 return memberModel;
343             }
344         }
345         throw new IllegalArgumentException("Unknown member variable name: " + memberVariableName);
346     }
347 
getMemberByName(String memberName)348     public MemberModel getMemberByName(String memberName) {
349         for (MemberModel memberModel : members) {
350             if (memberModel.getName().equals(memberName)) {
351                 return memberModel;
352             }
353         }
354         return null;
355     }
356 
getMemberByC2jName(String memberName)357     public MemberModel getMemberByC2jName(String memberName) {
358         for (MemberModel memberModel : members) {
359             if (memberModel.getC2jName().equals(memberName)) {
360                 return memberModel;
361             }
362         }
363         return null;
364     }
365 
getMembers()366     public List<MemberModel> getMembers() {
367         if (members == null) {
368             return Collections.emptyList();
369         }
370         return members;
371     }
372 
373     /**
374      * @return All non-streaming members of the shape.
375      */
getNonStreamingMembers()376     public List<MemberModel> getNonStreamingMembers() {
377         return getMembers().stream()
378                            // Filter out binary streaming members
379                            .filter(m -> !m.getHttp().getIsStreaming())
380                            // Filter out event stream members (if shape is null then it's primitive and we should include it).
381                            .filter(m -> m.getShape() == null || !m.getShape().isEventStream)
382                            .collect(Collectors.toList());
383     }
384 
setMembers(List<MemberModel> members)385     public void setMembers(List<MemberModel> members) {
386         this.members = members;
387     }
388 
addMember(MemberModel member)389     public void addMember(MemberModel member) {
390         if (this.members == null) {
391             this.members = new ArrayList<>();
392         }
393         members.add(member);
394     }
395 
getEnums()396     public List<EnumModel> getEnums() {
397         return enums;
398     }
399 
setEnums(List<EnumModel> enums)400     public void setEnums(List<EnumModel> enums) {
401         this.enums = enums;
402     }
403 
addEnum(EnumModel enumModel)404     public void addEnum(EnumModel enumModel) {
405         if (this.enums == null) {
406             this.enums = new ArrayList<>();
407         }
408         this.enums.add(enumModel);
409     }
410 
getVariable()411     public VariableModel getVariable() {
412         return variable;
413     }
414 
setVariable(VariableModel variable)415     public void setVariable(VariableModel variable) {
416         this.variable = variable;
417     }
418 
getMarshaller()419     public ShapeMarshaller getMarshaller() {
420         return marshaller;
421     }
422 
setMarshaller(ShapeMarshaller marshaller)423     public void setMarshaller(ShapeMarshaller marshaller) {
424         this.marshaller = marshaller;
425     }
426 
getUnmarshaller()427     public ShapeUnmarshaller getUnmarshaller() {
428         return unmarshaller;
429     }
430 
setUnmarshaller(ShapeUnmarshaller unmarshaller)431     public void setUnmarshaller(ShapeUnmarshaller unmarshaller) {
432         this.unmarshaller = unmarshaller;
433     }
434 
getCustomization()435     public ShapeCustomizationInfo getCustomization() {
436         return customization;
437     }
438 
setCustomization(ShapeCustomizationInfo customization)439     public void setCustomization(ShapeCustomizationInfo customization) {
440         this.customization = customization;
441     }
442 
getMembersAsMap()443     public Map<String, MemberModel> getMembersAsMap() {
444         Map<String, MemberModel> shapeMembers = new HashMap<>();
445 
446         // Creating a map of shape's members. This map is used below when
447         // fetching the details of a member.
448         List<MemberModel> memberModels = getMembers();
449         if (memberModels != null) {
450             for (MemberModel model : memberModels) {
451                 shapeMembers.put(model.getName(), model);
452             }
453         }
454         return shapeMembers;
455     }
456 
457     /**
458      * Tries to find the member model associated with the given c2j member name from this shape
459      * model. Returns the member model if present else returns null.
460      */
tryFindMemberModelByC2jName(String memberC2jName, boolean ignoreCase)461     public MemberModel tryFindMemberModelByC2jName(String memberC2jName, boolean ignoreCase) {
462 
463         List<MemberModel> memberModels = getMembers();
464         String expectedName = ignoreCase ? StringUtils.lowerCase(memberC2jName)
465                                                : memberC2jName;
466 
467         if (memberModels != null) {
468             for (MemberModel member : memberModels) {
469                 String actualName = ignoreCase ? StringUtils.lowerCase(member.getC2jName())
470                                                : member.getC2jName();
471 
472                 if (expectedName.equals(actualName)) {
473                     return member;
474                 }
475             }
476         }
477         return null;
478     }
479 
480     /**
481      * Returns the member model associated with the given c2j member name from this shape model.
482      */
findMemberModelByC2jName(String memberC2jName)483     public MemberModel findMemberModelByC2jName(String memberC2jName) {
484 
485         MemberModel model = tryFindMemberModelByC2jName(memberC2jName, false);
486 
487         if (model == null) {
488             throw new IllegalArgumentException(memberC2jName + " member (c2j name) does not exist in the shape.");
489         }
490 
491         return model;
492     }
493 
494     /**
495      * Takes in the c2j member name as input and removes if the shape contains a member with the
496      * given name. Return false otherwise.
497      */
removeMemberByC2jName(String memberC2jName, boolean ignoreCase)498     public boolean removeMemberByC2jName(String memberC2jName, boolean ignoreCase) {
499         // Implicitly depending on the default equals and hashcode
500         // implementation of the class MemberModel
501         MemberModel model = tryFindMemberModelByC2jName(memberC2jName, ignoreCase);
502         return model == null ? false : members.remove(model);
503     }
504 
505     /**
506      * Returns the enum model for the given enum value.
507      * Returns null if no such enum value exists.
508      */
findEnumModelByValue(String enumValue)509     public EnumModel findEnumModelByValue(String enumValue) {
510 
511         if (enums != null) {
512             for (EnumModel enumModel : enums) {
513                 if (enumValue.equals(enumModel.getValue())) {
514                     return enumModel;
515                 }
516             }
517         }
518         return null;
519     }
520 
521     @JsonIgnore
getDocumentationShapeName()522     public String getDocumentationShapeName() {
523         switch (getShapeType()) {
524             case Request:
525                 return removeFromEnd(shapeName, REQUEST_CLASS_SUFFIX);
526             case Response:
527                 return removeFromEnd(shapeName, RESPONSE_CLASS_SUFFIX);
528             default:
529                 return c2jName;
530         }
531     }
532 
getUnionTypeGetterDocumentation()533     public String getUnionTypeGetterDocumentation() {
534         return "Retrieve an enum value representing which member of this object is populated. "
535                + LF + LF
536                + "When this class is returned in a service response, this will be {@link Type#UNKNOWN_TO_SDK_VERSION} if the "
537                + "service returned a member that is only known to a newer SDK version."
538                + LF + LF
539                + "When this class is created directly in your code, this will be {@link Type#UNKNOWN_TO_SDK_VERSION} if zero "
540                + "members are set, and {@code null} if more than one member is set.";
541     }
542 
543     @Override
toString()544     public String toString() {
545         return shapeName;
546     }
547 
getErrorCode()548     public String getErrorCode() {
549         return errorCode;
550     }
551 
setErrorCode(String errorCode)552     public void setErrorCode(String errorCode) {
553         this.errorCode = errorCode;
554     }
555 
556     /**
557      * Return the httpStatusCode of the exception shape. This value is present only for modeled exceptions.
558      */
getHttpStatusCode()559     public Integer getHttpStatusCode() {
560         return httpStatusCode;
561     }
562 
setHttpStatusCode(Integer httpStatusCode)563     public void setHttpStatusCode(Integer httpStatusCode) {
564         this.httpStatusCode = httpStatusCode;
565     }
566 
isRequestSignerAware()567     public boolean isRequestSignerAware() {
568         return requestSignerClassFqcn != null;
569     }
570 
getRequestSignerClassFqcn()571     public String getRequestSignerClassFqcn() {
572         return requestSignerClassFqcn;
573     }
574 
setRequestSignerClassFqcn(String authorizerClass)575     public void setRequestSignerClassFqcn(String authorizerClass) {
576         this.requestSignerClassFqcn = authorizerClass;
577     }
578 
getEndpointDiscovery()579     public EndpointDiscovery getEndpointDiscovery() {
580         return endpointDiscovery;
581     }
582 
setEndpointDiscovery(EndpointDiscovery endpointDiscovery)583     public void setEndpointDiscovery(EndpointDiscovery endpointDiscovery) {
584         this.endpointDiscovery = endpointDiscovery;
585     }
586 
587     /**
588      * @return True if the shape is an 'eventstream' shape. The eventstream shape is the tagged union like
589      * container that holds individual 'events'.
590      */
isEventStream()591     public boolean isEventStream() {
592         return this.isEventStream;
593     }
594 
withIsEventStream(boolean isEventStream)595     public ShapeModel withIsEventStream(boolean isEventStream) {
596         this.isEventStream = isEventStream;
597         return this;
598     }
599 
600     /**
601      * @return True if the shape is an 'event'. I.E. It is a member of the eventstream and represents one logical event
602      * that can be delivered on the event stream.
603      */
isEvent()604     public boolean isEvent() {
605         return this.isEvent;
606     }
607 
withIsEvent(boolean isEvent)608     public ShapeModel withIsEvent(boolean isEvent) {
609         this.isEvent = isEvent;
610         return this;
611     }
612 
getXmlNamespace()613     public XmlNamespace getXmlNamespace() {
614         return xmlNamespace;
615     }
616 
withXmlNamespace(XmlNamespace xmlNamespace)617     public ShapeModel withXmlNamespace(XmlNamespace xmlNamespace) {
618         this.xmlNamespace = xmlNamespace;
619         return this;
620     }
621 
setXmlNamespace(XmlNamespace xmlNamespace)622     public void setXmlNamespace(XmlNamespace xmlNamespace) {
623         this.xmlNamespace = xmlNamespace;
624     }
625 
isDocument()626     public boolean isDocument() {
627         return document;
628     }
629 
withIsDocument(boolean document)630     public ShapeModel withIsDocument(boolean document) {
631         this.document = document;
632         return this;
633     }
634 
isUnion()635     public boolean isUnion() {
636         return union;
637     }
638 
withIsUnion(boolean union)639     public void withIsUnion(boolean union) {
640         this.union = union;
641     }
642 
isFault()643     public boolean isFault() {
644         return fault;
645     }
646 
withIsFault(boolean fault)647     public ShapeModel withIsFault(boolean fault) {
648         this.fault = fault;
649         return this;
650     }
651 }
652