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 XmlElement(Builder builder)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 */ elementName()52 public String elementName() { 53 return elementName; 54 } 55 56 /** 57 * @return The list of direct children of this element. May be empty. 58 */ children()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 */ getFirstChild()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 */ getElementsByName(String tagName)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 */ getElementByName(String tagName)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 */ getOptionalElementByName(String tagName)104 public Optional<XmlElement> getOptionalElementByName(String tagName) { 105 return Optional.ofNullable(getElementByName(tagName)); 106 } 107 108 /** 109 * @return Text content of this element. 110 */ textContent()111 public String textContent() { 112 return textContent; 113 } 114 115 /** 116 * Retrieves an optional attribute by attribute name. 117 */ getOptionalAttributeByName(String attribute)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 */ attributes()125 public Map<String, String> attributes() { 126 return attributes; 127 } 128 129 /** 130 * @return New {@link Builder} instance. 131 */ builder()132 public static Builder builder() { 133 return new Builder(); 134 } 135 136 /** 137 * @return An empty {@link XmlElement} (<eof/>). 138 */ empty()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 Builder()154 private Builder() { 155 } 156 elementName(String elementName)157 public Builder elementName(String elementName) { 158 this.elementName = elementName; 159 return this; 160 } 161 addChildElement(XmlElement childElement)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 textContent(String textContent)169 public Builder textContent(String textContent) { 170 this.textContent = textContent; 171 return this; 172 } 173 attributes(Map<String, String> attributes)174 public Builder attributes(Map<String, String> attributes) { 175 this.attributes = attributes; 176 return this; 177 } 178 build()179 public XmlElement build() { 180 return new XmlElement(this); 181 } 182 } 183 184 } 185