/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.impl.protocol.jabber.extensions; import java.net.*; import java.util.*; import org.jivesoftware.smack.packet.*; /** * A generic implementation of <tt>PacketExtension</tt>. The purpose of this * class is quite similar to that of smack's {@link DefaultPacketExtension} * with the main difference being that this one is meant primarily for * extension rather than using as a fallback for unknown elements. We let for * example our descendants handle child elements and we automate attribute * handling instead. * * @author Emil Ivov * @author Lyubomir Marinov */ public abstract class AbstractPacketExtension implements PacketExtension { /** * The name space of this packet extension. Should remain <tt>null</tt> if * there's no namespace associated with this element. */ private String namespace; /** * The name space of this packet extension. Should remain <tt>null</tt> if * there's no namespace associated with this element. */ private final String elementName; /** * A map of all attributes that this extension is currently using. */ protected final Map<String, Object> attributes = new LinkedHashMap<String, Object>(); /** * A list of all packets that are wrapped by this extension. */ private final List<Packet> packets = new LinkedList<Packet>(); /** * The text content of this packet extension, if any. */ private String textContent; /** * A list of extensions registered with this element. */ private final List<PacketExtension> childExtensions = new ArrayList<PacketExtension>(); /** * Creates an {@link AbstractPacketExtension} instance for the specified * <tt>namespace</tt> and <tt>elementName</tt>. * * @param namespace the XML namespace for this element. * @param elementName the name of the element */ protected AbstractPacketExtension(String namespace, String elementName) { this.namespace = namespace; this.elementName = elementName; } /** * Returns the name of the <tt>encryption</tt> element. * * @return the name of the <tt>encryption</tt> element. */ public String getElementName() { return elementName; } /** * Set the XML namespace for this element. * * @param namespace the XML namespace for this element. */ public void setNamespace(String namespace) { this.namespace = namespace; } /** * Returns the XML namespace for this element or <tt>null</tt> if the * element does not live in a namespace of its own. * * @return the XML namespace for this element or <tt>null</tt> if the * element does not live in a namespace of its own. */ public String getNamespace() { return namespace; } /** * Returns an XML representation of this extension. * * @return an XML representation of this extension. */ public String toXML() { StringBuilder bldr = new StringBuilder(); bldr.append("<").append(getElementName()).append(" "); String namespace = getNamespace(); if(namespace != null) bldr.append("xmlns='").append(namespace).append("'"); //add the rest of the attributes if any for(Map.Entry<String, Object> entry : attributes.entrySet()) { bldr.append(" ").append(entry.getKey()).append("='") .append(entry.getValue()).append("'"); } //add child elements if any List<? extends PacketExtension> childElements = getChildExtensions(); String text = getText(); List<Packet> packets = getPackets(); if (childElements == null && packets == null) { if ((text == null) || (text.length() == 0)) { bldr.append("/>"); return bldr.toString(); } else bldr.append('>'); } else { synchronized(childElements) { if (childElements.isEmpty() && packets.isEmpty() && ((text == null) || (text.length() == 0))) { bldr.append("/>"); return bldr.toString(); } else { bldr.append(">"); for(PacketExtension packExt : childElements) bldr.append(packExt.toXML()); for(Packet packet : packets) bldr.append(packet.toXML()); } } } //text content if any if((text != null) && (text.trim().length() > 0)) bldr.append(text); bldr.append("</").append(getElementName()).append(">"); return bldr.toString(); } /** * Returns all sub-elements for this <tt>AbstractPacketExtension</tt> or * <tt>null</tt> if there aren't any. * <p> * Overriding extensions may need to override this method if they would like * to have anything more elaborate than just a list of extensions. * * @return the {@link List} of elements that this packet extension contains. */ public List<? extends PacketExtension> getChildExtensions() { return childExtensions; } /** * Adds the specified <tt>childExtension</tt> to the list of extensions * registered with this packet. * <p/> * Overriding extensions may need to override this method if they would like * to have anything more elaborate than just a list of extensions (e.g. * casting separate instances to more specific. * * @param childExtension the extension we'd like to add here. */ public void addChildExtension(PacketExtension childExtension) { childExtensions.add(childExtension); } /** * Returns the list of packets. * * @return the list of packets. */ public List<Packet> getPackets() { return packets; } /** * Adds packet to the list of packets. * * @param packet the packet to add. */ public void addPacket(Packet packet) { packets.add(packet); } /** * Sets the value of the attribute named <tt>name</tt> to <tt>value</tt>. * * @param name the name of the attribute that we are setting. * @param value an {@link Object} whose <tt>toString()</tt> method returns * the XML value of the attribute we are setting or <tt>null</tt> if we'd * like to remove the attribute with the specified <tt>name</tt>. */ public void setAttribute(String name, Object value) { synchronized(attributes) { if(value != null) this.attributes.put(name, value); else this.attributes.remove(name); } } /** * Removes the attribute with the specified <tt>name</tt> from the list of * attributes registered with this packet extension. * * @param name the name of the attribute that we are removing. */ public void removeAttribute(String name) { synchronized(attributes) { attributes.remove(name); } } /** * Returns the attribute with the specified <tt>name</tt> from the list of * attributes registered with this packet extension. * * @param attribute the name of the attribute that we'd like to retrieve. * * @return the value of the specified <tt>attribute</tt> or <tt>null</tt> * if no such attribute is currently registered with this extension. */ public Object getAttribute(String attribute) { synchronized(attributes) { return attributes.get(attribute); } } /** * Returns the string value of the attribute with the specified * <tt>name</tt>. * * @param attribute the name of the attribute that we'd like to retrieve. * * @return the String value of the specified <tt>attribute</tt> or * <tt>null</tt> if no such attribute is currently registered with this * extension. */ public String getAttributeAsString(String attribute) { synchronized(attributes) { Object attributeVal = attributes.get(attribute); return attributeVal == null ? null : attributeVal.toString(); } } /** * Returns the <tt>int</tt> value of the attribute with the specified * <tt>name</tt>. * * @param attribute the name of the attribute that we'd like to retrieve. * * @return the <tt>int</tt> value of the specified <tt>attribute</tt> or * <tt>-1</tt> if no such attribute is currently registered with this * extension. */ public int getAttributeAsInt(String attribute) { return getAttributeAsInt(attribute, -1); } /** * Returns the <tt>int</tt> value of the attribute with the specified * <tt>name</tt>. * * @param attribute the name of the attribute that we'd like to retrieve * @param defaultValue the <tt>int</tt> to be returned as the value of the * specified attribute if no such attribute is currently registered with * this extension * @return the <tt>int</tt> value of the specified <tt>attribute</tt> or * <tt>defaultValue</tt> if no such attribute is currently registered with * this extension */ public int getAttributeAsInt(String attribute, int defaultValue) { synchronized(attributes) { String value = getAttributeAsString(attribute); return (value == null) ? defaultValue : Integer.parseInt(value); } } /** * Tries to parse the value of the specified <tt>attribute</tt> as an * <tt>URI</tt> and returns it. * * @param attribute the name of the attribute that we'd like to retrieve. * * @return the <tt>URI</tt> value of the specified <tt>attribute</tt> or * <tt>null</tt> if no such attribute is currently registered with this * extension. * @throws IllegalArgumentException if <tt>attribute</tt> is not a valid {@link * URI} */ public URI getAttributeAsURI(String attribute) throws IllegalArgumentException { synchronized(attributes) { String attributeVal = getAttributeAsString(attribute); if (attributeVal == null) return null; try { URI uri = new URI(attributeVal); return uri; } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } } } /** * Gets the names of the attributes which currently have associated values * in this extension. * * @return the names of the attributes which currently have associated * values in this extension */ public List<String> getAttributeNames() { synchronized (attributes) { return new ArrayList<String>(attributes.keySet()); } } /** * Specifies the text content of this extension. * * @param text the text content of this extension. */ public void setText(String text) { this.textContent = text; } /** * Returns the text content of this extension or <tt>null</tt> if no text * content has been specified so far. * * @return the text content of this extension or <tt>null</tt> if no text * content has been specified so far. */ public String getText() { return textContent; } /** * Returns this packet's first direct child extension that matches the * specified <tt>type</tt>. * * @param <T> the specific type of <tt>PacketExtension</tt> to be returned * * @param type the <tt>Class</tt> of the extension we are looking for. * * @return this packet's first direct child extension that matches specified * <tt>type</tt> or <tt>null</tt> if no such child extension was found. */ public <T extends PacketExtension> T getFirstChildOfType(Class<T> type) { List<? extends PacketExtension> childExtensions = getChildExtensions(); synchronized (childExtensions) { for(PacketExtension extension : childExtensions) { if(type.isInstance(extension)) { @SuppressWarnings("unchecked") T extensionAsType = (T) extension; return extensionAsType; } } } return null; } /** * Returns this packet's direct child extensions that match the * specified <tt>type</tt>. * * @param <T> the specific <tt>PacketExtension</tt> type of child extensions * to be returned * * @param type the <tt>Class</tt> of the extension we are looking for. * * @return a (possibly empty) list containing all of this packet's direct * child extensions that match the specified <tt>type</tt> */ public <T extends PacketExtension> List<T> getChildExtensionsOfType( Class<T> type) { List<? extends PacketExtension> childExtensions = getChildExtensions(); List<T> result = new ArrayList<T>(); if (childExtensions == null) return result; synchronized (childExtensions) { for(PacketExtension extension : childExtensions) { if(type.isInstance(extension)) { @SuppressWarnings("unchecked") T extensionAsType = (T) extension; result.add(extensionAsType); } } } return result; } /** * Clones the attributes, namespace and text of a specific * <tt>AbstractPacketExtension</tt> into a new * <tt>AbstractPacketExtension</tt> instance of the same run-time type. * * @param src the <tt>AbstractPacketExtension</tt> to be cloned * @return a new <tt>AbstractPacketExtension</tt> instance of the run-time * type of the specified <tt>src</tt> which has the same attributes, * namespace and text * @throws Exception if an error occurs during the cloning of the specified * <tt>src</tt> */ @SuppressWarnings("unchecked") public static <T extends AbstractPacketExtension> T clone(T src) { T dst = null; try { dst = (T) src.getClass().newInstance(); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } // attributes for (String name : src.getAttributeNames()) dst.setAttribute(name, src.getAttribute(name)); // namespace dst.setNamespace(src.getNamespace()); // text dst.setText(src.getText()); return dst; } }