Test::C2FIT::TypeAdapter - Base class of all TypeAdapters.


Test-C2FIT documentation Contained in the Test-C2FIT distribution.

Index


Code Index:

NAME

Top

Test::C2FIT::TypeAdapter - Base class of all TypeAdapters.

SYNOPSIS

Top

You typically subclass TypeAdapter. Implement at least parse(), eventually equals() and toString().

DESCRIPTION

Top

When your data is not stored as string, then you'll propably need an TypeAdapter. E.g.: duration, which is displayed (and entered) in the form "MMM:SS" but stored as number of seconds.

METHODS

Top

parse($string)

Returns the internal representation of $string. Either this is an object instance, but it can be also a scalar value.

toString()

Returns the stringified representation of the internal value.

SEE ALSO

Top

Extensive and up-to-date documentation on FIT can be found at: http://fit.c2.com/


Test-C2FIT documentation Contained in the Test-C2FIT distribution.

# $Id: TypeAdapter.pm,v 1.7 2008/01/24 14:28:26 tonyb Exp $
#
# Copyright (c) 2002-2005 Cunningham & Cunningham, Inc.
# Released under the terms of the GNU General Public License version 2 or later.
#
# Perl translation by Dave W. Smith <dws@postcognitive.com>
# Modified by Tony Byrne <fit4perl@byrnehq.com>

package Test::C2FIT::TypeAdapter;
use Test::C2FIT::Exception;
use Test::C2FIT::ScientificDouble;
use Error qw( :try );

use strict;

# Class methods

sub onMethod {
    my ( $pkg, $fixture, $name ) = @_;

    my $a =
      $pkg->onType( $fixture, $pkg->_guessMethodResultType( $fixture, $name ) );
    $a->{'method'} = $name;
    return $a;
}

sub onField {
    my ( $pkg, $fixture, $name ) = @_;

    my $a = $pkg->onType( $fixture, $pkg->_guessFieldType( $fixture, $name ) );
    $a->{'field'} = $name;
    return $a;
}

#
#   Distinction between onMethod and onSetter:
#   - onMethod - the method result type is assigned a TypeAdapter ("name" is the method name)
#   - onSetter - the method first (and only) parameter is assigned a TypeAdapter ("name" is the method name)
#
sub onSetter {
    my ( $pkg, $fixture, $name ) = @_;

    my $a =
      $pkg->onType( $fixture, $pkg->_guessMethodParamType( $fixture, $name ) );
    $a->{'method'} = $name;
    return $a;
}

#
# returns a fully qualified package name of appropriate Adapter
#
sub _guessFieldType {
    my ( $pkg, $fixture, $name ) = @_;

    my $typeName = $fixture->suggestFieldType($name);

    if ( !defined($typeName) ) {

        # n.b., Field might not exist when we're asked to build a TypeAdapter
        #  for accessing them. This can be addressed by adopting the convention
        #  of populating the object at creation time, rather than lazily, at
        #  least for those fields we're interested in.

        my $object = $fixture->{$name};
        if ( defined($object) ) {

            #DEBUG print "_guessType: ", ref($object), "\n" if ref($object);
            $typeName = "Test::C2FIT::GenericArrayAdapter"
              if ref($object) eq "ARRAY";
        }
    }
    $typeName = "Test::C2FIT::GenericAdapter" unless defined($typeName);
    return $typeName;
}

sub _guessMethodResultType {
    my ( $pkg, $fixture, $name ) = @_;

    my $typeName = $fixture->suggestMethodResultType($name);
    $typeName = "Test::C2FIT::GenericAdapter" unless defined($typeName);
    return $typeName;
}

sub _guessMethodParamType {
    my ( $pkg, $fixture, $name ) = @_;

    my $typeName = $fixture->suggestMethodParamType($name);
    $typeName = "Test::C2FIT::GenericAdapter" unless defined($typeName);
    return $typeName;
}

sub onType {
    my ( $pkg, $fixture, $typeAdapterName ) = @_;
    my $a = $pkg->_createInstance($typeAdapterName);
    $a->init( $fixture, $typeAdapterName );
    $a->{'target'} = $fixture;
    return $a;
}

sub _createInstance {
    my ( $self, $packageName ) = @_;
    my $instance;

    throw Test::C2FIT::Exception("Missing Parameter in _createInstance!")
      unless defined($packageName);

    try {
        $instance = $packageName->new();
      }
      otherwise {};
    if ( !ref($instance) ) {
        try {
            eval "use $packageName;";
            $instance = $packageName->new();
          }
          otherwise {
            my $e = shift;
            throw Test::C2FIT::Exception("Can't load $packageName: $e");
          };
    }

    throw Test::C2FIT::Exception(
        "$packageName - instantiation error")  # if new does not return a ref...
      unless ref($instance);

    throw Test::C2FIT::Exception("$packageName - is not a TypeAdapter!")
      unless $instance->isa('Test::C2FIT::TypeAdapter');

    return $instance;
}

# Instance creation

sub new {
    my $pkg = shift;
    bless { instance => undef, type => undef, @_ }, $pkg;
}

# Instance methods

