Fukurama::Class::AttributesHandler - Helper class to provide corrrect handling of attributes


Fukurama-Class documentation Contained in the Fukurama-Class distribution.

Index


Code Index:

NAME

Top

Fukurama::Class::AttributesHandler - Helper class to provide corrrect handling of attributes

VERSION

Top

Version 0.01 (beta)

SYNOPSIS

Top

 {
 	package MyAttributeHandler;
 	sub MyAttribute {
 		my $class = $_[0];
 		my $method_data = $_[1];

 		warn("Method '$method_data->{'sub_name'}' was resolved at compiletime with data: '$method_data->{'data'}'");
 		# says: Method 'my_own_method' was resolved at compiletime with data: 'foo, bar'
 	}
 }
 {
 	package MyClass;
 	use Fukurama::Class::AttributesHandler();
 	Fukurama::Class::AttributesHandler->register_attributes('MyAttributeHandler');
 	Fukurama::Class::AttributesHandler->export('MyClass');

 	sub my_own_method : MyAttribute(foo, bar) {}
 }

DESCRIPTION

Top

This module enables the possibility to define your own subroutine-attributes. This is also done with the CPAN Attribute module but here you get extra information for the subroutine, which use the attribute. E.g. the resolved methodname.

This helper class is used from Fukurama::Class::Attribute::OOStandard to enable the OO-method-signatures.

EXPORT

Top

MODIFY_CODE_ATTRIBUTES

would be decorated if it exist or created if it isn't in the current class.

METHODS

Top

register_attributes( attribute_handler_class:STRING ) return:BOOLEAN

Register a handler class which defines attributes. See How to define an attribute-handler-class below

export( export_to_class:STRING ) return:BOOLEAN

This will export or decorate the MODIFY_CODE_ATTRIBUTES to the export_to_class class. Be sure that you call this method in a BEGIN block. Perl check them all at compiletime and croak, if some is not defined.

get_registered_subs( ) return:HASHREF

Get the method-definitions from all methods in your code, which use attributes over this attribute handler. This is to check th code structure (or to create some documentation...)

register_helper_method( methodname:STRING ) return:VOID

All registered methodnames would be omitted as attributes, when a attribute-handler-class is parsed. But if they are missed in a attribute-handler-class, the registration would fail.

run_check( ) return:VOID

Resolve all method names, which are unresolved at compiletime, and calls the atribute-definition-methods in the handler-class. This is a helper method for static perl (see Fukurama::Class > BUGS)

How to define an attribute-handler-class

Top

All methods of an attribute-handler-class have to be attribute-definitions, except these, which are registered via register helper methods. This methods have to start with an uppercase letter (it is a perl specification). They will get a hash reference as single parameter. In this hash you will find information of the method which use your attribute. They are:

class:STRING

The name of the class, which contain the subroutine which use the attribute (*puh*). Can be empty in some cases. Look at resolved.

sub_name:STRING

The resolved name of the subroutine, which use the attribute. Perls attributes doesn't resolve the name by itself, so you will normally only get the sub-reference and not the name. It can be empty in some cases. Look at resolved.

data:STRING

The defined attribute-data. if you say 'sub new : MyAtt(this is a $test)' you will get the string 'this is a $test'.

sub:CODEREF

The code-refrence of the subroutine, which use the attribute.

resolved:BOOLEAN

A flag for the status of method name resolving for this method. In some cases, if you force a call, this flag will be FALSE and the sub_name will be empty.

attribute:STRING

The name of the attribute. This is the same like the name of your attribute-method.

handler:HASHREF

A reference to your attribute class and to the actual attribute method.

executed: BOOLEAN

An internal flag to avoid double callings of your attribute-methods.

AUTHOR, BUGS, SUPPORT, ACKNOWLEDGEMENTS, COPYRIGHT & LICENSE

Top

see perldoc of Fukurama::Class


Fukurama-Class documentation Contained in the Fukurama-Class distribution.

package Fukurama::Class::AttributesHandler;
use Fukurama::Class::Version(0.01);
use Fukurama::Class::Rigid;
use Fukurama::Class::Carp;

