view Lib/IMPL/Class/Property/Base.pm @ 103:c289ed9662ca

Schema beta 2 More strict validation, support for inflating a simple nodes and properties
author wizard
date Fri, 07 May 2010 18:17:40 +0400
parents f017c0d7527c
children a07a66fd8d5c
line wrap: on
line source

package IMPL::Class::Property::Base;
use strict;

use IMPL::Class::Property;

require IMPL::Class::Member;

sub factoryParams { qw($class $name $set $get $validator) };

my %factoryCache;

my $accessor_get_no = 'die new IMPL::Exception(\'The property is write only\',$name,$class) unless $get;';
my $accessor_set_no = 'die new IMPL::Exception(\'The property is read only\',$name,$class) unless $set;';

my $custom_accessor_get = 'unshift @_, $this and goto &$get;';
my $custom_accessor_set = 'unshift @_, $this and goto &$set;';

my $validator_code = '$this->$validator(@_);'; 

my %access_code = (
	IMPL::Class::Member::MOD_PUBLIC , "",
	IMPL::Class::Member::MOD_PROTECTED, "die new IMPL::Exception('Can\\'t access the protected member',\$name,\$class,scalar caller) unless UNIVERSAL::isa(scalar caller,\$class);",
	IMPL::Class::Member::MOD_PRIVATE, "die new IMPL::Exception('Can\\'t access the private member',\$name,\$class,scalar caller) unless caller eq \$class;" 
);

my $virtual_call = q(
		my $method = $this->can($name);
        return $this->$method(@_) unless $method == $accessor or caller->isa($class);
);

my $owner_check = "die new IMPL::Exception('Set accessor is restricted to the owner',\$name,\$class,scalar caller) unless caller eq \$class;";

sub GenerateAccessors {
	my ($self,$param,@params) = @_;
	
	my %accessors;
	
	if (not ref $param) {
		if ($param & prop_list) {
			$accessors{get} = ($param & prop_get) ? $self->GenerateGetList(@params) : $accessor_get_no;
			$accessors{set} = ($param & prop_set) ? $self->GenerateSetList(@params) : $accessor_set_no;
		} else {
			$accessors{get} = ($param & prop_get) ? $self->GenerateGet(@params) : $accessor_get_no;
			$accessors{set} = ($param & prop_set) ? $self->GenerateSet(@params) : $accessor_set_no;
		}
		$accessors{owner} = (($param & owner_set) == owner_set) ? $owner_check : "";
	} elsif (UNIVERSAL::isa($param,'HASH')) {
		$accessors{get} = $param->{get} ? $custom_accessor_get : $accessor_get_no;
		$accessors{set} = $param->{set} ? $custom_accessor_set : $accessor_set_no;
		$accessors{owner} = "";
	} else {
		die new IMPL::Exception('The unsupported accessor/mutators supplied',$param);
	}
	
	return \%accessors;
}

sub GenerateSet {
	die new IMPL::Exception("Standard accessors not supported",'Set');
}
	
sub GenerateGet {
	die new IMPL::Exception("Standard accessors not supported",'Get');
}

sub GenerateGetList {
	die new IMPL::Exception("Standard accessors not supported",'GetList');
}

sub GenerateSetList {
	my ($self) = @_;
	die new IMPL::Exception("Standard accessors not supported",'SetList');
}

sub Make {
	my ($self,$propInfo) = @_;
	
	my $key = $self->MakeFactoryKey($propInfo);
	
	my $factory = $factoryCache{$key};
	
	unless ($factory) {
		my $mutators = $self->GenerateAccessors($propInfo->Mutators);
		$factory = $self->CreateFactory(
			$access_code{ $propInfo->Access },
			$propInfo->Attributes->{validator} ? $validator_code : "",
			$mutators->{owner},
			$mutators->{get},
			$mutators->{set}
		);
		$factoryCache{$key} = $factory; 
	}
	
	{
		no strict 'refs';
		*{ $propInfo->Class.'::'.$propInfo->Name } = &$factory($self->RemapFactoryParams($propInfo));
	}
	
	my $mutators = $propInfo->Mutators;
	
	if (ref $mutators) {
		$propInfo->canGet( $mutators->{get} ? 1 : 0 );
		$propInfo->canSet( $mutators->{set} ? 1 : 0 );
		$propInfo->ownerSet(0);
	} else {
		$propInfo->canGet( $mutators & prop_get );
		$propInfo->canSet( $mutators & prop_set );
		$propInfo->ownerSet( ($mutators & owner_set) == owner_set );
	}
}

# extract from property info: class, name, get_accessor, set_accessor, validator
sub RemapFactoryParams {
	my ($self,$propInfo) = @_;
	
	my $mutators = $propInfo->Mutators;
	my $class = $propInfo->Class;
	my $validator = $propInfo->Attributes->{validator};
	
	die new IMPL::Exception('Can\'t find the specified validator',$class,$validator) if $validator and ref $validator ne 'CODE' and not $class->can($validator);

	return (
		$propInfo->get(qw(Class Name)),
		(ref $mutators?
			($mutators->{set},$mutators->{get})
			:
			(undef,undef)
		),
		$validator
	);
}

sub MakeFactoryKey {
	my ($self,$propInfo) = @_;
	
	my ($access,$mutators,$validator) = ($propInfo->get(qw(Access Mutators)),$propInfo->Attributes->{validator});
	
	my $implementor = ref $self || $self;
	
	return join ('',
		$implementor,
		$access,
		$validator ? 'v' : 'n',
		ref $mutators ?
			('c' , $mutators->{get} ? 1 : 0, $mutators->{set} ? 1 : 0)
			:
			(($mutators & prop_list) ? 'l' : 's' , ($mutators & prop_get) ? 1 : 0, ($mutators & prop_set) ? ((($mutators & owner_set) == owner_set) ? 2 : 1 ) : 0 ) 
	); 
}

sub CreateFactory {
	my ($self,$codeAccessCheck,$codeValidator,$codeOwnerCheck,$codeGet,$codeSet) = @_;
	
	my $strParams = join(',',$self->factoryParams);
	
	my $factory = <<FACTORY;
	
sub {
    my ($strParams) = \@_;
    my \$accessor;
    \$accessor = sub {
        my \$this = shift;
        $codeAccessCheck
        if (\@_) {
        	$codeOwnerCheck
        	$codeValidator
        	$codeSet
        } else {
        	$codeGet
        }
    }
}
FACTORY

	return ( eval $factory or die new IMPL::Exception("Syntax error due compiling the factory","$@") );
}

1;

__END__

=pod

=head1 DESCRIPTION

Базовый класс для реализации свойств.

По существу свойства состоят из двух методов для установки и получения значений. Также
существует несколько вариантов доступа к свойству, и метод верификации значения. Еще
свойства могут быть виртуальными.

Для создания реализатора свойств достаточно унаследовать от этого класса и описать
методы для генерации кода получения и установки значения.

=head1 MEMBERS

=over

=item C<Make($propertyInfo)>

Создает свойство у класса, на основе C<$propertyInfo>, описывающего свойство. C<IMPL::Class::PropertyInfo>.

=back 

=cut