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}