# $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;
=head1 NAME
Test::C2FIT::TypeAdapter - Base class of all TypeAdapters.
=head1 SYNOPSIS
You typically subclass TypeAdapter. Implement at least parse(), eventually equals() and toString().
=head1 DESCRIPTION
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.
=head1 METHODS
=over 4
=item B<parse($string)>
Returns the internal representation of $string. Either this is an object instance, but it can be also a scalar
value.
=item B<toString()>
Returns the stringified representation of the internal value.
=back
=head1 SEE ALSO
Extensive and up-to-date documentation on FIT can be found at:
http://fit.c2.com/
=cut
__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;
}
}
}