EvaluatingVisitorTest.java

package com.thoughtworks.qdox.builder.impl;

import com.thoughtworks.qdox.library.ClassLibrary;
import com.thoughtworks.qdox.model.JavaAnnotation;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.JavaType;
import com.thoughtworks.qdox.model.expression.*;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class EvaluatingVisitorTest
{
    private EvaluatingVisitor visitor = new EvaluatingVisitorStub();
    
    @Test
    public void testUnaryNumericResultTypeInteger()
    {
        Assertions.assertEquals(Integer.class, EvaluatingVisitor.unaryNumericResultType( 0 ));
        Assertions.assertEquals(Integer.class, EvaluatingVisitor.unaryNumericResultType( (byte) 0 ));
        Assertions.assertEquals(Integer.class, EvaluatingVisitor.unaryNumericResultType( (short) 0 ));
        Assertions.assertEquals(Integer.class, EvaluatingVisitor.unaryNumericResultType( (char) 0 ));
    }

    @Test
    public void testUnaryNumericResultTypeLong()
    {
        Assertions.assertEquals(Long.class, EvaluatingVisitor.unaryNumericResultType( 0L ));
    }

    @Test
    public void testUnaryNumericResultTypeVoid()
    {
        Assertions.assertEquals(void.class, EvaluatingVisitor.unaryNumericResultType( new Object() ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.unaryNumericResultType( (double) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.unaryNumericResultType( (float) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.unaryNumericResultType( null ));
    }

    @Test
    public void testUnaryResultTypeInteger()
    {
        Assertions.assertEquals(Integer.class, EvaluatingVisitor.unaryResultType( 0 ));
        Assertions.assertEquals(Integer.class, EvaluatingVisitor.unaryResultType( (byte) 0 ));
        Assertions.assertEquals(Integer.class, EvaluatingVisitor.unaryResultType( (short) 0 ));
        Assertions.assertEquals(Integer.class, EvaluatingVisitor.unaryResultType( (char) 0 ));
    }

    @Test
    public void testUnaryResultTypeLong()
    {
        Assertions.assertEquals(Long.class, EvaluatingVisitor.unaryResultType( 0L ));
    }

    @Test
    public void testUnaryResultTypeDouble()
    {
        Assertions.assertEquals(Double.class, EvaluatingVisitor.unaryResultType( (double) 0 ));
    }

    @Test
    public void testUnaryResultTypeFloat()
    {
        Assertions.assertEquals(Float.class, EvaluatingVisitor.unaryResultType( (float) 0 ));
    }

    @Test
    public void testUnaryResultTypeVoid()
    {
        Assertions.assertEquals(void.class, EvaluatingVisitor.unaryResultType( new Object() ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.unaryResultType( null ));
    }
    
    @Test
    public void testNumericResultTypeLong()
    {
        Assertions.assertEquals(Long.class, EvaluatingVisitor.numericResultType( (long) 0, (long) 0 ));
        Assertions.assertEquals(Long.class, EvaluatingVisitor.numericResultType( (int) 0, (long) 0 ));
        Assertions.assertEquals(Long.class, EvaluatingVisitor.numericResultType( (long) 0, (int) 0 ));
    }
    
    @Test
    public void testNumericResultTypeInteger()
    {
        Assertions.assertEquals(Integer.class, EvaluatingVisitor.numericResultType( (int) 0, (int) 0 ));
        Assertions.assertEquals(Integer.class, EvaluatingVisitor.numericResultType( (short) 0, (int) 0 ));
        Assertions.assertEquals(Integer.class, EvaluatingVisitor.numericResultType( (int) 0, (short) 0 ));
    }
    
    @Test
    public void testNumericResultTypeVoid()
    {
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( (double) 0, (double) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( (float) 0, (double) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( (double) 0, (float) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( (float) 0, (float) 0 ));

        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( (double) 0, new Object() ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( (float) 0, new Object() ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( (long) 0, new Object() ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( (int) 0, new Object() ));

        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( new Object(), (double) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( new Object(), (float) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( new Object(), (long) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( new Object(), (int) 0 ));
        
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( (double) 0, null ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( (float) 0, null ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( (long) 0, null ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( (int) 0, null ));

        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( null, (double) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( null, (float) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( null, (long) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.numericResultType( null, (int) 0 ));
    }

    @Test
    public void testResultTypeDouble()
    {
        // If either operand is of type double, the other is converted to double.
        Assertions.assertEquals(Double.class, EvaluatingVisitor.resultType( (double) 0, (double) 0 ));
        Assertions.assertEquals(Double.class, EvaluatingVisitor.resultType( (float) 0, (double) 0 ));
        Assertions.assertEquals(Double.class, EvaluatingVisitor.resultType( (int) 0, (double) 0 ));
        Assertions.assertEquals(Double.class, EvaluatingVisitor.resultType( (double) 0, (float) 0 ));
        Assertions.assertEquals(Double.class, EvaluatingVisitor.resultType( (double) 0, (int) 0 ));
    }

    @Test
    public void testResultTypeFloat()
    {
        // Otherwise, if either operand is of type float, the other is converted to float.
        Assertions.assertEquals(Float.class, EvaluatingVisitor.resultType( (float) 0, (float) 0 ));
        Assertions.assertEquals(Float.class, EvaluatingVisitor.resultType( (int) 0, (float) 0 ));
        Assertions.assertEquals(Float.class, EvaluatingVisitor.resultType( (float) 0, (int) 0 ));
    }

    @Test
    public void testResultTypeLong()
    {
        // Otherwise, if either operand is of type long, the other is converted to long.
        Assertions.assertEquals(Long.class, EvaluatingVisitor.resultType( (long) 0, (long) 0 ));
        Assertions.assertEquals(Long.class, EvaluatingVisitor.resultType( (int) 0, (long) 0 ));
        Assertions.assertEquals(Long.class, EvaluatingVisitor.resultType( (long) 0, (int) 0 ));
    }

    @Test
    public void testResultTypeInteger()
    {
        // Otherwise, if either operand is of type long, the other is converted to long.
        Assertions.assertEquals(Integer.class, EvaluatingVisitor.resultType( (int) 0, (int) 0 ));
        Assertions.assertEquals(Integer.class, EvaluatingVisitor.resultType( (short) 0, (int) 0 ));
        Assertions.assertEquals(Integer.class, EvaluatingVisitor.resultType( (int) 0, (short) 0 ));
    }
    
    @Test
    public void testResultTypeVoid()
    {
        // Otherwise, if either operand is of type long, the other is converted to long.
        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( (double) 0, new Object() ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( (float) 0, new Object() ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( (long) 0, new Object() ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( (int) 0, new Object() ));

        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( new Object(), (double) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( new Object(), (float) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( new Object(), (long) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( new Object(), (int) 0 ));
        
        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( (double) 0, null ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( (float) 0, null ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( (long) 0, null ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( (int) 0, null ));

        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( null, (double) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( null, (float) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( null, (long) 0 ));
        Assertions.assertEquals(void.class, EvaluatingVisitor.resultType( null, (int) 0 ));
    }
    
    @Test
    public void testVisitAdd()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Doubles
        when( lhs.accept( any( EvaluatingVisitor.class ) ) ).thenReturn( 7.0D );
        when( rhs.accept( any( EvaluatingVisitor.class ) ) ).thenReturn( 2.0D );
        Assertions.assertEquals(7.0D + 2.0D, visitor.visit(  new Add( lhs, rhs ) ));

        // Floats
        when( lhs.accept( any( EvaluatingVisitor.class ) ) ).thenReturn( 7.0F );
        when( rhs.accept( any( EvaluatingVisitor.class ) ) ).thenReturn( 2.0F );
        Assertions.assertEquals(7.0F + 2.0F, visitor.visit(  new Add( lhs, rhs ) ));

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L + 2L, visitor.visit(  new Add( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 + 2, visitor.visit(  new Add( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new Add( lhs, rhs ) );
            Assertions.fail("Additive operations (+  and -) can only be performed on numeric types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void testVisitAnd()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L & 2L, visitor.visit(  new And( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 & 2, visitor.visit(  new And( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new And( lhs, rhs ) );
            Assertions.fail("The and(&) operator can only be performed on integral types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }

    @Test
    public void testVisitAnnotation()
    {
        try{
            JavaAnnotation annotation = mock(JavaAnnotation.class);
            visitor.visit( annotation );
            Assertions.fail("Visiting an annotation is not supported and should throw an UnsupportedOperationException");
        }
        catch (UnsupportedOperationException e) {
        }
    }
    
    @Test
    public void testVisitAnnotationValueList() {
        {
            List<AnnotationValue> emptyList = Collections.emptyList();
            List<?> visitedResult = visitor.visit( new AnnotationValueList( emptyList ) );
            Assertions.assertEquals(0, visitedResult.size());
        }
        
        {
            AnnotationValue annoVal = mock( AnnotationValue.class );
            Object singleResult = new Object();
            when( annoVal.accept( visitor ) ).thenReturn( singleResult );
            List<?> visitedResult = visitor.visit( new AnnotationValueList( Collections.singletonList( annoVal ) ) );
            Assertions.assertEquals(1, visitedResult.size());
            Assertions.assertSame(singleResult, visitedResult.get( 0 ));
        }
    }

    @Test
    public void testVisitCast() throws Exception
    {
        AnnotationValue value = mock( AnnotationValue.class );
        when( value.accept( visitor ) ).thenReturn( 7 );

        JavaClass primitiveClass = mock( JavaClass.class );
        when( primitiveClass.isPrimitive() ).thenReturn( true );

        when( primitiveClass.getFullyQualifiedName() ).thenReturn( "byte" );
        Assertions.assertEquals((byte) 7, visitor.visit( new Cast( primitiveClass, value ) ));
        when( primitiveClass.getFullyQualifiedName() ).thenReturn( "char" );
        Assertions.assertEquals((char) 7, visitor.visit( new Cast( primitiveClass, value ) ));
        when( primitiveClass.getFullyQualifiedName() ).thenReturn( "short" );
        Assertions.assertEquals((short) 7, visitor.visit( new Cast( primitiveClass, value ) ));
        when( primitiveClass.getFullyQualifiedName() ).thenReturn( "int" );
        Assertions.assertEquals((int) 7, visitor.visit( new Cast( primitiveClass, value ) ));
        when( primitiveClass.getFullyQualifiedName() ).thenReturn( "long" );
        Assertions.assertEquals((long) 7, visitor.visit( new Cast( primitiveClass, value ) ));
        when( primitiveClass.getFullyQualifiedName() ).thenReturn( "float" );
        Assertions.assertEquals((float) 7, visitor.visit( new Cast( primitiveClass, value ) ));
        when( primitiveClass.getFullyQualifiedName() ).thenReturn( "double" );
        Assertions.assertEquals((double) 7, visitor.visit( new Cast( primitiveClass, value ) ));

        try
        {
            when( primitiveClass.getFullyQualifiedName() ).thenReturn( "void" );
            visitor.visit( new Cast( primitiveClass, value ) );

            Assertions.fail("Although 'void' is a primitive, you can't cast to it");
        }
        catch ( IllegalArgumentException iae )
        {
        }

        JavaClass stringClass = mock( JavaClass.class );
        when( stringClass.getFullyQualifiedName() ).thenReturn( "java.lang.String" );
        when( value.accept( visitor ) ).thenReturn( "hello world" );
        Assertions.assertEquals((String) "hello world", visitor.visit( new Cast( stringClass, value ) ));

        JavaClass listClass = mock( JavaClass.class );
        when( listClass.getFullyQualifiedName() ).thenReturn( "java.util.List" );
        Object list = Collections.EMPTY_LIST;
        when( value.accept( visitor ) ).thenReturn( list );
        Assertions.assertEquals((List<?>) list, visitor.visit( new Cast( listClass, value ) ));
    }
    
    @Test
    public void testVisitDivide()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Doubles
        when( lhs.accept( visitor ) ).thenReturn( 7.0D );
        when( rhs.accept( visitor ) ).thenReturn( 2.0D );
        Assertions.assertEquals(7.0D / 2.0D, visitor.visit(  new Divide( lhs, rhs ) ));

        // Floats
        when( lhs.accept( visitor ) ).thenReturn( 7.0F );
        when( rhs.accept( visitor ) ).thenReturn( 2.0F );
        Assertions.assertEquals(7.0F / 2.0F, visitor.visit(  new Divide( lhs, rhs ) ));

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L / 2L, visitor.visit(  new Divide( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 / 2, visitor.visit(  new Divide( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new Divide( lhs, rhs ) );
            Assertions.fail("The divide(/) operator can only be performed on numeric types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void testVisitEquals() 
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Doubles
        when( lhs.accept( visitor ) ).thenReturn( 7.0D );
        when( rhs.accept( visitor ) ).thenReturn( 2.0D );
        Assertions.assertEquals(7.0D == 2.0D, visitor.visit(  new Equals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7.0D );
        Assertions.assertEquals(7.0D == 7.0D, visitor.visit(  new Equals( lhs, rhs ) ));

        // Floats
        when( lhs.accept( visitor ) ).thenReturn( 7.0F );
        when( rhs.accept( visitor ) ).thenReturn( 2.0F );
        Assertions.assertEquals(7.0F == 2.0F, visitor.visit(  new Equals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7.0F );
        Assertions.assertEquals(7.0F == 7.0F, visitor.visit(  new Equals( lhs, rhs ) ));

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L == 2L, visitor.visit(  new Equals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7L );
        Assertions.assertEquals(7L == 7L, visitor.visit(  new Equals( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 == 2, visitor.visit(  new Equals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7 );
        Assertions.assertEquals(7 == 7, visitor.visit(  new Equals( lhs, rhs ) ));

        // Objects
        Object object1 = new Object();
        Object object2 = new Object();
        when( lhs.accept( visitor ) ).thenReturn( object1 );
        when( rhs.accept( visitor ) ).thenReturn( object2 );
        Assertions.assertEquals(object1 == object2, visitor.visit(  new Equals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( object1 );
        Assertions.assertEquals(object1 == object1, visitor.visit(  new Equals( lhs, rhs ) ));
    }

    @Test
    public void testVisitExlusiveOr() 
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L ^ 2L, visitor.visit(  new ExclusiveOr( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 ^ 2, visitor.visit(  new ExclusiveOr( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new ExclusiveOr( lhs, rhs ) );
            Assertions.fail("The exclusive-or(^) operator can only be performed on integral types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void testVisitFieldRef() 
    {
        FieldRef ref = new FieldRef( "fieldname" );
        try {
            visitor.visit( ref );
            Assertions.fail("fieldname should be a unresolvable field");
        }
        catch( IllegalArgumentException e )
        {
        }
        
        JavaField nonStaticNonFinalfield = mock( JavaField.class );
        JavaClass declaringClass = mock( JavaClass.class );
        when( declaringClass.getFieldByName( "fieldname" ) ).thenReturn( nonStaticNonFinalfield );
        when( nonStaticNonFinalfield.getDeclaringClass() ).thenReturn( declaringClass );

        ref.setDeclaringClass( declaringClass );
        try 
        {
            visitor.visit(  ref );
            Assertions.fail("fieldname should fail, because it's not-static and non-final");
        }
        catch( IllegalArgumentException e)
        {
        }
        

        JavaField staticFinalfield = mock( JavaField.class );
        when( staticFinalfield.isStatic() ).thenReturn( true );
        when( staticFinalfield.isFinal() ).thenReturn( true );
        when( declaringClass.getFieldByName( "fieldname" ) ).thenReturn( staticFinalfield );

        ref = new FieldRef( "fieldname" );
        ref.setDeclaringClass( declaringClass );
        Assertions.assertSame(EvaluatingVisitorStub.fieldReferenceValue, visitor.visit( ref ));

        ref = new FieldRef( "fieldname" );
        ref.setDeclaringClass( declaringClass );
        Assertions.assertSame(EvaluatingVisitorStub.fieldReferenceValue, visitor.visit( ref ));

        ref = new FieldRef( "a.B.fieldname" );
        ref.setDeclaringClass( null );
        JavaClass b = mock( JavaClass.class );
        when( b.getFieldByName( "fieldname" ) ).thenReturn( staticFinalfield );
        ClassLibrary classLibrary = mock( ClassLibrary.class );
        when( classLibrary.hasClassReference( "a.B" ) ).thenReturn( true );
        when( classLibrary.getJavaClass( "a.B" ) ).thenReturn( b );
        ref.setClassLibrary( classLibrary );
        Assertions.assertSame(EvaluatingVisitorStub.fieldReferenceValue, visitor.visit( ref ));
    }
    
    
    @Test
    public void testVisitGreaterEquals()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Doubles
        when( lhs.accept( visitor ) ).thenReturn( 7.0D );
        when( rhs.accept( visitor ) ).thenReturn( 2.0D );
        Assertions.assertEquals(7.0D >= 2.0D, visitor.visit(  new GreaterEquals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7.0D );
        Assertions.assertEquals(7.0D >= 7.0D, visitor.visit(  new GreaterEquals( lhs, rhs ) ));
        
        when( lhs.accept( visitor ) ).thenReturn( 2.0D );
        Assertions.assertEquals(2.0D >= 7.0D, visitor.visit(  new GreaterEquals( lhs, rhs ) ));

        // Floats
        when( lhs.accept( visitor ) ).thenReturn( 7.0F );
        when( rhs.accept( visitor ) ).thenReturn( 2.0F );
        Assertions.assertEquals(7.0F >= 2.0F, visitor.visit(  new GreaterEquals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7.0F );
        Assertions.assertEquals(7.0F >= 7.0F, visitor.visit(  new GreaterEquals( lhs, rhs ) ));

        when( lhs.accept( visitor ) ).thenReturn( 2.0F );
        Assertions.assertEquals(2.0F >= 7.0F, visitor.visit(  new GreaterEquals( lhs, rhs ) ));

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L >= 2L, visitor.visit(  new GreaterEquals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7L );
        Assertions.assertEquals(7L >= 7L, visitor.visit(  new GreaterEquals( lhs, rhs ) ));

        when( lhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(2L >= 7L, visitor.visit(  new GreaterEquals( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 >= 2, visitor.visit(  new GreaterEquals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7 );
        Assertions.assertEquals(7 >=7, visitor.visit(  new GreaterEquals( lhs, rhs ) ));

        when( lhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(2 >= 7, visitor.visit(  new GreaterEquals( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new GreaterEquals( lhs, rhs ) );
            Assertions.fail("The greater-equals(>=) operator can only be performed on numeric types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void testVisitGreaterThan()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Doubles
        when( lhs.accept( visitor ) ).thenReturn( 7.0D );
        when( rhs.accept( visitor ) ).thenReturn( 2.0D );
        Assertions.assertEquals(7.0D > 2.0D, visitor.visit(  new GreaterThan( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7.0D );
        Assertions.assertEquals(7.0D > 7.0D, visitor.visit(  new GreaterThan( lhs, rhs ) ));
        
        when( lhs.accept( visitor ) ).thenReturn( 2.0D );
        Assertions.assertEquals(2.0D > 7.0D, visitor.visit(  new GreaterThan( lhs, rhs ) ));

        // Floats
        when( lhs.accept( visitor ) ).thenReturn( 7.0F );
        when( rhs.accept( visitor ) ).thenReturn( 2.0F );
        Assertions.assertEquals(7.0F > 2.0F, visitor.visit(  new GreaterThan( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7.0F );
        Assertions.assertEquals(7.0F > 7.0F, visitor.visit(  new GreaterThan( lhs, rhs ) ));

        when( lhs.accept( visitor ) ).thenReturn( 2.0F );
        Assertions.assertEquals(2.0F > 7.0F, visitor.visit(  new GreaterThan( lhs, rhs ) ));

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L > 2L, visitor.visit(  new GreaterThan( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7L );
        Assertions.assertEquals(7L > 7L, visitor.visit(  new GreaterThan( lhs, rhs ) ));

        when( lhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(2L > 7L, visitor.visit(  new GreaterThan( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 > 2, visitor.visit(  new GreaterThan( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7 );
        Assertions.assertEquals(7 > 7, visitor.visit(  new GreaterThan( lhs, rhs ) ));

        when( lhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(2 > 7, visitor.visit(  new GreaterThan( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new GreaterThan( lhs, rhs ) );
            Assertions.fail("The greater-than(>) operator can only be performed on numeric types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void testVisitLessEquals()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Doubles
        when( lhs.accept( visitor ) ).thenReturn( 7.0D );
        when( rhs.accept( visitor ) ).thenReturn( 2.0D );
        Assertions.assertEquals(7.0D <= 2.0D, visitor.visit(  new LessEquals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7.0D );
        Assertions.assertEquals(7.0D <= 7.0D, visitor.visit(  new LessEquals( lhs, rhs ) ));
        
        when( lhs.accept( visitor ) ).thenReturn( 2.0D );
        Assertions.assertEquals(2.0D <= 7.0D, visitor.visit(  new LessEquals( lhs, rhs ) ));

        // Floats
        when( lhs.accept( visitor ) ).thenReturn( 7.0F );
        when( rhs.accept( visitor ) ).thenReturn( 2.0F );
        Assertions.assertEquals(7.0F <= 2.0F, visitor.visit(  new LessEquals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7.0F );
        Assertions.assertEquals(7.0F <= 7.0F, visitor.visit(  new LessEquals( lhs, rhs ) ));

        when( lhs.accept( visitor ) ).thenReturn( 2.0F );
        Assertions.assertEquals(2.0F <= 7.0F, visitor.visit(  new LessEquals( lhs, rhs ) ));

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L <= 2L, visitor.visit(  new LessEquals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7L );
        Assertions.assertEquals(7L <= 7L, visitor.visit(  new LessEquals( lhs, rhs ) ));

        when( lhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(2L <= 7L, visitor.visit(  new LessEquals( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 <= 2, visitor.visit(  new LessEquals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7 );
        Assertions.assertEquals(7 <= 7, visitor.visit(  new LessEquals( lhs, rhs ) ));

        when( lhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(2 <= 7, visitor.visit(  new LessEquals( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new LessEquals( lhs, rhs ) );
            Assertions.fail("The less-equals(<=) operator can only be performed on numeric types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void testVisitLessThan()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Doubles
        when( lhs.accept( visitor ) ).thenReturn( 7.0D );
        when( rhs.accept( visitor ) ).thenReturn( 2.0D );
        Assertions.assertEquals(7.0D < 2.0D, visitor.visit(  new LessThan( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7.0D );
        Assertions.assertEquals(7.0D < 7.0D, visitor.visit(  new LessThan( lhs, rhs ) ));
        
        when( lhs.accept( visitor ) ).thenReturn( 2.0D );
        Assertions.assertEquals(2.0D < 7.0D, visitor.visit(  new LessThan( lhs, rhs ) ));

        // Floats
        when( lhs.accept( visitor ) ).thenReturn( 7.0F );
        when( rhs.accept( visitor ) ).thenReturn( 2.0F );
        Assertions.assertEquals(7.0F < 2.0F, visitor.visit(  new LessThan( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7.0F );
        Assertions.assertEquals(7.0F < 7.0F, visitor.visit(  new LessThan( lhs, rhs ) ));

        when( lhs.accept( visitor ) ).thenReturn( 2.0F );
        Assertions.assertEquals(2.0F < 7.0F, visitor.visit(  new LessThan( lhs, rhs ) ));

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L < 2L, visitor.visit(  new LessThan( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7L );
        Assertions.assertEquals(7L < 7L, visitor.visit(  new LessThan( lhs, rhs ) ));

        when( lhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(2L < 7L, visitor.visit(  new LessThan( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 < 2, visitor.visit(  new LessThan( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7 );
        Assertions.assertEquals(7 < 7, visitor.visit(  new LessThan( lhs, rhs ) ));

        when( lhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(2 < 7, visitor.visit(  new LessThan( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new LessThan( lhs, rhs ) );
            Assertions.fail("The less-than(<) operator can only be performed on numeric types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }

    @Test
    public void visitLogicalAnd()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        when( lhs.accept( visitor ) ).thenReturn( true );
        when( rhs.accept( visitor ) ).thenReturn( true );
        Assertions.assertEquals(true && true, visitor.visit( new LogicalAnd( lhs, rhs ) ));


        when( lhs.accept( visitor ) ).thenReturn( false );
        when( rhs.accept( visitor ) ).thenReturn( false );
        Assertions.assertEquals(false && false, visitor.visit( new LogicalAnd( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new LogicalAnd( lhs, rhs ) );
            Assertions.fail("The logical and(&&) operator can only be performed on booleans");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void visitLogicalNot()
    {
        AnnotationValue value = mock( AnnotationValue.class );

        when( value.accept( visitor ) ).thenReturn( true );
        Assertions.assertEquals(!true, visitor.visit( new LogicalNot( value ) ));

        when( value.accept( visitor ) ).thenReturn( false );
        Assertions.assertEquals(!false, visitor.visit( new LogicalNot( value ) ));

        // Objects
        when( value.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit( new LogicalNot( value ) );
            Assertions.fail("The logical not(!) operator can only be performed on booleans");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }

    @Test
    public void visitLogicalOr()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        when( lhs.accept( visitor ) ).thenReturn( true );
        when( rhs.accept( visitor ) ).thenReturn( true );
        Assertions.assertEquals(true || true, visitor.visit( new LogicalOr( lhs, rhs ) ));


        when( lhs.accept( visitor ) ).thenReturn( false );
        when( rhs.accept( visitor ) ).thenReturn( false );
        Assertions.assertEquals(false || false, visitor.visit( new LogicalOr( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new LogicalOr( lhs, rhs ) );
            Assertions.fail("The logical or(||) operator can only be performed on booleans");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void testVisitMinusSign()
    {
        AnnotationValue value = mock( AnnotationValue.class );
        
        // Double
        when( value.accept( visitor ) ).thenReturn( 7.0D );
        Assertions.assertEquals(-7.0D, visitor.visit( new MinusSign( value ) ));
        
        // Float
        when( value.accept( visitor ) ).thenReturn( 7.0F );
        Assertions.assertEquals(-7.0F, visitor.visit( new MinusSign( value ) ));
        
        // Long
        when( value.accept( visitor ) ).thenReturn( 7L );
        Assertions.assertEquals(-7L, visitor.visit( new MinusSign( value ) ));

        // Integer
        when( value.accept( visitor ) ).thenReturn( 7 );
        Assertions.assertEquals(-7, visitor.visit( new MinusSign( value ) ));
        
        when( value.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit( new MinusSign( value ) );
            Assertions.fail("The minus(-) sign operator can only be performed on numeric types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void testVisitMultiply()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Doubles
        when( lhs.accept( visitor ) ).thenReturn( 7.0D );
        when( rhs.accept( visitor ) ).thenReturn( 2.0D );
        Assertions.assertEquals(7.0D * 2.0D, visitor.visit(  new Multiply( lhs, rhs ) ));

        // Floats
        when( lhs.accept( visitor ) ).thenReturn( 7.0F );
        when( rhs.accept( visitor ) ).thenReturn( 2.0F );
        Assertions.assertEquals(7.0F * 2.0F, visitor.visit(  new Multiply( lhs, rhs ) ));

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L * 2L, visitor.visit(  new Multiply( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 * 2, visitor.visit(  new Multiply( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new Multiply( lhs, rhs ) );
            Assertions.fail("The multiply(*) operator can only be performed on numeric types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void testVisitNot() 
    {
        AnnotationValue value = mock( AnnotationValue.class );
        
        // Longs
        when( value.accept( visitor ) ).thenReturn( 7L );
        Assertions.assertEquals(~7L, visitor.visit(  new Not( value ) ));

        // Integers
        when( value.accept( visitor ) ).thenReturn( 7 );
        Assertions.assertEquals(~7, visitor.visit(  new Not( value) ));

        // Objects
        when( value.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new Not( value ) );
            Assertions.fail("The not(~) operator can only be performed on integral types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void testVisitNotEquals()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Doubles
        when( lhs.accept( visitor ) ).thenReturn( 7.0D );
        when( rhs.accept( visitor ) ).thenReturn( 2.0D );
        Assertions.assertEquals(7.0D != 2.0D, visitor.visit(  new NotEquals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7.0D );
        Assertions.assertEquals(7.0D != 7.0D, visitor.visit(  new NotEquals( lhs, rhs ) ));

        // Floats
        when( lhs.accept( visitor ) ).thenReturn( 7.0F );
        when( rhs.accept( visitor ) ).thenReturn( 2.0F );
        Assertions.assertEquals(7.0F != 2.0F, visitor.visit(  new NotEquals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7.0F );
        Assertions.assertEquals(7.0F != 7.0F, visitor.visit(  new NotEquals( lhs, rhs ) ));

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L != 2L, visitor.visit(  new NotEquals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7L );
        Assertions.assertEquals(7L != 7L, visitor.visit(  new NotEquals( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 != 2, visitor.visit(  new NotEquals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( 7 );
        Assertions.assertEquals(7 != 7, visitor.visit(  new NotEquals( lhs, rhs ) ));

        // Objects
        Object object1 = new Object();
        Object object2 = new Object();
        when( lhs.accept( visitor ) ).thenReturn( object1 );
        when( rhs.accept( visitor ) ).thenReturn( object2 );
        Assertions.assertEquals(object1 != object2, visitor.visit(  new NotEquals( lhs, rhs ) ));

        when( rhs.accept( visitor ) ).thenReturn( object1 );
        Assertions.assertEquals(object1 != object1, visitor.visit(  new NotEquals( lhs, rhs ) ));
    }
    
    @Test
    public void testVisitOr()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L | 2L, visitor.visit(  new Or( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 | 2, visitor.visit(  new Or( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new Or( lhs, rhs ) );
            Assertions.fail("The or(|) operator can only be performed on integral types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void testVisitParenExpression()
    {
        AnnotationValue value = mock( AnnotationValue.class );
        
        Object acceptResult = new Object();
        when( value.accept( visitor ) ).thenReturn( acceptResult );
        Assertions.assertSame(acceptResult, visitor.visit( new ParenExpression( value ) ));
    }
    
    @Test
    public void testVisitPlusSign() 
    {
        AnnotationValue value = mock( AnnotationValue.class );
        
        // Double
        when( value.accept( visitor ) ).thenReturn( 7.0D );
        Assertions.assertEquals(7.0D, visitor.visit( new PlusSign( value ) ));
        
        // Float
        when( value.accept( visitor ) ).thenReturn( 7.0F );
        Assertions.assertEquals(7.0F, visitor.visit( new PlusSign( value ) ));
        
        // Long
        when( value.accept( visitor ) ).thenReturn( 7L );
        Assertions.assertEquals(7L, visitor.visit( new PlusSign( value ) ));

        // Integer
        when( value.accept( visitor ) ).thenReturn( 7 );
        Assertions.assertEquals(7, visitor.visit( new PlusSign( value ) ));
        
        when( value.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit( new PlusSign( value ) );
            Assertions.fail("The plus sign operator can only be performed on numeric types");
        }
        catch ( IllegalArgumentException iae )
        {

        }
    }
    
    @Test
    public void testVisitQuery()
    {
        AnnotationValue condition = mock( AnnotationValue.class );
        AnnotationValue trueExpr = mock( AnnotationValue.class );
        AnnotationValue falseExpr = mock( AnnotationValue.class );

        when( trueExpr.accept( visitor ) ).thenReturn( "consequent" );
        when( falseExpr.accept( visitor ) ).thenReturn( "alternative" );

        // true condition
        when( condition.accept( visitor ) ).thenReturn( Boolean.TRUE );
        Assertions.assertEquals("consequent", visitor.visit( new Query( condition, trueExpr, falseExpr ) ));

        when( condition.accept( visitor ) ).thenReturn( true );
        Assertions.assertEquals("consequent", visitor.visit( new Query( condition, trueExpr, falseExpr ) ));

        // false condition
        when( condition.accept( visitor ) ).thenReturn( Boolean.FALSE );
        Assertions.assertEquals("alternative", visitor.visit( new Query( condition, trueExpr, falseExpr ) ));

        when( condition.accept( visitor ) ).thenReturn( false );
        Assertions.assertEquals("alternative", visitor.visit( new Query( condition, trueExpr, falseExpr ) ));

        when( condition.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit( new Query( condition, trueExpr, falseExpr ) );
            Assertions.fail("The condition of the query( ? : ) must be a boolean");
        }
        catch ( IllegalArgumentException iae )
        {

        }
    }
    
    @Test
    public void testVisitRemainder()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Doubles
        when( lhs.accept( visitor ) ).thenReturn( 7.0D );
        when( rhs.accept( visitor ) ).thenReturn( 2.0D );
        Assertions.assertEquals(7.0D % 2.0D, visitor.visit(  new Remainder( lhs, rhs ) ));

        // Floats
        when( lhs.accept( visitor ) ).thenReturn( 7.0F );
        when( rhs.accept( visitor ) ).thenReturn( 2.0F );
        Assertions.assertEquals(7.0F % 2.0F, visitor.visit(  new Remainder( lhs, rhs ) ));

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L % 2L, visitor.visit(  new Remainder( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 % 2, visitor.visit(  new Remainder( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new Remainder( lhs, rhs ) );
            Assertions.fail("The remainder(%) operator can only be performed on numeric types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void testVisitShiftLeft()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L << 2L, visitor.visit( new ShiftLeft( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 << 2, visitor.visit( new ShiftLeft( lhs, rhs ) ));

        // Objects
        when( lhs.accept( any( EvaluatingVisitor.class ) ) ).thenReturn( new Object() );
        when( rhs.accept( any( EvaluatingVisitor.class ) ) ).thenReturn( new Object() );
        try
        {
            visitor.visit( new ShiftLeft( lhs, rhs ) );
            Assertions.fail("Bitwise and bit shift operations can only be performed on integral types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void testVisitShiftRight()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );
        
        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L >> 2L, visitor.visit( new ShiftRight( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 >> 2, visitor.visit( new ShiftRight( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit( new ShiftRight( lhs, rhs ) );
            Assertions.fail("Bitwise and bit shift operations can only be performed on integral types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void testVisitSubtract()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Doubles
        when( lhs.accept( any( EvaluatingVisitor.class ) ) ).thenReturn( 7.0D );
        when( rhs.accept( any( EvaluatingVisitor.class ) ) ).thenReturn( 2.0D );
        Assertions.assertEquals(7.0D - 2.0D, visitor.visit(  new Subtract( lhs, rhs ) ));

        // Floats
        when( lhs.accept( any( EvaluatingVisitor.class ) ) ).thenReturn( 7.0F );
        when( rhs.accept( any( EvaluatingVisitor.class ) ) ).thenReturn( 2.0F );
        Assertions.assertEquals(7.0F - 2.0F, visitor.visit(  new Subtract( lhs, rhs ) ));

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L - 2L, visitor.visit(  new Subtract( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 - 2, visitor.visit(  new Subtract( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new Subtract( lhs, rhs ) );
            Assertions.fail("Additive operations (+  and -) can only be performed on numeric types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    @Test
    public void testVisitTypeRef()
    {
        JavaType type = mock( JavaType.class );
        Assertions.assertSame(type, visitor.visit( new TypeRef( type ) ));
    }

    @Test
    public void testVisitUnsignedShiftRight()
    {
        AnnotationValue lhs = mock( AnnotationValue.class );
        AnnotationValue rhs = mock( AnnotationValue.class );

        // Longs
        when( lhs.accept( visitor ) ).thenReturn( 7L );
        when( rhs.accept( visitor ) ).thenReturn( 2L );
        Assertions.assertEquals(7L >>> 2L, visitor.visit(  new UnsignedShiftRight( lhs, rhs ) ));

        // Integers
        when( lhs.accept( visitor ) ).thenReturn( 7 );
        when( rhs.accept( visitor ) ).thenReturn( 2 );
        Assertions.assertEquals(7 >>> 2, visitor.visit(  new UnsignedShiftRight( lhs, rhs ) ));

        // Objects
        when( lhs.accept( visitor ) ).thenReturn( new Object() );
        when( rhs.accept( visitor ) ).thenReturn( new Object() );
        try
        {
            visitor.visit(  new UnsignedShiftRight( lhs, rhs ) );
            Assertions.fail("Bitwise and bit shift operations can only be performed on integral types");
        }
        catch ( IllegalArgumentException iae )
        {
        }
    }
    
    private static class EvaluatingVisitorStub extends EvaluatingVisitor {
        
        static final Object fieldReferenceValue = new Object();
        
        @Override
        protected Object getFieldReferenceValue( JavaField javaField )
        {
            return fieldReferenceValue;
        }
    }
}