sub init {
    my $self = shift;
    my ( $fixture, $type ) = @_;
    $self->{'fixture'} = $fixture;
    $self->{'type'}    = $type;
}

sub target {
    my $self = shift;
    my ($target) = @_;
    $self->{'target'} = $target;
}

sub field {
    my $self = shift;
    return $self->{'field'};
}

sub method {
    my $self = shift;
    return $self->{'method'};
}

sub get {
    my $self = shift;
    return $self->{'target'}->{ $self->field() } if $self->field();
    return $self->invoke()                       if $self->method();
    return undef;
}

sub set {
    my $self    = shift;
    my ($value) = @_;
    my $field   = $self->{'field'};
    throw Test::C2FIT::Exception("can't set without a field\n") unless $field;
    $self->{'target'}->{$field} = $value;
}

sub invoke {
    my $self   = shift;
    my $method = $self->{'method'};
    throw Test::C2FIT::Exception("can't invoke without method\n")
      unless $method;
    $self->{'target'}->$method();
}

sub parse {
    my $self = shift;
    my ($s) = @_;

    # is this right, or do we assume that all subclasses will override?
    return $self->{'fixture'}->parse($s);
}

sub equals {
    my $self = shift;
    my ( $a, $b ) = @_;
    if ( !defined($a) ) {
        return !defined($b);
    }

    #
    #   if the instance has an equals method, use it
    #
    #  ( $] > 5.008 )
    #  ? UNIVERSAL->can( $a, "equals" )
    #  : UNIVERSAL::can( $a, "equals" );

    my $can = UNIVERSAL::can( $a, "equals" );
    return $a->equals($b) if ($can);

    # We need to be ugly to handle booleans
    return 1 if $a eq "true"  and $b == 1;
    return 1 if $a eq "false" and $b == 0;

    # We need to be ugly here to handle numbers
    if ( $self->_isnumber($a) and $self->_isnumber($b) ) {
        my $scA = Test::C2FIT::ScientificDouble->new($a);
        my $scB = Test::C2FIT::ScientificDouble->new($b);
        return $scA->equals($scB);
    }

    return $a eq $b;
}

