001/**
002 *
003 * Copyright 2015 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 org.jivesoftware.smack.util.MultiMap;
025import org.jivesoftware.smack.util.Objects;
026import org.jivesoftware.smack.util.StringUtils;
027import org.jivesoftware.smack.util.XmlStringBuilder;
028
029import org.jxmpp.util.XmppStringUtils;
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<String, 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<String, StandardExtensionElement> elements) {
069        this.name = StringUtils.requireNotNullOrEmpty(name, "Name must not be null or empty");
070        this.namespace = StringUtils.requireNotNullOrEmpty(namespace, "Namespace must not be null or 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        String key = XmppStringUtils.generateKey(element, namespace);
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        String key = XmppStringUtils.generateKey(element, namespace);
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() {
136        return toXML(null);
137    }
138
139    public XmlStringBuilder toXML(String enclosingNamespace) {
140        if (xmlCache != null) {
141            return xmlCache;
142        }
143        XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
144        for (Map.Entry<String, String> entry : attributes.entrySet()) {
145            xml.attribute(entry.getKey(), entry.getValue());
146        }
147        xml.rightAngleBracket();
148
149        xml.optEscape(text);
150
151        if (elements != null) {
152            for (Map.Entry<String, StandardExtensionElement> entry : elements.entrySet()) {
153                xml.append(entry.getValue().toXML(getNamespace()));
154            }
155        }
156
157        xml.closeElement(this);
158        xmlCache = xml;
159        return xml;
160    }
161
162    public static Builder builder(String name, String namespace) {
163        return new Builder(name, namespace);
164    }
165
166    public static final class Builder {
167        private final String name;
168        private final String namespace;
169
170        private Map<String, String> attributes;
171        private String text;
172        private MultiMap<String, StandardExtensionElement> elements;
173
174        private Builder(String name, String namespace) {
175            this.name = name;
176            this.namespace = namespace;
177        }
178
179        public Builder addAttribute(String name, String value) {
180            StringUtils.requireNotNullOrEmpty(name, "Attribute name must be set");
181            Objects.requireNonNull(value, "Attribute value must be not null");
182            if (attributes == null) {
183                attributes = new LinkedHashMap<>();
184            }
185            attributes.put(name, value);
186            return this;
187        }
188
189        public Builder addAttributes(Map<String, String> attributes) {
190            if (this.attributes == null) {
191                this.attributes = new LinkedHashMap<>(attributes.size());
192            }
193            this.attributes.putAll(attributes);
194            return this;
195        }
196
197        public Builder setText(String text) {
198            this.text = Objects.requireNonNull(text, "Text must be not null");
199            return this;
200        }
201
202        public Builder addElement(StandardExtensionElement element) {
203            Objects.requireNonNull(element, "Element must not be null");
204            if (elements == null) {
205                elements = new MultiMap<>();
206            }
207            String key = XmppStringUtils.generateKey(element.getElementName(), element.getNamespace());
208            elements.put(key, element);
209            return this;
210        }
211
212        public Builder addElement(String name, String textValue) {
213            StandardExtensionElement element = StandardExtensionElement.builder(name, this.namespace).setText(
214                            textValue).build();
215            return addElement(element);
216        }
217
218        public StandardExtensionElement build() {
219            return new StandardExtensionElement(name, namespace, attributes, text, elements);
220        }
221    }
222}