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}