my $ATT_METHODS;
my $EXPORTED;
my $LAST_ATTRIBUTE_METHOD;
my $SUBS;
my $HELPER_METHODS;
BEGIN {
	$ATT_METHODS = {};
	$EXPORTED = {};
	$SUBS = {};
	$LAST_ATTRIBUTE_METHOD = undef;
	$HELPER_METHODS = {};
}

# STATIC boolean
sub register_attributes {
	my $class = $_[0];
	my $attribute_class = $_[1];
	
	my @subs = ();
	my $check_methods_exist = {};
	{
		
		no strict 'refs';
		
		my %symbols = %{$attribute_class . '::'};
		if(!scalar(%symbols) && !eval("use $attribute_class;return 1;")) {
			_croak("Failed to load attribute-class '$attribute_class' (maybe this class is empty?): $@\n");
			return 0;
		}
		foreach my $name (keys(%symbols)) {
			next if(!*{$attribute_class . '::' . $name}{'CODE'});
			if($HELPER_METHODS->{$name}) {
				$check_methods_exist->{$name} = 1;
				next;
			}
			push(@subs, $name);
		}
	}
	
	my @missed_helper_methods = ();
	foreach my $name (keys(%$HELPER_METHODS)) {
		next if($check_methods_exist->{$name});
		push(@missed_helper_methods, $name);
	} 
	if(scalar(@missed_helper_methods)) {
		my $msg = join("', '", @missed_helper_methods);
		_croak("Needed helper method(s) '$msg' is/are not defined in attribute-class '$attribute_class'. (Maybe class is not compiled yet?)");
	}
	
	foreach my $name (@subs) {
		$class->_register_attribute($attribute_class, $name, 0);
	}
	foreach my $name (@subs) {
		$class->_register_attribute($attribute_class, $name, 1);
	}
	return 1;
}
# STATIC hashref
sub get_registered_subs {
	my $class = $_[0];
	
	return $SUBS;
}
# STATIC void
sub _register_attribute {
	my $class = $_[0];
	my $attribute_class = $_[1];
	my $name = $_[2];
	my $execute_register = $_[3];
	
	if($ATT_METHODS->{$name}) {
		_croak("Attribute '$name' from attribute-class '$attribute_class' always registered for '$ATT_METHODS->{$name}->{'class'}'", 1);
		return;
	}
	if($name !~ m/^[A-Z]/) {
		my $helper_msg = "'" . join("', '", keys(%$HELPER_METHODS)) . "'";
		_croak("Every attribute must start with an uppercase letter (except the helper-method(s) $helper_msg " .
			"which is/are not an attribute).\n" .
			"Attribute '$name' from attribute-class '$attribute_class' is not allowed.", 1);
		return;
	}

	return if(!$execute_register);
	{
		
		no strict 'refs';
		
		$ATT_METHODS->{$name} = {
			class		=> $attribute_class,
			coderef		=> *{$attribute_class . '::' . $name}{'CODE'},
		};
	}
	return;
}
# STATIC boolean
sub export {
	my $class = $_[0];
	my $export_to_class = $_[1];
	
	return 0 if($EXPORTED->{$export_to_class});
	$EXPORTED->{$export_to_class} = 1;
	$class->_decorate_attribute_handler($export_to_class, "$export_to_class\::MODIFY_CODE_ATTRIBUTES");
	return 1;
}
# STATIC void
sub register_helper_method {
	my $class = $_[0];
	my $methodname = $_[1];
	
	$HELPER_METHODS->{$methodname} = 1;
	return;
}
# STATIC void
sub _decorate_attribute_handler {
	my $class = $_[0];
	my $caller_class = $_[1];
	my $identifier = $_[2];

	no warnings 'redefine';
	no strict 'refs';

	my $old = *{$identifier}{CODE};
	*{$identifier} = sub {
		my @unknown_attributes = &_attribute_handler(@_);
		if($old && @unknown_attributes) {
			my $caller_class = $_[0];
			my $sub_ref = $_[1];
			
			@_ = ($caller_class, $sub_ref, @unknown_attributes);
			goto &$old;
		}
		return @unknown_attributes;
	};
	return;
}
# AUTOMAGIC string()
sub _attribute_handler {
	my $caller_class = shift(@_);
	my $sub_ref = shift(@_);
	my @attributes = @_;

	if($LAST_ATTRIBUTE_METHOD && !$LAST_ATTRIBUTE_METHOD->{'resolved'}) {
		my $succes = __PACKAGE__->_resolve_sub($LAST_ATTRIBUTE_METHOD);
		if(!$LAST_ATTRIBUTE_METHOD->{'executed'}) {
			my $success = __PACKAGE__->_exec_attribute($LAST_ATTRIBUTE_METHOD);
		}
	}
	my @unknown_attributes = ();
	foreach my $attribute_string (@attributes) {
		my ($name, $data) = __PACKAGE__->_split_attribute($attribute_string);
		my $handler = $ATT_METHODS->{$name};
		if(!$handler) {
			push(@unknown_attributes, $attribute_string);
			next;
		}
		if($SUBS->{int($sub_ref)}) {
			_croak("Internal failure: subroutine '$sub_ref' allways registered");
		}
		$LAST_ATTRIBUTE_METHOD = {
			'attribute'	=> $name,
			'handler'	=> $handler,
			'sub'		=> $sub_ref,
			'class'		=> $caller_class,
			'data'		=> $data,
			'resolved'	=> 0,
			'executed'	=> 0,
		};
		$SUBS->{int($sub_ref)} = $LAST_ATTRIBUTE_METHOD;
	}
	return @unknown_attributes;
}
# STATIC boolean
sub _resolve_sub {
	my $class = $_[0];
	my $sub_data = $_[1];
	
	return 1 if($sub_data->{'resolved'});
	
	no strict 'refs';

	my $symbols = \%{$sub_data->{'class'} . '::'};
	foreach my $key (keys(%$symbols)) {
		next if(!$symbols->{$key} || !*{$symbols->{$key}}{CODE});
		if(*{$symbols->{$key}}{CODE} == $sub_data->{'sub'}) {
			$sub_data->{'sub_name'} = $key;
			$sub_data->{'resolved'} = 1;
			return 1;
		}
	}
	return 0;
}
# STATIC string()
sub _split_attribute {
	my $class = $_[0];
	my $string = $_[1];

	my ($name, $data) = $string =~ m/^([^\(]*)(?:\((.*)\)|)$/i;
	if(!$name) {
		_croak("Attribute '$string' is malformed", 1);
	}
	return ($name, $data);
}
# STATIC sub
sub _exec_attribute {
	my $class = $_[0];
	my $sub_data = $_[1];
	
	return 1 if($sub_data->{'executed'});
	my $att_class = $sub_data->{'handler'}->{'class'};
	my $att_method = $sub_data->{'handler'}->{'coderef'};
	
	local $Carp::CarpLevel = $Carp::CarpLevel + 2;
	if($att_class->$att_method($sub_data)) {
		$sub_data->{'executed'} = 1;
		return 1;
	}
	return 0;
}
# STATIC void
sub run_check {
	my $class = $_[0];
	
	foreach my $ref_no (keys %$SUBS) {
		my $entry = $SUBS->{$ref_no};
		if(!$entry->{'executed'}) {
			if(!__PACKAGE__->_resolve_sub($entry)) {
				_croak("Internal error: can't resolve sub '$entry->{'sub'}'");
			}
			if(!__PACKAGE__->_exec_attribute($entry)) {
				_croak("Internal error: can't execute attribute '$entry->{'attribute'}' for sub '$entry->{'class'}->$entry->{'sub_name'}'");
			}
		}
	}
	return;
}

no warnings 'void'; # avoid 'Too late to run CHECK/INIT block'

# AUTOMAGIC
CHECK {
	__PACKAGE__->run_check();
}
# AUTOMAGIC
END {
	__PACKAGE__->run_check();
}
1;