Config-Model

 view release on metacpan or  search on metacpan

lib/Config/Model/WarpedNode.pm  view on Meta::CPAN

#
# This file is part of Config-Model
#
# This software is Copyright (c) 2005-2022 by Dominique Dumont.
#
# This is free software, licensed under:
#
#   The GNU Lesser General Public License, Version 2.1, February 1999
#
package Config::Model::WarpedNode 2.162;

use v5.20;
use Mouse;

use Carp qw(cluck croak);

use Config::Model::Exception;
use Config::Model::Warper;
use Data::Dumper ();
use Log::Log4perl qw(get_logger :levels);
use Storable qw/dclone/;
use Scalar::Util qw/weaken/;

extends qw/Config::Model::AnyThing/;

with "Config::Model::Role::NodeLoader";
with "Config::Model::Role::Grab";

use feature qw/postderef signatures/;
no warnings qw/experimental::postderef experimental::signatures/;

my $logger = get_logger("Tree::Node::Warped");

# don't authorize to warp 'morph' parameter as it may lead to
# difficult maintenance

# status is not warpable either as an obsolete parameter must stay
# obsolete

my @allowed_warp_params = qw/config_class_name level gist/;

has 'backup' => ( is => 'rw', isa => 'HashRef', default => sub { {}; } );

has 'warp'  => ( is => 'rw', isa => 'HashRef', default => sub { {}; });
has 'morph' => ( is => 'ro', isa => 'Bool', default => 0 );

has warper => ( is => 'rw', isa => 'Config::Model::Warper' );

has is_building => (
    is => 'ro',
    isa     => 'Bool',
    traits  => ['Bool'],
    default => 0,
    handles => {
        building  => 'set',
        build_done => 'unset',
    },
);

my @backup_list = @allowed_warp_params;

around BUILDARGS => sub ($orig, $class, %args) {
    my %h     = map { ( $_ => $args{$_} ); } grep { defined $args{$_} } @backup_list;
    return $class->$orig( backup => dclone( \%h ), %args );
};

sub BUILD ($self, $) {
    # WarpedNode registers this object in a Value object (the
    # warper).  When the warper gets a new value, it modifies the
    # WarpedNode according to the data passed by the user.

    $self->building;
    my $warp_info = $self->warp;
    $warp_info->{follow} //= {};
    $warp_info->{rules}  //= [];
    my $w = Config::Model::Warper->new(
        warped_object => $self,
        %$warp_info,
        allowed       => \@allowed_warp_params
    );

    $self->warper($w);
    $self->build_done;
    return $self;
}

sub config_model ($self) {
    return $self->parent->config_model;
}

# Forward selected methods (See man perltootc)
foreach my $method (
    qw/fetch_element config_class_name copy_from get_element_name
    get_info fetch_gist has_element is_element_available element_type load
    fetch_element_value get_type get_cargo_type dump_tree needs_save
    describe get_help get_help_as_text children get set accept_regexp/
    ) {
    # to register new methods in package
    no strict "refs"; ## no critic TestingAndDebugging::ProhibitNoStrict

    *$method = sub ($self,@args) {
        if ($self->check) {
            return $self->{data}->$method(@args);
        }

        # return undef if no class was warped in
        return ;
    };
}

sub name ($self) {
    return $self->location;
}

sub is_accessible ($self) {
    return defined $self->{data} ? 1 : 0;
}

sub get_actual_node ($self) {
    $self->check;
    return $self->{data};    # might be undef
}

sub check ($self, $check = 'yes') {
    # must croak if element is not available
    if ( not defined $self->{data} ) {

        # a node can be retrieved either for a store operation or for
        # a fetch.
        if ( $check eq 'yes' and not $self->building) {
            Config::Model::Exception::User->throw(
                object  => $self,
                message => "Object '$self->{element_name}' is not accessible.\n\t"
                    . $self->warp_error
            );
        }
        else {
            return 0;
        }
    }
    return 1;
}

sub set_properties ($self, @args) {
    my %args = ( %{ $self->backup }, @args );

    # mega cleanup
    foreach my $awp (@allowed_warp_params) {
        delete $self->{$awp}
    }

    $logger->trace( $self->name . " set_properties called with ",
        Data::Dumper->Dump( [ \%args ], ['set_properties_args'] ) );

    my $config_class_name = delete $args{config_class_name};
    my $node_class = delete $args{class} || 'Config::Model::Node';

    my @prop_args = ( qw/property level element/, $self->element_name );

    my $original_level = $self->config_model->get_element_property(
        class => $self->parent->config_class_name,
        @prop_args,
    );

    my $next_level =
          defined $args{level}       ? $args{level}
        : defined $config_class_name ? $original_level
        :                              'hidden';

    $self->parent->set_element_property( @prop_args, value => $next_level )
        unless defined $self->index_value;

    unless ( defined $config_class_name ) {
        $self->clear;
        return;
    }

    my @class_args;
    ( $config_class_name, @class_args ) = @$config_class_name
        if ref $config_class_name;

    # check if some action is needed (ie. create or morph node)
    return
        if defined $self->{config_class_name}
        and $self->{config_class_name} eq $config_class_name;

    my $old_object            = $self->{data};
    my $old_config_class_name = $self->{config_class_name};

    # create a new object from scratch
    my $new_object = $self->create_node( $config_class_name, @class_args );

    $self->{config_class_name} = $config_class_name;
    $self->{data}              = $new_object;

    if ( defined $old_object and $self->{morph} ) {

        # there an old object that we need to translate
        $logger->debug( "WarpedNode: morphing ", $old_object->name, " to ", $new_object->name )
            if $logger->is_debug;

        $new_object->copy_from( from => $old_object, check => 'skip' );
    }

    # bringing a new object does not really modify the content of the config tree.



( run in 0.611 second using v1.01-cache-2.11-cpan-39bf76dae61 )