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.protocols.query.unmarshall;
17
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Optional;
24 import software.amazon.awssdk.annotations.SdkProtectedApi;
25 import software.amazon.awssdk.core.exception.SdkClientException;
26
27 /**
28  * Represents an element in an XML document.
29  */

30 @SdkProtectedApi
31 public final class XmlElement {
32
33     private static final XmlElement EMPTY = XmlElement.builder().elementName("eof").build();
34
35     private final String elementName;
36     private final HashMap<String, List<XmlElement>> childrenByElement;
37     private final List<XmlElement> children;
38     private final String textContent;
39     private final Map<String, String> attributes;
40
41     private XmlElement(Builder builder) {
42         this.elementName = builder.elementName;
43         this.childrenByElement = new HashMap<>(builder.childrenByElement);
44         this.children = Collections.unmodifiableList(new ArrayList<>(builder.children));
45         this.textContent = builder.textContent;
46         this.attributes = Collections.unmodifiableMap(new HashMap<>(builder.attributes));
47     }
48
49     /**
50      * @return Tag name of the element.
51      */

52     public String elementName() {
53         return elementName;
54     }
55
56     /**
57      * @return The list of direct children of this element. May be empty.
58      */

59     public List<XmlElement> children() {
60         return children;
61     }
62
63     /**
64      * @return The first child element of this element. Null if this element has no children.
65      */

66     public XmlElement getFirstChild() {
67         return children.isEmpty() ? null : children.get(0);
68     }
69
70     /**
71      * Get all child elements by the given tag name. This only returns direct children elements.
72      *
73      * @param tagName Tag name of elements to retrieve.
74      * @return List of elements or empty list of no elements found with given name.
75      */

76     public List<XmlElement> getElementsByName(String tagName) {
77         return childrenByElement.getOrDefault(tagName, Collections.emptyList());
78     }
79
80     /**
81      * Retrieves a single child element by tag name. If more than one element is found then this method will throw an exception.
82      *
83      * @param tagName Tag name of element to get.
84      * @return XmlElement with the matching tag name or null if no element exists.
85      * @throws SdkClientException If more than one element with the given tag name is found.
86      */

87     public XmlElement getElementByName(String tagName) {
88         List<XmlElement> elementsByName = getElementsByName(tagName);
89         if (elementsByName.size() > 1) {
90             throw SdkClientException.create(
91                 String.format("Did not expect more than one element with the name %s in the XML event %s",
92                               tagName, this.elementName));
93         }
94         return elementsByName.size() == 1 ? elementsByName.get(0) : null;
95     }
96
97     /**
98      * Retrieves a single child element by tag name. If more than one element is found then this method will throw an exception.
99      *
100      * @param tagName Tag name of element to get.
101      * @return Fulfilled {@link Optional} of XmlElement with the matching tag name or empty {@link Optional} if no element exists.
102      * @throws SdkClientException If more than one element with the given tag name is found.
103      */

104     public Optional<XmlElement> getOptionalElementByName(String tagName) {
105         return Optional.ofNullable(getElementByName(tagName));
106     }
107
108     /**
109      * @return Text content of this element.
110      */

111     public String textContent() {
112         return textContent;
113     }
114
115     /**
116      * Retrieves an optional attribute by attribute name.
117      */

118     public Optional<String> getOptionalAttributeByName(String attribute) {
119         return Optional.ofNullable(attributes.get(attribute));
120     }
121
122     /**
123      * Retrieves the attributes associated with the element
124      */

125     public Map<String, String> attributes() {
126         return attributes;
127     }
128
129     /**
130      * @return New {@link Builder} instance.
131      */

132     public static Builder builder() {
133         return new Builder();
134     }
135
136     /**
137      * @return An empty {@link XmlElement} (<eof/>).
138      */

139     public static XmlElement empty() {
140         return EMPTY;
141     }
142
143     /**
144      * Builder for {@link XmlElement}.
145      */

146     public static final class Builder {
147
148         private String elementName;
149         private final Map<String, List<XmlElement>> childrenByElement = new HashMap<>();
150         private final List<XmlElement> children = new ArrayList<>();
151         private String textContent = "";
152         private Map<String, String> attributes = new HashMap<>();
153
154         private Builder() {
155         }
156
157         public Builder elementName(String elementName) {
158             this.elementName = elementName;
159             return this;
160         }
161
162         public Builder addChildElement(XmlElement childElement) {
163             this.childrenByElement.computeIfAbsent(childElement.elementName(), s -> new ArrayList<>());
164             this.childrenByElement.get(childElement.elementName()).add(childElement);
165             this.children.add(childElement);
166             return this;
167         }
168
169         public Builder textContent(String textContent) {
170             this.textContent = textContent;
171             return this;
172         }
173
174         public Builder attributes(Map<String, String> attributes) {
175             this.attributes = attributes;
176             return this;
177         }
178
179         public XmlElement build() {
180             return new XmlElement(this);
181         }
182     }
183
184 }
185