sub _isnumber {
	my ($self, $test) = @_;
	
	# Handle fractions.
	if ($test =~ /\//)
	{
		my ($a, $b) = split /\//, $test;
		return $self->_isnumber($a) && $self->_isnumber($b);
	}
	
	defined scalar $self->_getnum($test);
}

sub _getnum {
    use POSIX qw(strtod);
    my ($self, $str) = @_;
    $str =~ s/^\s+//;
    $str =~ s/\s+$//;
    $! = 0;
    my($num, $unparsed) = strtod($str);
    if (($str eq '') || ($unparsed != 0) || $!) {
        return;
    } else {
        return $num;
    } 
} 

sub toString {
    my $self = shift;
    my ($o) = @_;
    $o = "null" unless defined $o;
    return $o;
}

1;

__END__

package fit;

// Copyright (c) 2002 Cunningham & Cunningham, Inc.
// Released under the terms of the GNU General Public License version 2 or later.

import java.lang.reflect.*;
import java.util.StringTokenizer;

public class TypeAdapter {
    public Object target;
    public Fixture fixture;
    public Field field;
    public Method method;
    public Class type;


    // Factory //////////////////////////////////

    public static TypeAdapter on(Fixture target, Class type) {
        TypeAdapter a = adapterFor(type);
        a.init(target, type);
        return a;
    }

    public static TypeAdapter on(Fixture fixture, Field field) {
        TypeAdapter a = on(fixture, field.getType());
        a.target = fixture;
        a.field = field;
        return a;
    }

    public static TypeAdapter on(Fixture fixture, Method method) {
        TypeAdapter a = on(fixture, method.getReturnType());
        a.target = fixture;
        a.method = method;
        return a;
    }

    public static TypeAdapter adapterFor(Class type) throws UnsupportedOperationException {
        if (type.isPrimitive()) {

            if (type.equals(byte.class)) return new ByteAdapter();
            if (type.equals(short.class)) return new ShortAdapter();
            if (type.equals(int.class)) return new IntAdapter();
            if (type.equals(long.class)) return new LongAdapter();
            if (type.equals(float.class)) return new FloatAdapter();
            if (type.equals(double.class)) return new DoubleAdapter();
            if (type.equals(char.class)) return new CharAdapter();
            if (type.equals(boolean.class)) return new BooleanAdapter();
            throw new UnsupportedOperationException ("can't yet adapt "+type);
        } else {
            if (type.equals(Byte.class)) return new ClassByteAdapter();
            if (type.equals(Short.class)) return new ClassShortAdapter();
            if (type.equals(Integer.class)) return new ClassIntegerAdapter();
            if (type.equals(Long.class)) return new ClassLongAdapter();
            if (type.equals(Float.class)) return new ClassFloatAdapter();
            if (type.equals(Double.class)) return new ClassDoubleAdapter();
            if (type.equals(Character.class)) return new ClassCharacterAdapter();
            if (type.equals(Boolean.class)) return new ClassBooleanAdapter();
            if (type.isArray()) return new ArrayAdapter();
            return new TypeAdapter();
        }
    }


    // Accessors ////////////////////////////////

    protected void init (Fixture fixture, Class type) {
        this.fixture = fixture;
        this.type = type;
    }

    public Object get() throws IllegalAccessException, InvocationTargetException {
        if (field != null)  {return field.get(target);}
        if (method != null) {return invoke();}
        return null;
    }

    public void set(Object value) throws IllegalAccessException {
        field.set(target, value);
    }

    public Object invoke() throws IllegalAccessException, InvocationTargetException {
        Object params[] = {};
        return method.invoke(target, params);
    }

    public Object parse(String s) throws Exception {
        return fixture.parse(s, type);
    }

    public boolean equals(Object a, Object b) {
        if (a==null) {
            return b==null;
        }
        return a.equals(b);
    }

    public String toString(Object o) {
        if (o==null) {
            return "null";
        }
        return o.toString();
    }


    // Subclasses ///////////////////////////////

    static class ByteAdapter extends ClassByteAdapter {
        public void set(Object i) throws IllegalAccessException {
            field.setByte(target, ((Byte)i).byteValue());
        }
    }

    static class ClassByteAdapter extends TypeAdapter {
        public Object parse(String s) {
            return new Byte(Byte.parseByte(s));
        }
    }

    static class ShortAdapter extends ClassShortAdapter {
        public void set(Object i) throws IllegalAccessException {
            field.setShort(target, ((Short)i).shortValue());
        }
    }

    static class ClassShortAdapter extends TypeAdapter {
        public Object parse(String s) {
            return new Short(Short.parseShort(s));
        }
    }

    static class IntAdapter extends ClassIntegerAdapter {
        public void set(Object i) throws IllegalAccessException {
            field.setInt(target, ((Integer)i).intValue());
        }
    }

    static class ClassIntegerAdapter extends TypeAdapter {
        public Object parse(String s) {
            return new Integer(Integer.parseInt(s));
        }
    }

    static class LongAdapter extends ClassLongAdapter {
        public void set(Long i) throws IllegalAccessException {
            field.setLong(target, i.longValue());
        }
    }

    static class ClassLongAdapter extends TypeAdapter {
        public Object parse(String s) {
            return new Long(Long.parseLong(s));
        }
    }

    static class FloatAdapter extends ClassFloatAdapter {
        public void set(Object i) throws IllegalAccessException {
            field.setFloat(target, ((Number)i).floatValue());
        }
        public Object parse(String s) {
            return new Float(Float.parseFloat(s));
        }
    }

    static class ClassFloatAdapter extends TypeAdapter {
        public Object parse(String s) {
            return new Float(Float.parseFloat(s));
        }
    }

    static class DoubleAdapter extends ClassDoubleAdapter {
        public void set(Object i) throws IllegalAccessException {
            field.setDouble(target, ((Number)i).doubleValue());
        }
        public Object parse(String s) {
            return new Double(Double.parseDouble(s));
        }
    }

    static class ClassDoubleAdapter extends TypeAdapter {
        public Object parse(String s) {
            return new Double(Double.parseDouble(s));
        }
    }

    static class CharAdapter extends ClassCharacterAdapter {
        public void set(Object i) throws IllegalAccessException {
            field.setChar(target, ((Character)i).charValue());
        }
    }

    static class ClassCharacterAdapter extends TypeAdapter {
        public Object parse(String s) {
            return new Character(s.charAt(0));
        }
    }

    static class BooleanAdapter extends ClassBooleanAdapter {
        public void set(Object i) throws IllegalAccessException {
            field.setBoolean(target, ((Boolean)i).booleanValue());
        }
    }

    static class ClassBooleanAdapter extends TypeAdapter {
        public Object parse(String s) {
            return new Boolean(s);
        }
    }

    static class ArrayAdapter extends TypeAdapter {
        Class componentType;
        TypeAdapter componentAdapter;

        protected void init(Fixture target, Class type) {
            super.init(target, type);
            componentType = type.getComponentType();
            componentAdapter = on(target, componentType);
        }

        public Object parse(String s) throws Exception {
            StringTokenizer t = new StringTokenizer(s, ",");
            Object array = Array.newInstance(componentType, t.countTokens());
            for (int i=0; t.hasMoreTokens(); i++) {
                Array.set(array, i, componentAdapter.parse(t.nextToken().trim()));
            }
            return array;
        }

        public String toString(Object o) {
            if (o==null) return "";
            int length = Array.getLength(o);
            StringBuffer b = new StringBuffer(5*length);
            for (int i=0; i<length; i++) {
                b.append(componentAdapter.toString(Array.get(o, i)));
                if (i < (length-1)) {
                    b.append(", ");
                }
            }
            return b.toString();
        }

        public boolean equals(Object a, Object b) {
            int length = Array.getLength(a);
            if (length != Array.getLength(b)) return false;
            for (int i=0; i<length; i++) {
                if (!componentAdapter.equals(Array.get(a,i), Array.get(b,i))) return false;
            }
            return true;
        }
    }
}