/*
* EuroCarbDB, a framework for carbohydrate bioinformatics
*
* Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
* A copy of this license accompanies this distribution in the file LICENSE.txt.
*
* 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.
*
* Last commit: $Rev: 1147 $ by $Author: glycoslave $ on $Date:: 2009-06-04 #$
*/
package test.eurocarbdb.util;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import org.testng.Assert;
import org.testng.annotations.*;
import org.apache.log4j.Logger;
import org.eurocarbdb.util.Visitor;
import static org.eurocarbdb.util.StringUtils.join;
import static org.eurocarbdb.util.StringUtils.repeat;
/**
* Tests {@link Visitor}.
*/
@Test( groups="util.visitor" )
public class VisitorTest
{
/** When true, runs an extended & benchmarked comparison of visitor speed,
* comparing cached reflection-based double-dispatch versus direct dispatch. */
public static final boolean Run_Time_Trial = true;
/** logging handle */
static Logger log = Logger.getLogger( VisitorTest.class );
/*~~~~~~ some simple data structures of test classes to visit ~~~~~~~*/
/** A test data structure */
private Map<String,Object> m = new HashMap<String,Object>();
private void init() // populates Map<String,Object> m
{
m.clear();
m.put("aaaa", 1111 );
m.put("bbbb", 3333 );
m.put("cccc", 5555 );
m.put("dddd", new char[] { 'x', 'y', 'z' } );
m.put("an object of A", new A() );
m.put("an object of B", new B() );
}
/**
* an arbitrary data structure used to compare classic & reflection-based visitors.
* order of traversal should be:
*<pre>
* C, B, C, A, C, A, B, C, A, C, C, B, A, A, C, A
*</pre>
* note here that {@link VD} is a subclass of {@link A}.
*/
private final VC data_structure
= new VC( // C
new VB(), // B
new VC( // C
new VA(), // A
new VC( // C
new VA(), // A
new VB() // B
),
new VC() // C
),
new VA(), // A
new VC( // C
new VC( // C
new VB(), // B
new VD(), // A
new VA() // A
)
),
new VC(), // C
new VD() // A
);
/*~~~~~~~~~~~~~~~~ test methods ~~~~~~~~~~~~~~~~~~*/
/** Creates a {@link SimpleVisitor} and visits a simple data structure. */
@Test
public void visitorSmokeTest()
{
init();
SimpleVisitor s = new SimpleVisitor();
s.visit( m );
}
/**
* Ensures that dispatching to accept(Xxxx) methods properly respects
* inheritance and interfaces.
*/
@Test
public void visitorDispatch()
{
SimpleVisitor s = new SimpleVisitor();
A a = new A();
// test basic visitation
System.out.println("expecting to visit A...");
s.visitedOrder.clear();
s.visit( a );
assert s.visitedOrder.equals( Arrays.asList( A.class ) );
// test regular visitation, while casting to different things
B b = new B();
System.out.println("expecting to visit B...");
s.visitedOrder.clear();
s.visit( b );
assert s.visitedOrder.equals( Arrays.asList( B.class ) );
System.out.println("expecting to visit B... ");
s.visitedOrder.clear();
s.visit( (A) b );
assert s.visitedOrder.equals( Arrays.asList( B.class ) );
System.out.println("expecting to visit B... ");
s.visitedOrder.clear();
s.visit( (Object) b );
assert s.visitedOrder.equals( Arrays.asList( B.class ) );
// test regular visitation of a nested data structure
C c = new C( new A(), new B(), new C() );
System.out.println("expecting to visit C, A, B, C... ");
s.visitedOrder.clear();
s.visit( c );
assert s.visitedOrder.equals( Arrays.asList( C.class, A.class, B.class, C.class ) );
D d = new D();
E e = new E();
// test regular visitation of accept( superclass )
System.out.println("expecting to visit A... ");
s.visitedOrder.clear();
s.visit( d );
assert s.visitedOrder.equals( Arrays.asList( A.class ) );
System.out.println("expecting to visit A... ");
s.visitedOrder.clear();
s.visit( (I) d );
assert s.visitedOrder.equals( Arrays.asList( A.class ) );
// check visitation when there are no matching accept methods
System.out.println("expecting to visit nothing... ");
s.visitedOrder.clear();
s.visit( e );
assert s.visitedOrder.equals( Collections.emptyList() );
s = new InterfaceVisitor();
// test regular visitation of accept( superclass ) when there
// is also a matching accept( interface ) method
System.out.println("expecting to visit A... ");
s.visit( d );
assert s.visitedOrder.equals( Arrays.asList( A.class ) );
// check visitation of accept( interface )
System.out.println("expecting to visit II... ");
s.visitedOrder.clear();
s.visit( e );
assert s.visitedOrder.equals( Arrays.asList( II.class ) );
// test a plain Object
Object x = new Object();
System.out.println("expecting to visit Object... ");
s.visitedOrder.clear();
s.visit( x );
assert s.visitedOrder.equals( Arrays.asList( Object.class ) );
F f = new F();
System.out.println("expecting to visit Object... ");
s.visitedOrder.clear();
s.visit( f );
assert s.visitedOrder.equals( Arrays.asList( Object.class ) );
}
/**
* Compares the reflection-based {@link Visitor} class to an internal
* "classic-style" Visitor based on the typical Visitor pattern.
*/
@Test
public void visitorComparison()
{
// smoke test the data-structure for both visitors
System.out.println("The following 2 traversals should look identical:");
System.out.println();
System.out.println("1) reflection visitor:");
SimpleVisitor v1 = new SimpleVisitor();
v1.visit( data_structure );
System.out.println();
System.out.println("2) traditional visitor:");
ClassicDoubleDispatchVisitor v2 = new ClassicDoubleDispatchVisitor();
v2.visit( data_structure );
// check visitation order was the same
System.out.println();
boolean b = v1.visitedOrder.equals( v2.visitedOrder );
System.out.println("was order of objects traversed the same? " + b );
if ( ! b )
{
System.out.print("reflection visitor (" + v1.visitedOrder.size() + " items): " );
for ( Object x : v1.visitedOrder )
System.out.print(" -> " + x );
System.out.println();
System.out.print("traditional visitor (" + v2.visitedOrder.size() + " items):" );
for ( Object x : v2.visitedOrder )
System.out.print(" -> " + x );
System.out.println();
}
assert b;
if ( ! Run_Time_Trial )
return;
System.out.println();
System.out.println("comparing traversal speed of classic-v-reflection visitor:");
System.out.println();
timeTrial( 1 );
timeTrial( 10 );
timeTrial( 100 );
timeTrial( 1000 );
timeTrial( 10000 );
timeTrial( 100000 );
timeTrial( 500000 );
}
/**
* Compares visit times of a pre-defined {@link data_structure} by a
* {@link SimpleVisitor} and a {@link ClassicDoubleDispatchVisitor}.
*/
private final void timeTrial( int iterations )
{
System.out.println("timing " + iterations + " iteration(s):" );
SimpleVisitor v1 = new SimpleVisitor() { void print( Object x ) {/* eliminate print time */} };
long start1 = System.currentTimeMillis();
for ( int i = 0; i < iterations; i++ )
{
v1.visit( data_structure );
v1.visitedOrder.clear();
}
long end1 = System.currentTimeMillis();
ClassicDoubleDispatchVisitor v2 = new ClassicDoubleDispatchVisitor() { void print( Object x ) {/* eliminate print time */} };
long start2 = System.currentTimeMillis();
for ( int i = 0; i < iterations; i++ )
{
v2.visit( data_structure );
v2.visitedOrder.clear();
}
long end2 = System.currentTimeMillis();
System.out.println("reflection visitor took " + (end1-start1) );
System.out.println("traditional visitor took " + (end2-start2) );
System.out.println();
}
/*~~~~~~~~~~~~~~~~ some basic test classes ~~~~~~~~~~~~~~~~~~*/
/** Empty class used for testing purposes only */
static class A {}
/** Empty class used for testing purposes only */
static class B extends A {}
/** Empty class used for testing purposes only */
static class C extends B
{
public final A[] list;
public C() { list = null; }
public C( A... a ) { list = a; }
public String toString() { return super.toString() + "[" + join(", ", list) + "]"; }
}
/** Empty class used for testing purposes only */
static interface I {}
/** Empty class used for testing purposes only */
static interface II extends I {}
/** Empty class used for testing purposes only */
static class D extends A implements I {}
/** Empty class used for testing purposes only */
static class E implements II {}
/** Empty class used for testing purposes only */
static class F {}
/*~~~~~~~~~~~~~~~~ test Visitor classes ~~~~~~~~~~~~~~~~~~*/
/** Visitor sub-class based on the reflection-based {@link Visitor} class. */
static class SimpleVisitor extends Visitor
{
int indent = -1;
/** records order of visitation */
public final List<Class> visitedOrder = new ArrayList<Class>();
void before() { indent++; }
void after() { indent--; }
void print( Object x )
{
System.out.print( repeat(" ", indent ) );
System.out.print( "> " );
System.out.println( x );
}
public void accept( String x )
{
before();
visitedOrder.add( String.class );
print("String '" + x + "' visited");
after();
}
public void accept( Integer x )
{
before();
visitedOrder.add( Integer.class );
print("Integer '" + x + "' visited");
after();
}
public void accept( Map x )
{
before();
visitedOrder.add( Map.class );
print("Map of " + x.size() + " element(s) visited");
for ( Object e : x.entrySet() )
visit( e );
after();
}
public void accept( Map.Entry x )
{
before();
visitedOrder.add( Map.Entry.class );
print("Map.Entry '" + x + "' visited");
visit( x.getKey() );
visit( x.getValue() );
after();
}
public void accept( char[] x )
{
before();
visitedOrder.add( char[].class );
print("char[] '" + x + "' visited");
for ( char c : x )
visit( c );
after();
}
public void accept( Character x )
{
before();
visitedOrder.add( Character.class );
print("char '" + x + "' visited");
after();
}
public void accept( A x )
{
before();
visitedOrder.add( A.class );
print("Object A visited");
after();
}
public void accept( B x )
{
before();
visitedOrder.add( B.class );
print("Object B visited");
after();
}
public void accept( C x )
{
before();
visitedOrder.add( C.class );
print("Object C visited");
if ( x.list != null )
for ( Object a : x.list )
visit( a );
after();
}
}
/** Visitor sub-class based on the reflection-based {@link Visitor} class. */
static class InterfaceVisitor extends SimpleVisitor
{
public void accept( Object x )
{
before();
visitedOrder.add( Object.class );
print("Object '" + x + "' visited");
after();
}
public void accept( I i )
{
before();
visitedOrder.add( I.class );
print("Object '" + i + "' visited through interface I");
after();
}
public void accept( II ii )
{
before();
visitedOrder.add( II.class );
print("Object '" + ii + "' visited through interface II");
after();
}
}
/** Visitor implemented the 'classic' way, using a {@link Visitable} interface */
static class ClassicDoubleDispatchVisitor
{
int indent = -1;
/** records order of visitation */
public final List<Class> visitedOrder = new ArrayList<Class>();
void before() { indent++; }
void after() { indent--; }
void print( Object x )
{
System.out.print( repeat(" ", indent ) );
System.out.print( "> " );
System.out.println( x );
}
public void visit( A x )
{
before();
visitedOrder.add( A.class );
print("Object A visited");
after();
}
public void visit( B x )
{
before();
visitedOrder.add( B.class );
print("Object B visited");
after();
}
// this is only accept method that needs changing since
// have to dispatch differently under the hood.
public void visit( C x )
{
before();
visitedOrder.add( C.class );
print("Object C visited");
if ( x.list != null )
for ( Object a : x.list )
((Visitable) a ).accept( this );
after();
}
}
/** Indicates the implementing class can be visited by an instance of
* {@link ClassicDoubleDispatchVisitor}. */
static interface Visitable
{
public void accept( ClassicDoubleDispatchVisitor visitor );
}
/** {@link Visitable} version of class {@link A} */
static class VA extends A implements Visitable
{
public void accept( ClassicDoubleDispatchVisitor visitor )
{
visitor.visit( this );
}
}
/** {@link Visitable} version of class {@link B} */
static class VB extends B implements Visitable
{
public void accept( ClassicDoubleDispatchVisitor visitor )
{
visitor.visit( this );
}
}
/** {@link Visitable} version of class {@link C} */
static class VC extends C implements Visitable
{
public VC() { super(); }
public VC( A... a ) { super( a ); }
public void accept( ClassicDoubleDispatchVisitor visitor )
{
visitor.visit( this );
}
}
/** {@link Visitable} version of class {@link D} */
static class VD extends VA {}
} // end class