org.codehaus.plexus.util.xml.Xpp3Dom Maven / Gradle / Ivy
package org.codehaus.plexus.util.xml;
/*
* Copyright The Codehaus Foundation.
*
* 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.
*/
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.pull.XmlSerializer;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
/**
* NOTE: remove all the util code in here when separated, this class should be pure data.
*/
public class Xpp3Dom
implements Serializable
{
private static final long serialVersionUID = 2567894443061173996L;
protected String name;
protected String value;
protected Map attributes;
protected final List childList;
protected Xpp3Dom parent;
/**
* @since 3.2.0
*/
protected Object inputLocation;
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final Xpp3Dom[] EMPTY_DOM_ARRAY = new Xpp3Dom[0];
public static final String CHILDREN_COMBINATION_MODE_ATTRIBUTE = "combine.children";
public static final String CHILDREN_COMBINATION_MERGE = "merge";
public static final String CHILDREN_COMBINATION_APPEND = "append";
/**
* This default mode for combining children DOMs during merge means that where element names match, the process will
* try to merge the element data, rather than putting the dominant and recessive elements (which share the same
* element name) as siblings in the resulting DOM.
*/
public static final String DEFAULT_CHILDREN_COMBINATION_MODE = CHILDREN_COMBINATION_MERGE;
public static final String SELF_COMBINATION_MODE_ATTRIBUTE = "combine.self";
public static final String SELF_COMBINATION_OVERRIDE = "override";
public static final String SELF_COMBINATION_MERGE = "merge";
public static final String SELF_COMBINATION_REMOVE = "remove";
/**
* This default mode for combining a DOM node during merge means that where element names match, the process will
* try to merge the element attributes and values, rather than overriding the recessive element completely with the
* dominant one. This means that wherever the dominant element doesn't provide the value or a particular attribute,
* that value or attribute will be set from the recessive DOM node.
*/
public static final String DEFAULT_SELF_COMBINATION_MODE = SELF_COMBINATION_MERGE;
public Xpp3Dom( String name )
{
this.name = name;
childList = new ArrayList<>();
}
/**
* @since 3.2.0
* @param inputLocation The input location.
* @param name The name of the Dom.
*/
public Xpp3Dom( String name, Object inputLocation )
{
this( name );
this.inputLocation = inputLocation;
}
/**
* Copy constructor.
* @param src The source Dom.
*/
public Xpp3Dom( Xpp3Dom src )
{
this( src, src.getName() );
}
/**
* Copy constructor with alternative name.
* @param src The source Dom.
* @param name The name of the Dom.
*/
public Xpp3Dom( Xpp3Dom src, String name )
{
this.name = name;
this.inputLocation = src.inputLocation;
int childCount = src.getChildCount();
childList = new ArrayList( childCount );
setValue( src.getValue() );
String[] attributeNames = src.getAttributeNames();
for ( String attributeName : attributeNames )
{
setAttribute( attributeName, src.getAttribute( attributeName ) );
}
for ( int i = 0; i < childCount; i++ )
{
addChild( new Xpp3Dom( src.getChild( i ) ) );
}
}
// ----------------------------------------------------------------------
// Name handling
// ----------------------------------------------------------------------
public String getName()
{
return name;
}
// ----------------------------------------------------------------------
// Value handling
// ----------------------------------------------------------------------
public String getValue()
{
return value;
}
public void setValue( String value )
{
this.value = value;
}
// ----------------------------------------------------------------------
// Attribute handling
// ----------------------------------------------------------------------
public String[] getAttributeNames()
{
if ( null == attributes || attributes.isEmpty() )
{
return EMPTY_STRING_ARRAY;
}
else
{
return attributes.keySet().toArray( EMPTY_STRING_ARRAY );
}
}
public String getAttribute( String name )
{
return ( null != attributes ) ? attributes.get( name ) : null;
}
/**
*
* @param name name of the attribute to be removed
* @return true
if the attribute has been removed
* @since 3.4.0
*/
public boolean removeAttribute( String name )
{
return StringUtils.isEmpty( name ) ? false: attributes.remove( name ) == null;
}
/**
* Set the attribute value
*
* @param name String not null
* @param value String not null
*/
public void setAttribute( String name, String value )
{
if ( null == value )
{
throw new NullPointerException( "Attribute value can not be null" );
}
if ( null == name )
{
throw new NullPointerException( "Attribute name can not be null" );
}
if ( null == attributes )
{
attributes = new HashMap();
}
attributes.put( name, value );
}
// ----------------------------------------------------------------------
// Child handling
// ----------------------------------------------------------------------
public Xpp3Dom getChild( int i )
{
return childList.get( i );
}
public Xpp3Dom getChild( String name )
{
if ( name != null )
{
ListIterator it = childList.listIterator( childList.size() );
while ( it.hasPrevious() )
{
Xpp3Dom child = it.previous();
if ( name.equals( child.getName() ) )
{
return child;
}
}
}
return null;
}
public void addChild( Xpp3Dom xpp3Dom )
{
xpp3Dom.setParent( this );
childList.add( xpp3Dom );
}
public Xpp3Dom[] getChildren()
{
if ( null == childList || childList.isEmpty() )
{
return EMPTY_DOM_ARRAY;
}
else
{
return childList.toArray( EMPTY_DOM_ARRAY );
}
}
public Xpp3Dom[] getChildren( String name )
{
return getChildrenAsList( name ).toArray( EMPTY_DOM_ARRAY );
}
private List getChildrenAsList( String name )
{
if ( null == childList )
{
return Collections.emptyList();
}
else
{
ArrayList children = null;
for ( Xpp3Dom configuration : childList )
{
if ( name.equals( configuration.getName() ) )
{
if ( children == null )
{
children = new ArrayList();
}
children.add( configuration );
}
}
if ( children != null )
{
return children;
}
else
{
return Collections.emptyList();
}
}
}
public int getChildCount()
{
if ( null == childList )
{
return 0;
}
return childList.size();
}
public void removeChild( int i )
{
Xpp3Dom child = getChild( i );
childList.remove( i );
// In case of any dangling references
child.setParent( null );
}
public void removeChild( Xpp3Dom child )
{
childList.remove( child );
// In case of any dangling references
child.setParent( null );
}
// ----------------------------------------------------------------------
// Parent handling
// ----------------------------------------------------------------------
public Xpp3Dom getParent()
{
return parent;
}
public void setParent( Xpp3Dom parent )
{
this.parent = parent;
}
// ----------------------------------------------------------------------
// Input location handling
// ----------------------------------------------------------------------
/**
* @since 3.2.0
* @return input location
*/
public Object getInputLocation()
{
return inputLocation;
}
/**
* @since 3.2.0
* @param inputLocation input location to set
*/
public void setInputLocation( Object inputLocation )
{
this.inputLocation = inputLocation;
}
// ----------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------
public void writeToSerializer( String namespace, XmlSerializer serializer )
throws IOException
{
// TODO: WARNING! Later versions of plexus-utils psit out an header due to thinking this is a new
// document - not the desired behaviour!
SerializerXMLWriter xmlWriter = new SerializerXMLWriter( namespace, serializer );
Xpp3DomWriter.write( xmlWriter, this );
if ( xmlWriter.getExceptions().size() > 0 )
{
throw (IOException) xmlWriter.getExceptions().get( 0 );
}
}
/**
* Merges one DOM into another, given a specific algorithm and possible override points for that algorithm.
* The algorithm is as follows:
*
* - if the recessive DOM is null, there is nothing to do... return.
* - Determine whether the dominant node will suppress the recessive one (flag=mergeSelf).
*
* - retrieve the 'combine.self' attribute on the dominant node, and try to match against 'override'...
* if it matches 'override', then set mergeSelf == false...the dominant node suppresses the recessive one
* completely.
* - otherwise, use the default value for mergeSelf, which is true...this is the same as specifying
* 'combine.self' == 'merge' as an attribute of the dominant root node.
*
* - If mergeSelf == true
*
* - if the dominant root node's value is empty, set it to the recessive root node's value
* - For each attribute in the recessive root node which is not set in the dominant root node, set it.
* - Determine whether children from the recessive DOM will be merged or appended to the dominant DOM as
* siblings (flag=mergeChildren).
*
* - if childMergeOverride is set (non-null), use that value (true/false)
* - retrieve the 'combine.children' attribute on the dominant node, and try to match against
* 'append'...
* - if it matches 'append', then set mergeChildren == false...the recessive children will be appended as
* siblings of the dominant children.
* - otherwise, use the default value for mergeChildren, which is true...this is the same as specifying
* 'combine.children' == 'merge' as an attribute on the dominant root node.
*
* - Iterate through the recessive children, and:
*
* - if mergeChildren == true and there is a corresponding dominant child (matched by element name),
* merge the two.
* - otherwise, add the recessive child as a new child on the dominant root node.
*
*
*
*/
private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride )
{
// TODO: share this as some sort of assembler, implement a walk interface?
if ( recessive == null )
{
return;
}
boolean mergeSelf = true;
String selfMergeMode = dominant.getAttribute( SELF_COMBINATION_MODE_ATTRIBUTE );
if ( SELF_COMBINATION_OVERRIDE.equals( selfMergeMode ) )
{
mergeSelf = false;
}
if ( mergeSelf )
{
if ( isEmpty( dominant.getValue() ) && !isEmpty( recessive.getValue() ) )
{
dominant.setValue( recessive.getValue() );
dominant.setInputLocation( recessive.getInputLocation() );
}
if ( recessive.attributes != null )
{
for ( String attr : recessive.attributes.keySet() )
{
if ( isEmpty( dominant.getAttribute( attr ) ) )
{
dominant.setAttribute( attr, recessive.getAttribute( attr ) );
}
}
}
if ( recessive.getChildCount() > 0 )
{
boolean mergeChildren = true;
if ( childMergeOverride != null )
{
mergeChildren = childMergeOverride;
}
else
{
String childMergeMode = dominant.getAttribute( CHILDREN_COMBINATION_MODE_ATTRIBUTE );
if ( CHILDREN_COMBINATION_APPEND.equals( childMergeMode ) )
{
mergeChildren = false;
}
}
if ( !mergeChildren )
{
Xpp3Dom[] dominantChildren = dominant.getChildren();
// remove these now, so we can append them to the recessive list later.
dominant.childList.clear();
for ( int i = 0, recessiveChildCount = recessive.getChildCount(); i < recessiveChildCount; i++ )
{
Xpp3Dom recessiveChild = recessive.getChild( i );
dominant.addChild( new Xpp3Dom( recessiveChild ) );
}
// now, re-add these children so they'll be appended to the recessive list.
for ( Xpp3Dom aDominantChildren : dominantChildren )
{
dominant.addChild( aDominantChildren );
}
}
else
{
Map> commonChildren = new HashMap>();
for ( Xpp3Dom recChild : recessive.childList )
{
if ( commonChildren.containsKey( recChild.name ) )
{
continue;
}
List dominantChildren = dominant.getChildrenAsList( recChild.name );
if ( dominantChildren.size() > 0 )
{
commonChildren.put( recChild.name, dominantChildren.iterator() );
}
}
for ( int i = 0, recessiveChildCount = recessive.getChildCount(); i < recessiveChildCount; i++ )
{
Xpp3Dom recessiveChild = recessive.getChild( i );
Iterator it = commonChildren.get( recessiveChild.getName() );
if ( it == null )
{
dominant.addChild( new Xpp3Dom( recessiveChild ) );
}
else if ( it.hasNext() )
{
Xpp3Dom dominantChild = it.next();
String dominantChildCombinationMode =
dominantChild.getAttribute( SELF_COMBINATION_MODE_ATTRIBUTE );
if ( SELF_COMBINATION_REMOVE.equals( dominantChildCombinationMode ) )
{
dominant.removeChild( dominantChild );
}
else
{
mergeIntoXpp3Dom( dominantChild, recessiveChild, childMergeOverride );
}
}
}
}
}
}
}
/**
* Merge two DOMs, with one having dominance in the case of collision.
*
* @see #CHILDREN_COMBINATION_MODE_ATTRIBUTE
* @see #SELF_COMBINATION_MODE_ATTRIBUTE
* @param dominant The dominant DOM into which the recessive value/attributes/children will be merged
* @param recessive The recessive DOM, which will be merged into the dominant DOM
* @param childMergeOverride Overrides attribute flags to force merging or appending of child elements into the
* dominant DOM
* @return merged DOM
*/
public static Xpp3Dom mergeXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride )
{
if ( dominant != null )
{
mergeIntoXpp3Dom( dominant, recessive, childMergeOverride );
return dominant;
}
return recessive;
}
/**
* Merge two DOMs, with one having dominance in the case of collision. Merge mechanisms (vs. override for nodes, or
* vs. append for children) is determined by attributes of the dominant root node.
*
* @see #CHILDREN_COMBINATION_MODE_ATTRIBUTE
* @see #SELF_COMBINATION_MODE_ATTRIBUTE
* @param dominant The dominant DOM into which the recessive value/attributes/children will be merged
* @param recessive The recessive DOM, which will be merged into the dominant DOM
* @return merged DOM
*/
public static Xpp3Dom mergeXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive )
{
if ( dominant != null )
{
mergeIntoXpp3Dom( dominant, recessive, null );
return dominant;
}
return recessive;
}
// ----------------------------------------------------------------------
// Standard object handling
// ----------------------------------------------------------------------
@Override
public boolean equals( Object obj )
{
if ( obj == this )
{
return true;
}
if ( !( obj instanceof Xpp3Dom ) )
{
return false;
}
Xpp3Dom dom = (Xpp3Dom) obj;
if ( name == null ? dom.name != null : !name.equals( dom.name ) )
{
return false;
}
else if ( value == null ? dom.value != null : !value.equals( dom.value ) )
{
return false;
}
else if ( attributes == null ? dom.attributes != null : !attributes.equals( dom.attributes ) )
{
return false;
}
else if ( childList == null ? dom.childList != null : !childList.equals( dom.childList ) )
{
return false;
}
else
{
return true;
}
}
@Override
public int hashCode()
{
int result = 17;
result = 37 * result + ( name != null ? name.hashCode() : 0 );
result = 37 * result + ( value != null ? value.hashCode() : 0 );
result = 37 * result + ( attributes != null ? attributes.hashCode() : 0 );
result = 37 * result + ( childList != null ? childList.hashCode() : 0 );
return result;
}
@Override
public String toString()
{
// TODO: WARNING! Later versions of plexus-utils psit out an header due to thinking this is a new
// document - not the desired behaviour!
StringWriter writer = new StringWriter();
XMLWriter xmlWriter = new PrettyPrintXMLWriter( writer, "UTF-8", null );
Xpp3DomWriter.write( xmlWriter, this );
return writer.toString();
}
public String toUnescapedString()
{
// TODO: WARNING! Later versions of plexus-utils psit out an header due to thinking this is a new
// document - not the desired behaviour!
StringWriter writer = new StringWriter();
XMLWriter xmlWriter = new PrettyPrintXMLWriter( writer, "UTF-8", null );
Xpp3DomWriter.write( xmlWriter, this, false );
return writer.toString();
}
public static boolean isNotEmpty( String str )
{
return ( ( str != null ) && ( str.length() > 0 ) );
}
public static boolean isEmpty( String str )
{
return ( ( str == null ) || ( str.trim().length() == 0 ) );
}
}