/** * 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; } }