Skip to content

Commit

Permalink
Improve speed of Xpp3Dom clone/merge operations (#67)
Browse files Browse the repository at this point in the history
This closes #67
  • Loading branch information
gnodet authored and michael-o committed Jul 4, 2019
1 parent 0438170 commit 6dfc0a6
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 25 deletions.
12 changes: 12 additions & 0 deletions pom.xml
Expand Up @@ -57,6 +57,18 @@ limitations under the License.
<version>1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.21</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.21</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
78 changes: 53 additions & 25 deletions src/main/java/org/codehaus/plexus/util/xml/Xpp3Dom.java
Expand Up @@ -23,9 +23,11 @@
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

/**
Expand All @@ -44,8 +46,6 @@ public class Xpp3Dom

protected final List<Xpp3Dom> childList;

protected final Map<String, Xpp3Dom> childMap;

protected Xpp3Dom parent;

/**
Expand Down Expand Up @@ -88,7 +88,6 @@ public Xpp3Dom( String name )
{
this.name = name;
childList = new ArrayList<Xpp3Dom>();
childMap = new HashMap<String, Xpp3Dom>();
}

/**
Expand Down Expand Up @@ -119,7 +118,6 @@ public Xpp3Dom( Xpp3Dom src, String name )
int childCount = src.getChildCount();

childList = new ArrayList<Xpp3Dom>( childCount );
childMap = new HashMap<String, Xpp3Dom>( childCount << 1 );

setValue( src.getValue() );

Expand Down Expand Up @@ -170,13 +168,13 @@ public String[] getAttributeNames()
}
else
{
return (String[]) attributes.keySet().toArray( new String[attributes.size()] );
return attributes.keySet().toArray( EMPTY_STRING_ARRAY );
}
}

public String getAttribute( String name )
{
return ( null != attributes ) ? (String) attributes.get( name ) : null;
return ( null != attributes ) ? attributes.get( name ) : null;
}

/**
Expand Down Expand Up @@ -209,19 +207,30 @@ public void setAttribute( String name, String value )

public Xpp3Dom getChild( int i )
{
return (Xpp3Dom) childList.get( i );
return childList.get( i );
}

public Xpp3Dom getChild( String name )
{
return (Xpp3Dom) childMap.get( name );
if ( name != null )
{
ListIterator<Xpp3Dom> 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 );
childMap.put( xpp3Dom.getName(), xpp3Dom );
}

public Xpp3Dom[] getChildren()
Expand All @@ -232,31 +241,45 @@ public Xpp3Dom[] getChildren()
}
else
{
return (Xpp3Dom[]) childList.toArray( new Xpp3Dom[childList.size()] );
return childList.toArray( EMPTY_DOM_ARRAY );
}
}

public Xpp3Dom[] getChildren( String name )
{
return getChildrenAsList( name ).toArray( EMPTY_DOM_ARRAY );
}

private List<Xpp3Dom> getChildrenAsList( String name )
{
if ( null == childList )
{
return EMPTY_DOM_ARRAY;
return Collections.emptyList();
}
else
{
ArrayList<Xpp3Dom> children = new ArrayList<Xpp3Dom>();
int size = childList.size();
ArrayList<Xpp3Dom> children = null;

for ( Xpp3Dom aChildList : childList )
for ( Xpp3Dom configuration : childList )
{
Xpp3Dom configuration = (Xpp3Dom) aChildList;
if ( name.equals( configuration.getName() ) )
{
if ( children == null )
{
children = new ArrayList<Xpp3Dom>();
}
children.add( configuration );
}
}

return (Xpp3Dom[]) children.toArray( new Xpp3Dom[children.size()] );
if ( children != null )
{
return children;
}
else
{
return Collections.emptyList();
}
}
}

Expand All @@ -273,7 +296,6 @@ public int getChildCount()
public void removeChild( int i )
{
Xpp3Dom child = getChild( i );
childMap.values().remove( child );
childList.remove( i );
// In case of any dangling references
child.setParent( null );
Expand Down Expand Up @@ -392,12 +414,14 @@ private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boole
dominant.setInputLocation( recessive.getInputLocation() );
}

String[] recessiveAttrs = recessive.getAttributeNames();
for ( String attr : recessiveAttrs )
if ( recessive.attributes != null )
{
if ( isEmpty( dominant.getAttribute( attr ) ) )
for ( String attr : recessive.attributes.keySet() )
{
dominant.setAttribute( attr, recessive.getAttribute( attr ) );
if ( isEmpty( dominant.getAttribute( attr ) ) )
{
dominant.setAttribute( attr, recessive.getAttribute( attr ) );
}
}
}

Expand Down Expand Up @@ -441,12 +465,16 @@ private static void mergeIntoXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boole
{
Map<String, Iterator<Xpp3Dom>> commonChildren = new HashMap<String, Iterator<Xpp3Dom>>();

for ( String childName : recessive.childMap.keySet() )
for ( Xpp3Dom recChild : recessive.childList )
{
Xpp3Dom[] dominantChildren = dominant.getChildren( childName );
if ( dominantChildren.length > 0 )
if ( commonChildren.containsKey( recChild.name ) )
{
continue;
}
List<Xpp3Dom> dominantChildren = dominant.getChildrenAsList( recChild.name );
if ( dominantChildren.size() > 0 )
{
commonChildren.put( childName, Arrays.asList( dominantChildren ).iterator() );
commonChildren.put( recChild.name, dominantChildren.iterator() );
}
}

Expand Down
80 changes: 80 additions & 0 deletions src/test/java/org/codehaus/plexus/util/xml/Xpp3DomPerfTest.java
@@ -0,0 +1,80 @@
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 java.io.IOException;
import java.io.StringReader;
import java.util.concurrent.TimeUnit;

import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.TimeValue;

@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 3, timeUnit = TimeUnit.SECONDS)
public class Xpp3DomPerfTest
{
@State(Scope.Benchmark)
static public class AdditionState {
Xpp3Dom dom1;
Xpp3Dom dom2;

@Setup(Level.Iteration)
public void setUp() throws IOException, XmlPullParserException {
String testDom = "<configuration><items thing='blah'><item>one</item><item>two</item></items></configuration>";
dom1 = Xpp3DomBuilder.build( new StringReader( testDom ) );
dom2 = new Xpp3Dom( dom1 );
}
}


@Benchmark
public Xpp3Dom benchmarkClone(AdditionState state)
{
return new Xpp3Dom( state.dom1 );
}

@Benchmark
public void benchmarkMerge(AdditionState state)
{
Xpp3Dom.mergeXpp3Dom( state.dom1, state.dom2 );
}

public static void main( String... args )
throws RunnerException
{
Options opts = new OptionsBuilder()
.measurementIterations( 3 )
.measurementTime( TimeValue.milliseconds( 3000 ) )
.forks( 1 )
.build();
new Runner( opts ).run();
}
}

0 comments on commit 6dfc0a6

Please sign in to comment.