/**
* Copyright (C) 2016 Orbeon, Inc.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation; either version
* 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
*/
package org.orbeon.oxf.transformer.xupdate.dom4j;
import org.orbeon.dom4j.DocumentFactory;
import org.orbeon.dom4j.util.NodeComparator;
import org.orbeon.dom4j.util.NonLazyElement;
/**
* 4/7/2005 d : Under JDK 1.5 the fact that dom4j isn't thread safe by default became apparent.
* In particular DefaultElement ( and sub-classes thereof ) are not thread safe because of the
* following :
* o DefaultElement has a single field, private Object content, by which it refers to all of its
* child nodes. If there is a single child node then content points to it. If there are more
* then content points to a java.util.List which in turns points to all the children.
* o However, if you do almost anything with an instance of DefaultElement, i.e. iterate over
* children, it will first create and fill a list before completing the operation. This even
* if there was only a single child.
* The consequence of the above is that DefaultElement and its sub-classes aren't thread safe,
* even if all of the threads are just readers.
*
* The 'usual' solution is to use dom4j's NonLazyElement and NonLazyElementDocumentFactory.
* However in our case we were using a sub-class of DefaultElement, UserDataElement, whose
* functionality is unmatched by NonLazyElement. Hence this class, a subclass of NonLazyElement
* with the safe functionality as UserDataElement.
*
* Btw NonLazyUserDataElement also tries to be smart wrt to cloning and parent specifying. That
* is, if you clone the clone will have parent == null but will have all of the requisite
* namespace declarations and if you setParent( notNull ) then any redundant namespace declarations
* are removed.
*
*/
public class NonLazyUserDataElement extends NonLazyElement {
private Object data;
public NonLazyUserDataElement( final String name ) {
super( name );
}
public NonLazyUserDataElement( final org.orbeon.dom4j.QName qname ) {
super( qname );
}
public NonLazyUserDataElement( final String nm, final org.orbeon.dom4j.Namespace ns ) {
super( nm, ns );
}
/**
* Doesn't try to grab name space decls from ancestors.
* @return a clone. May be missing some necessary namespace decls.
* @see #clone
*/
private NonLazyUserDataElement cloneInternal() {
final NonLazyUserDataElement ret = ( NonLazyUserDataElement )super.clone();
if ( ret != this ) {
ret.content = null;
ret.attributes = null;
ret.appendAttributes( this );
ret.appendContent( this );
ret.data = getCopyOfUserData();
}
return ret;
}
protected java.util.List createContentList() {
return createContentList( 2 );
}
protected Object getCopyOfUserData() {
return data;
}
protected org.orbeon.dom4j.Element createElement( final String name ) {
final org.orbeon.dom4j.DocumentFactory factory = getDocumentFactory();
final org.orbeon.dom4j.QName qnam = factory.createQName( name );
return createElement( qnam );
}
protected org.orbeon.dom4j.Element createElement( final org.orbeon.dom4j.QName qName ) {
final DocumentFactory factory = NonLazyUserDataDocumentFactory.getInstance();
final org.orbeon.dom4j.Element ret = factory.createElement( qName );
final Object dta = getCopyOfUserData();
ret.setData( dta );
return ret;
}
protected org.orbeon.dom4j.DocumentFactory getDocumentFactory() {
return NonLazyUserDataDocumentFactory.getInstance();
}
public Object getData() {
return data;
}
public void setData( final Object d ) {
data = d;
}
public String toString() {
return super.toString() + " userData: " + data;
}
public void appendAttributes( final org.orbeon.dom4j.Element src ) {
for ( int i = 0, size = src.attributeCount(); i < size; i++) {
final org.orbeon.dom4j.Attribute att = src.attribute( i );
final org.orbeon.dom4j.Attribute attCln = ( org.orbeon.dom4j.Attribute )att.clone();
add( attCln );
}
}
public void appendContent( final org.orbeon.dom4j.Branch branch ) {
final int size = branch.nodeCount();
for ( int i = 0; i < size; i++ ) {
final org.orbeon.dom4j.Node node = branch.node( i );
final org.orbeon.dom4j.Node cln;
if ( node.getNodeType() == org.orbeon.dom4j.Node.ELEMENT_NODE ) {
cln = ( ( NonLazyUserDataElement )node ).cloneInternal();
} else {
cln = ( org.orbeon.dom4j.Node )node.clone();
}
add( cln );
}
}
/**
* @return A clone. The clone will have parent == null but will have any necessary namespace
* declarations this element's ancestors.
*/
public Object clone() {
final NonLazyUserDataElement ret = cloneInternal();
org.orbeon.dom4j.Element anstr = getParent();
done : if ( anstr != null ) {
final NodeComparator nc = new NodeComparator();
final java.util.TreeSet nsSet = new java.util.TreeSet( nc );
do {
final java.util.List sibs = anstr.content();
for ( final java.util.Iterator itr = sibs.iterator(); itr.hasNext(); ) {
final org.orbeon.dom4j.Node sib = ( org.orbeon.dom4j.Node )itr.next();
if ( sib.getNodeType() != org.orbeon.dom4j.Node.NAMESPACE_NODE ) continue;
nsSet.add( sib );
}
anstr = anstr.getParent();
}
while ( anstr != null );
if ( nsSet.isEmpty() ) break done;
for ( final java.util.Iterator itr = nsSet.iterator(); itr.hasNext(); ) {
final org.orbeon.dom4j.Namespace ns = ( org.orbeon.dom4j.Namespace )itr.next();
final String pfx = ns.getPrefix();
if ( ret.getNamespaceForPrefix( pfx ) != null ) continue;
ret.add( ns );
}
}
return ret;
}
/**
* If parent != null checks with ancestors and removes any redundant namespace declarations.
*/
public void setParent( final org.orbeon.dom4j.Element prnt ) {
super.setParent( prnt );
done : if ( prnt != null ) {
final org.orbeon.dom4j.Namespace myNs = getNamespace();
if ( myNs != org.orbeon.dom4j.Namespace.NO_NAMESPACE ) {
final String myPfx = myNs.getPrefix();
final org.orbeon.dom4j.Namespace prntNs = prnt.getNamespaceForPrefix( myPfx );
if ( myPfx.equals( prntNs ) ) {
final String myNm = myNs.getName();
final org.orbeon.dom4j.QName newNm = new org.orbeon.dom4j.QName( myNm );
setQName( newNm );
}
}
if ( content == null ) break done;
for ( final java.util.Iterator itr = content.iterator(); itr.hasNext(); ) {
final org.orbeon.dom4j.Node chld = ( org.orbeon.dom4j.Node )itr.next();
if ( chld.getNodeType() != org.orbeon.dom4j.Node.NAMESPACE_NODE ) continue;
final org.orbeon.dom4j.Namespace ns = ( org.orbeon.dom4j.Namespace )chld;
final String pfx = ns.getPrefix();
final org.orbeon.dom4j.Namespace prntNs = prnt.getNamespaceForPrefix( pfx );
if ( ns.equals( prntNs ) ) itr.remove();
}
}
}
public org.orbeon.dom4j.Element createCopy() {
final NonLazyUserDataElement ret = ( NonLazyUserDataElement )super.createCopy();
ret.data = getCopyOfUserData();
return ret;
}
public org.orbeon.dom4j.Element createCopy( final org.orbeon.dom4j.QName qName ) {
final NonLazyUserDataElement ret = ( NonLazyUserDataElement )super.createCopy( qName );
ret.data = getCopyOfUserData();
return ret;
}
public org.orbeon.dom4j.Element createCopy( final String name ) {
final NonLazyUserDataElement ret = ( NonLazyUserDataElement )super.createCopy( name );
ret.data = getCopyOfUserData();
return ret;
}
}