# $Id: ColumnFixture.pm,v 1.7 2006/05/15 08:37:07 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::ColumnFixture;

use base 'Test::C2FIT::Fixture';
use strict;
use Test::C2FIT::TypeAdapter;

sub new
{
	my $pkg = shift;
	return $pkg->SUPER::new(columnBindings => [], hasExecuted => 0, @_);
}

sub doRows
{
	my $self = shift;
	my($rows) = @_;
	$self->bind($rows->parts());
	$self->SUPER::doRows($rows->more());
}

sub doRow
{
	my $self = shift;
	my($row) = @_;

	$self->{'hasExecuted'} = 0;
	eval
	{
		$self->reset();
		$self->SUPER::doRow($row);
		$self->execute unless $self->{'hasExecuted'};
	};
	if ( $@ ) {
		$self->exception($row->leaf(), $@);
	}
}

sub doCell
{
	my $self = shift;
	my($cell, $column) = @_;

	my $adapter = $self->{'columnBindings'}->[$column];
	eval
	{
		my $string = $cell->text();
		if ( $string eq "" )
		{
	    	$self->check($cell, $adapter);
		}
		elsif ( not defined($adapter) )
		{
			$self->ignore($cell);
		}
		elsif ( $adapter->field() )
		{
			$adapter->set($adapter->parse($string));
		}
		elsif ( $adapter->method() )
		{
			$self->check($cell, $adapter);
		}
	};
	if ( $@ )
	{
		$self->exception($cell, $@);
	}
}

sub check
{
	my $self = shift;
	my($cell, $adapter) = @_;

	if ($self->{'hasExecuted'}) {
    	$self->SUPER::check(@_);
    } 
    elsif ( ! $self->{'hasExecuted'} )
	{
  		$self->{'hasExecuted'} = 1;
		eval
		{
			$self->execute();
        	$self->SUPER::check(@_);
		};
		if ( $@ )
		{
			$self->exception($cell, $@);
		}
	}
}

sub reset
{
	my($self) = @_;
	# about to process first cell of row
}

sub execute
{
	my($self) = @_;
	# about to process first method call of row
}

sub bind
{
	my($self, $heads) = @_;
	my $column = 0;

	$self->{'columnBindings'} = [];
	while ( $heads )
	{
		my $name = $heads->text();
		eval
		{
			if ( $name eq "" )
			{
				$self->{'columnBindings'}->[$column] = undef;
	    		}
			elsif ( $name =~ /^(.*)\(\)$/ )
			{
				$self->{'columnBindings'}->[$column] = $self->bindMethod($1);
			}
			else
			{
				$self->{'columnBindings'}->[$column] = $self->bindField($name);
			}
		};
		if ( $@ )
		{
			$self->exception($heads, $@);
		}
		$heads = $heads->more();
		++$column;
	}
}

sub bindMethod
{
	my $self = shift;
	my($name) = @_;
	return Test::C2FIT::TypeAdapter::onMethod($self, $name);
}

sub bindField
{
	my $self = shift;
	my($name) = @_;
	return Test::C2FIT::TypeAdapter::onField($self, $name);
}

sub getTargetClass
{
	my $self = shift;
	ref($self);
}

1;


=pod

=head1 NAME

Test::C2FIT::ColumnFixture - A ColumnFixture maps columns in the test data to fields or methods of its subclasses.

=head1 SYNOPSIS

Normally, you subclass ColumnFixture.

	package MyColumnFixture;
	use base 'Test::C2FIT::ColumnFixture;'

	sub getX {
	 my $self = shift;
	 return $self->{X};
	}

=head1 DESCRIPTION

Column headings with braces (e.g. getX()) or column names consisting of more words ("get name" will
bound to methods, i.e. the data entered in your document will be checked against the result of
the respective method. ("get name" will be mapped to "getName()")

Column headings without braces consisting of one word will be bound to instance variables (=fields).
In perl these need not to be predeclared. E.g. when column heading is "surname", then the ColumnFixture
puts the text of the respective cell to a variable which can be used by C<$self->{surname}>.

When your data is not stored as string, then you'll propably need an TypeAdapter. See more in L<Fixture>.

=head1 METHODS

=over 4

=item B<reset()>

Will be called before a row gets processed

=item B<execute()>

Will be called either after a row has been processed or before the first usage of a method-column in the
row, depending upon which case occurs first.

=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.

public class ColumnFixture extends Fixture {

    protected TypeAdapter columnBindings[];
    protected boolean hasExecuted = false;

    // Traversal ////////////////////////////////

    public void doRows(Parse rows) {
        bind(rows.parts);
        super.doRows(rows.more);
    }

    public void doRow(Parse row) {
        hasExecuted = false;
        try {
            reset();
            super.doRow(row);
            if (!hasExecuted) {
                execute();
            }
        } catch (Exception e) {
            exception (row.leaf(), e);
        }
    }

    public void doCell(Parse cell, int column) {
        TypeAdapter a = columnBindings[column];
        try {
            String text = cell.text();
            if (text.equals("")) {
                check(cell, a);
            } else if (a == null) {
                ignore(cell);
            } else if (a.field != null) {
                a.set(a.parse(text));
            } else if (a.method != null) {
                check(cell, a);
            }
        } catch(Exception e) {
            exception(cell, e);
        }
    }

    public void check(Parse cell, TypeAdapter a) {
        if (!hasExecuted) {
            try {
                execute();
            } catch (Exception e) {
                exception (cell, e);
            }
            hasExecuted = true;
        }
        super.check(cell, a);
    }

    public void reset() throws Exception {
        // about to process first cell of row
    }

    public void execute() throws Exception {
        // about to process first method call of row
    }

    // Utility //////////////////////////////////

    protected void bind (Parse heads) {
        columnBindings = new TypeAdapter[heads.size()];
        for (int i=0; heads!=null; i++, heads=heads.more) {
            String name = heads.text();
            String suffix = "()";
            try {
                if (name.equals("")) {
                    columnBindings[i] = null;
                } else if (name.endsWith(suffix)) {
                    columnBindings[i] = bindMethod(name.substring(0,name.length()-suffix.length()));
                } else {
                    columnBindings[i] = bindField(name);
                }
            }
            catch (Exception e) {
                exception (heads, e);
            }
        }

    }

    protected TypeAdapter bindMethod (String name) throws Exception {
        return TypeAdapter.on(this, getTargetClass().getMethod(camel(name), new Class[]{}));
    }

    protected TypeAdapter bindField (String name) throws Exception {
        return TypeAdapter.on(this, getTargetClass().getField(camel(name)));
    }

    protected Class getTargetClass() {
        return getClass();
    }
}
