| Element.java |
/*
* $Id: Element.java,v 1.49 2010/09/29 20:04:51 agoubard Exp $
*
* See the COPYRIGHT file for redistribution and use restrictions.
*/
package org.xins.common.xml;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.xins.common.Log;
import org.xins.common.MandatoryArgumentChecker;
import org.xins.common.Utils;
import org.xins.common.collections.ProtectedList;
import org.xins.common.text.ParseException;
/**
* XML Element.
*
* <p>Note that this class is not thread-safe. It should not be used from
* different threads at the same time. This applies even to read operations.
*
* <p>Note that the namespace URIs and local names are not checked for
* validity in this class.
*
* <p>Instances of this class cannot be created directly, using a constructor.
* Instead, use {@link ElementBuilder} to build an XML element, or
* {@link ElementParser} to parse an XML string.
*
* @version $Revision: 1.49 $ $Date: 2010/09/29 20:04:51 $
* @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
* @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
*
* @since XINS 1.1.0
* @see org.w3c.dom.Element
* @deprecated since XINS 3.0. Use {@link org.w3c.dom.Element}
*/
@Deprecated
public class Element implements Cloneable {
/**
* Fully-qualified name of this class.
*/
private static final String CLASSNAME = Element.class.getName();
/**
* The secret key to use to add child elements.
*/
private static final Object SECRET_KEY = new Object();
/**
* The namespace prefix. This field can be <code>null</code>, but it can never
* be an empty string.
*/
private String _namespacePrefix;
/**
* The namespace URI. This field can be <code>null</code>, but it can never
* be an empty string.
*/
private String _namespaceURI;
/**
* The local name. This field is never <code>null</code>.
*/
private String _localName;
/**
* The child elements. This field is lazily initialized is initially
* <code>null</code>.
*/
private ArrayList<Object> _children;
/**
* The attributes. This field is lazily initialized and is initially
* <code>null</code>.
*/
private LinkedHashMap<QualifiedName,String> _attributes;
/**
* The character content for this element. Can be <code>null</code>.
*/
private String _text;
/**
* Creates a new <code>Element</code> with no namespace.
*
* @param localName
* the local name of the element, cannot be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>localName == null</code>.
*
* @since XINS 2.0
*/
public Element(String localName)
throws IllegalArgumentException {
this(null, null, localName);
}
/**
* Creates a new <code>Element</code>.
*
* @param namespaceURI
* the namespace URI for the element, can be <code>null</code>; an empty
* string is equivalent to <code>null</code>.
*
* @param localName
* the local name of the element, cannot be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>localName == null</code>.
*
* @since XINS 2.0
*/
public Element(String namespaceURI, String localName)
throws IllegalArgumentException {
this(null, namespaceURI, localName);
}
/**
* Creates a new <code>Element</code>.
*
* @param namespacePrefix
* the namespace prefix for the element, can be <code>null</code>; an empty
* string is equivalent to <code>null</code>.
*
* @param namespaceURI
* the namespace URI for the element, can be <code>null</code>; an empty
* string is equivalent to <code>null</code>.
*
* @param localName
* the local name of the element, cannot be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>localName == null</code>.
*
* @since XINS 2.1
*/
public Element(String namespacePrefix, String namespaceURI, String localName)
throws IllegalArgumentException {
// Check preconditions
MandatoryArgumentChecker.check("localName", localName);
// An empty namespace prefix is equivalent to null
if (namespacePrefix != null && namespacePrefix.length() < 1) {
_namespacePrefix = null;
} else {
_namespacePrefix = namespacePrefix;
}
// An empty namespace URI is equivalent to null
if (namespaceURI != null && namespaceURI.length() < 1) {
_namespaceURI = null;
} else {
_namespaceURI = namespaceURI;
}
_localName = localName;
}
/**
* Sets the namespace prefix.
*
* @param namespacePrefix
* the namespace prefix of this element, can be <code>null</code>.
* If the value if <code>null</code> or an empty string, the element is
* consider to have no namespace prefix.
*
* @since XINS 2.1.
*/
public void setNamespacePrefix(String namespacePrefix) {
if (namespacePrefix != null && namespacePrefix.length() == 0) {
_namespacePrefix = null;
} else {
_namespacePrefix = namespacePrefix;
}
}
/**
* Gets the namespace prefix.
*
* @return
* the namespace prefix for this element, or <code>null</code> if there is
* none, but never an empty string.
*
* @since XINS 2.1
*/
public String getNamespacePrefix() {
return _namespacePrefix;
}
/**
* Sets the namespace URI.
*
* @param namespaceURI
* the namespace URI of this element, can be <code>null</code>.
* If the value if <code>null</code> or an empty string, the element is
* consider to have no namespace URI.
*
* @since XINS 2.1.
*/
public void setNamespaceURI(String namespaceURI) {
if (namespaceURI != null && namespaceURI.length() == 0) {
_namespaceURI = null;
} else {
_namespaceURI = namespaceURI;
}
}
/**
* Gets the namespace URI.
*
* @return
* the namespace URI for this element, or <code>null</code> if there is
* none, but never an empty string.
*/
public String getNamespaceURI() {
return _namespaceURI;
}
/**
* Gets the local name.
*
* @return
* the local name of this element, cannot be <code>null</code>.
*/
public String getLocalName() {
return _localName;
}
/**
* Sets the local name.
*
* @param localName
* the local name of this element, cannot be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>localName == null</code>.
*
* @since XINS 2.0
*/
public void setLocalName(String localName) throws IllegalArgumentException {
MandatoryArgumentChecker.check("localName", localName);
_localName = localName;
}
/**
* Sets the specified attribute. If the value for the specified
* attribute is already set, then the previous value is replaced.
*
* @param localName
* the local name for the attribute, cannot be <code>null</code>.
*
* @param value
* the value for the attribute, can be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>localName == null</code>.
*
* @since XINS 2.0
*/
public void setAttribute(String localName, String value)
throws IllegalArgumentException {
setAttribute(null, null, localName, value);
}
/**
* Sets the specified attribute. If the value for the specified
* attribute is already set, then the previous value is replaced.
*
* @param namespaceURI
* the namespace URI for the attribute, can be <code>null</code>; an
* empty string is equivalent to <code>null</code>.
*
* @param localName
* the local name for the attribute, cannot be <code>null</code>.
*
* @param value
* the value for the attribute, can be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>localName == null</code>.
*
* @since XINS 2.0
*/
public void setAttribute(String namespaceURI, String localName, String value)
throws IllegalArgumentException {
setAttribute(null, namespaceURI, localName, value);
}
/**
* Sets the specified attribute. If the value for the specified
* attribute is already set, then the previous value is replaced.
*
* @param namespacePrefix
* the namespace prefix for the attribute, can be <code>null</code>; an
* empty string is equivalent to <code>null</code>.
*
* @param namespaceURI
* the namespace URI for the attribute, can be <code>null</code>; an
* empty string is equivalent to <code>null</code>.
*
* @param localName
* the local name for the attribute, cannot be <code>null</code>.
*
* @param value
* the value for the attribute, can be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>localName == null</code>.
*
* @since XINS 2.1
*/
public void setAttribute(String namespacePrefix, String namespaceURI, String localName, String value)
throws IllegalArgumentException {
// Construct a QualifiedName object. This will check the preconditions.
QualifiedName qn = new QualifiedName(namespacePrefix, namespaceURI, localName);
if (_attributes == null) {
if (value == null) {
return;
}
// Lazily initialize
_attributes = new LinkedHashMap<QualifiedName,String>();
}
// Set or reset the attribute
if (value == null) {
_attributes.remove(qn);
} else {
_attributes.put(qn, value);
}
}
/**
* Removes the specified attribute. If no attribute with the specified name
* exists, nothing happens.
*
* @param localName
* the local name for the attribute, cannot be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>localName == null</code>.
*
* @since XINS 2.0
*/
public void removeAttribute(String localName)
throws IllegalArgumentException {
removeAttribute(null, localName);
}
/**
* Removes the specified attribute. If no attribute with the specified name
* exists, nothing happens.
*
* @param namespaceURI
* the namespace URI for the attribute, can be <code>null</code>; an
* empty string is equivalent to <code>null</code>.
*
* @param localName
* the local name for the attribute, cannot be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>localName == null</code>.
*
* @since XINS 2.0
*/
public void removeAttribute(String namespaceURI, String localName)
throws IllegalArgumentException {
// Construct a QualifiedName object. This will check the preconditions.
QualifiedName qn = new QualifiedName(namespaceURI, localName);
if (_attributes != null) {
_attributes.remove(qn);
}
}
/**
* Gets the attributes of this element.
*
* @return
* a {@link Map} (never <code>null</code>) which contains the attributes;
* each key in the <code>Map</code> is a {@link QualifiedName} instance
* (not <code>null</code>) and each value in it is a <code>String</code>
* instance (not <code>null</code>).
*/
public Map getAttributeMap() {
if (_attributes == null) {
_attributes = new LinkedHashMap<QualifiedName,String>();
}
return _attributes;
}
/**
* Gets the value of the attribute with the qualified name. If the
* qualified name does not specify a namespace, then only an attribute that
* does not have a namespace will match.
*
* @param qn
* a combination of an optional namespace and a mandatory local name, or
* <code>null</code>.
*
* @return
* the value of the attribute that matches the specified namespace and
* local name, or <code>null</code> if such an attribute is either not
* set or set to <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>qn == null</code>.
*/
public String getAttribute(QualifiedName qn)
throws IllegalArgumentException {
// Check preconditions
MandatoryArgumentChecker.check("qn", qn);
if (_attributes == null) {
return null;
} else {
return (String) _attributes.get(qn);
}
}
/**
* Gets the value of the attribute with the specified namespace and local
* name. The namespace is optional. If the namespace is not given, then only
* an attribute that does not have a namespace will match.
*
* @param namespaceURI
* the namespace URI for the attribute, can be <code>null</code>; an
* empty string is equivalent to <code>null</code>; if specified this
* string must be a valid namespace URI.
*
* @param localName
* the local name of the attribute, cannot be <code>null</code>.
*
* @return
* the value of the attribute that matches the specified namespace and
* local name, or <code>null</code> if such an attribute is either not
* set or set to <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>localName == null</code>.
*/
public String getAttribute(String namespaceURI, String localName)
throws IllegalArgumentException {
QualifiedName qn = new QualifiedName(namespaceURI, localName);
return getAttribute(qn);
}
/**
* Gets the value of an attribute that has no namespace.
*
* @param localName
* the local name of the attribute, cannot be <code>null</code>.
*
* @return
* the value of the attribute that matches the specified local name and
* has no namespace defined, or <code>null</code> if the attribute is
* either not set or set to <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>localName == null</code>.
*/
public String getAttribute(String localName)
throws IllegalArgumentException {
return getAttribute(null, localName);
}
/**
* Adds a new child element.
*
* @param child
* the new child to add to this element, cannot be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>child == null || child == <em>this</em></code>.
*
* @since XINS 2.0.
*/
public void addChild(Element child) throws IllegalArgumentException {
final String METHODNAME = "addChild(Element)";
// Check preconditions
MandatoryArgumentChecker.check("child", child);
if (child == this) {
String message = "child == this";
Log.log_1050(CLASSNAME, METHODNAME, Utils.getCallingClass(), Utils.getCallingMethod(), message);
throw new IllegalArgumentException(message);
}
// Lazily initialize
if (_children == null) {
_children = new ArrayList<Object>();
}
_children.add(child);
}
/**
* Removes a child element. If the child is not found, nothing is removed.
*
* @param child
* the child to be removed to this element, cannot be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>child == null || child == <em>this</em></code>.
*
* @since XINS 2.0
*/
public void removeChild(Element child) throws IllegalArgumentException {
final String METHODNAME = "removeChild(Element)";
// Check preconditions
MandatoryArgumentChecker.check("child", child);
if (child == this) {
String message = "child == this";
Log.log_1050(CLASSNAME, METHODNAME, Utils.getCallingClass(), Utils.getCallingMethod(), message);
throw new IllegalArgumentException(message);
}
// Lazily initialize
if (_children == null) {
return;
}
_children.remove(child);
}
/**
* Gets the list of all child elements.
*
* @return
* the {@link List} containing all child elements; each
* element in the list is another <code>Element</code> instance;
* never <code>null</code>.
*/
public List getChildElements() {
if (_children == null) {
_children = new ArrayList();
}
return _children;
}
/**
* Gets the list of child elements that match the specified name.
*
* @param name
* the name for the child elements to match, cannot be
* <code>null</code>.
*
* @return
* a {@link List} containing each child element that matches the
* specified name as another <code>Element</code> instance;
* never <code>null</code>. The list cannot be modified.
*
* @throws IllegalArgumentException
* if <code>name == null</code>.
*/
public List getChildElements(String name)
throws IllegalArgumentException {
// TODO: Support namespaces
// Check preconditions
MandatoryArgumentChecker.check("name", name);
// If there are no children, then return null
if (_children == null || _children.size() == 0) {
return Collections.EMPTY_LIST;
// There are children, find all matching ones
} else {
ProtectedList matches = new ProtectedList(SECRET_KEY);
Iterator it = _children.iterator();
while (it.hasNext()) {
Element child = (Element) it.next();
if (name.equals(child.getLocalName())) {
matches.add(SECRET_KEY, child);
}
}
// If there are no matching children, then return null
if (matches.size() == 0) {
return Collections.EMPTY_LIST;
// Otherwise return an immutable list with all matches
} else {
return matches;
}
}
}
/**
* Sets the character content. The existing character content, if any, is
* replaced
*
* @param text
* the character content for this element, or <code>null</code>.
*
* @since XINS 2.0.
*/
public void setText(String text) {
_text = text;
}
/**
* Gets the character content, if any.
*
* @return
* the character content of this element, or <code>null</code> if no
* text has been specified for this element.
*/
public String getText() {
return _text;
}
/**
* Gets the unique child of this element with the specified name.
*
* @param elementName
* the name of the child element to get, or <code>null</code> if the
* element name is irrelevant.
*
* @return
* the sub-element of this element, never <code>null</code>.
*
* @throws ParseException
* if no child with the specified name was found,
* or if more than one child with the specified name was found.
*
* @since XINS 1.4.0
*/
public Element getUniqueChildElement(String elementName)
throws ParseException {
List childList;
if (elementName == null) {
childList = getChildElements();
} else {
childList = getChildElements(elementName);
}
if (childList.size() == 0) {
throw new ParseException("No \"" + elementName +
"\" children found in the \"" + getLocalName() +
"\" element.");
} else if (childList.size() > 1) {
throw new ParseException("More than one \"" + elementName +
"\" children found in the \"" + getLocalName() +
"\" element.");
}
return (Element) childList.get(0);
}
/**
* Gets the unique child of this element.
*
* @return
* the sub-element of this element, never <code>null</code>.
*
* @throws ParseException
* if no child was found or more than one child was found.
*
* @since XINS 2.2
*/
public Element getUniqueChildElement() throws ParseException {
return getUniqueChildElement(null);
}
public int hashCode() {
int hashCode = _localName.hashCode();
if (_namespaceURI != null) {
hashCode += _namespaceURI.hashCode();
}
if (_attributes != null) {
hashCode += _attributes.hashCode();
}
if (_children != null) {
hashCode += _children.hashCode();
}
if (_text != null) {
hashCode += _text.hashCode();
}
return hashCode;
}
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Element)) {
return false;
}
Element other = (Element) obj;
if (!_localName.equals(other.getLocalName()) ||
(_namespaceURI != null && !_namespaceURI.equals(other._namespaceURI)) ||
(_namespaceURI == null && other._namespaceURI != null)) {
return false;
}
if ((_attributes != null && !_attributes.equals(other._attributes)) ||
(_attributes == null && other._attributes != null)) {
return false;
}
// XXX This should be changed as the order of the children should no matter
if ((_children != null && !_children.equals(other._children)) ||
(_children == null && other._children != null)) {
return false;
}
if ((_text != null && !_text.equals(other._text)) ||
(_text == null && other._text != null)) {
return false;
}
return true;
}
/**
* Clones this object. The clone will have the same namespace URI and local
* name and equivalent attributes, children and character content.
*
* @return
* a new clone of this object, never <code>null</code>.
*/
public Object clone() {
// Construct a new Element, copy all field values (shallow copy)
Element clone;
try {
clone = (Element) super.clone();
} catch (CloneNotSupportedException exception) {
throw Utils.logProgrammingError(exception);
}
// Deep copy the children
if (_children != null) {
clone._children = (ArrayList<Object>) _children.clone();
}
// Deep copy the attributes
if (_attributes != null) {
clone._attributes = (LinkedHashMap<QualifiedName,String>) _attributes.clone();
}
return clone;
}
/**
* Overrides the {@link Object#toString()} method to return
* the element as its XML representation.
*
* @return
* the XML representation of this element without the XML declaration,
* never <code>null</code>.
*/
@Override
public String toString() {
return new ElementSerializer().serialize(this);
}
/**
* Qualified name for an element or attribute. This is a combination of an
* optional namespace URI and a mandatory local name.
*
* @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
*
* @since XINS 1.1.0
*/
public static final class QualifiedName {
/**
* The hash code for this object.
*/
private final int _hashCode;
/**
* The namespace prefix. Can be <code>null</code>.
*/
private final String _namespacePrefix;
/**
* The namespace URI. Can be <code>null</code>.
*/
private final String _namespaceURI;
/**
* The local name. Cannot be <code>null</code>.
*/
private final String _localName;
/**
* Constructs a new <code>QualifiedName</code> with the specified
* namespace and local name.
*
* @param namespaceURI
* the namespace URI for the element, can be <code>null</code>; an
* empty string is equivalent to <code>null</code>.
*
* @param localName
* the local name of the element, cannot be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>localName == null</code>.
*/
public QualifiedName(String namespaceURI, String localName)
throws IllegalArgumentException {
this(null, namespaceURI, localName);
}
/**
* Constructs a new <code>QualifiedName</code> with the specified
* namespace and local name.
*
* @param namespacePrefix
* the namespace prefix for the element, can be <code>null</code>; an
* empty string is equivalent to <code>null</code>.
*
* @param namespaceURI
* the namespace URI for the element, can be <code>null</code>; an
* empty string is equivalent to <code>null</code>.
*
* @param localName
* the local name of the element, cannot be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>localName == null</code>.
*
* @since XINS 2.1
*/
public QualifiedName(String namespacePrefix, String namespaceURI, String localName)
throws IllegalArgumentException {
// Check preconditions
MandatoryArgumentChecker.check("localName", localName);
// An empty namespace prefix is equivalent to null
if (namespacePrefix != null && namespacePrefix.length() < 1) {
_namespacePrefix = null;
} else {
_namespacePrefix = namespacePrefix;
}
// An empty namespace URI is equivalent to null
if (namespaceURI != null && namespaceURI.length() < 1) {
_namespaceURI = null;
} else {
_namespaceURI = namespaceURI;
}
// Initialize fields
_hashCode = localName.hashCode();
_localName = localName;
}
/**
* Returns the hash code value for this object.
*
* @return
* the hash code value.
*/
public int hashCode() {
return _hashCode;
}
/**
* Compares this object with the specified object for equality.
*
* @param obj
* the object to compare with, or <code>null</code>.
*
* @return
* <code>true</code> if this object and the argument are considered
* equal, <code>false</code> otherwise.
*/
public boolean equals(Object obj) {
if (! (obj instanceof QualifiedName)) {
return false;
}
QualifiedName qn = (QualifiedName) obj;
return ((_namespaceURI == null && qn._namespaceURI == null) ||
(_namespaceURI != null && _namespaceURI.equals(qn._namespaceURI)))
&& _localName.equals(qn._localName);
}
/**
* Gets the namespace prefix.
*
* @return
* the namespace prefix, can be <code>null</code>.
*
* @since XINS 2.1
*/
public String getNamespacePrefix() {
return _namespacePrefix;
}
/**
* Gets the namespace URI.
*
* @return
* the namespace URI, can be <code>null</code>.
*/
public String getNamespaceURI() {
return _namespaceURI;
}
/**
* Gets the local name.
*
* @return
* the local name, never <code>null</code>.
*/
public String getLocalName() {
return _localName;
}
}
}