001/** 002 * 003 * Copyright 2015-2020 Florian Schmaus. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack.packet; 018 019import java.util.Collections; 020import java.util.LinkedHashMap; 021import java.util.List; 022import java.util.Map; 023 024import javax.xml.namespace.QName; 025 026import org.jivesoftware.smack.util.MultiMap; 027import org.jivesoftware.smack.util.Objects; 028import org.jivesoftware.smack.util.StringUtils; 029import org.jivesoftware.smack.util.XmlStringBuilder; 030 031/** 032 * An {@link ExtensionElement} modeling the often required and used XML features when using XMPP. It 033 * is therefore suitable for most use cases. Use 034 * {@link StandardExtensionElement#builder(String, String)} to build these elements. 035 * <p> 036 * Note the this is only meant as catch-all if no particular extension element provider is 037 * registered. Protocol implementations should prefer to model their own extension elements tailored 038 * to their use cases. 039 * </p> 040 * 041 * @since 4.2 042 * @author Florian Schmaus 043 */ 044public final class StandardExtensionElement implements ExtensionElement { 045 046 private final String name; 047 private final String namespace; 048 private final Map<String, String> attributes; 049 private final String text; 050 private final MultiMap<QName, StandardExtensionElement> elements; 051 052 private XmlStringBuilder xmlCache; 053 054 /** 055 * Constructs a new extension element with the given name and namespace and nothing else. 056 * <p> 057 * This is meant to construct extension elements used as simple flags in Stanzas. 058 * <p> 059 * 060 * @param name the name of the extension element. 061 * @param namespace the namespace of the extension element. 062 */ 063 public StandardExtensionElement(String name, String namespace) { 064 this(name, namespace, null, null, null); 065 } 066 067 private StandardExtensionElement(String name, String namespace, Map<String, String> attributes, String text, 068 MultiMap<QName, StandardExtensionElement> elements) { 069 this.name = StringUtils.requireNotNullNorEmpty(name, "Name must not be null nor empty"); 070 this.namespace = StringUtils.requireNotNullNorEmpty(namespace, "Namespace must not be null nor empty"); 071 if (attributes == null) { 072 this.attributes = Collections.emptyMap(); 073 } 074 else { 075 this.attributes = attributes; 076 } 077 this.text = text; 078 this.elements = elements; 079 } 080 081 @Override 082 public String getElementName() { 083 return name; 084 } 085 086 @Override 087 public String getNamespace() { 088 return namespace; 089 } 090 091 public String getAttributeValue(String attribute) { 092 return attributes.get(attribute); 093 } 094 095 public Map<String, String> getAttributes() { 096 return Collections.unmodifiableMap(attributes); 097 } 098 099 public StandardExtensionElement getFirstElement(String element, String namespace) { 100 if (elements == null) { 101 return null; 102 } 103 QName key = new QName(namespace, element); 104 return elements.getFirst(key); 105 } 106 107 public StandardExtensionElement getFirstElement(String element) { 108 return getFirstElement(element, namespace); 109 } 110 111 public List<StandardExtensionElement> getElements(String element, String namespace) { 112 if (elements == null) { 113 return null; 114 } 115 QName key = new QName(namespace, element); 116 return elements.getAll(key); 117 } 118 119 public List<StandardExtensionElement> getElements(String element) { 120 return getElements(element, namespace); 121 } 122 123 public List<StandardExtensionElement> getElements() { 124 if (elements == null) { 125 return Collections.emptyList(); 126 } 127 return elements.values(); 128 } 129 130 public String getText() { 131 return text; 132 } 133 134 @Override 135 public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { 136 if (xmlCache != null) { 137 return xmlCache; 138 } 139 XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); 140 for (Map.Entry<String, String> entry : attributes.entrySet()) { 141 xml.attribute(entry.getKey(), entry.getValue()); 142 } 143 xml.rightAngleBracket(); 144 145 if (text != null) { 146 xml.text(text); 147 } 148 149 if (elements != null) { 150 for (Map.Entry<QName, StandardExtensionElement> entry : elements.entrySet()) { 151 xml.append(entry.getValue().toXML(getNamespace())); 152 } 153 } 154 155 xml.closeElement(this); 156 xmlCache = xml; 157 return xml; 158 } 159 160 public static Builder builder(String name, String namespace) { 161 return new Builder(name, namespace); 162 } 163 164 public static final class Builder { 165 private final String name; 166 private final String namespace; 167 168 private Map<String, String> attributes; 169 private String text; 170 private MultiMap<QName, StandardExtensionElement> elements; 171 172 private Builder(String name, String namespace) { 173 this.name = name; 174 this.namespace = namespace; 175 } 176 177 public Builder addAttribute(String name, String value) { 178 StringUtils.requireNotNullNorEmpty(name, "Attribute name must be set"); 179 Objects.requireNonNull(value, "Attribute value must be not null"); 180 if (attributes == null) { 181 attributes = new LinkedHashMap<>(); 182 } 183 attributes.put(name, value); 184 return this; 185 } 186 187 public Builder addAttributes(Map<String, String> attributes) { 188 if (this.attributes == null) { 189 this.attributes = new LinkedHashMap<>(attributes.size()); 190 } 191 this.attributes.putAll(attributes); 192 return this; 193 } 194 195 public Builder setText(String text) { 196 this.text = Objects.requireNonNull(text, "Text must be not null"); 197 return this; 198 } 199 200 public Builder addElement(StandardExtensionElement element) { 201 Objects.requireNonNull(element, "Element must not be null"); 202 if (elements == null) { 203 elements = new MultiMap<>(); 204 } 205 206 QName key = element.getQName(); 207 elements.put(key, element); 208 return this; 209 } 210 211 public Builder addElement(String name, String textValue) { 212 StandardExtensionElement element = StandardExtensionElement.builder(name, this.namespace).setText( 213 textValue).build(); 214 return addElement(element); 215 } 216 217 public StandardExtensionElement build() { 218 return new StandardExtensionElement(name, namespace, attributes, text, elements); 219 } 220 } 221}