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(String 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        xml.optEscape(text);
146
147        if (elements != null) {
148            for (Map.Entry<String, StandardExtensionElement> entry : elements.entrySet()) {
149                xml.append(entry.getValue().toXML(getNamespace()));
150            }
151        }
152
153        xml.closeElement(this);
154        xmlCache = xml;
155        return xml;
156    }
157
158    public static Builder builder(String name, String namespace) {
159        return new Builder(name, namespace);
160    }
161
162    public static final class Builder {
163        private final String name;
164        private final String namespace;
165
166        private Map<String, String> attributes;
167        private String text;
168        private MultiMap<String, StandardExtensionElement> elements;
169
170        private Builder(String name, String namespace) {
171            this.name = name;
172            this.namespace = namespace;
173        }
174
175        public Builder addAttribute(String name, String value) {
176            StringUtils.requireNotNullOrEmpty(name, "Attribute name must be set");
177            Objects.requireNonNull(value, "Attribute value must be not null");
178            if (attributes == null) {
179                attributes = new LinkedHashMap<>();
180            }
181            attributes.put(name, value);
182            return this;
183        }
184
185        public Builder addAttributes(Map<String, String> attributes) {
186            if (this.attributes == null) {
187                this.attributes = new LinkedHashMap<>(attributes.size());
188            }
189            this.attributes.putAll(attributes);
190            return this;
191        }
192
193        public Builder setText(String text) {
194            this.text = Objects.requireNonNull(text, "Text must be not null");
195            return this;
196        }
197
198        public Builder addElement(StandardExtensionElement element) {
199            Objects.requireNonNull(element, "Element must not be null");
200            if (elements == null) {
201                elements = new MultiMap<>();
202            }
203            String key = XmppStringUtils.generateKey(element.getElementName(), element.getNamespace());
204            elements.put(key, element);
205            return this;
206        }
207
208        public Builder addElement(String name, String textValue) {
209            StandardExtensionElement element = StandardExtensionElement.builder(name, this.namespace).setText(
210                            textValue).build();
211            return addElement(element);
212        }
213
214        public StandardExtensionElement build() {
215            return new StandardExtensionElement(name, namespace, attributes, text, elements);
216        }
217    }
218}