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;
028import org.jxmpp.util.XmppStringUtils;
029
030/**
031 * An {@link ExtensionElement} modeling the often required and used XML features when using XMPP. It
032 * is therefore suitable for most use cases. Use
033 * {@link StandardExtensionElement#builder(String, String)} to build these elements.
034 * <p>
035 * Note the this is only meant as catch-all if no particular extension element provider is
036 * registered. Protocol implementations should prefer to model their own extension elements tailored
037 * to their use cases.
038 * </p>
039 *
040 * @since 4.2
041 * @author Florian Schmaus
042 */
043public final class StandardExtensionElement implements ExtensionElement {
044
045    private final String name;
046    private final String namespace;
047    private final Map<String, String> attributes;
048    private final String text;
049    private final MultiMap<String, StandardExtensionElement> elements;
050
051    private XmlStringBuilder xmlCache;
052
053    /**
054     * Constructs a new extension element with the given name and namespace and nothing else.
055     * <p>
056     * This is meant to construct extension elements used as simple flags in Stanzas.
057     * <p>
058     *
059     * @param name the name of the extension element.
060     * @param namespace the namespace of the extension element.
061     */
062    public StandardExtensionElement(String name, String namespace) {
063        this(name, namespace, null, null, null);
064    }
065
066    private StandardExtensionElement(String name, String namespace, Map<String, String> attributes, String text,
067                    MultiMap<String, StandardExtensionElement> elements) {
068        this.name = StringUtils.requireNotNullOrEmpty(name, "Name must not be null or empty");
069        this.namespace = StringUtils.requireNotNullOrEmpty(namespace, "Namespace must not be null or empty");
070        if (attributes == null) {
071            this.attributes = Collections.emptyMap();
072        }
073        else {
074            this.attributes = attributes;
075        }
076        this.text = text;
077        this.elements = elements;
078    }
079
080    @Override
081    public String getElementName() {
082        return name;
083    }
084
085    @Override
086    public String getNamespace() {
087        return namespace;
088    }
089
090    public String getAttributeValue(String attribute) {
091        return attributes.get(attribute);
092    }
093
094    public Map<String, String> getAttributes() {
095        return Collections.unmodifiableMap(attributes);
096    }
097
098    public StandardExtensionElement getFirstElement(String element, String namespace) {
099        if (elements == null) {
100            return null;
101        }
102        String key = XmppStringUtils.generateKey(element, namespace);
103        return elements.getFirst(key);
104    }
105
106    public StandardExtensionElement getFirstElement(String element) {
107        return getFirstElement(element, namespace);
108    }
109
110    public List<StandardExtensionElement> getElements(String element, String namespace) {
111        if (elements == null) {
112            return null;
113        }
114        String key = XmppStringUtils.generateKey(element, namespace);
115        return elements.getAll(key);
116    }
117
118    public List<StandardExtensionElement> getElements(String element) {
119        return getElements(element, namespace);
120    }
121
122    public List<StandardExtensionElement> getElements() {
123        if (elements == null){
124            return Collections.emptyList();
125        }
126        return elements.values();
127    }
128
129    public String getText() {
130        return text;
131    }
132
133    @Override
134    public XmlStringBuilder toXML() {
135        return toXML(null);
136    }
137
138    public XmlStringBuilder toXML(String enclosingNamespace) {
139        if (xmlCache != null) {
140            return xmlCache;
141        }
142        XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
143        for (Map.Entry<String, String> entry : attributes.entrySet()) {
144            xml.attribute(entry.getKey(), entry.getValue());
145        }
146        xml.rightAngleBracket();
147
148        xml.optEscape(text);
149
150        if (elements != null) {
151            for (Map.Entry<String, StandardExtensionElement> entry : elements.entrySet()) {
152                xml.append(entry.getValue().toXML(getNamespace()));
153            }
154        }
155
156        xml.closeElement(this);
157        xmlCache = xml;
158        return xml;
159    }
160
161    public static Builder builder(String name, String namespace) {
162        return new Builder(name, namespace);
163    }
164
165    public static final class Builder {
166        private final String name;
167        private final String namespace;
168
169        private Map<String, String> attributes;
170        private String text;
171        private MultiMap<String, StandardExtensionElement> elements;
172
173        private Builder(String name, String namespace) {
174            this.name = name;
175            this.namespace = namespace;
176        }
177
178        public Builder addAttribute(String name, String value) {
179            StringUtils.requireNotNullOrEmpty(name, "Attribute name must be set");
180            Objects.requireNonNull(value, "Attribute value must be not null");
181            if (attributes == null) {
182                attributes = new LinkedHashMap<>();
183            }
184            attributes.put(name, value);
185            return this;
186        }
187
188        public Builder addAttributes(Map<String, String> attributes) {
189            if (this.attributes == null) {
190                this.attributes = new LinkedHashMap<>(attributes.size());
191            }
192            this.attributes.putAll(attributes);
193            return this;
194        }
195
196        public Builder setText(String text) {
197            this.text = Objects.requireNonNull(text, "Text must be not null");
198            return this;
199        }
200
201        public Builder addElement(StandardExtensionElement element) {
202            Objects.requireNonNull(element, "Element must not be null");
203            if (elements == null) {
204                elements = new MultiMap<>();
205            }
206            String key = XmppStringUtils.generateKey(element.getElementName(), element.getNamespace());
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}