File: //usr/local/lib64/perl5/Cpanel/Class/Meta/Attribute.pm
package Cpanel::Class::Meta::Attribute;
use strict;
use warnings;
use Carp 'confess';
use Scalar::Util 'blessed', 'reftype', 'isweak', 'weaken';
use base 'Cpanel::Class::Meta::Class';
our $VERSION = '1.0.5';
sub new {
my ( $class, $args ) = @_;
confess "Argument to new must be hash reference" if reftype $args ne 'HASH';
confess 'package is required argument'
if !exists $args->{'package'} and !$args->{'package'};
my $self = $class->SUPER::new($args);
$self = {
%{$self},
'$!attr_name' => $args->{'attr'},
'$!reader' => $args->{'reader'},
'$!writer' => $args->{'writer'},
'$!default' => $args->{'default'},
'$!is' => $args->{'is'},
'$!handles' => $args->{'handles'},
'$!predicate' => $args->{'predicate'},
'$!init_arg' => $args->{'init_arg'},
'$!lazy' => $args->{'lazy'},
'$!weaken_ref' => $args->{'weaken_ref'},
'$!auto_deref' => $args->{'auto_deref'},
'$!trigger' => $args->{'trigger'},
'$!clearer' => $args->{'clearer'},
'$!builder' => $args->{'builder'},
'$!required' => $args->{'required'},
'$!associated_class' => '',
};
delete $self->{'%!attributes'};
delete $self->{'$!attribute_metaclass'};
delete $self->{'%!methods'};
return bless $self, $class;
}
sub associated_class { $_[0]->{'$!associated_class'} }
sub attr {
my ($self) = @_;
return $self->{'$!attr_name'};
}
sub reader {
my ($self) = @_;
return $self->{'$!reader'};
}
sub writer {
my ($self) = @_;
return $self->{'$!writer'};
}
sub default {
my ($self) = @_;
return $self->{'$!default'};
}
sub is {
my ($self) = @_;
return $self->{'$!is'};
}
sub handles {
my ($self) = @_;
return $self->{'$!handles'};
}
sub predicate {
my ($self) = @_;
return $self->{'$!predicate'};
}
sub init_arg {
my ($self) = @_;
return $self->{'$!init_arg'};
}
sub lazy {
my ($self) = @_;
return $self->{'$!lazy'};
}
sub weaken_ref {
my ($self) = @_;
return $self->{'$!weaken_ref'};
}
sub auto_deref {
my ($self) = @_;
return $self->{'$!auto_deref'};
}
sub trigger {
my ($self) = @_;
return $self->{'$!trigger'};
}
sub clearer {
my ($self) = @_;
return $self->{'$!clearer'};
}
sub builder {
my ($self) = @_;
return $self->{'$!builder'};
}
sub required {
my ($self) = @_;
return $self->{'$!required'};
}
sub has_is { defined( $_[0]->is ) ? 1 : 0 }
sub has_reader { defined( $_[0]->reader ) ? 1 : 0 }
sub has_writer { defined( $_[0]->writer ) ? 1 : 0 }
sub has_predicate { defined( $_[0]->predicate ) ? 1 : 0 }
sub has_init_arg { defined( $_[0]->init_arg ) ? 1 : 0 }
sub has_default { defined( $_[0]->default ) ? 1 : 0 }
sub has_handles { defined( $_[0]->handles ) ? 1 : 0 }
sub has_trigger { defined( $_[0]->trigger ) ? 1 : 0 }
sub has_clearer { defined( $_[0]->clearer ) ? 1 : 0 }
sub has_builder { defined( $_[0]->builder ) ? 1 : 0 }
sub is_lazy { $_[0]->lazy }
sub is_weak { $_[0]->weaken_ref }
sub is_auto_deref { $_[0]->auto_deref }
sub is_required { $_[0]->required }
sub build_attribute {
my ($self) = @_;
confess 'triggers cannot be called on read-only(ro) attributes'
if $self->has_trigger and $self->is eq 'ro';
confess 'trigger must be a code ref'
if $self->has_trigger and reftype $self->trigger ne 'CODE';
confess 'Setting both default and builder is not allowed'
if $self->has_default and $self->has_builder;
confess 'builder must be a defined scalar value which is a method name'
if $self->has_builder
and ( ref $self->builder || !( defined( $self->builder ) ) );
if ( $self->reader eq $self->writer ) {
if ( $self->has_is and $self->is eq 'ro' ) {
$self->_ro_method();
}
elsif ( $self->has_is and $self->is eq 'wo' ) {
$self->_wo_method();
}
elsif ( $self->has_is and $self->is eq 'rw' ) {
$self->_rw_method();
}
}
else {
if ( $self->has_is and $self->is eq 'ro' ) {
$self->_ro_method();
}
elsif ( $self->has_is and $self->is eq 'wo' ) {
$self->_wo_method();
}
elsif ( $self->has_is and $self->is eq 'rw' ) {
$self->_ro_method();
$self->_wo_method();
}
}
if (
$self->has_handles
and ( reftype $self->handles eq 'HASH'
or reftype $self->handles eq 'ARRAY' )
) {
$self->build_handles();
}
if ( $self->has_predicate ) {
$self->build_predicate();
}
if ( $self->has_clearer ) {
$self->build_clearer();
}
}
sub attach_to_class {
my ( $self, $class ) = @_;
weaken( $self->{'$!associated_class'} = $class );
}
sub build_clearer {
my ($attribute) = @_;
$attribute->associated_class->add_method(
$attribute->clearer => sub {
my ($self) = @_;
delete $self->{ $attribute->attr };
}
);
}
sub build_handles {
my ($attribute) = @_;
if ( reftype $attribute->handles eq 'HASH' ) {
$attribute->_handles_hash( $attribute->attr, $attribute->reader, $attribute->handles );
}
elsif ( reftype $attribute->handles eq 'ARRAY' ) {
$attribute->_handles_array( $attribute->attr, $attribute->reader, $attribute->handles );
}
}
sub build_predicate {
my ($attribute) = @_;
$attribute->associated_class->add_method(
$attribute->predicate => sub {
my ($self) = @_;
if ( exists $self->{ $attribute->attr }
and ref $self->{ $attribute->attr } ) {
if ( ref $self->{ $attribute->attr } eq 'ARRAY' ) {
return ( @{ $self->{ $attribute->attr } } ) ? 1 : 0;
}
elsif ( ref $self->{ $attribute->attr } eq 'HASH' ) {
return ( %{ $self->{ $attribute->attr } } ) ? 1 : 0;
}
else {
return ( defined $self->{ $attribute->attr } ) ? 1 : 0;
}
}
elsif ( exists $self->{ $attribute->attr } ) {
return ( defined $self->{ $attribute->attr } ) ? 1 : 0;
}
else {
return 0;
}
}
);
}
# Private
sub _ro_method {
my ($attribute) = @_;
my %seen_default = ();
$attribute->associated_class->add_method(
$attribute->reader => sub {
my ($self) = @_;
confess $attribute->reader . ' is a read only accessor.'
if scalar @_ > 1;
my $id = $self + 0;
if ( $attribute->builder ) {
if ( my $build_method = $self->can( $attribute->builder ) ) {
$self->{ $attribute->attr } ||= $self->$build_method($self);
}
}
if ( !exists $seen_default{$id} or !$self->{ $attribute->attr } ) {
if ( !$seen_default{$id} ) {
$self->{ $attribute->attr } ||=
( $attribute->default and ref $attribute->default eq 'CODE' )
? $attribute->default->($self)
: $attribute->default;
$seen_default{$id} = 1;
}
}
if ( $attribute->is_weak
and $self->{ $attribute->attr }
and !isweak( $self->{ $attribute->attr } ) ) {
weaken( $self->{ $attribute->attr } );
}
if ( $attribute->is_required
and !defined( $self->{ $attribute->attr } ) ) {
my $name = $attribute->attr;
confess "attribute \"$name\" is required to be set";
}
return _return_attr( $self, $attribute->attr, $attribute->is_auto_deref );
}
);
}
sub _rw_method {
my ($attribute) = @_;
my %seen_default = ();
$attribute->associated_class->add_method(
$attribute->writer => sub {
my ( $self, $value ) = @_;
my $id = $self + 0;
if ( $attribute->builder ) {
if ( my $build_method = $self->can( $attribute->builder ) ) {
$self->{ $attribute->attr } ||= $self->$build_method($self);
}
}
if ( !exists $seen_default{$id} or !$value ) {
if ( !$seen_default{$id} ) {
$self->{ $attribute->attr } ||=
( $attribute->default and ref $attribute->default eq 'CODE' )
? $attribute->default->($self)
: $attribute->default;
$seen_default{$id} = 1;
}
}
$self->{ $attribute->attr } = $value if defined $value;
if ( $attribute->is_weak
and $self->{ $attribute->attr }
and !isweak( $self->{ $attribute->attr } ) ) {
weaken( $self->{ $attribute->attr } );
}
if ( $attribute->has_trigger
and reftype $attribute->trigger eq 'CODE'
and defined $value ) {
$attribute->trigger->( $self, $self->{ $attribute->attr }, $attribute );
}
if ( $attribute->is_required
and !defined( $self->{ $attribute->attr } ) ) {
my $name = $attribute->attr;
confess "attribute \"$name\" is required to be set";
}
return _return_attr( $self, $attribute->attr, $attribute->is_auto_deref );
}
);
}
sub _wo_method {
my ($attribute) = @_;
my %seen_default = ();
$attribute->associated_class->add_method(
$attribute->writer => sub {
my ( $self, $value ) = @_;
my $id = $self + 0;
if ( $attribute->builder ) {
if ( my $build_method = $self->can( $attribute->builder ) ) {
$self->{ $attribute->attr } ||= $self->$build_method($self);
}
}
if ( !exists $seen_default{$id} or !$value ) {
if ( !$seen_default{$id} ) {
$self->{ $attribute->attr } ||=
( $attribute->default and ref $attribute->default eq 'CODE' )
? $attribute->default->($self)
: $attribute->default;
$seen_default{$id} = 1;
}
}
$self->{ $attribute->attr } = $value if defined $value;
if ( $attribute->is_weak
and $self->{ $attribute->attr }
and !isweak( $self->{ $attribute->attr } ) ) {
weaken( $self->{ $attribute->attr } );
}
if ( $attribute->has_trigger
and reftype $attribute->trigger eq 'CODE'
and defined $value ) {
$attribute->trigger->( $self, $self->{ $attribute->attr }, $attribute );
}
if ( $attribute->is_required
and !defined( $self->{ $attribute->attr } ) ) {
my $name = $attribute->attr;
confess "attribute \"$name\" is required to be set";
}
return _return_attr( $self, $attribute->attr, $attribute->is_auto_deref );
}
);
}
sub _handles_array {
my ( $self, $attr, $reader, $handles ) = @_;
foreach my $method ( @{$handles} ) {
$self->associated_class->add_method(
$method => sub {
my ( $self, @args ) = @_;
if ( $self->$reader->can($method) ) {
return $self->$reader->$method(@args);
}
else {
confess( 'Cannot call ' . $method . ' from ' . $reader );
}
}
);
}
}
sub _handles_hash {
my ( $self, $attr, $reader, $handles ) = @_;
foreach my $method ( keys %{$handles} ) {
my $handle = $handles->{$method};
$self->associated_class->add_method(
$method => sub {
my ( $self, @args ) = @_;
if ( $self->$reader->can($handle) ) {
return $self->$reader->$handle(@args);
}
else {
croak( 'Cannot call ' . $handle . ' from ' . $reader );
}
}
);
}
}
sub _return_attr {
my ( $self, $attr, $is_auto_deref ) = @_;
if ( $is_auto_deref and defined $self->{$attr} ) {
if ( reftype $self->{$attr} eq 'ARRAY' ) {
return (wantarray) ? @{ $self->{$attr} } : $self->{$attr};
}
elsif ( reftype $self->{$attr} eq 'HASH' ) {
return (wantarray) ? %{ $self->{$attr} } : $self->{$attr};
}
else {
return $self->{$attr};
}
}
else {
return $self->{$attr};
}
}
sub detach_from_class {
my $self = shift;
$self->{'$!associated_class'} = undef;
}
{
my $_remove_accessor = sub {
my ( $accessor, $class ) = @_;
if ( reftype($accessor) && reftype($accessor) eq 'HASH' ) {
($accessor) = keys %{$accessor};
}
my $method = $class->get_method($accessor);
$class->remove_method($accessor)
if ( blessed($method) && $method->isa('Class::MOP::Method::Accessor') );
};
sub remove_accessors {
my $self = shift;
$_remove_accessor->( $self->reader(), $self->associated_class() ) if $self->has_reader();
$_remove_accessor->( $self->writer(), $self->associated_class() ) if $self->has_writer();
$_remove_accessor->( $self->predicate(), $self->associated_class() ) if $self->has_predicate();
$_remove_accessor->( $self->clearer(), $self->associated_class() ) if $self->has_clearer();
return;
}
}
1;